PageRenderTime 90ms CodeModel.GetById 27ms app.highlight 58ms RepoModel.GetById 1ms app.codeStats 0ms

/Python/Product/TestAdapter/visualstudio_py_testlauncher.py

https://gitlab.com/SplatoonModdingHub/PTVS
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()