├── MANIFEST.in ├── .gitreview ├── openstack ├── __init__.py └── nose_plugin.py ├── setup.py └── README.rst /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | exclude .gitreview 3 | -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=review.opendev.org 3 | port=29418 4 | project=openstack/openstack-nose.git 5 | -------------------------------------------------------------------------------- /openstack/__init__.py: -------------------------------------------------------------------------------- 1 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 2 | 3 | # Copyright 2011 OpenStack LLC. 4 | # All Rights Reserved. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | # This ensures the openstack namespace is defined 19 | try: 20 | import pkg_resources 21 | pkg_resources.declare_namespace(__name__) 22 | except ImportError: 23 | import pkgutil 24 | __path__ = pkgutil.extend_path(__path__, __name__) 25 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | version = "0.11" 4 | 5 | setup(name="openstack.nose_plugin", 6 | version=version, 7 | description="openstack run_tests.py style output for nosetests", 8 | long_description=open("README.rst").read(), 9 | classifiers=[ 10 | 'Programming Language :: Python :: 2', 11 | 'Programming Language :: Python :: 2.6', 12 | 'Programming Language :: Python :: 2.7', 13 | 'Programming Language :: Python :: 3', 14 | 'Programming Language :: Python :: 3.3', 15 | ], 16 | keywords="nose", 17 | author="Jason K\xc3\xb6lker", 18 | author_email="jason@koelker.net", 19 | url="https://github.com/jkoelker/openstack-nose", 20 | license="Apache Software License", 21 | packages=find_packages(exclude=["ez_setup", "examples", "tests"]), 22 | install_requires=[ 23 | "nose", 24 | "colorama", 25 | "termcolor", 26 | ], 27 | entry_points=""" 28 | # -*- Entry points: -*- 29 | [nose.plugins.0.10] 30 | openstack.nose_plugin = openstack.nose_plugin:Openstack 31 | """, 32 | namespace_packages=['openstack'], 33 | ) 34 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | openstack.nose_plugin - Nose plugin for openstack style test output 2 | =================================================================== 3 | 4 | openstack.nose_plugin provides a nose plugin that allow's nosetests output to 5 | mimic the output of openstack's run_tests.py. 6 | 7 | Installation 8 | ------------ 9 | pip install openstack.nose_plugin 10 | 11 | Usage 12 | ----- 13 | 14 | The following options are available: 15 | 16 | --with-openstack Enable plugin Openstack: Nova style output 17 | generator 18 | [NOSE_WITH_OPENSTACK] 19 | --openstack-red=OPENSTACK_RED 20 | Colorize run times greater than value red. 21 | [NOSE_OPENSTACK_RED] or 1.0 22 | --openstack-yellow=OPENSTACK_YELLOW 23 | Colorize run times greater than value yellow. 24 | [NOSE_OPENSTACK_RED] or 0.25 25 | --openstack-show-elapsed 26 | Show the elaped runtime of tests. 27 | [NOSE_OPENSTACK_SHOW_ELAPSED] 28 | --openstack-color Colorize output. [NOSE_OPENSTACK_COLOR] 29 | --openstack-nocolor Disable colorize output. [NOSE_OPENSTACK_COLOR] 30 | --openstack-num-slow=OPENSTACK_NUM_SLOW 31 | Number top slowest tests to report. 32 | [NOSE_OPENSTACK_NUM_SLOW] 33 | -------------------------------------------------------------------------------- /openstack/nose_plugin.py: -------------------------------------------------------------------------------- 1 | """ 2 | Openstack run_tests.py style output for nosetests 3 | """ 4 | 5 | import heapq 6 | import logging 7 | import os 8 | import sys 9 | import time 10 | 11 | import colorama 12 | import termcolor 13 | from nose import plugins 14 | from nose import suite 15 | from nose import core 16 | from nose import SkipTest 17 | 18 | 19 | log = logging.getLogger("openstack.nose") 20 | 21 | 22 | class Colorizer(object): 23 | def __init__(self, stream): 24 | self.stream = stream 25 | colorama.init() 26 | 27 | def write(self, text, color): 28 | self.stream.write(termcolor.colored(text, color)) 29 | 30 | def writeln(self, text, color): 31 | self.stream.writeln(termcolor.colored(text, color)) 32 | 33 | 34 | class NullColorizer(object): 35 | def __init__(self, stream): 36 | self.stream = stream 37 | 38 | def write(self, text, color): 39 | self.stream.write(text) 40 | 41 | def writeln(self, text, color): 42 | self.stream.writeln(text) 43 | 44 | 45 | class Openstack(plugins.Plugin): 46 | """Nova style output generator""" 47 | 48 | name = "openstack" 49 | times = {} 50 | 51 | def _get_color(self, elapsed): 52 | if elapsed > self.red: 53 | return "red" 54 | elif elapsed > self.yellow: 55 | return "yellow" 56 | else: 57 | return "green" 58 | 59 | def _get_name(self, test): 60 | address = test.address() 61 | if address[2] is None: 62 | return None, None 63 | parts = address[2].split(".") 64 | if len(parts) == 2: 65 | return tuple(parts) 66 | else: 67 | return None, parts[0] 68 | 69 | def _writeResult(self, test, long_result, color, short_result, 70 | show_elapsed=True): 71 | if isinstance(test, suite.ContextSuite): 72 | return 73 | 74 | name = self._get_name(test) 75 | elapsed = self.times[name][1] - self.times[name][0] 76 | item = (elapsed, name) 77 | if len(self._slow_tests) >= self.num_slow: 78 | heapq.heappushpop(self._slow_tests, item) 79 | else: 80 | heapq.heappush(self._slow_tests, item) 81 | 82 | if self.show_all: 83 | self.colorizer.write(long_result, color) 84 | if self.show_elapsed and show_elapsed: 85 | color = self._get_color(elapsed) 86 | self.colorizer.write(" %.2f" % elapsed, color) 87 | self.stream.writeln() 88 | elif self.dots: 89 | self.colorizer.write(short_result, color) 90 | self.stream.flush() 91 | 92 | # These functions are for patching into the result object 93 | def _add_error(self, test, err): 94 | if isinstance(test, suite.ContextSuite): 95 | return 96 | if isinstance(err[1], SkipTest): 97 | return self._add_skip(test, err) 98 | 99 | name = self._get_name(test) 100 | self.times[name].append(time.time()) 101 | self._writeResult(test, "ERROR", "red", "E") 102 | self._result_addError(test, err) 103 | 104 | def _add_failure(self, test, err): 105 | if isinstance(test, suite.ContextSuite): 106 | return 107 | 108 | name = self._get_name(test) 109 | self.times[name].append(time.time()) 110 | self._writeResult(test, "FAIL", "red", "F") 111 | self._result_addFailure(test, err) 112 | 113 | def _add_success(self, test): 114 | if isinstance(test, suite.ContextSuite): 115 | return 116 | 117 | name = self._get_name(test) 118 | self.times[name].append(time.time()) 119 | self._writeResult(test, "OK ", "green", ".") 120 | self._result_addSuccess(test) 121 | 122 | def _add_skip(self, test, reason): 123 | if isinstance(test, suite.ContextSuite): 124 | return 125 | 126 | name = self._get_name(test) 127 | self.times[name].append(time.time()) 128 | self._writeResult(test, "SKIP", "blue", "S", show_elapsed=False) 129 | self._result_addSkip(test, reason) 130 | 131 | def _print_errors(self): 132 | if self.dots or self.show_all: 133 | self.stream.writeln() 134 | self._result_printErrors() 135 | 136 | def _is_a_tty(self): 137 | return getattr(os, 'isatty') and os.isatty(sys.stdout.fileno()) 138 | 139 | def configure(self, options, conf): 140 | plugins.Plugin.configure(self, options, conf) 141 | self.conf = conf 142 | self.red = float(options.openstack_red) 143 | self.yellow = float(options.openstack_yellow) 144 | self.show_elapsed = options.openstack_show_elapsed 145 | self.num_slow = int(options.openstack_num_slow) 146 | self.color = options.openstack_color 147 | self.use_stdout = options.openstack_stdout 148 | self.colorizer = None 149 | self._cls = None 150 | self._slow_tests = [] 151 | 152 | def options(self, parser, env): 153 | plugins.Plugin.options(self, parser, env) 154 | parser.add_option("--openstack-red", 155 | default=env.get("NOSE_OPENSTACK_RED", 1.0), 156 | dest="openstack_red", 157 | help="Colorize run times greater than value red. " 158 | "[NOSE_OPENSTACK_RED] or 1.0") 159 | parser.add_option("--openstack-yellow", 160 | default=env.get("NOSE_OPENSTACK_YELLOW", 0.25), 161 | dest="openstack_yellow", 162 | help="Colorize run times greater than value " 163 | "yellow. [NOSE_OPENSTACK_RED] or 0.25") 164 | parser.add_option("--openstack-show-elapsed", action="store_true", 165 | default=env.get("NOSE_OPENSTACK_SHOW_ELAPSED"), 166 | dest="openstack_show_elapsed", 167 | help="Show the elaped runtime of tests. " 168 | "[NOSE_OPENSTACK_SHOW_ELAPSED]") 169 | parser.add_option("--openstack-color", action="store_true", 170 | default=env.get("NOSE_OPENSTACK_COLOR"), 171 | dest="openstack_color", 172 | help="Colorize output. [NOSE_OPENSTACK_COLOR]") 173 | parser.add_option("--openstack-nocolor", action="store_false", 174 | default=env.get("NOSE_OPENSTACK_COLOR"), 175 | dest="openstack_color", 176 | help=("Disable colorized output. " 177 | "[NOSE_OPENSTACK_COLOR]")) 178 | parser.add_option("--openstack-num-slow", 179 | dest="openstack_num_slow", 180 | default=env.get("NOSE_OPENSTACK_NUM_SLOW", 5), 181 | help="Number top slowest tests to report. " 182 | "[NOSE_OPENSTACK_NUM_SLOW]") 183 | parser.add_option("--openstack-stdout", action="store_true", 184 | default=env.get("NOSE_OPENSTACK_STDOUT"), 185 | dest="openstack_stdout", 186 | help="Output to stdout. [NOSE_OPENSTACK_STDOUT]") 187 | 188 | def prepareTestRunner(self, runner): 189 | if (not isinstance(runner, core.TextTestRunner) or 190 | not self.use_stdout): 191 | return 192 | 193 | new_runner = core.TextTestRunner(stream=sys.__stdout__, 194 | descriptions=runner.descriptions, 195 | verbosity=runner.verbosity, 196 | config=runner.config) 197 | return new_runner 198 | 199 | def prepareTestResult(self, result): 200 | self._result = result 201 | self.show_all = result.showAll 202 | self.dots = result.dots 203 | self.separator1 = result.separator1 204 | self.separator2 = result.separator2 205 | result.showAll = False 206 | result.dots = False 207 | 208 | self._result_addError = result.addError 209 | result.addError = self._add_error 210 | 211 | self._result_addFailure = result.addFailure 212 | result.addFailure = self._add_failure 213 | 214 | self._result_addSuccess = result.addSuccess 215 | result.addSuccess = self._add_success 216 | 217 | if hasattr(result, "addSkip"): 218 | self._result_addSkip = result.addSkip 219 | result.addSkip = self._add_skip 220 | 221 | self._result_printErrors = result.printErrors 222 | result.printErrors = self._print_errors 223 | 224 | def report(self, stream): 225 | slow_tests = [item for item in self._slow_tests 226 | if self._get_color(item[0]) != "green"] 227 | if slow_tests: 228 | slow_total_time = sum(item[0] for item in slow_tests) 229 | stream.writeln("Slowest %i tests took %.2f secs:" 230 | % (len(slow_tests), slow_total_time)) 231 | for time, test in sorted(slow_tests, reverse=True): 232 | name = '.'.join([str(i) for i in test]) 233 | self.colorizer.writeln(" %.2f %s" % (time, name), 234 | self._get_color(time)) 235 | 236 | def setOutputStream(self, stream): 237 | self.stream = stream 238 | if self.color and self._is_a_tty: 239 | self.colorizer = Colorizer(self.stream) 240 | else: 241 | self.colorizer = NullColorizer(self.stream) 242 | self.stream.writeln() 243 | 244 | def startTest(self, test): 245 | if isinstance(test, suite.ContextSuite): 246 | return 247 | 248 | cls, name = self._get_name(test) 249 | if self.show_all: 250 | if cls != self._cls: 251 | self.stream.writeln(str(cls)) 252 | self._cls = cls 253 | self.stream.write(' {0}'.format(name).ljust(65)) 254 | self.stream.flush() 255 | self.times[(cls, name)] = [time.time()] 256 | --------------------------------------------------------------------------------