def __init__(self, parent, fixture_test_case_data): """ Construct a test item. :param parent: The parent collector :type parent: ServicePlanTestCaseCollector :param fixture_test_case_data: The test case data :type fixture_test_case_data: FixtureTestCaseData """ test_name = 'test__{fixture}__{test}'.format( fixture=fixture_test_case_data.fixture_name, test=fixture_test_case_data.name, ) # First, we have to give the test plan test case class a method with this name, otherwise the TestCase class # cannot be instantiated. However, this should never be called, because the plugin overrides it. def fake_test(*_, **__): raise TypeError('The incorrect test method was called') fake_test.__doc__ = fixture_test_case_data.description if hasattr(parent.obj, test_name): # Lazy importing ensures that pytest-cov loads up coverage before this plugin loads other classes in PySOA from pysoa.test.plan.errors import StatusError raise StatusError( 'Duplicate test name "{name}" in fixture "{fixture}"'.format( name=fixture_test_case_data.name, fixture=fixture_test_case_data.fixture_file), ) setattr(parent.obj, test_name, fake_test) # Next we call super super(ServicePlanTestCaseTestFunction, self).__init__(name=test_name, parent=parent) # Finally, we do some magic to trick PyTest into accepting and displaying the actual location of the test (the # fixture file and the line in that file) instead of the PySOA test plan parsing code. self._location = ( self.session.fspath.bestrelpath( py.path.local(fixture_test_case_data.fixture_file)), fixture_test_case_data.line_number, self.location[2], ) self.fspath = py.path.local(fixture_test_case_data.fixture_file) self._nodeid = '::'.join( self.nodeid.split('::', 2)[:2] + [fixture_test_case_data.fixture_name, fixture_test_case_data.name ], ) self.fixture_test_case_data = fixture_test_case_data # Copy any class-level PyTest markers from the ServicePlanTestCase class to each fixture test case # This allows things like pytest.mark.skip[if], pytest.mark.django_db, etc. to work for mark in _get_unpacked_marks(parent.obj): mark_copy = getattr(MARK_GEN, mark.name)(*mark.args, **mark.kwargs) self.add_marker(mark_copy) if mark.name == 'skip' or (mark.name == 'skipif' and mark.args and mark.args[0]): PLUGIN_STATISTICS['fixture_tests_skipped'] += 1
def __init__(self, parent, fixture_test_case_data): # type: (ServicePlanTestInstanceCollector, FixtureTestCaseData) -> None """ Construct a test item. :param parent: The parent collector :param fixture_test_case_data: The test case data """ cls = parent.parent.obj # First we construct the test method and attach it to the test class test_name = 'plan__{fixture}__{test}'.format( fixture=fixture_test_case_data.fixture_name, test=fixture_test_case_data.name, ) if hasattr(cls, test_name): raise StatusError( 'Duplicate test name "{name}" in fixture "{fixture}"'.format( name=fixture_test_case_data.name, fixture=fixture_test_case_data.fixture_file), ) fixture_test_case_data.callable.__doc__ = fixture_test_case_data.description setattr(cls, test_name, fixture_test_case_data.callable) # Next we call super super(ServicePlanTestCaseTestFunction, self).__init__(name=test_name, parent=parent) # Thirdly, we do some magic to trick PyTest into accepting and displaying the actual location of the test (the # fixture file and the line in that file) instead of the PySOA test plan parsing code. self._location = ( self.session.fspath.bestrelpath( py.path.local(fixture_test_case_data.fixture_file)), fixture_test_case_data.line_number, self.location[2], ) self.fspath = py.path.local(fixture_test_case_data.fixture_file) self._nodeid = '::'.join( self.nodeid.split('::', 2)[:2] + [fixture_test_case_data.fixture_name, fixture_test_case_data.name ], ) self.fixture_test_case_data = fixture_test_case_data # Finally, copy any class-level PyTest markers from the ServicePlanTestCase class to each fixture test case # This allows things like pytest.mark.skip[if], pytest.mark.django_db, etc. to work skipped = False for mark in _get_unpacked_marks(cls): mark_copy = getattr(MARK_GEN, mark.name)(*mark.args, **mark.kwargs) self.add_marker(mark_copy) if mark.name == 'skip' or (mark.name == 'skipif' and mark.args and mark.args[0]): skipped = True if not skipped and fixture_test_case_data.skip: self.add_marker( pytest.mark.skip(reason=fixture_test_case_data.skip))
def substitute_variables(data, *sources): # type: (Union[MutableMapping, List], *Union[Mapping, List, Tuple, AbstractSet]) -> None """ Overlay [[NAME]] values with values from sources, if possible. """ for path in get_all_paths(data): try: value = path_get(data, path) except (KeyError, IndexError): continue if not value: continue if not isinstance(value, six.text_type): continue replacements = [{ 'token': m[0], 'full_path': m[1], 'action': m[2], 'action_path': m[3] if m[2] else None } for m in VARIABLE_SUBSTITUTION_RE.findall(value)] if not replacements: continue for replacement in replacements: find_path = replacement['full_path'] if replacement['action']: potential_action_name = replacement['action'].lower() for source in sources: if potential_action_name in source: # `action.#` paths don't denote a sublist, unlike most path expressions ... instead, the # entire `action.#` value is a key in a dict, so we need to escape it. The result is # `{action.#}.rest.of.path`. find_path = '{{{}}}{}'.format( potential_action_name, replacement['action_path']) try: replace_with = _find_path_in_sources(find_path, *sources) except KeyError: raise StatusError( 'Could not find value {path} for {replacement} in sources {sources}' .format( path=find_path, replacement=replacement['token'], sources=sources, )) if value == replacement['token']: # preserve the type if this is the only replacement in the value value = replace_with else: value = value.replace(replacement['token'], six.text_type(replace_with)) path_put(data, path, value)
def test_function(self, *args, **kwargs): """ This guy does the actual work of running a test case, and is invoked by PyTest when the time comes. :param self: The test case instance :type self: ServicePlanTestCase """ # noinspection PyUnusedLocal # This instructs the traceback manipulator that this frame belongs to test_function, which is simpler than # having it analyze the code path details to determine the frame location. _test_function_frame = True # noqa F841 if not hasattr(self.__class__, '_test_fixture_setup_called'): self.__class__._test_fixture_setup_called = {} if not hasattr(self.__class__, '_test_fixture_setup_succeeded'): self.__class__._test_fixture_setup_succeeded = {} if not self._test_fixture_setup_called.get(fixture_name, False): # If this is the first test in the fixture, we need to set up the fixture self._test_fixture_setup_called[fixture_name] = self, test_fixture self.set_up_test_fixture(test_fixture) self._run_directive_hook('set_up_test_fixture', test_fixture) # After the fixture has set up without error, we note this so that all fixture tests can run self._test_fixture_setup_succeeded[fixture_name] = True if not self._test_fixture_setup_succeeded.get(fixture_name, False): # If the fixture was not successfully set up, then fixture setup must have failed on the first test, so # all remaining tests in this fixture are also invalid. raise StatusError('Test fixture {} not set up'.format(fixture_name)) outer_exception = False try: # LABEL: 1 # First, we call the standard TestCase setUp, which we have taken over self.setUp() # Next, we call the fixture test case setup on the class and on all directives self.set_up_test_case(test_case, test_fixture) self._run_directive_hook('set_up_test_case', test_case, test_fixture) try: # LABEL: 2 # Now we can actually run the test! self._run_test_case(test_case, test_fixture, test_fixture_results, *args, **kwargs) except BaseException as e: # We're only catching (everything) so that we can record which error happened in TRY 1 outer_exception = e raise finally: try: # LABEL: 3 # Now we need to call the fixture test case teardown on the class and on all directives self._run_directive_hook('tear_down_test_case', test_case, test_fixture) self.tear_down_test_case(test_case, test_fixture) except KeyboardInterrupt: if outer_exception: # If an error happened in TRY 2, raise it instead of the interrupt so we don't mask it raise outer_exception raise except BaseException as e: if not outer_exception: raise # If an error did not happen in TRY 2, just raise the tear-down error self.addError(self, sys.exc_info()) # Otherwise, record the tear-down error so we don't mask except BaseException as e: outer_exception = e raise finally: try: # LABEL: 4 # Almost done, we call the standard TestCase tearDown, which we have taken over self.tearDown() except KeyboardInterrupt: if outer_exception: # If an error happened in TRY 1 - 3, raise it instead of the interrupt so we don't mask it raise outer_exception raise except BaseException as e: if not outer_exception: outer_exception = e raise # If an error did not happen in TRY 1 - 3, just raise the tear-down error self.addError(self, sys.exc_info()) # Otherwise, record the tear-down error so we don't mask finally: if test_function._last_fixture_test: # If this is the last fixture test case, we need to assert and clean up the fixture try: # LABEL: 5 self._run_directive_hook('assert_test_fixture_results', test_fixture_results, test_fixture) except KeyboardInterrupt: if outer_exception: # If an error happened in TRY 1 - 4, raise it instead of the interrupt so we don't mask raise outer_exception raise except self.failureException as e: # If the tear-down asserts raised an assertion error if not outer_exception: outer_exception = e raise # If an error did not happen in TRY 1 - 4, just raise the assertion error self.addFailure(self, sys.exc_info()) # Otherwise, record the assertion error so no mask except BaseException as e: if not outer_exception: outer_exception = e raise # If an error did not happen in TRY 1 - 4, just raise the on-assert error self.addError(self, sys.exc_info()) # Otherwise, record the tear-down error so no mask finally: # noinspection PyBroadException try: # LABEL: 6 self._test_fixture_setup_succeeded[fixture_name] = False self._test_fixture_setup_called[fixture_name] = False self._run_directive_hook('tear_down_test_fixture', test_fixture) self.tear_down_test_fixture(test_fixture) except KeyboardInterrupt: if outer_exception: # If an error happened in TRY 1 - 5, raise it instead of the interrupt so no mask raise outer_exception raise except BaseException: if not outer_exception: raise # If an error did not happen in TRY 1 - 5, just raise the tear-down error self.addError(self, sys.exc_info()) # Otherwise, record the tear-down error so no mask