| 187 |
|
|
| 188 |
|
def collect(self):
|
| 189 |
|
"""Collect tests to run. This method is a generator.
|
| 190 |
|
"""
|
| 191 |
|
for test in self.find_tests():
|
| 192 |
|
yield self.load_from_name(test)
|
| 193 |
|
|
| 194 |
|
def exc_info(self):
|
| 195 |
|
return sys.exc_info()
|
| 196 |
|
|
| 197 |
|
def find_tests(self):
|
| 198 |
|
"""Find tests in the target directory.
|
| 199 |
|
"""
|
| 200 |
|
return self.find_in_dir(self.path)
|
| 201 |
|
|
| 202 |
|
def find_in_dir(self, dirname, package=None):
|
| 203 |
|
"""Find tests in a directory.
|
| 204 |
|
|
| 205 |
|
Each item in the directory is tested against self.selector, want_file
|
| 206 |
|
or want_directory as appropriate. Those that are wanted are returned.
|
| 207 |
|
"""
|
| 208 |
|
log.info("%s looking for tests in %s [%s]", self, dirname, package)
|
| 209 |
|
|
| 210 |
|
def test_last(a, b, m=self.conf.testMatch):
|
| 211 |
|
if m.search(a) and not m.search(b):
|
| 212 |
|
return 1
|
| 213 |
|
elif m.search(b) and not m.search(a):
|
| 214 |
|
return -1
|
| 215 |
|
return cmp(a, b)
|
| 216 |
|
|
| 217 |
|
if not os.path.isabs(dirname):
|
| 218 |
|
raise ValueError("Directory paths must be specified as "
|
| 219 |
|
"absolute paths (%s)" % dirname)
|
| 220 |
|
tests = []
|
| 221 |
|
|
| 222 |
|
entries = os.listdir(dirname)
|
| 223 |
|
|
| 224 |
|
# to ensure that lib paths are set up correctly before tests are
|
| 225 |
|
# run, examine directories that look like lib or package
|
| 226 |
|
# directories first and tests last
|
| 227 |
|
entries.sort(test_last)
|
| 228 |
|
for item in entries:
|
| 229 |
|
log.debug("candidate %s in %s", item, dirname)
|
| 230 |
|
path = os.path.join(dirname, item)
|
| 231 |
|
if os.path.isfile(path):
|
| 232 |
|
if self.selector.want_file(path, package):
|
| 233 |
|
tests.append(path)
|
| 234 |
|
elif os.path.isdir(path):
|
| 235 |
|
if self.selector.want_directory(path):
|
| 236 |
|
tests.append(path)
|
| 237 |
|
else:
|
| 238 |
|
# ignore non-file, non-path item
|
| 239 |
|
log.warning("%s in %s is neither file nor path", item, dirname)
|
| 240 |
|
if tests:
|
| 241 |
|
log.info("Collected tests %s in %s", tests, dirname)
|
| 242 |
|
return tests
|
| 243 |
|
|
| 244 |
|
def load_from_name(self, filename, path=None, package=None):
|
| 245 |
|
"""Load tests from a file, with an optional package prefix and load
|
| 246 |
|
path.
|
| 247 |
|
|
| 248 |
|
"""
|
| 249 |
|
log.debug("Load tests from %s", filename)
|
| 250 |
|
head, test = os.path.split(filename)
|
| 251 |
|
if path is None:
|
| 252 |
|
path = head
|
| 253 |
|
|
| 254 |
|
log.debug("Name %s is %s in %s", filename, test, path)
|
| 255 |
|
if os.path.isfile(filename):
|
| 256 |
|
if test.endswith('.py'):
|
| 257 |
|
# trim the extension of python files
|
| 258 |
|
test = test[:-3]
|
| 259 |
|
if package is not None:
|
| 260 |
|
test = "%s.%s" % (package, test)
|
| 261 |
|
return TestModule(test, path, self.selector, self.conf)
|
| 262 |
|
# give plugins a chance
|
| 263 |
|
res = call_plugins(self.plugins, 'load_from_name',
|
| 264 |
|
filename, path, package)
|
| 265 |
|
if res is not None:
|
| 266 |
|
return res
|
| 267 |
|
else:
|
| 268 |
|
raise Exception("%s is not a python source file" % filename)
|
| 269 |
|
elif os.path.isdir(filename):
|
| 270 |
|
init = os.path.join(filename, '__init__.py')
|
| 271 |
|
if not os.path.exists(init):
|
| 272 |
|
return TestDirectory(filename, self.selector, self.conf)
|
| 273 |
|
else:
|
| 274 |
|
if package is not None:
|
| 275 |
|
test = "%s.%s" % (package, test)
|
| 276 |
|
return TestModule(test, path, self.selector, self.conf)
|
| 277 |
|
else:
|
| 278 |
|
# FIXME give plugins a chance?
|
| 279 |
|
raise Exception("%s is not a directory or python source file" %
|
| 280 |
|
filename)
|
| 281 |
|
|
| 282 |
|
|
| 283 |
|
def run(self, result):
|
| 284 |
|
"""Collect and run all tests, running setup before and teardown
|
| 285 |
|
after.
|
| 286 |
|
"""
|
| 287 |
|
try:
|
| 288 |
|
self.setUp()
|
| 289 |
|
except KeyboardInterrupt:
|
| 290 |
|
raise
|
| 291 |
|
except:
|
| 292 |
|
result.addError(self, self.exc_info())
|
| 293 |
|
return
|
| 294 |
|
for test in self.collect():
|
| 295 |
|
log.debug("running test %s", test)
|
| 296 |
|
if result.shouldStop:
|
| 297 |
|
break
|
| 298 |
|
test(result)
|
| 299 |
|
try:
|
| 300 |
|
self.tearDown()
|
| 301 |
|
except KeyboardInterrupt:
|
| 302 |
|
raise
|
| 303 |
|
except:
|
| 304 |
|
result.addError(self, self.exc_info())
|
| 305 |
|
return result
|
| 306 |
|
|
| 307 |
|
def setUp(self):
|
| 308 |
|
pass
|
| 309 |
|
|
| 310 |
|
def shortDescription(self):
|
| 311 |
|
return str(self) # FIXME
|
| 312 |
|
|
| 313 |
|
def tearDown(self):
|
| 314 |
|
pass
|
| 355 |
|
|
| 356 |
|
class TestModule(TestCollector):
|
| 357 |
|
"""Test collector that collects tests from modules and packages.
|
| 358 |
|
|
| 359 |
|
This collector collects module members that match the testMatch
|
| 360 |
|
regular expression. For packages, it also collects any modules or
|
| 361 |
|
packages found in the package __path__ that match testMatch. For
|
| 362 |
|
modules that themselves do not match testMatch, the collector collects
|
| 363 |
|
doctests instead of test functions.
|
| 364 |
|
|
| 365 |
|
Before returning the first collected test, any defined setup method
|
| 366 |
|
will be run. Packages may define setup, setUp, setup_package or
|
| 367 |
|
setUpPackage, modules setup, setUp, setup_module, setupModule or
|
| 368 |
|
setUpModule. Likewise, teardown will be run if defined and if setup
|
| 369 |
|
ran successfully; teardown methods follow the same naming rules as
|
| 370 |
|
setup methods.
|
| 371 |
|
"""
|
| 372 |
|
from_directory = None
|
| 373 |
|
|
| 374 |
|
def __init__(self, module_name, path, selector, conf):
|
| 375 |
|
self.module_name = module_name
|
| 376 |
|
self.path = path
|
| 377 |
|
self.module = None
|
| 378 |
|
self.selector = selector
|
| 379 |
|
self.conf = conf
|
| 380 |
|
self.plugins = conf.plugins
|
| 381 |
|
|
| 382 |
|
def __repr__(self):
|
| 383 |
|
return "test module %s in %s" % (self.module_name, self.path)
|
| 384 |
|
__str__ = __repr__
|
| 385 |
|
|
| 386 |
|
def collect(self):
|
| 387 |
|
"""Collect tests in the module. Packages will also collect tests
|
| 388 |
|
in the package directory. Non-test modules will collect only
|
| 389 |
|
doctests.
|
| 390 |
|
"""
|
| 391 |
|
# find tests defined in the module proper -- if it's a test module
|
| 392 |
|
# and we want to examine it
|
| 393 |
|
if self.selector.want_module_tests(self.module):
|
| 394 |
|
log.debug("collect tests in %s", self.module.__name__)
|
| 395 |
|
for test in self.find_in_module(self.module):
|
| 396 |
|
yield test
|
| 397 |
|
|
| 398 |
|
# give plugins a chance
|
| 399 |
|
for plug in self.plugins:
|
| 400 |
|
# plugins need not define want_module_tests; in that case
|
| 401 |
|
# they will try to find tests in all modules
|
| 402 |
|
if hasattr(plug, 'tests_in_module'):
|
| 403 |
|
log.debug("collect tests in %s with plugin %s",
|
| 404 |
|
self.module.__name__, plug.name)
|
| 405 |
|
for test in plug.tests_in_module(self.module):
|
| 406 |
|
yield test
|
| 407 |
|
|
| 408 |
|
# recurse into all modules
|
| 409 |
|
if hasattr(self.module, '__path__'):
|
| 410 |
|
path = self.module.__path__[0]
|
| 411 |
|
for test in self.find_in_dir(path,
|
| 412 |
|
package=self.module.__name__):
|
| 413 |
|
# setting the package prefix means that we're
|
| 414 |
|
# loading from our own parent directory, since we're
|
| 415 |
|
# loading xxx.yyy, not just yyy, so ask the loader
|
| 416 |
|
# to load from self.path (the path we loaded from),
|
| 417 |
|
# not path (the path we're at now)
|
| 418 |
|
yield self.load_from_name(test, self.path,
|
| 419 |
|
self.module.__name__)
|
| 420 |
|
|
| 421 |
|
def find_in_module(self, module):
|
| 422 |
|
"""Find functions and classes matching testMatch, as well as
|
| 423 |
|
classes that descend from unittest.TestCase, return all found
|
| 424 |
|
(properly wrapped) as tests.
|
| 425 |
|
"""
|
| 426 |
|
def cmp_line(a, b):
|
| 427 |
|
"""Compare functions by their line numbers
|
| 428 |
|
"""
|
| 429 |
|
try:
|
| 430 |
|
a_ln = a.func_code.co_firstlineno
|
| 431 |
|
b_ln = b.func_code.co_firstlineno
|
| 432 |
|
except AttributeError:
|
| 433 |
|
return 0
|
| 434 |
|
return cmp(a_ln, b_ln)
|
| 435 |
|
|
| 436 |
|
entries = dir(module)
|
| 437 |
|
tests = []
|
| 438 |
|
func_tests = []
|
| 439 |
|
for item in entries:
|
| 440 |
|
test = getattr(module, item)
|
| 441 |
|
if isinstance(test, (type, types.ClassType)):
|
| 442 |
|
if self.selector.want_class(test):
|
| 443 |
|
tests.append(TestClass(test, self.selector, self.conf))
|
| 444 |
|
elif callable(test):
|
| 445 |
|
if not self.selector.want_function(test):
|
| 446 |
|
continue
|
| 447 |
|
# might be a generator
|
| 448 |
|
if self.is_generator(test):
|
| 449 |
|
func_tests.extend(self.generate_tests(test))
|
| 450 |
|
else:
|
| 451 |
|
# nope, simple functional test
|
| 452 |
|
func_tests.append(test)
|
| 453 |
|
|
| 454 |
|
# run functional tests in the order in which they are defined
|
| 455 |
|
func_tests.sort(cmp_line)
|
| 456 |
|
tests.extend([ FunctionTestCase(test,
|
| 457 |
|
from_directory=self.from_directory)
|
| 458 |
|
for test in func_tests ])
|
| 459 |
|
return tests
|
| 460 |
|
|
| 461 |
|
def generate_tests(self, test):
|
| 462 |
|
cases = []
|
| 463 |
|
for expr in test():
|
| 464 |
|
# build a closure to run the test, and give it a nice name
|
| 465 |
|
def run(expr=expr):
|
| 466 |
|
expr[0](*expr[1:])
|
| 467 |
|
run.__module__ = test.__module__
|
| 468 |
|
try:
|
| 469 |
|
run.__name__ = '%s:%s' % (test.__name__, expr[1:])
|
| 470 |
|
except TypeError:
|
| 471 |
|
# can't set func name in python 2.3
|
| 472 |
|
run.compat_func_name = '%s:%s' % (test.__name__, expr[1:])
|
| 473 |
|
pass
|
| 474 |
|
setup = ('setup', 'setUp', 'setUpFunc')
|
| 475 |
|
teardown = ('teardown', 'tearDown', 'tearDownFunc')
|
| 476 |
|
for name in setup:
|
| 477 |
|
if hasattr(test, name):
|
| 478 |
|
setattr(run, name, getattr(test, name))
|
| 479 |
|
break
|
| 480 |
|
for name in teardown:
|
| 481 |
|
if hasattr(test, name):
|
| 482 |
|
setattr(run, name, getattr(test, name))
|
| 483 |
|
break
|
| 484 |
|
cases.append(run)
|
| 485 |
|
return cases
|
| 486 |
|
|
| 487 |
|
def id(self):
|
| 488 |
|
return self.__str__()
|
| 489 |
|
|
| 490 |
|
def is_generator(self, test):
|
| 491 |
|
from compiler.consts import CO_GENERATOR
|
| 492 |
|
try:
|
| 493 |
|
return test.func_code.co_flags & CO_GENERATOR != 0
|
| 494 |
|
except AttributeError:
|
| 495 |
|
return False
|
| 496 |
|
|
| 497 |
|
def setUp(self):
|
| 498 |
|
"""Run any package or module setup function found. For packages, setup
|
| 499 |
|
functions may be named 'setupPackage', 'setup_package', 'setUp',
|
| 500 |
|
or 'setup'. For modules, setup functions may be named
|
| 501 |
|
'setupModule', 'setup_module', 'setUp', or 'setup'. The setup
|
| 502 |
|
function may optionally accept a single argument; in that case,
|
| 503 |
|
the test package or module will be passed to the setup function.
|
| 504 |
|
"""
|
| 505 |
|
self.module = _import(self.module_name, [self.path], self.conf)
|
| |
192 |
# FIXME if any plugin implements prepareTest, here must patch
|
| |
193 |
# unittest.TextTestRunner.run with run like nose's TextTestRunner
|
| |
194 |
return TestCollector(conf)
|
| 514 |
|
def tearDown(self):
|
| 515 |
|
"""Run any package or module teardown function found. For packages,
|
| 516 |
|
teardown functions may be named 'teardownPackage',
|
| 517 |
|
'teardown_package' or 'teardown'. For modules, teardown functions
|
| 518 |
|
may be named 'teardownModule', 'teardown_module' or
|
| 519 |
|
'teardown'. The teardown function may optionally accept a single
|
| 520 |
|
argument; in that case, the test package or module will be passed
|
| 521 |
|
to the teardown function.
|
| 522 |
|
|
| 523 |
|
The teardown function will be run only if any package or module
|
| 524 |
|
setup function completed successfully.
|
| 525 |
|
"""
|
| 526 |
|
if hasattr(self.module, '__path__'):
|
| 527 |
|
names = ['teardownPackage', 'teardown_package']
|
| 528 |
|
else:
|
| 529 |
|
names = ['teardownModule', 'teardown_module']
|
| 530 |
|
names += ['tearDown', 'teardown']
|
| 531 |
|
try_run(self.module, names)
|
| 532 |
|
|
| 533 |
|
|
| 534 |
|
class TestClass(TestCollector):
|
| 535 |
|
"""Collects tests from a class.
|
| 536 |
|
"""
|
| 537 |
|
def __init__(self, cls, selector, conf):
|
| 538 |
|
self.cls = cls
|
| 539 |
|
self.selector = selector
|
| 540 |
|
self.conf = conf
|
| 541 |
|
|
| 542 |
|
def __str__(self):
|
| 543 |
|
return self.__repr__()
|
| 544 |
|
|
| 545 |
|
def __repr__(self):
|
| 546 |
|
return 'test class %s' % self.cls
|
| 547 |
|
|
| 548 |
|
def collect(self):
|
| 549 |
|
cls = self.cls
|
| 550 |
|
log.debug("collect tests in class %s", cls)
|
| 551 |
|
for item in dir(cls):
|
| 552 |
|
attr = getattr(cls, item)
|
| 553 |
|
wanted = False
|
| 554 |
|
if callable(attr) and self.selector.want_method(attr):
|
| 555 |
|
if issubclass(cls, unittest.TestCase):
|
| 556 |
|
yield cls(item)
|
| 557 |
|
else:
|
| 558 |
|
yield MethodTestCase(cls, item)
|
| 559 |
|
|
| 560 |
|
|