# Copyright (c) 2015 testtools developers. See LICENSE for details. """A collection of sample TestCases. These are primarily of use in testing the test framework. """ from testscenarios import multiply_scenarios from testtools import TestCase from testtools.matchers import ( AfterPreprocessing, Contains, Equals, MatchesDict, MatchesListwise, ) def make_test_case(test_method_name, set_up=None, test_body=None, tear_down=None, cleanups=(), pre_set_up=None, post_tear_down=None): """Make a test case with the given behaviors. All callables are unary callables that receive this test as their argument. :param str test_method_name: The name of the test method. :param callable set_up: Implementation of setUp. :param callable test_body: Implementation of the actual test. Will be assigned to the test method. :param callable tear_down: Implementation of tearDown. :param cleanups: Iterable of callables that will be added as cleanups. :param callable pre_set_up: Called before the upcall to setUp(). :param callable post_tear_down: Called after the upcall to tearDown(). :return: A ``testtools.TestCase``. """ set_up = set_up if set_up else _do_nothing test_body = test_body if test_body else _do_nothing tear_down = tear_down if tear_down else _do_nothing pre_set_up = pre_set_up if pre_set_up else _do_nothing post_tear_down = post_tear_down if post_tear_down else _do_nothing return _ConstructedTest( test_method_name, set_up, test_body, tear_down, cleanups, pre_set_up, post_tear_down, ) class _ConstructedTest(TestCase): """A test case defined by arguments, rather than overrides.""" def __init__(self, test_method_name, set_up, test_body, tear_down, cleanups, pre_set_up, post_tear_down): """Construct a test case. See ``make_test_case`` for full documentation. """ setattr(self, test_method_name, self.test_case) super().__init__(test_method_name) self._set_up = set_up self._test_body = test_body self._tear_down = tear_down self._test_cleanups = cleanups self._pre_set_up = pre_set_up self._post_tear_down = post_tear_down def setUp(self): self._pre_set_up(self) super().setUp() for cleanup in self._test_cleanups: self.addCleanup(cleanup, self) self._set_up(self) def test_case(self): self._test_body(self) def tearDown(self): self._tear_down(self) super().tearDown() self._post_tear_down(self) def _do_nothing(case): pass _success = _do_nothing def _error(case): 1/0 # arbitrary non-failure exception def _failure(case): case.fail('arbitrary failure') def _skip(case): case.skipTest('arbitrary skip message') def _expected_failure(case): case.expectFailure('arbitrary expected failure', _failure, case) def _unexpected_success(case): case.expectFailure('arbitrary unexpected success', _success, case) behaviors = [ ('success', _success), ('fail', _failure), ('error', _error), ('skip', _skip), ('xfail', _expected_failure), ('uxsuccess', _unexpected_success), ] def _make_behavior_scenarios(stage): """Given a test stage, iterate over behavior scenarios for that stage. e.g. >>> list(_make_behavior_scenarios('set_up')) [('set_up=success', {'set_up_behavior': }), ('set_up=fail', {'set_up_behavior': }), ('set_up=error', {'set_up_behavior': }), ('set_up=skip', {'set_up_behavior': }), ('set_up=xfail', {'set_up_behavior': ), ('set_up=uxsuccess', {'set_up_behavior': })] Ordering is not consistent. """ return ( (f'{stage}={behavior}', {f'{stage}_behavior': function}) for (behavior, function) in behaviors ) def make_case_for_behavior_scenario(case): """Given a test with a behavior scenario installed, make a TestCase.""" cleanup_behavior = getattr(case, 'cleanup_behavior', None) cleanups = [cleanup_behavior] if cleanup_behavior else [] return make_test_case( case.getUniqueString(), set_up=getattr(case, 'set_up_behavior', _do_nothing), test_body=getattr(case, 'body_behavior', _do_nothing), tear_down=getattr(case, 'tear_down_behavior', _do_nothing), cleanups=cleanups, pre_set_up=getattr(case, 'pre_set_up_behavior', _do_nothing), post_tear_down=getattr(case, 'post_tear_down_behavior', _do_nothing), ) class _SetUpFailsOnGlobalState(TestCase): """Fail to upcall setUp on first run. Fail to upcall tearDown after. This simulates a test that fails to upcall in ``setUp`` if some global state is broken, and fails to call ``tearDown`` when the global state breaks but works after that. """ first_run = True def setUp(self): if not self.first_run: return super().setUp() def test_success(self): pass def tearDown(self): if not self.first_run: super().tearDown() self.__class__.first_run = False @classmethod def make_scenario(cls): case = cls('test_success') return { 'case': case, 'expected_first_result': _test_error_traceback( case, Contains('TestCase.tearDown was not called')), 'expected_second_result': _test_error_traceback( case, Contains('TestCase.setUp was not called')), } def _test_error_traceback(case, traceback_matcher): """Match result log of single test that errored out. ``traceback_matcher`` is applied to the text of the traceback. """ return MatchesListwise([ Equals(('startTest', case)), MatchesListwise([ Equals('addError'), Equals(case), MatchesDict({ 'traceback': AfterPreprocessing( lambda x: x.as_text(), traceback_matcher, ) }) ]), Equals(('stopTest', case)), ]) """ A list that can be used with testscenarios to test every deterministic sample case that we have. """ deterministic_sample_cases_scenarios = multiply_scenarios( _make_behavior_scenarios('set_up'), _make_behavior_scenarios('body'), _make_behavior_scenarios('tear_down'), _make_behavior_scenarios('cleanup'), ) + [ ('tear_down_fails_after_upcall', { 'post_tear_down_behavior': _error, }), ] """ A list that can be used with testscenarios to test every non-deterministic sample case that we have. """ nondeterministic_sample_cases_scenarios = [ ('setup-fails-global-state', _SetUpFailsOnGlobalState.make_scenario()), ]