1 # Copyright (C) 2013,2014,2015 Nippon Telegraph and Telephone Corporation.
2 # Copyright (C) 2013,2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
25 from nose import result
27 from nose import config
30 class _AnsiColorizer(object):
32 A colorizer is an object that loosely wraps around a stream, allowing
33 callers to write text to the stream in a particular color.
35 Colorizer classes must implement C{supported()} and C{write(text, color)}.
37 _colors = dict(black=30, red=31, green=32, yellow=33,
38 blue=34, magenta=35, cyan=36, white=37)
40 def __init__(self, stream):
43 def supported(cls, stream=sys.stdout):
45 A class method that returns True if the current platform supports
46 coloring terminal output using this method. Returns False otherwise.
48 if not stream.isatty():
49 return False # auto color only on TTYs
57 return curses.tigetnum("colors") > 2
60 return curses.tigetnum("colors") > 2
62 # guess false in case of error
64 supported = classmethod(supported)
66 def write(self, text, color):
68 Write the given text to the stream in the given color.
70 @param text: Text to be written to the stream.
72 @param color: A string label for a color. e.g. 'red', 'white'.
74 color = self._colors[color]
75 self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text))
78 class _Win32Colorizer(object):
80 See _AnsiColorizer docstring.
83 def __init__(self, stream):
84 from win32console import GetStdHandle, STD_OUT_HANDLE
85 from win32console import FOREGROUND_RED, FOREGROUND_BLUE
86 from win32console import FOREGROUND_GREEN, FOREGROUND_INTENSITY
87 red, green, blue, bold = (FOREGROUND_RED, FOREGROUND_GREEN,
88 FOREGROUND_BLUE, FOREGROUND_INTENSITY)
90 self.screenBuffer = GetStdHandle(STD_OUT_HANDLE)
92 'normal': red | green | blue,
94 'green': green | bold,
96 'yellow': red | green | bold,
97 'magenta': red | blue | bold,
98 'cyan': green | blue | bold,
99 'white': red | green | blue | bold}
101 def supported(cls, stream=sys.stdout):
104 screenBuffer = win32console.GetStdHandle(
105 win32console.STD_OUT_HANDLE)
110 screenBuffer.SetConsoleTextAttribute(
111 win32console.FOREGROUND_RED |
112 win32console.FOREGROUND_GREEN |
113 win32console.FOREGROUND_BLUE)
114 except pywintypes.error:
118 supported = classmethod(supported)
120 def write(self, text, color):
121 color = self._colors[color]
122 self.screenBuffer.SetConsoleTextAttribute(color)
123 self.stream.write(text)
124 self.screenBuffer.SetConsoleTextAttribute(self._colors['normal'])
127 class _NullColorizer(object):
129 See _AnsiColorizer docstring.
132 def __init__(self, stream):
135 def supported(cls, stream=sys.stdout):
137 supported = classmethod(supported)
139 def write(self, text, color):
140 self.stream.write(text)
143 class RyuTestResult(result.TextTestResult):
144 def __init__(self, *args, **kw):
145 result.TextTestResult.__init__(self, *args, **kw)
146 self._last_case = None
147 self.colorizer = None
148 # NOTE(vish, tfukushima): reset stdout for the terminal check
149 stdout = sys.__stdout__
150 sys.stdout = sys.__stdout__
151 for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]:
152 if colorizer.supported():
153 self.colorizer = colorizer(self.stream)
157 def getDescription(self, test):
160 # NOTE(vish, tfukushima): copied from unittest with edit to add color
161 def addSuccess(self, test):
162 unittest.TestResult.addSuccess(self, test)
164 self.colorizer.write("OK", 'green')
165 self.stream.writeln()
167 self.stream.write('.')
170 # NOTE(vish, tfukushima): copied from unittest with edit to add color
171 def addFailure(self, test, err):
172 unittest.TestResult.addFailure(self, test, err)
174 self.colorizer.write("FAIL", 'red')
175 self.stream.writeln()
177 self.stream.write('F')
180 # NOTE(vish, tfukushima): copied from unittest with edit to add color
181 def addError(self, test, err):
182 """Overrides normal addError to add support for errorClasses.
183 If the exception is a registered class, the error will be added
184 to the list for that class, not errors.
186 stream = getattr(self, 'stream', None)
189 exc_info = self._exc_info_to_string(err, test)
191 # This is for compatibility with Python 2.3.
192 exc_info = self._exc_info_to_string(err)
193 for cls, (storage, label, isfail) in self.errorClasses.items():
194 if result.isclass(ec) and issubclass(ec, cls):
197 storage.append((test, exc_info))
198 # Might get patched into a streamless result
199 if stream is not None:
202 detail = result._exception_detail(err[1])
204 message.append(detail)
205 stream.writeln(": ".join(message))
207 stream.write(label[:1])
209 self.errors.append((test, exc_info))
211 if stream is not None:
213 self.colorizer.write("ERROR", 'red')
214 self.stream.writeln()
218 def startTest(self, test):
219 unittest.TestResult.startTest(self, test)
220 current_case = test.test.__class__.__name__
223 if current_case != self._last_case:
224 self.stream.writeln(current_case)
225 self._last_case = current_case
226 # NOTE(salvatore-orlando):
227 # slightly changed in order to print test case class
228 # together with unit test name
230 ' %s' % str(test.test).ljust(60))
234 class RyuTestRunner(core.TextTestRunner):
235 def _makeResult(self):
236 return RyuTestResult(self.stream,
242 def run_tests(c=None):
243 logger = logging.getLogger()
244 hdlr = logging.StreamHandler()
245 formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
246 hdlr.setFormatter(formatter)
247 logger.addHandler(hdlr)
248 logger.setLevel(logging.DEBUG)
250 # NOTE(bgh): I'm not entirely sure why but nose gets confused here when
251 # calling run_tests from a plugin directory run_tests.py (instead of the
252 # main run_tests.py). It will call run_tests with no arguments and the
253 # testing of run_tests will fail (though the plugin tests will pass). For
254 # now we just return True to let the run_tests test pass.
258 runner = RyuTestRunner(stream=c.stream,
259 verbosity=c.verbosity,
261 return not core.run(config=c, testRunner=runner)
264 def add_method(cls, method_name, method):
265 """Add the method to the class dynamically, keeping unittest/nose happy."""
266 method.func_name = method_name
267 method.__name__ = method_name
269 methodtype = types.MethodType(method, cls)
270 if not hasattr(method, "__qualname__"):
271 method.__qualname__ = "%s.%s" % (cls.__qualname__, method_name)
273 methodtype = types.MethodType(method, None, cls)
274 setattr(cls, method_name, methodtype)