PageRenderTime 26ms CodeModel.GetById 0ms RepoModel.GetById 0ms 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. __author__ = "Microsoft Corporation <ptvshelp@microsoft.com>"
  17. __version__ = "3.0.0.0"
  18. import sys
  19. import json
  20. import unittest
  21. import socket
  22. import traceback
  23. from types import CodeType, FunctionType
  24. try:
  25. import thread
  26. except:
  27. import _thread as thread
  28. class _TestOutput(object):
  29. """file like object which redirects output to the repl window."""
  30. errors = 'strict'
  31. def __init__(self, old_out, is_stdout):
  32. self.is_stdout = is_stdout
  33. self.old_out = old_out
  34. if sys.version >= '3.' and hasattr(old_out, 'buffer'):
  35. self.buffer = _TestOutputBuffer(old_out.buffer, is_stdout)
  36. def flush(self):
  37. if self.old_out:
  38. self.old_out.flush()
  39. def writelines(self, lines):
  40. for line in lines:
  41. self.write(line)
  42. @property
  43. def encoding(self):
  44. return 'utf8'
  45. def write(self, value):
  46. _channel.send_event('stdout' if self.is_stdout else 'stderr', content=value)
  47. if self.old_out:
  48. self.old_out.write(value)
  49. def isatty(self):
  50. return True
  51. def next(self):
  52. pass
  53. @property
  54. def name(self):
  55. if self.is_stdout:
  56. return "<stdout>"
  57. else:
  58. return "<stderr>"
  59. def __getattr__(self, name):
  60. return getattr(self.old_out, name)
  61. class _TestOutputBuffer(object):
  62. def __init__(self, old_buffer, is_stdout):
  63. self.buffer = old_buffer
  64. self.is_stdout = is_stdout
  65. def write(self, data):
  66. _channel.send_event('stdout' if self.is_stdout else 'stderr', content=data)
  67. self.buffer.write(data)
  68. def flush(self):
  69. self.buffer.flush()
  70. def truncate(self, pos = None):
  71. return self.buffer.truncate(pos)
  72. def tell(self):
  73. return self.buffer.tell()
  74. def seek(self, pos, whence = 0):
  75. return self.buffer.seek(pos, whence)
  76. class _IpcChannel(object):
  77. def __init__(self, socket):
  78. self.socket = socket
  79. self.seq = 0
  80. self.lock = thread.allocate_lock()
  81. def send_event(self, name, **args):
  82. with self.lock:
  83. body = {'type': 'event', 'seq': self.seq, 'event':name, 'body':args}
  84. self.seq += 1
  85. content = json.dumps(body).encode('utf8')
  86. headers = ('Content-Length: %d\n\n' % (len(content), )).encode('utf8')
  87. self.socket.send(headers)
  88. self.socket.send(content)
  89. _channel = None
  90. class VsTestResult(unittest.TextTestResult):
  91. def startTest(self, test):
  92. super(VsTestResult, self).startTest(test)
  93. if _channel is not None:
  94. _channel.send_event(
  95. name='start',
  96. test = test.id()
  97. )
  98. def addError(self, test, err):
  99. super(VsTestResult, self).addError(test, err)
  100. self.sendResult(test, 'failed', err)
  101. def addFailure(self, test, err):
  102. super(VsTestResult, self).addFailure(test, err)
  103. self.sendResult(test, 'failed', err)
  104. def addSuccess(self, test):
  105. super(VsTestResult, self).addSuccess(test)
  106. self.sendResult(test, 'passed')
  107. def addSkip(self, test, reason):
  108. super(VsTestResult, self).addSkip(test, reason)
  109. self.sendResult(test, 'skipped')
  110. def addExpectedFailure(self, test, err):
  111. super(VsTestResult, self).addExpectedFailure(test, err)
  112. self.sendResult(test, 'failed', err)
  113. def addUnexpectedSuccess(self, test):
  114. super(VsTestResult, self).addUnexpectedSuccess(test)
  115. self.sendResult(test, 'passed')
  116. def sendResult(self, test, outcome, trace = None):
  117. if _channel is not None:
  118. tb = None
  119. message = None
  120. if trace is not None:
  121. traceback.print_exc()
  122. formatted = traceback.format_exception(*trace)
  123. # Remove the 'Traceback (most recent call last)'
  124. formatted = formatted[1:]
  125. tb = ''.join(formatted)
  126. message = str(trace[1])
  127. _channel.send_event(
  128. name='result',
  129. outcome=outcome,
  130. traceback = tb,
  131. message = message,
  132. test = test.id()
  133. )
  134. def main():
  135. import os
  136. import sys
  137. import unittest
  138. from optparse import OptionParser
  139. global _channel
  140. parser = OptionParser(prog = 'visualstudio_py_testlauncher', usage = 'Usage: %prog [<option>] <test names>... ')
  141. parser.add_option('-s', '--secret', metavar='<secret>', help='restrict server to only allow clients that specify <secret> when connecting')
  142. parser.add_option('-p', '--port', type='int', metavar='<port>', help='listen for debugger connections on <port>')
  143. parser.add_option('-x', '--mixed-mode', action='store_true', help='wait for mixed-mode debugger to attach')
  144. parser.add_option('-t', '--test', type='str', dest='tests', action='append', help='specifies a test to run')
  145. parser.add_option('-c', '--coverage', type='str', help='enable code coverage and specify filename')
  146. parser.add_option('-r', '--result-port', type='int', help='connect to port on localhost and send test results')
  147. (opts, _) = parser.parse_args()
  148. sys.path[0] = os.getcwd()
  149. if opts.result_port:
  150. _channel = _IpcChannel(socket.create_connection(('127.0.0.1', opts.result_port)))
  151. sys.stdout = _TestOutput(sys.stdout, is_stdout = True)
  152. sys.stderr = _TestOutput(sys.stderr, is_stdout = False)
  153. if opts.secret and opts.port:
  154. from ptvsd.visualstudio_py_debugger import DONT_DEBUG, DEBUG_ENTRYPOINTS, get_code
  155. from ptvsd.attach_server import DEFAULT_PORT, enable_attach, wait_for_attach
  156. DONT_DEBUG.append(os.path.normcase(__file__))
  157. DEBUG_ENTRYPOINTS.add(get_code(main))
  158. enable_attach(opts.secret, ('127.0.0.1', getattr(opts, 'port', DEFAULT_PORT)), redirect_output = True)
  159. wait_for_attach()
  160. elif opts.mixed_mode:
  161. # For mixed-mode attach, there's no ptvsd and hence no wait_for_attach(),
  162. # so we have to use Win32 API in a loop to do the same thing.
  163. from time import sleep
  164. from ctypes import windll, c_char
  165. while True:
  166. if windll.kernel32.IsDebuggerPresent() != 0:
  167. break
  168. sleep(0.1)
  169. try:
  170. debugger_helper = windll['Microsoft.PythonTools.Debugger.Helper.x86.dll']
  171. except WindowsError:
  172. debugger_helper = windll['Microsoft.PythonTools.Debugger.Helper.x64.dll']
  173. isTracing = c_char.in_dll(debugger_helper, "isTracing")
  174. while True:
  175. if isTracing.value != 0:
  176. break
  177. sleep(0.1)
  178. cov = None
  179. try:
  180. if opts.coverage:
  181. try:
  182. import coverage
  183. cov = coverage.coverage(opts.coverage)
  184. cov.load()
  185. cov.start()
  186. except:
  187. pass
  188. tests = unittest.defaultTestLoader.loadTestsFromNames(opts.tests)
  189. runner = unittest.TextTestRunner(verbosity=0, resultclass=VsTestResult)
  190. result = runner.run(tests)
  191. sys.exit(not result.wasSuccessful())
  192. finally:
  193. if cov is not None:
  194. cov.stop()
  195. cov.save()
  196. cov.xml_report(outfile = opts.coverage + '.xml', omit=__file__)
  197. if _channel is not None:
  198. _channel.send_event(
  199. name='done'
  200. )
  201. _channel.socket.close()
  202. if __name__ == '__main__':
  203. main()