diff --git a/src/pytest_plugins/filler/filler.py b/src/pytest_plugins/filler/filler.py index 6da3955dab7..0b337c65c31 100644 --- a/src/pytest_plugins/filler/filler.py +++ b/src/pytest_plugins/filler/filler.py @@ -9,6 +9,7 @@ import configparser import datetime import os +import shutil import warnings from enum import Enum from pathlib import Path @@ -431,9 +432,28 @@ def pytest_terminal_summary( Emphasize that fixtures have only been filled; they must now be executed to actually run the tests. """ - yield if config.fixture_output.is_stdout or hasattr(config, "workerinput"): # type: ignore[attr-defined] + yield return + # if no tests were collected, suppress the "Generated html report" message; + # the html report will be deleted in pytest_sessionfinish hook + session = terminalreporter._session + if getattr(session, "testscollected", 0) == 0: + orig_write_sep = terminalreporter.write_sep + + def filtered_write_sep(sep, msg, **kwargs): + if isinstance(msg, str) and "Generated html report:" in msg: + return # swallow it + return orig_write_sep(sep, msg, **kwargs) + + terminalreporter.write_sep = filtered_write_sep # type: ignore + try: + yield # let all other terminal_summary hooks run + finally: + terminalreporter.write_sep = orig_write_sep # type: ignore + else: + # if tests ran, no action needed, just yield to run the rest of the hooks + yield stats = terminalreporter.stats if "passed" in stats and stats["passed"]: # Custom message for Phase 1 (pre-allocation group generation) @@ -1162,6 +1182,7 @@ def pytest_collection_modifyitems( items.pop(i) +@pytest.hookimpl(hookwrapper=True, tryfirst=True) def pytest_sessionfinish(session: pytest.Session, exitstatus: int): """ Perform session finish tasks. @@ -1179,25 +1200,35 @@ def pytest_sessionfinish(session: pytest.Session, exitstatus: int): pre_alloc_groups_folder = fixture_output.pre_alloc_groups_folder_path pre_alloc_groups_folder.mkdir(parents=True, exist_ok=True) session.config.pre_alloc_groups.to_folder(pre_alloc_groups_folder) - return if xdist.is_xdist_worker(session): + yield return if fixture_output.is_stdout or is_help_or_collectonly_mode(session.config): + yield return # Remove any lock files that may have been created. for file in fixture_output.directory.rglob("*.lock"): file.unlink() - # Generate index file for all produced fixtures. + yield # ensure that the following runs after pytest-html's pytest_sessionfinish hook + + # Generate index file for all produced fixtures, respectively remove output dir. if session.config.getoption("generate_index") and not session.config.getoption( "generate_pre_alloc_groups" ): - generate_fixtures_index( - fixture_output.directory, quiet_mode=True, force_flag=False, disable_infer_format=False - ) + amount_of_collected_tests = getattr(session, "testscollected", 0) + if amount_of_collected_tests > 0: + generate_fixtures_index( + fixture_output.directory, + quiet_mode=True, + force_flag=False, + disable_infer_format=False, + ) + else: + shutil.rmtree(fixture_output.output_path) # Create tarball of the output directory if the output is a tarball. fixture_output.create_tarball() diff --git a/src/pytest_plugins/filler/tests/test_filler.py b/src/pytest_plugins/filler/tests/test_filler.py index 65636d2583b..95d797b7262 100644 --- a/src/pytest_plugins/filler/tests/test_filler.py +++ b/src/pytest_plugins/filler/tests/test_filler.py @@ -624,6 +624,103 @@ def test_fixture_output_based_on_command_line_args( assert properties["build"] == build_name +test_module_no_matching_tests = textwrap.dedent( + """\ + import pytest + + from ethereum_test_tools import Account, Environment, TestAddress, Transaction + + @pytest.mark.valid_from("Paris") + def test_will_not_match_filter(state_test): + state_test(env=Environment(), + pre={TestAddress: Account(balance=1_000_000)}, post={}, tx=Transaction()) + """ +) + + +def test_no_tests_collected_deletes_output_directory(testdir, default_t8n): + """ + Test that when no tests are collected/run, the output directory is deleted. + """ + tests_dir = testdir.mkdir("tests") + + test_module = tests_dir.join("test_no_match.py") + test_module.write(test_module_no_matching_tests) + + testdir.copy_example(name="src/cli/pytest_commands/pytest_ini_files/pytest-fill.ini") + args = [ + "-c", + "pytest-fill.ini", + "-v", + "--t8n-server-url", + default_t8n.server_url, + "-k", + "no_such_test_contains_this_filter", # Filter that won't match any test + ] + + result = testdir.runpytest(*args) + result.assert_outcomes( + passed=0, + failed=0, + skipped=0, + errors=0, + ) + + # Check that no tests were selected (all were deselected) + assert "0 selected" in result.stdout.str() + + # The output directory should be deleted when no tests ran + output_dir = Path(default_output_directory()).absolute() + assert not output_dir.exists(), ( + f"Output directory {output_dir} should have been deleted when no tests ran" + ) + + +def test_html_report_message_suppressed_when_no_tests_ran(testdir, default_t8n): + """ + Test that the HTML report message is suppressed when no tests are collected/run. + """ + tests_dir = testdir.mkdir("tests") + + test_module = tests_dir.join("test_no_match.py") + test_module.write(test_module_no_matching_tests) + + testdir.copy_example(name="src/cli/pytest_commands/pytest_ini_files/pytest-fill.ini") + args = [ + "-c", + "pytest-fill.ini", + "-v", + "--t8n-server-url", + default_t8n.server_url, + "-k", + "no_such_test_contains_this_filter", # Filter that won't match any test + ] + + result = testdir.runpytest(*args) + result.assert_outcomes( + passed=0, + failed=0, + skipped=0, + errors=0, + ) + + # Check that no tests were selected (all were deselected) + assert "0 selected" in result.stdout.str() + + # The HTML report message should NOT be present when no tests ran + # (even though HTML reporting is enabled, the message should be suppressed) + stdout_str = result.stdout.str() + assert "Generated html report:" not in stdout_str, ( + "HTML report message should be suppressed when no tests ran" + ) + + # The output directory should still be deleted when no tests ran + output_dir = Path(default_output_directory()).absolute() + assert not output_dir.exists(), ( + f"Output directory {output_dir} should have been deleted when no tests ran" + ) + + test_module_environment_variables = textwrap.dedent( """\ import pytest