Shared filesystem management project for OpenStack.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

334 lines
12KB

  1. #!/usr/bin/env python
  2. # Copyright (c) 2013, Nebula, Inc.
  3. # Copyright 2010 United States Government as represented by the
  4. # Administrator of the National Aeronautics and Space Administration.
  5. # All Rights Reserved.
  6. #
  7. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  8. # not use this file except in compliance with the License. You may obtain
  9. # a copy of the License at
  10. #
  11. # http://www.apache.org/licenses/LICENSE-2.0
  12. #
  13. # Unless required by applicable law or agreed to in writing, software
  14. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  15. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  16. # License for the specific language governing permissions and limitations
  17. # under the License.
  18. #
  19. # Colorizer Code is borrowed from Twisted:
  20. # Copyright (c) 2001-2010 Twisted Matrix Laboratories.
  21. #
  22. # Permission is hereby granted, free of charge, to any person obtaining
  23. # a copy of this software and associated documentation files (the
  24. # "Software"), to deal in the Software without restriction, including
  25. # without limitation the rights to use, copy, modify, merge, publish,
  26. # distribute, sublicense, and/or sell copies of the Software, and to
  27. # permit persons to whom the Software is furnished to do so, subject to
  28. # the following conditions:
  29. #
  30. # The above copyright notice and this permission notice shall be
  31. # included in all copies or substantial portions of the Software.
  32. #
  33. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  34. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  35. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  36. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  37. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  38. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  39. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  40. """Display a subunit stream through a colorized unittest test runner."""
  41. import heapq
  42. import sys
  43. import unittest
  44. import six
  45. import subunit
  46. import testtools
  47. class _AnsiColorizer(object):
  48. """Colorizer allows callers to write text in a particular color.
  49. A colorizer is an object that loosely wraps around a stream, allowing
  50. callers to write text to the stream in a particular color.
  51. Colorizer classes must implement C{supported()} and C{write(text, color)}.
  52. """
  53. _colors = dict(black=30, red=31, green=32, yellow=33,
  54. blue=34, magenta=35, cyan=36, white=37)
  55. def __init__(self, stream):
  56. self.stream = stream
  57. def supported(cls, stream=sys.stdout):
  58. """Check is the current platform supports coloring terminal output.
  59. A class method that returns True if the current platform supports
  60. coloring terminal output using this method. Returns False otherwise.
  61. """
  62. if not stream.isatty():
  63. return False # auto color only on TTYs
  64. try:
  65. import curses
  66. except ImportError:
  67. return False
  68. else:
  69. try:
  70. try:
  71. return curses.tigetnum("colors") > 2
  72. except curses.error:
  73. curses.setupterm()
  74. return curses.tigetnum("colors") > 2
  75. except Exception:
  76. # guess false in case of error
  77. return False
  78. supported = classmethod(supported)
  79. def write(self, text, color):
  80. """Write the given text to the stream in the given color.
  81. @param text: Text to be written to the stream.
  82. @param color: A string label for a color. e.g. 'red', 'white'.
  83. """
  84. color = self._colors[color]
  85. self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text))
  86. class _Win32Colorizer(object):
  87. """See _AnsiColorizer docstring."""
  88. def __init__(self, stream):
  89. import win32console
  90. red, green, blue, bold = (win32console.FOREGROUND_RED,
  91. win32console.FOREGROUND_GREEN,
  92. win32console.FOREGROUND_BLUE,
  93. win32console.FOREGROUND_INTENSITY)
  94. self.stream = stream
  95. self.screenBuffer = win32console.GetStdHandle(
  96. win32console.STD_OUT_HANDLE)
  97. self._colors = {
  98. 'normal': red | green | blue,
  99. 'red': red | bold,
  100. 'green': green | bold,
  101. 'blue': blue | bold,
  102. 'yellow': red | green | bold,
  103. 'magenta': red | blue | bold,
  104. 'cyan': green | blue | bold,
  105. 'white': red | green | blue | bold,
  106. }
  107. def supported(cls, stream=sys.stdout):
  108. try:
  109. import win32console
  110. screenBuffer = win32console.GetStdHandle(
  111. win32console.STD_OUT_HANDLE)
  112. except ImportError:
  113. return False
  114. import pywintypes
  115. try:
  116. screenBuffer.SetConsoleTextAttribute(
  117. win32console.FOREGROUND_RED |
  118. win32console.FOREGROUND_GREEN |
  119. win32console.FOREGROUND_BLUE)
  120. except pywintypes.error:
  121. return False
  122. else:
  123. return True
  124. supported = classmethod(supported)
  125. def write(self, text, color):
  126. color = self._colors[color]
  127. self.screenBuffer.SetConsoleTextAttribute(color)
  128. self.stream.write(text)
  129. self.screenBuffer.SetConsoleTextAttribute(self._colors['normal'])
  130. class _NullColorizer(object):
  131. """See _AnsiColorizer docstring."""
  132. def __init__(self, stream):
  133. self.stream = stream
  134. def supported(cls, stream=sys.stdout):
  135. return True
  136. supported = classmethod(supported)
  137. def write(self, text, color):
  138. self.stream.write(text)
  139. def get_elapsed_time_color(elapsed_time):
  140. if elapsed_time > 1.0:
  141. return 'red'
  142. elif elapsed_time > 0.25:
  143. return 'yellow'
  144. else:
  145. return 'green'
  146. class OpenStackTestResult(testtools.TestResult):
  147. def __init__(self, stream, descriptions, verbosity):
  148. super(OpenStackTestResult, self).__init__()
  149. self.stream = stream
  150. self.showAll = verbosity > 1
  151. self.num_slow_tests = 10
  152. self.slow_tests = [] # this is a fixed-sized heap
  153. self.colorizer = None
  154. # NOTE(vish): reset stdout for the terminal check
  155. stdout = sys.stdout
  156. sys.stdout = sys.__stdout__
  157. for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]:
  158. if colorizer.supported():
  159. self.colorizer = colorizer(self.stream)
  160. break
  161. sys.stdout = stdout
  162. self.start_time = None
  163. self.last_time = {}
  164. self.results = {}
  165. self.last_written = None
  166. def _writeElapsedTime(self, elapsed):
  167. color = get_elapsed_time_color(elapsed)
  168. self.colorizer.write(" %.2f" % elapsed, color)
  169. def _addResult(self, test, *args):
  170. try:
  171. name = test.id()
  172. except AttributeError:
  173. name = 'Unknown.unknown'
  174. test_class, test_name = name.rsplit('.', 1)
  175. elapsed = (self._now() - self.start_time).total_seconds()
  176. item = (elapsed, test_class, test_name)
  177. if len(self.slow_tests) >= self.num_slow_tests:
  178. heapq.heappushpop(self.slow_tests, item)
  179. else:
  180. heapq.heappush(self.slow_tests, item)
  181. self.results.setdefault(test_class, [])
  182. self.results[test_class].append((test_name, elapsed) + args)
  183. self.last_time[test_class] = self._now()
  184. self.writeTests()
  185. def _writeResult(self, test_name, elapsed, long_result, color,
  186. short_result, success):
  187. if self.showAll:
  188. self.stream.write(' %s' % str(test_name).ljust(66))
  189. self.colorizer.write(long_result, color)
  190. if success:
  191. self._writeElapsedTime(elapsed)
  192. self.stream.writeln()
  193. else:
  194. self.colorizer.write(short_result, color)
  195. def addSuccess(self, test):
  196. super(OpenStackTestResult, self).addSuccess(test)
  197. self._addResult(test, 'OK', 'green', '.', True)
  198. def addFailure(self, test, err):
  199. if test.id() == 'process-returncode':
  200. return
  201. super(OpenStackTestResult, self).addFailure(test, err)
  202. self._addResult(test, 'FAIL', 'red', 'F', False)
  203. def addError(self, test, err):
  204. super(OpenStackTestResult, self).addFailure(test, err)
  205. self._addResult(test, 'ERROR', 'red', 'E', False)
  206. def addSkip(self, test, reason=None, details=None):
  207. super(OpenStackTestResult, self).addSkip(test, reason, details)
  208. self._addResult(test, 'SKIP', 'blue', 'S', True)
  209. def startTest(self, test):
  210. self.start_time = self._now()
  211. super(OpenStackTestResult, self).startTest(test)
  212. def writeTestCase(self, cls):
  213. if not self.results.get(cls):
  214. return
  215. if cls != self.last_written:
  216. self.colorizer.write(cls, 'white')
  217. self.stream.writeln()
  218. for result in self.results[cls]:
  219. self._writeResult(*result)
  220. del self.results[cls]
  221. self.stream.flush()
  222. self.last_written = cls
  223. def writeTests(self):
  224. time = self.last_time.get(self.last_written, self._now())
  225. if not self.last_written or (self._now() - time).total_seconds() > 2.0:
  226. diff = 3.0
  227. while diff > 2.0:
  228. classes = self.results.keys()
  229. oldest = min(classes, key=lambda x: self.last_time[x])
  230. diff = (self._now() - self.last_time[oldest]).total_seconds()
  231. self.writeTestCase(oldest)
  232. else:
  233. self.writeTestCase(self.last_written)
  234. def done(self):
  235. self.stopTestRun()
  236. def stopTestRun(self):
  237. for cls in list(six.iterkeys(self.results)):
  238. self.writeTestCase(cls)
  239. self.stream.writeln()
  240. self.writeSlowTests()
  241. def writeSlowTests(self):
  242. # Pare out 'fast' tests
  243. slow_tests = [item for item in self.slow_tests
  244. if get_elapsed_time_color(item[0]) != 'green']
  245. if slow_tests:
  246. slow_total_time = sum(item[0] for item in slow_tests)
  247. slow = ("Slowest %i tests took %.2f secs:"
  248. % (len(slow_tests), slow_total_time))
  249. self.colorizer.write(slow, 'yellow')
  250. self.stream.writeln()
  251. last_cls = None
  252. # sort by name
  253. for elapsed, cls, name in sorted(slow_tests,
  254. key=lambda x: x[1] + x[2]):
  255. if cls != last_cls:
  256. self.colorizer.write(cls, 'white')
  257. self.stream.writeln()
  258. last_cls = cls
  259. self.stream.write(' %s' % str(name).ljust(68))
  260. self._writeElapsedTime(elapsed)
  261. self.stream.writeln()
  262. def printErrors(self):
  263. if self.showAll:
  264. self.stream.writeln()
  265. self.printErrorList('ERROR', self.errors)
  266. self.printErrorList('FAIL', self.failures)
  267. def printErrorList(self, flavor, errors):
  268. for test, err in errors:
  269. self.colorizer.write("=" * 70, 'red')
  270. self.stream.writeln()
  271. self.colorizer.write(flavor, 'red')
  272. self.stream.writeln(": %s" % test.id())
  273. self.colorizer.write("-" * 70, 'red')
  274. self.stream.writeln()
  275. self.stream.writeln("%s" % err)
  276. test = subunit.ProtocolTestCase(sys.stdin, passthrough=None)
  277. if sys.version_info[0:2] <= (2, 6):
  278. runner = unittest.TextTestRunner(verbosity=2)
  279. else:
  280. runner = unittest.TextTestRunner(verbosity=2,
  281. resultclass=OpenStackTestResult)
  282. if runner.run(test).wasSuccessful():
  283. exit_code = 0
  284. else:
  285. exit_code = 1
  286. sys.exit(exit_code)