blob: 5c93af7c93b280ef130c0fffc98048e4847759cc [file] [log] [blame]
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from __future__ import absolute_import
import unittest
from expect_tests.type_definitions import (
Test, Result, MultiTest, FuncCall, Bind)
from expect_tests.util import covers
def _SetUpClass(test_class):
inst = test_class('__init__')
inst.setUpClass()
return inst
def _TearDownClass(test_class_inst):
test_class_inst.tearDownClass()
def _RunTestCaseSingle(test_case, test_name, test_instance=None):
# The hack is so that unittest.TestCase has something to pretend is the
# test method without the BS of wrapping each method in a new TestCase
# class...
test_instance = test_instance or test_case('__init__')
test_method = getattr(test_instance, test_name)
# Check if the test is supposed to be skipped.
if (getattr(test_instance.__class__, '__unittest_skip__', False) or
getattr(test_method, '__unittest__skip__', False)):
skip_why = (getattr(test_instance.__class__, '__unittest_skip_why__', '') or
getattr(test_method, '__unittest_skip_why__', ''))
raise unittest.SkipTest(skip_why)
test_instance.setUp()
test_instance._test_failed_with_exception = False
try:
return Result(test_method())
except KeyboardInterrupt:
raise
except:
test_instance._test_failed_with_exception = True
raise
finally:
test_instance.tearDown()
def UnittestTestCase(test_case, verbose):
"""Yield a MultiTest or multiple Test instances for the unittest.TestCase
derived |test_case|.
If the TestCase has a field `__expect_tests_serial__` defined to be True, then
all test methods in the TestCase will be guaranteed to run in a single process
with the same instance. This is automatically set to True if your test class
relies on setUpClass/tearDownClass.
If the TestCase has a field `__expect_tests_atomic__` defined to be True, then
in the event of a test filter which matches any test method in |test_case|,
the ENTIRE |test_case| will be executed (i.e. the TestCase has interdependant
test methods). This should only need to be set for very poorly designed tests.
`__expect_tests_atomic__` implies `__expect_tests_serial__`.
@type test_case: unittest.TestCase
"""
if verbose:
# Set default maxDiff to no limit if verbosity is requested.
test_case.maxDiff = None
@covers(lambda: Test.covers_obj(test_case))
def _inner():
name_prefix = '.'.join((test_case.__module__, test_case.__name__))
def _tests_from_class(cls, *args, **kwargs):
for test_name in unittest.defaultTestLoader.getTestCaseNames(cls):
yield Test(
name_prefix + '.' + test_name,
FuncCall(_RunTestCaseSingle, cls, test_name, *args, **kwargs),
expect_dir=Test.expect_dir_obj(cls),
expect_base=cls.__name__ + '.' + test_name,
break_funcs=[getattr(cls, test_name)],
covers=Test.covers_obj(cls)
)
if hasattr(test_case, '__expect_tests_serial__'):
serial = getattr(test_case, '__expect_tests_serial__', False)
else:
default_setup = unittest.TestCase.setUpClass.__func__
default_teardown = unittest.TestCase.tearDownClass.__func__
serial = (
test_case.setUpClass.__func__ is not default_setup or
test_case.tearDownClass.__func__ is not default_teardown)
atomic = getattr(test_case, '__expect_tests_atomic__', False)
if atomic or serial:
yield MultiTest(
name_prefix,
FuncCall(_SetUpClass, test_case),
FuncCall(_TearDownClass, Bind(name='context')),
list(_tests_from_class(test_case,
test_instance=Bind(name='context'))),
atomic
)
else:
for test in _tests_from_class(test_case):
yield test
return _inner
def _is_unittest(obj):
return isinstance(obj, type) and issubclass(obj, unittest.TestCase)
def UnitTestModule(test_module):
"""Yield MultiTest's and/or Test's for the python module |test_module| which
contains zero or more unittest.TestCase implementations.
@type test_module: types.ModuleType
"""
for name in dir(test_module):
obj = getattr(test_module, name)
if _is_unittest(obj):
for test in UnittestTestCase(obj)():
yield test
# TODO(iannucci): Make this compatible with the awful load_tests hack?