/usr/lib/python3/dist-packages/plainbox/testing_utils/testcases.py is in python3-plainbox 0.5.3-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 | # This file is part of Checkbox.
#
# Copyright 2012 Canonical Ltd.
# Written by:
# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
#
# Checkbox is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3,
# as published by the Free Software Foundation.
#
# Checkbox is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
"""
:mod:`plainbox.testing_utils.testcases` -- additional TestCase classes
======================================================================
Implementation of additional TestCase classes that aid in testing.
"""
from unittest import TestCase
from unittest.util import strclass
def load_tests(loader, suite, pattern):
# Because of the default patterns used to discover test cases
# we need to prevent the unittest machinery from treating the
# TestCase* classes as actual tests.
#
# The discovery mechanism has little-known feature whereas the discovery
# can be customized on a per-package and per-module level by defining the
# load_tests() function. This function must return a TestSuite object
#
# Here we simply return an empty suite.
return loader.suiteClass([])
# Treat this as a part of TestCase implementation detail, removing it from the
# tracebacks that are reported to the developer.
__unittest = True
class TestCaseParameters:
"""
Class for holding test case parameters
Instances of this class are provided as the .parameters attribute to
instances of TestCaseWithParameters during run().
The class supports attribute lookup so tests can be written in convenient
form. Having a test case with parameters 'foo' and 'bar' they can be
accessed as self.parameters.foo and self.parameters.bar respectively.
"""
__slots__ = ('_names', '_values')
def __init__(self, names, values):
"""
Initialize with a tuple of parameter names and values.
Both arguments have to be tuples.
"""
self._names = names
self._values = values
def __eq__(self, other):
if not isinstance(other, TestCaseParameters):
return NotImplemented
return (self._names == other._names
and self._values == other._values)
def __getattr__(self, attr):
try:
return self._values[self._names.index(attr)]
except (ValueError, LookupError):
# index() raises ValueError
# [] raises LookupError subclass
raise AttributeError(attr)
def __str__(self):
return ", ".join([
"{}: {}".format(name, value)
for name, value in zip(self._names, self._values)])
def __repr__(self):
return "<{} {}>".format(self.__class__.__name__, str(self))
class TestCaseWithParameters(TestCase):
"""
TestCase parametrized by any number of named parameters.
This class can save a lot of typing or creating dummy base classes and
other implementation tricks that aim to reduce duplication in code that
tests multiple values of a parameter.
For all intents and purposes this is just a standard TestClass instance,
what makes it different are the few tricks employed to make it appear as a
collection of test cases instead.
Each TestCase created from the test_** methods will be invoked for all the
parameter values. Any number of parameters that can be provided. A tuple of
values must be provided for each parameter.
In simple cases the developer only needs to provide two class attributes,
parameter_names and parameter_values. In more complex cases behavior can be
customized by defining two class methods, get_parameter_names() and
get_parameter_values(). Note that you _must_ define a non-empty list of
parameter values, otherwise this test will behave as if it never existed
(analogous how multiplication by zero works).
.. note::
Technical note for tinkerers and subclass authors. Python unittest
framework is pretty annoying to work with or extend. In practice you
should always keep the source code (of a particular python version)
open to reason about it.
Parametrization is implemented by creating additional instances of
TestCaseWithParameters with immutable, bound, parameters. That logic is
implemented in run(). The multiplication of TestCase instances happens
in _parametrize(). If your sub-class needs to do something special
there you might need to override it.
Ideally the unittest framework could allow customizing discovery in a
standard way. If that were true then we could instantiate parametrized
copies early and then let the normal run() mechanics work. Sadly this
is not the case.
Most special python methods also had to be overridden to take account
of the new _parameters instance attribute. This includes __str__(),
__repr__(), __eq__() and __hash__().
Additional methods unique to unittest framework were implemented to
convey the value of the parameter back to the user / developer. Those
include id() and countTestCases()
"""
def __init__(self, methodName="runTest", parameters=None):
"""
Overridden method of TestCase
Instantiates a new TestCase that will run a given method. By default
creates the 'unparameterized' version which spawns real test cases
(with the bound parameter) when .run() is called.
"""
super(TestCaseWithParameters, self).__init__(methodName)
self._parameters = parameters
@property
def parameters(self):
"""
Return the value of parameters that specialize this test case
Normal instances always return None here, the special, parametrized,
instance that is constructed once for each parameter value during
run() (where testing actually happens), returns the real value.
"""
return self._parameters
parameter_names = ()
@classmethod
def get_parameter_names(cls):
"""
Return a tuple of parameters that affect this test case.
"""
return cls.parameter_names
parameter_values = ()
@classmethod
def get_parameter_values(cls):
"""
Return a tuple of tuple of values that should be mapped to subsequent
attributes named, as returned by get_parameter_names()
"""
return cls.parameter_values
def _parametrize(self, parameters):
"""
Internal implementation method of TestCaseWithParameters.
Creates a new instance of the sub-class that is about to be tested
binding the particular value of the parameters supplied. This is
where test cases are actually instantiated / multiplied.
"""
return type(self)(self._testMethodName, parameters)
def countTestCases(self):
"""
Overridden method of unittest.TestCase()
Behaves different depending on whether it is called on the original
instance or the parametrized instance. The original instance
behaves like a TestSuite, returning the number of parameter values.
Each parametrized instance (created with _parametrize()) returns 1
"""
if self.parameters is None:
try:
return len(self.get_parameter_values())
except TypeError:
# if get_parameter_values() returns a generator then
# we need to count it ourselves
return len(list(self.get_parameter_values()))
else:
return 1
def run(self, result=None):
"""
Overridden version of TestCase.run()
Creates additional instances of the class being tested, one for each
value returned by get_parameter_values() by calling _parametrize().
Each of the parametrized instances is tested normally.
"""
# Get a result object if we don't have one
if result is None:
result = self.defaultTestResult()
# Get the list of parameter names
names = self.get_parameter_names()
# For each list of parameter values:
for iteration_index, values in enumerate(self.get_parameter_values()):
if len(values) != len(names):
raise RuntimeError((
"incorrect get_parameter_values() or parameter_values for"
" iteration {}. Expected to see {} item but saw {} instead"
).format(iteration_index, len(names), len(values)))
# Construct the parameter placeholder
parameters = TestCaseParameters(names, values)
# Construct a parametrized version of this test case
parametrized_test_case = self._parametrize(parameters)
# Super-call the run() method of the new instance
super(TestCaseWithParameters, parametrized_test_case).run(result)
def id(self):
"""
Overridden version of TestCase.id()
This is an internal implementation detail of TestCase, it is used in
certain places instead of __str__(). It behaves very similar to what
__str__() does, except that it displays the class name differently
"""
if self.parameters is None:
return "{}.{} [<unparameterized>]".format(
strclass(self.__class__), self._testMethodName)
else:
return "{}.{} [{}]".format(
strclass(self.__class__), self._testMethodName,
self.parameters)
def __str__(self):
"""
Overridden version of TestCase.__str__()
This version takes the current value of parameters into account. During
testing in run(), when parameters are not None, they are added to the
value returned by the superclass. At other times the string
<unparameterized> is used.
"""
if self.parameters is None:
return "{} [<unparameterized>]".format(
super(TestCaseWithParameters, self).__str__())
else:
return "{} [{}]".format(
super(TestCaseWithParameters, self).__str__(),
self.parameters)
def __repr__(self):
"""
Overridden version of TestCase.__repr__()
This version displays the value of the parameters attribute
"""
return "<{} testMethod={} parameters={!r}>".format(
strclass(self.__class__), self._testMethodName,
self.parameters)
def __eq__(self, other):
"""
Overridden version of TestCase.__eq__()
This version also compares the parameters attribute
"""
if not isinstance(other, TestCaseWithParameters):
return NotImplemented
return (self._testMethodName == other._testMethodName
and self._parameters == other.parameters)
def __hash__(self):
"""
Overridden version of TestCase.__hash__()
This version also uses the parameters attribute
"""
return hash((type(self), self._testMethodName, self._parameters))
|