From f7f43f4cdb88d8bccddb1ed350dfce588d864fa0 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sun, 21 Apr 2024 22:03:28 +0200 Subject: [PATCH] Refactor tests using subprocesses. --- docs/source/changes.md | 1 + tests/conftest.py | 20 ++++++++++++++ tests/test_capture.py | 57 +++++++++++++++++---------------------- tests/test_cli.py | 8 +++--- tests/test_config.py | 21 ++++++--------- tests/test_hook_module.py | 20 ++++++-------- tests/test_warnings.py | 12 ++++----- 7 files changed, 71 insertions(+), 68 deletions(-) diff --git a/docs/source/changes.md b/docs/source/changes.md index b949527f8..53eeca489 100644 --- a/docs/source/changes.md +++ b/docs/source/changes.md @@ -43,6 +43,7 @@ releases are available on [PyPI](https://pypi.org/project/pytask) and - {pull}`596` add project management with rye. - {pull}`598` replaces requests with httpx. - {pull}`599` adds a test fixture for switching the cwd. +- {pull}`600` refactors test using subprocesses. ## 0.4.7 - 2024-03-19 diff --git a/tests/conftest.py b/tests/conftest.py index 1dc375b18..ab0e07aa1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,10 +2,12 @@ import os import re +import subprocess import sys from contextlib import contextmanager from pathlib import Path from typing import Any +from typing import NamedTuple import pytest from click.testing import CliRunner @@ -110,6 +112,24 @@ def runner(): return CustomCliRunner() +class Result(NamedTuple): + """A named tuple to store the result of a command.""" + + exit_code: int + stdout: str + stderr: str + + +def run_in_subprocess(cmd: tuple[str, ...], cwd: Path | None = None) -> Result: + """Run a command in a subprocess and return the output.""" + result = subprocess.run(cmd, cwd=cwd, check=False, capture_output=True) + return Result( + exit_code=result.returncode, + stdout=result.stdout.decode("utf-8", "replace").replace("\r\n", "\n"), + stderr=result.stderr.decode("utf-8", "replace").replace("\r\n", "\n"), + ) + + def pytest_collection_modifyitems(session, config, items) -> None: # noqa: ARG001 """Add markers to Jupyter notebook tests.""" for item in items: diff --git a/tests/test_capture.py b/tests/test_capture.py index c00924ce7..a157307a7 100644 --- a/tests/test_capture.py +++ b/tests/test_capture.py @@ -21,6 +21,7 @@ from pytask import cli from tests.conftest import enter_directory +from tests.conftest import run_in_subprocess @pytest.mark.end_to_end() @@ -85,30 +86,27 @@ def task_show_capture(): """ tmp_path.joinpath("workflow.py").write_text(textwrap.dedent(source)) - result = subprocess.run( # noqa: PLW1510 - ("python", "workflow.py"), cwd=tmp_path, capture_output=True - ) + result = run_in_subprocess(("python", "workflow.py"), cwd=tmp_path) - assert result.returncode == ExitCode.FAILED + assert result.exit_code == ExitCode.FAILED - output = result.stdout.decode() if show_capture == "no": - assert "Captured" not in output + assert "Captured" not in result.stdout elif show_capture == "stdout": - assert "Captured stdout" in output - assert "xxxx" in output - assert "Captured stderr" not in output - # assert "zzzz" not in output + assert "Captured stdout" in result.stdout + assert "xxxx" in result.stdout + assert "Captured stderr" not in result.stdout + # assert "zzzz" not in result.stdout elif show_capture == "stderr": - assert "Captured stdout" not in output - # assert "xxxx" not in output - assert "Captured stderr" in output - assert "zzzz" in output + assert "Captured stdout" not in result.stdout + # assert "xxxx" not in result.stdout + assert "Captured stderr" in result.stdout + assert "zzzz" in result.stdout elif show_capture == "all": - assert "Captured stdout" in output - assert "xxxx" in output - assert "Captured stderr" in output - assert "zzzz" in output + assert "Captured stdout" in result.stdout + assert "xxxx" in result.stdout + assert "Captured stderr" in result.stdout + assert "zzzz" in result.stdout else: # pragma: no cover raise NotImplementedError @@ -130,14 +128,11 @@ def test_wrong_capture_method(tmp_path): """ tmp_path.joinpath("workflow.py").write_text(textwrap.dedent(source)) - result = subprocess.run( # noqa: PLW1510 - ("python", "workflow.py"), cwd=tmp_path, capture_output=True - ) - - assert result.returncode == ExitCode.CONFIGURATION_FAILED - assert "Value 'a' is not a valid" in result.stdout.decode() - assert "Traceback" not in result.stdout.decode() - assert not result.stderr.decode() + result = run_in_subprocess(("python", "workflow.py"), cwd=tmp_path) + assert result.exit_code == ExitCode.CONFIGURATION_FAILED + assert "Value 'a' is not a valid" in result.stdout + assert "Traceback" not in result.stdout + assert not result.stderr # Following tests are copied from pytest. @@ -263,13 +258,9 @@ def task_unicode(): tmp_path.joinpath("workflow.py").write_text( textwrap.dedent(source), encoding="utf-8" ) - - result = subprocess.run( # noqa: PLW1510 - ("python", "workflow.py"), cwd=tmp_path, capture_output=True - ) - - assert "1 Succeeded" in result.stdout.decode() - assert result.returncode == ExitCode.OK + result = run_in_subprocess(("python", "workflow.py"), cwd=tmp_path) + assert result.exit_code == ExitCode.OK + assert "1 Succeeded" in result.stdout @pytest.mark.end_to_end() diff --git a/tests/test_cli.py b/tests/test_cli.py index 0cde7b970..01b40c846 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,17 +1,17 @@ from __future__ import annotations -import subprocess - import pytest from pytask import ExitCode from pytask import __version__ from pytask import cli +from tests.conftest import run_in_subprocess + @pytest.mark.end_to_end() def test_version_option(): - process = subprocess.run(["pytask", "--version"], capture_output=True, check=False) - assert "pytask, version " + __version__ in process.stdout.decode("utf-8") + result = run_in_subprocess(("pytask", "--version")) + assert "pytask, version " + __version__ in result.stdout @pytest.mark.end_to_end() diff --git a/tests/test_config.py b/tests/test_config.py index 84af88b18..874bdf627 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,7 +1,6 @@ from __future__ import annotations import os -import subprocess import sys import textwrap @@ -10,6 +9,8 @@ from pytask import build from pytask import cli +from tests.conftest import run_in_subprocess + @pytest.mark.end_to_end() def test_debug_pytask(capsys, tmp_path): @@ -90,12 +91,9 @@ def test_paths_are_relative_to_configuration_file_cli(tmp_path): source = "def task_example(): ..." tmp_path.joinpath("tasks", "task_example.py").write_text(source) - result = subprocess.run( - ("pytask", "src"), cwd=tmp_path, check=False, capture_output=True - ) - - assert result.returncode == ExitCode.OK - assert "1 Succeeded" in result.stdout.decode() + result = run_in_subprocess(("pytask", "src"), cwd=tmp_path) + assert result.exit_code == ExitCode.OK + assert "1 Succeeded" in result.stdout @pytest.mark.end_to_end() @@ -122,9 +120,6 @@ def test_paths_are_relative_to_configuration_file(tmp_path): session = build(paths=[Path("src")]) """ tmp_path.joinpath("script.py").write_text(textwrap.dedent(source)) - result = subprocess.run( - ("python", "script.py"), cwd=tmp_path, check=False, capture_output=True - ) - - assert result.returncode == ExitCode.OK - assert "1 Succeeded" in result.stdout.decode() + result = run_in_subprocess(("python", "script.py"), cwd=tmp_path) + assert result.exit_code == ExitCode.OK + assert "1 Succeeded" in result.stdout diff --git a/tests/test_hook_module.py b/tests/test_hook_module.py index 40a712d98..9dd2f6297 100644 --- a/tests/test_hook_module.py +++ b/tests/test_hook_module.py @@ -8,6 +8,8 @@ import pytest from pytask import ExitCode +from tests.conftest import run_in_subprocess + @pytest.mark.end_to_end() @pytest.mark.parametrize( @@ -50,10 +52,9 @@ def pytask_extend_command_line_interface(cli): else: args = ("pytask", "build", "--hook-module", "hooks/hooks.py", "--help") - result = subprocess.run(args, cwd=tmp_path, capture_output=True, check=True) - - assert result.returncode == ExitCode.OK - assert "--new-option" in result.stdout.decode() + result = run_in_subprocess(args, cwd=tmp_path) + assert result.exit_code == ExitCode.OK + assert "--new-option" in result.stdout @pytest.mark.end_to_end() @@ -92,14 +93,9 @@ def pytask_extend_command_line_interface(cli): else: args = ("pytask", "build", "--help") - result = subprocess.run( - args, - cwd=tmp_path, - capture_output=True, - check=True, - ) - assert result.returncode == ExitCode.OK - assert "--new-option" in result.stdout.decode() + result = run_in_subprocess(args, cwd=tmp_path) + assert result.exit_code == ExitCode.OK + assert "--new-option" in result.stdout @pytest.mark.end_to_end() diff --git a/tests/test_warnings.py b/tests/test_warnings.py index 4535c6f60..63b30580f 100644 --- a/tests/test_warnings.py +++ b/tests/test_warnings.py @@ -1,6 +1,5 @@ from __future__ import annotations -import subprocess import sys import textwrap @@ -9,6 +8,8 @@ from pytask import build from pytask import cli +from tests.conftest import run_in_subprocess + @pytest.mark.end_to_end() @pytest.mark.parametrize( @@ -155,11 +156,10 @@ def warn_now(): path_to_warn_module.write_text(textwrap.dedent(warn_module)) # Cannot use runner since then warnings are not ignored by default. - result = subprocess.run(("pytask"), cwd=tmp_path, capture_output=True, check=False) - - assert result.returncode == ExitCode.OK - assert "Warnings" not in result.stdout.decode() - assert "warning!!!" not in result.stdout.decode() + result = run_in_subprocess(("pytask"), cwd=tmp_path) + assert result.exit_code == ExitCode.OK + assert "Warnings" not in result.stdout + assert "warning!!!" not in result.stdout @pytest.mark.end_to_end()