/Python/Product/TestAdapter/visualstudio_py_testlauncher.py
Python | 244 lines | 192 code | 34 blank | 18 comment | 34 complexity | 4f3323d85c07516501979b8df9946500 MD5 | raw file
1# Python Tools for Visual Studio
2# Copyright(c) Microsoft Corporation
3# All rights reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the License); you may not use
6# this file except in compliance with the License. You may obtain a copy of the
7# License at http://www.apache.org/licenses/LICENSE-2.0
8#
9# THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
10# OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
11# IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
12# MERCHANTABLITY OR NON-INFRINGEMENT.
13#
14# See the Apache Version 2.0 License for specific language governing
15# permissions and limitations under the License.
16
17__author__ = "Microsoft Corporation <ptvshelp@microsoft.com>"
18__version__ = "3.0.0.0"
19
20import sys
21import json
22import unittest
23import socket
24import traceback
25from types import CodeType, FunctionType
26try:
27 import thread
28except:
29 import _thread as thread
30
31class _TestOutput(object):
32 """file like object which redirects output to the repl window."""
33 errors = 'strict'
34
35 def __init__(self, old_out, is_stdout):
36 self.is_stdout = is_stdout
37 self.old_out = old_out
38 if sys.version >= '3.' and hasattr(old_out, 'buffer'):
39 self.buffer = _TestOutputBuffer(old_out.buffer, is_stdout)
40
41 def flush(self):
42 if self.old_out:
43 self.old_out.flush()
44
45 def writelines(self, lines):
46 for line in lines:
47 self.write(line)
48
49 @property
50 def encoding(self):
51 return 'utf8'
52
53 def write(self, value):
54 _channel.send_event('stdout' if self.is_stdout else 'stderr', content=value)
55 if self.old_out:
56 self.old_out.write(value)
57
58 def isatty(self):
59 return True
60
61 def next(self):
62 pass
63
64 @property
65 def name(self):
66 if self.is_stdout:
67 return "<stdout>"
68 else:
69 return "<stderr>"
70
71 def __getattr__(self, name):
72 return getattr(self.old_out, name)
73
74class _TestOutputBuffer(object):
75 def __init__(self, old_buffer, is_stdout):
76 self.buffer = old_buffer
77 self.is_stdout = is_stdout
78
79 def write(self, data):
80 _channel.send_event('stdout' if self.is_stdout else 'stderr', content=data)
81 self.buffer.write(data)
82
83 def flush(self):
84 self.buffer.flush()
85
86 def truncate(self, pos = None):
87 return self.buffer.truncate(pos)
88
89 def tell(self):
90 return self.buffer.tell()
91
92 def seek(self, pos, whence = 0):
93 return self.buffer.seek(pos, whence)
94
95class _IpcChannel(object):
96 def __init__(self, socket):
97 self.socket = socket
98 self.seq = 0
99 self.lock = thread.allocate_lock()
100
101 def send_event(self, name, **args):
102 with self.lock:
103 body = {'type': 'event', 'seq': self.seq, 'event':name, 'body':args}
104 self.seq += 1
105 content = json.dumps(body).encode('utf8')
106 headers = ('Content-Length: %d\n\n' % (len(content), )).encode('utf8')
107 self.socket.send(headers)
108 self.socket.send(content)
109
110_channel = None
111
112
113class VsTestResult(unittest.TextTestResult):
114 def startTest(self, test):
115 super(VsTestResult, self).startTest(test)
116 if _channel is not None:
117 _channel.send_event(
118 name='start',
119 test = test.id()
120 )
121
122 def addError(self, test, err):
123 super(VsTestResult, self).addError(test, err)
124 self.sendResult(test, 'failed', err)
125
126 def addFailure(self, test, err):
127 super(VsTestResult, self).addFailure(test, err)
128 self.sendResult(test, 'failed', err)
129
130 def addSuccess(self, test):
131 super(VsTestResult, self).addSuccess(test)
132 self.sendResult(test, 'passed')
133
134 def addSkip(self, test, reason):
135 super(VsTestResult, self).addSkip(test, reason)
136 self.sendResult(test, 'skipped')
137
138 def addExpectedFailure(self, test, err):
139 super(VsTestResult, self).addExpectedFailure(test, err)
140 self.sendResult(test, 'failed', err)
141
142 def addUnexpectedSuccess(self, test):
143 super(VsTestResult, self).addUnexpectedSuccess(test)
144 self.sendResult(test, 'passed')
145
146 def sendResult(self, test, outcome, trace = None):
147 if _channel is not None:
148 tb = None
149 message = None
150 if trace is not None:
151 traceback.print_exc()
152 formatted = traceback.format_exception(*trace)
153 # Remove the 'Traceback (most recent call last)'
154 formatted = formatted[1:]
155 tb = ''.join(formatted)
156 message = str(trace[1])
157 _channel.send_event(
158 name='result',
159 outcome=outcome,
160 traceback = tb,
161 message = message,
162 test = test.id()
163 )
164
165def main():
166 import os
167 import sys
168 import unittest
169 from optparse import OptionParser
170 global _channel
171
172 parser = OptionParser(prog = 'visualstudio_py_testlauncher', usage = 'Usage: %prog [<option>] <test names>... ')
173 parser.add_option('-s', '--secret', metavar='<secret>', help='restrict server to only allow clients that specify <secret> when connecting')
174 parser.add_option('-p', '--port', type='int', metavar='<port>', help='listen for debugger connections on <port>')
175 parser.add_option('-x', '--mixed-mode', action='store_true', help='wait for mixed-mode debugger to attach')
176 parser.add_option('-t', '--test', type='str', dest='tests', action='append', help='specifies a test to run')
177 parser.add_option('-c', '--coverage', type='str', help='enable code coverage and specify filename')
178 parser.add_option('-r', '--result-port', type='int', help='connect to port on localhost and send test results')
179 (opts, _) = parser.parse_args()
180
181 sys.path[0] = os.getcwd()
182
183 if opts.result_port:
184 _channel = _IpcChannel(socket.create_connection(('127.0.0.1', opts.result_port)))
185 sys.stdout = _TestOutput(sys.stdout, is_stdout = True)
186 sys.stderr = _TestOutput(sys.stderr, is_stdout = False)
187
188 if opts.secret and opts.port:
189 from ptvsd.visualstudio_py_debugger import DONT_DEBUG, DEBUG_ENTRYPOINTS, get_code
190 from ptvsd.attach_server import DEFAULT_PORT, enable_attach, wait_for_attach
191
192 DONT_DEBUG.append(os.path.normcase(__file__))
193 DEBUG_ENTRYPOINTS.add(get_code(main))
194
195 enable_attach(opts.secret, ('127.0.0.1', getattr(opts, 'port', DEFAULT_PORT)), redirect_output = True)
196 wait_for_attach()
197 elif opts.mixed_mode:
198 # For mixed-mode attach, there's no ptvsd and hence no wait_for_attach(),
199 # so we have to use Win32 API in a loop to do the same thing.
200 from time import sleep
201 from ctypes import windll, c_char
202 while True:
203 if windll.kernel32.IsDebuggerPresent() != 0:
204 break
205 sleep(0.1)
206 try:
207 debugger_helper = windll['Microsoft.PythonTools.Debugger.Helper.x86.dll']
208 except WindowsError:
209 debugger_helper = windll['Microsoft.PythonTools.Debugger.Helper.x64.dll']
210 isTracing = c_char.in_dll(debugger_helper, "isTracing")
211 while True:
212 if isTracing.value != 0:
213 break
214 sleep(0.1)
215
216 cov = None
217 try:
218 if opts.coverage:
219 try:
220 import coverage
221 cov = coverage.coverage(opts.coverage)
222 cov.load()
223 cov.start()
224 except:
225 pass
226 tests = unittest.defaultTestLoader.loadTestsFromNames(opts.tests)
227 runner = unittest.TextTestRunner(verbosity=0, resultclass=VsTestResult)
228
229 result = runner.run(tests)
230
231 sys.exit(not result.wasSuccessful())
232 finally:
233 if cov is not None:
234 cov.stop()
235 cov.save()
236 cov.xml_report(outfile = opts.coverage + '.xml', omit=__file__)
237 if _channel is not None:
238 _channel.send_event(
239 name='done'
240 )
241 _channel.socket.close()
242
243if __name__ == '__main__':
244 main()