diff --git a/allure-behave/src/formatter.py b/allure-behave/src/formatter.py index d2c53922..febd2d96 100644 --- a/allure-behave/src/formatter.py +++ b/allure-behave/src/formatter.py @@ -12,10 +12,10 @@ def __init__(self, stream_opener, config): super(AllureFormatter, self).__init__(stream_opener, config) self.listener = AllureListener(config) - file_logger = AllureFileLogger(self.stream_opener.name) + self.file_logger = AllureFileLogger(self.stream_opener.name) allure_commons.plugin_manager.register(self.listener) - allure_commons.plugin_manager.register(file_logger) + allure_commons.plugin_manager.register(self.file_logger) self.testplan = get_testplan() @@ -45,5 +45,14 @@ def result(self, result): def eof(self): self.listener.stop_feature() + def close(self): + try: + super().close() + finally: + for plugin in [self.file_logger, self.listener]: + name = allure_commons.plugin_manager.get_name(plugin) + if allure_commons.plugin_manager.has_plugin(name): + allure_commons.plugin_manager.unregister(name=name) + def close_stream(self): self.listener.stop_session() diff --git a/allure-behave/src/hooks.py b/allure-behave/src/hooks.py index 86427523..7a153020 100644 --- a/allure-behave/src/hooks.py +++ b/allure-behave/src/hooks.py @@ -6,6 +6,7 @@ from behave.configuration import Configuration HOOKS = [ + "after_all", "before_feature", "after_feature", "before_scenario", @@ -42,15 +43,25 @@ def allure_report(result_dir="allure_results"): class AllureHooks: def __init__(self, result_dir): self.listener = AllureListener(Configuration()) + self.plugins = [] if not hasattr(_storage, 'file_logger'): - _storage.file_logger = AllureFileLogger(result_dir) - allure_commons.plugin_manager.register(_storage.file_logger) + logger = AllureFileLogger(result_dir) + _storage.file_logger = logger + allure_commons.plugin_manager.register(logger) + self.plugins.append(logger) allure_commons.plugin_manager.register(self.listener) + self.plugins.append(self.listener) + + def after_all(self, context): + for plugin in self.plugins: + name = allure_commons.plugin_manager.get_name(plugin) + if allure_commons.plugin_manager.has_plugin(name): + allure_commons.plugin_manager.unregister(name=name) def before_feature(self, context, feature): - self.listener.start_feature() + self.listener.start_file() def after_feature(self, context, feature): self.listener.stop_feature() diff --git a/tests/allure_behave/behave_runner.py b/tests/allure_behave/behave_runner.py index defb1849..da49d738 100644 --- a/tests/allure_behave/behave_runner.py +++ b/tests/allure_behave/behave_runner.py @@ -1,3 +1,4 @@ +from contextlib import contextmanager import behave.step_registry import sys @@ -16,7 +17,8 @@ from allure_behave.formatter import AllureFormatter -def __fix_behave_in_memory_run(): +@contextmanager +def _fixed_in_memory_run(): # Behave has poor support for consecutive prigrammatic runs. This is due to # how step decorators are cached. # There are three ways to introduce behave step decorators (i.e., @given) @@ -50,6 +52,10 @@ def __fixed_add_step_definition(self, *args, **kwargs): StepRegistry.add_step_definition = __fixed_add_step_definition + yield + + StepRegistry.add_step_definition = original_add_step_definition + class _InMemoryBehaveRunner(Runner): def __init__(self, features, steps, environment, args=None): @@ -159,32 +165,32 @@ def run_behave( :attr:`allure_results` attribute. """ - return self._run( - self._get_all_content( - paths=feature_paths, - literals=feature_literals, - rst_ids=feature_rst_ids - ), - self._get_all_content( - paths=step_paths, - literals=step_literals, - rst_ids=step_rst_ids - ), - self._resolve_content( - path=environment_path, - literal=environment_literal, - rst_id=environment_rst_id - ), - testplan_content=testplan_content, - testplan_path=testplan_path, - testplan_rst_id=testplan_rst_id, - options=options - ) + + with _fixed_in_memory_run(): + return self._run( + self._get_all_content( + paths=feature_paths, + literals=feature_literals, + rst_ids=feature_rst_ids + ), + self._get_all_content( + paths=step_paths, + literals=step_literals, + rst_ids=step_rst_ids + ), + self._resolve_content( + path=environment_path, + literal=environment_literal, + rst_id=environment_rst_id + ), + testplan_content=testplan_content, + testplan_path=testplan_path, + testplan_rst_id=testplan_rst_id, + options=options + ) def _run_framework(self, features, steps, environment, options): _InMemoryBehaveRunner(features, steps, environment, options).run() -__fix_behave_in_memory_run() - __all__ = ["AllureBehaveRunner"] diff --git a/tests/allure_behave/defects/issue858_test.py b/tests/allure_behave/defects/issue858_test.py new file mode 100644 index 00000000..80c3168b --- /dev/null +++ b/tests/allure_behave/defects/issue858_test.py @@ -0,0 +1,38 @@ +import allure +import shlex + +from tests.allure_behave.behave_runner import AllureBehaveRunner +from ...e2e import allure_file_context + +from behave import __main__ as runner + + +@allure.issue("858") +def test_test_results_leak(behave_runner: AllureBehaveRunner): + feature_path = behave_runner.pytester.makefile( + ".feature", + ( + """ + Feature: Foo + Scenario: Bar + Given baz + """ + ), + ) + behave_runner.pytester.makefile( + ".py", + **{"steps/steps": "given('baz')(lambda *_: None)"}, + ) + + args = shlex.join([ + feature_path.name, + "-f", "allure_behave.formatter:AllureFormatter", + "-o", "allure-results", + "--no-summary", + ]) + + with allure_file_context("allure-results") as context: + runner.main(args) + runner.main(args) + + assert len(context.allure_results.test_cases) == 2