diff --git a/CHANGES.md b/CHANGES.md index 7d86f66..b43a8c5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,11 @@ chronological order. Releases follow [semantic versioning](https://semver.org/) releases are available on [PyPI](https://pypi.org/project/pytask-stata) and [Anaconda.org](https://anaconda.org/conda-forge/pytask-stata). -## 0.3.0 - 2023-xx-xx +## 0.4.0 - 2023-10-08 + +- {pull}`36` makes pytask-stata compatible with pytask v0.4.0. + +## 0.3.0 - 2023-01-23 - {pull}`24` adds ruff and refurb. - {pull}`25` adds docformatter. diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 88437a4..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,12 +0,0 @@ -prune tests - -exclude .coveragerc -exclude *.md -exclude *.yml -exclude *.yaml -exclude tox.ini - -include README.md -include LICENSE - -recursive-include src py.typed diff --git a/environment.yml b/environment.yml deleted file mode 100644 index 0c59547..0000000 --- a/environment.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: pytask-stata - -channels: - - conda-forge - - nodefaults - -dependencies: - - python >=3.8 - - pip - - setuptools_scm - - toml - - # Package dependencies - - pytask <0.4 - - pytask-parallel <0.4 - - # Misc - - black - - pre-commit - - pytest-cov - - pytest-xdist - - tox-conda - - - pip: - - -e . diff --git a/pyproject.toml b/pyproject.toml index ba0323d..c43c39a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] -requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.0"] -build-backend = "setuptools.build_meta" +requires = ["hatchling", "hatch_vcs"] +build-backend = "hatchling.build" [project] name = "pytask_stata" @@ -15,7 +15,7 @@ classifiers = [ "Programming Language :: Python :: 3 :: Only", ] requires-python = ">=3.8" -dependencies = ["click", "pytask>=0.3,<0.4"] +dependencies = ["click", "pytask>=0.4"] dynamic = ["version"] [project.readme] @@ -36,19 +36,28 @@ Changelog = "https://github.com/pytask-dev/pytask-stata/blob/main/CHANGES.md" [project.entry-points] pytask = { pytask_stata = "pytask_stata.plugin" } -[tool.setuptools] -include-package-data = true -package-dir = { "" = "src" } -zip-safe = false -platforms = ["any"] -license-files = ["LICENSE"] +[tool.rye] +managed = true +dev-dependencies = [ + "tox-uv>=1.8.2", +] + +[tool.hatch.build.hooks.vcs] +version-file = "src/pytask_stata/_version.py" + +[tool.hatch.build.targets.sdist] +exclude = ["tests"] +only-packages = true + +[tool.hatch.build.targets.wheel] +exclude = ["tests"] +only-packages = true -[tool.setuptools.packages.find] -where = ["src"] -namespaces = false +[tool.hatch.version] +source = "vcs" -[tool.setuptools_scm] -write_to = "src/pytask_stata/_version.py" +[tool.hatch.metadata] +allow-direct-references = true [tool.mypy] files = ["src", "tests"] diff --git a/src/pytask_stata/collect.py b/src/pytask_stata/collect.py index aae9034..a37a5c4 100644 --- a/src/pytask_stata/collect.py +++ b/src/pytask_stata/collect.py @@ -4,34 +4,40 @@ import functools import subprocess -from types import FunctionType -from typing import TYPE_CHECKING +import warnings +from pathlib import Path from typing import Any from pytask import Mark +from pytask import NodeInfo +from pytask import PathNode +from pytask import PTask +from pytask import PythonNode from pytask import Session from pytask import Task -from pytask import depends_on +from pytask import TaskWithoutPath from pytask import has_mark from pytask import hookimpl -from pytask import parse_nodes -from pytask import produces +from pytask import is_task_function +from pytask import parse_dependencies_from_task_function +from pytask import parse_products_from_task_function from pytask import remove_marks from pytask_stata.shared import convert_task_id_to_name_of_log_file from pytask_stata.shared import stata -if TYPE_CHECKING: - from pathlib import Path - def run_stata_script( - executable: str, script: Path, options: list[str], log_name: list[str], cwd: Path + _executable: str, + _script: Path, + _options: list[str], + _log_name: str, + _cwd: Path, ) -> None: """Run an R script.""" - cmd = [executable, "-e", "do", script.as_posix(), *options, *log_name] + cmd = [_executable, "-e", "do", _script.as_posix(), *_options, f"-{_log_name}"] print("Executing " + " ".join(cmd) + ".") # noqa: T201 - subprocess.run(cmd, cwd=cwd, check=True) # noqa: S603 + subprocess.run(cmd, cwd=_cwd, check=True) # noqa: S603 @hookimpl @@ -43,11 +49,11 @@ def pytask_collect_task( if ( (name.startswith("task_") or has_mark(obj, "task")) - and callable(obj) + and is_task_function(obj) and has_mark(obj, "stata") ): + # Parse the @pytask.mark.stata decorator. obj, marks = remove_marks(obj, "stata") - if len(marks) > 1: msg = ( f"Task {name!r} has multiple @pytask.mark.stata marks, but only one is " @@ -57,50 +63,123 @@ def pytask_collect_task( mark = _parse_stata_mark(mark=marks[0]) script, options = stata(**marks[0].kwargs) - obj.pytask_meta.markers.append(mark) - dependencies = parse_nodes(session, path, name, obj, depends_on) - products = parse_nodes(session, path, name, obj, produces) + # Collect the nodes in @pytask.mark.julia and validate them. + path_nodes = Path.cwd() if path is None else path.parent - markers = obj.pytask_meta.markers if hasattr(obj, "pytask_meta") else [] - kwargs = obj.pytask_meta.kwargs if hasattr(obj, "pytask_meta") else {} - - task = Task( - base_name=name, - path=path, - function=_copy_func(run_stata_script), # type: ignore[arg-type] - depends_on=dependencies, - produces=products, - markers=markers, - kwargs=kwargs, - ) + if isinstance(script, str): + warnings.warn( + "Passing a string to the @pytask.mark.stata parameter 'script' is " + "deprecated. Please, use a pathlib.Path instead.", + stacklevel=1, + ) + script = Path(script) script_node = session.hook.pytask_collect_node( - session=session, path=path, node=script + session=session, + path=path_nodes, + node_info=NodeInfo( + arg_name="script", path=(), value=script, task_path=path, task_name=name + ), ) - if isinstance(task.depends_on, dict): - task.depends_on["__script"] = script_node + if not (isinstance(script_node, PathNode) and script_node.path.suffix == ".do"): + msg = ( + "The 'script' keyword of the @pytask.mark.stata decorator must point " + f"to a file with the .do suffix, but it is {script_node}." + ) + raise ValueError(msg) + + options_node = session.hook.pytask_collect_node( + session=session, + path=path_nodes, + node_info=NodeInfo( + arg_name="_options", + path=(), + value=options, + task_path=path, + task_name=name, + ), + ) + + executable_node = session.hook.pytask_collect_node( + session=session, + path=path_nodes, + node_info=NodeInfo( + arg_name="_executable", + path=(), + value=session.config["stata"], + task_path=path, + task_name=name, + ), + ) + + cwd_node = session.hook.pytask_collect_node( + session=session, + path=path_nodes, + node_info=NodeInfo( + arg_name="_cwd", + path=(), + value=path.parent.as_posix(), + task_path=path, + task_name=name, + ), + ) + + dependencies = parse_dependencies_from_task_function( + session, path, name, path_nodes, obj + ) + products = parse_products_from_task_function( + session, path, name, path_nodes, obj + ) + + # Add script + dependencies["_script"] = script_node + dependencies["_options"] = options_node + dependencies["_cwd"] = cwd_node + dependencies["_executable"] = executable_node + + partialed = functools.partial(run_stata_script, _cwd=path.parent) + markers = obj.pytask_meta.markers if hasattr(obj, "pytask_meta") else [] + + task: PTask + if path is None: + task = TaskWithoutPath( + name=name, + function=partialed, + depends_on=dependencies, + produces=products, + markers=markers, + ) else: - task.depends_on = {0: task.depends_on, "__script": script_node} + task = Task( + base_name=name, + path=path, + function=partialed, + depends_on=dependencies, + produces=products, + markers=markers, + ) + # Add log_name node that depends on the task id. if session.config["platform"] == "win32": - log_name = convert_task_id_to_name_of_log_file(task.short_name) - log_name_arg = [f"-{log_name}"] + log_name = convert_task_id_to_name_of_log_file(task) else: - log_name_arg = [] - - stata_function = functools.partial( - task.function, - executable=session.config["stata"], - script=task.depends_on["__script"].path, - options=options, - log_name=log_name_arg, - cwd=task.path.parent, + log_name = "" + + log_name_node = session.hook.pytask_collect_node( + session=session, + path=path_nodes, + node_info=NodeInfo( + arg_name="_log_name", + path=(), + value=PythonNode(value=log_name), + task_path=path, + task_name=name, + ), ) - - task.function = stata_function + task.depends_on["_log_name"] = log_name_node return task return None @@ -109,32 +188,5 @@ def pytask_collect_task( def _parse_stata_mark(mark: Mark) -> Mark: """Parse a Stata mark.""" script, options = stata(**mark.kwargs) - parsed_kwargs = {"script": script or None, "options": options or []} - return Mark("stata", (), parsed_kwargs) - - -def _copy_func(func: FunctionType) -> FunctionType: - """Create a copy of a function. - - Based on https://stackoverflow.com/a/13503277/7523785. - - Example - ------- - >>> def _func(): pass - >>> copied_func = _copy_func(_func) - >>> _func is copied_func - False - - """ - new_func = FunctionType( - func.__code__, - func.__globals__, - name=func.__name__, - argdefs=func.__defaults__, - closure=func.__closure__, - ) - new_func = functools.update_wrapper(new_func, func) - new_func.__kwdefaults__ = func.__kwdefaults__ - return new_func diff --git a/src/pytask_stata/execute.py b/src/pytask_stata/execute.py index 529343a..34d0398 100644 --- a/src/pytask_stata/execute.py +++ b/src/pytask_stata/execute.py @@ -3,18 +3,19 @@ from __future__ import annotations import re +from pathlib import Path +from pytask import PTask +from pytask import PTaskWithPath from pytask import Session -from pytask import Task from pytask import has_mark from pytask import hookimpl from pytask_stata.shared import STATA_COMMANDS -from pytask_stata.shared import convert_task_id_to_name_of_log_file @hookimpl -def pytask_execute_task_setup(session: Session, task: Task) -> None: +def pytask_execute_task_setup(session: Session, task: PTask) -> None: """Check if Stata is found on the PATH.""" if has_mark(task, "stata") and session.config["stata"] is None: msg = ( @@ -27,7 +28,7 @@ def pytask_execute_task_setup(session: Session, task: Task) -> None: @hookimpl -def pytask_execute_task_teardown(session: Session, task: Task) -> None: +def pytask_execute_task_teardown(session: Session, task: PTask) -> None: """Check if the log file contains no error code. Stata has the weird behavior of always returning an exit code of 0 even if an error @@ -39,10 +40,13 @@ def pytask_execute_task_teardown(session: Session, task: Task) -> None: """ if has_mark(task, "stata"): if session.config["platform"] == "win32": - log_name = convert_task_id_to_name_of_log_file(task.short_name) - path_to_log = task.path.with_name(log_name).with_suffix(".log") + log_name = task.depends_on["_log_name"].load() + if isinstance(task, PTaskWithPath): + path_to_log = task.path.with_name(log_name).with_suffix(".log") + else: + path_to_log = Path.cwd(log_name).with_name(log_name).with_suffix(".log") else: - node = task.depends_on["__script"] + node = task.depends_on["_script"] path_to_log = node.path.with_suffix(".log") n_lines = session.config["stata_check_log_lines"] diff --git a/src/pytask_stata/parametrize.py b/src/pytask_stata/parametrize.py deleted file mode 100644 index a30f982..0000000 --- a/src/pytask_stata/parametrize.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Parametrize tasks.""" - -from __future__ import annotations - -from typing import Any -from typing import Callable - -import pytask -from _pytask.config import hookimpl - - -@hookimpl -def pytask_parametrize_kwarg_to_marker(obj: Callable[..., Any], kwargs: Any) -> None: - """Attach parametrized stata arguments to the function with a marker.""" - if callable(obj) and "stata" in kwargs: - pytask.mark.stata(**kwargs.pop("stata"))(obj) diff --git a/src/pytask_stata/plugin.py b/src/pytask_stata/plugin.py index 84db2ae..80e17ad 100644 --- a/src/pytask_stata/plugin.py +++ b/src/pytask_stata/plugin.py @@ -10,7 +10,6 @@ from pytask_stata import collect from pytask_stata import config from pytask_stata import execute -from pytask_stata import parametrize if TYPE_CHECKING: from pluggy import PluginManager @@ -23,4 +22,3 @@ def pytask_add_hooks(pm: PluginManager) -> None: pm.register(collect) pm.register(config) pm.register(execute) - pm.register(parametrize) diff --git a/src/pytask_stata/shared.py b/src/pytask_stata/shared.py index 8464a14..96068c2 100644 --- a/src/pytask_stata/shared.py +++ b/src/pytask_stata/shared.py @@ -11,6 +11,8 @@ if TYPE_CHECKING: from pathlib import Path + from pytask import PTask + if sys.platform == "darwin": STATA_COMMANDS = [ @@ -44,7 +46,7 @@ def stata( *, - script: str | Path | None = None, + script: str | Path, options: str | Iterable[str] | None = None, ) -> tuple[str | Path | None, str | Iterable[str] | None]: """Specify command line options for Stata. @@ -59,7 +61,7 @@ def stata( return script, options -def convert_task_id_to_name_of_log_file(id_: str) -> str: +def convert_task_id_to_name_of_log_file(task: PTask) -> str: """Convert task to id to name of log file. If one passes the complete task id as the log file name, Stata would remove parent @@ -79,7 +81,14 @@ def convert_task_id_to_name_of_log_file(id_: str) -> str: 'task_example_py_task_example[arg1]' """ - return id_.rsplit("/")[-1].replace(".", "_").replace("::", "_") + id_ = getattr(task, "short_name", task.name) + return ( + id_.rsplit("/")[-1] + .replace(".", "_") + .replace("::", "_") + .replace("<", "") + .replace(">", "") + ) def _to_list(scalar_or_iter: Any) -> list[Any]: diff --git a/tests/conftest.py b/tests/conftest.py index 5f39d95..18b9414 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,13 @@ from __future__ import annotations import shutil +import sys +from contextlib import contextmanager +from typing import Callable import pytest from click.testing import CliRunner +from pytask import storage from pytask_stata.config import STATA_COMMANDS needs_stata = pytest.mark.skipif( @@ -15,6 +19,65 @@ ) +class SysPathsSnapshot: + """A snapshot for sys.path.""" + + def __init__(self) -> None: + self.__saved = sys.path.copy(), sys.meta_path.copy() + + def restore(self) -> None: + sys.path[:], sys.meta_path[:] = self.__saved + + +class SysModulesSnapshot: + """A snapshot for sys.modules.""" + + def __init__(self, preserve: Callable[[str], bool] | None = None) -> None: + self.__preserve = preserve + self.__saved = sys.modules.copy() + + def restore(self) -> None: + if self.__preserve: + self.__saved.update( + (k, m) for k, m in sys.modules.items() if self.__preserve(k) + ) + sys.modules.clear() + sys.modules.update(self.__saved) + + +@contextmanager +def restore_sys_path_and_module_after_test_execution(): + sys_path_snapshot = SysPathsSnapshot() + sys_modules_snapshot = SysModulesSnapshot() + yield + sys_modules_snapshot.restore() + sys_path_snapshot.restore() + + +@pytest.fixture(autouse=True) +def _restore_sys_path_and_module_after_test_execution(): + """Restore sys.path and sys.modules after every test execution. + + This fixture became necessary because most task modules in the tests are named + `task_example`. Since the change in #424, the same module is not reimported which + solves errors with parallelization. At the same time, modules with the same name in + the tests are overshadowing another and letting tests fail. + + The changes to `sys.path` might not be necessary to restore, but we do it anyways. + + """ + with restore_sys_path_and_module_after_test_execution(): + yield + + +class CustomCliRunner(CliRunner): + def invoke(self, *args, **kwargs): + """Restore sys.path and sys.modules after an invocation.""" + storage.create() + with restore_sys_path_and_module_after_test_execution(): + return super().invoke(*args, **kwargs) + + @pytest.fixture() def runner(): - return CliRunner() + return CustomCliRunner() diff --git a/tests/test_config.py b/tests/test_config.py index 95a1121..2a00be9 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,12 +1,12 @@ from __future__ import annotations import pytest -from pytask import main +from pytask import build @pytest.mark.end_to_end() def test_marker_is_configured(tmp_path): - session = main({"paths": tmp_path}) + session = build(paths=tmp_path) assert "stata" in session.config assert "stata" in session.config["markers"] diff --git a/tests/test_execute.py b/tests/test_execute.py index e8fc7c0..6aa5103 100644 --- a/tests/test_execute.py +++ b/tests/test_execute.py @@ -10,8 +10,8 @@ from pytask import Mark from pytask import Session from pytask import Task +from pytask import build from pytask import cli -from pytask import main from pytask_stata.config import STATA_COMMANDS from pytask_stata.execute import pytask_execute_task_setup @@ -105,10 +105,10 @@ def run_do_file(): @pytest.mark.end_to_end() def test_raise_error_if_stata_is_not_found(tmp_path, monkeypatch): task_source = """ - import pytask + from pytask import mark, task - @pytask.mark.stata(script="script.do") - @pytask.mark.produces("out.dta") + @task(kwargs={"produces": "out.dta"}) + @mark.stata(script="script.do") def task_run_do_file(): pass """ @@ -121,7 +121,7 @@ def task_run_do_file(): lambda x: None, # noqa: ARG005 ) - session = main({"paths": tmp_path}) + session = build(paths=tmp_path) assert session.exit_code == ExitCode.FAILED assert isinstance(session.execution_reports[0].exc_info[1], RuntimeError) @@ -150,6 +150,7 @@ def task_run_do_file(): result = runner.invoke(cli, [tmp_path.as_posix()]) assert result.exit_code == ExitCode.OK + assert tmp_path.joinpath("out.dta").exists() @needs_stata @@ -197,3 +198,33 @@ def task_run_do_file(): assert result.exit_code == ExitCode.COLLECTION_FAILED assert "has multiple @pytask.mark.stata marks" in result.output + + +@needs_stata +@pytest.mark.end_to_end() +def test_with_task_without_path(runner, tmp_path): + task_source = """ + import pytask + from pytask import task + + task_example = pytask.mark.stata(script="script.do")( + pytask.mark.produces("auto.dta")(task()(lambda x: None)) + ) + """ + tmp_path.joinpath("task_example.py").write_text(textwrap.dedent(task_source)) + + do_file = """ + sysuse auto, clear + save auto + """ + tmp_path.joinpath("script.do").write_text(textwrap.dedent(do_file)) + + result = runner.invoke(cli, [tmp_path.as_posix(), "--stata-keep-log"]) + + assert result.exit_code == ExitCode.OK + assert tmp_path.joinpath("auto.dta").exists() + + if sys.platform == "win32": + assert tmp_path.joinpath("task_example_py_lambda.log").exists() + else: + assert not tmp_path.joinpath("task_example_py_lambda.log").exists() diff --git a/tests/test_normal_execution_w_plugin.py b/tests/test_normal_execution_w_plugin.py index a7b211d..c2128de 100644 --- a/tests/test_normal_execution_w_plugin.py +++ b/tests/test_normal_execution_w_plugin.py @@ -5,6 +5,7 @@ import textwrap import pytest +from pytask import ExitCode from pytask import cli @@ -36,4 +37,4 @@ def task_example(depends_on, produces): tmp_path.joinpath(dependency).touch() result = runner.invoke(cli, [tmp_path.as_posix()]) - assert result.exit_code == 0 + assert result.exit_code == ExitCode.OK diff --git a/tests/test_parallel.py b/tests/test_parallel.py index 52873c9..e511f02 100644 --- a/tests/test_parallel.py +++ b/tests/test_parallel.py @@ -3,7 +3,6 @@ from __future__ import annotations import textwrap -import time import pytest from pytask import ExitCode @@ -24,46 +23,6 @@ ) -@needs_stata -@pytest.mark.end_to_end() -def test_parallel_parametrization_over_source_files_w_parametrize(runner, tmp_path): - source = """ - import pytask - - @pytask.mark.parametrize( - "stata, produces", [( - {"script": "script_1.do"}, "1.dta"), ({"script": "script_2.do"}, "2.dta") - ] - ) - def task_execute_do_file(): - pass - """ - tmp_path.joinpath("task_example.py").write_text(textwrap.dedent(source)) - - for i in range(1, 3): - do_file = f""" - sleep 4000 - sysuse auto, clear - save {i} - """ - tmp_path.joinpath(f"script_{i}.do").write_text(textwrap.dedent(do_file)) - - start = time.time() - result = runner.invoke(cli, [tmp_path.as_posix()]) - assert result.exit_code == ExitCode.OK - duration_normal = time.time() - start - - for name in ("1.dta", "2.dta"): - tmp_path.joinpath(name).unlink() - - start = time.time() - result = runner.invoke(cli, [tmp_path.as_posix(), "-n", 2]) - assert result.exit_code == ExitCode.OK - duration_parallel = time.time() - start - - assert duration_parallel < duration_normal - - @needs_stata @pytest.mark.end_to_end() def test_parallel_parametrization_over_source_files_w_loop(runner, tmp_path): @@ -81,75 +40,19 @@ def task_execute_do_file(): tmp_path.joinpath("task_example.py").write_text(textwrap.dedent(source)) do_file = """ - sleep 4000 sysuse auto, clear save 1 """ tmp_path.joinpath("script_1.do").write_text(textwrap.dedent(do_file)) do_file = """ - sleep 4000 sysuse auto, clear save 2 """ tmp_path.joinpath("script_2.do").write_text(textwrap.dedent(do_file)) - start = time.time() - result = runner.invoke(cli, [tmp_path.as_posix()]) - assert result.exit_code == ExitCode.OK - duration_normal = time.time() - start - - for name in ("1.dta", "2.dta"): - tmp_path.joinpath(name).unlink() - - start = time.time() - result = runner.invoke(cli, [tmp_path.as_posix(), "-n", 2]) - assert result.exit_code == ExitCode.OK - duration_parallel = time.time() - start - - assert duration_parallel < duration_normal - - -@needs_stata -@pytest.mark.end_to_end() -def test_parallel_parametrization_over_source_file_w_parametrize(runner, tmp_path): - source = """ - import pytask - - @pytask.mark.parametrize( - "produces, stata", - [ - ("output_1.dta", {"script": "script.do", "options": ("output_1",)}), - ("output_2.dta", {"script": "script.do", "options": ("output_2",)}) - ], - ) - def task_execute_do_file(): - pass - """ - tmp_path.joinpath("task_example.py").write_text(textwrap.dedent(source)) - - do_file = """ - sleep 4000 - sysuse auto, clear - args produces - save "`produces'" - """ - tmp_path.joinpath("script.do").write_text(textwrap.dedent(do_file)) - - start = time.time() - result = runner.invoke(cli, [tmp_path.as_posix()]) - assert result.exit_code == ExitCode.OK - duration_normal = time.time() - start - - for name in ("output_1.dta", "output_2.dta"): - tmp_path.joinpath(name).unlink() - - start = time.time() result = runner.invoke(cli, [tmp_path.as_posix(), "-n", 2]) assert result.exit_code == ExitCode.OK - duration_parallel = time.time() - start - - assert duration_parallel < duration_normal @needs_stata @@ -169,24 +72,11 @@ def task_execute_do_file(): tmp_path.joinpath("task_example.py").write_text(textwrap.dedent(source)) do_file = """ - sleep 4000 sysuse auto, clear args produces save "`produces'" """ tmp_path.joinpath("script.do").write_text(textwrap.dedent(do_file)) - start = time.time() - result = runner.invoke(cli, [tmp_path.as_posix()]) - assert result.exit_code == ExitCode.OK - duration_normal = time.time() - start - - for name in ("output_1.dta", "output_2.dta"): - tmp_path.joinpath(name).unlink() - - start = time.time() result = runner.invoke(cli, [tmp_path.as_posix(), "-n", 2]) assert result.exit_code == ExitCode.OK - duration_parallel = time.time() - start - - assert duration_parallel < duration_normal diff --git a/tests/test_parametrize.py b/tests/test_parametrize.py index 4ac5d98..f571e8d 100644 --- a/tests/test_parametrize.py +++ b/tests/test_parametrize.py @@ -10,36 +10,6 @@ from tests.conftest import needs_stata -@needs_stata -@pytest.mark.end_to_end() -def test_parametrized_execution_of_do_file_w_parametrize(runner, tmp_path): - task_source = """ - import pytask - - @pytask.mark.parametrize( - "stata, produces", [( - {"script": "script_1.do"}, "1.dta"), ({"script": "script_2.do"}, "2.dta") - ] - ) - def task_execute_do_file(): - pass - """ - tmp_path.joinpath("task_example.py").write_text(textwrap.dedent(task_source)) - - for i in range(1, 3): - do_file = f""" - sysuse auto, clear - save {i} - """ - tmp_path.joinpath(f"script_{i}.do").write_text(textwrap.dedent(do_file)) - - result = runner.invoke(cli, [tmp_path.as_posix()]) - - assert result.exit_code == ExitCode.OK - assert tmp_path.joinpath("1.dta").exists() - assert tmp_path.joinpath("2.dta").exists() - - @needs_stata @pytest.mark.end_to_end() def test_parametrized_execution_of_do_file_w_loop(runner, tmp_path): @@ -70,49 +40,6 @@ def task_execute_do_file(): assert tmp_path.joinpath("2.dta").exists() -@needs_stata -@pytest.mark.end_to_end() -def test_parametrize_command_line_options_w_parametrize(runner, tmp_path): - task_source = """ - import pytask - - @pytask.mark.parametrize( - "produces, stata", - [ - ("output_1.dta", {"script": "script.do", "options": ("output_1",)}), - ("output_2.dta", {"script": "script.do", "options": ("output_2",)}) - ], - ) - def task_execute_do_file(): - pass - """ - tmp_path.joinpath("task_example.py").write_text(textwrap.dedent(task_source)) - - latex_source = """ - sysuse auto, clear - args produces - save "`produces'" - """ - tmp_path.joinpath("script.do").write_text(textwrap.dedent(latex_source)) - - result = runner.invoke(cli, [tmp_path.as_posix(), "--stata-keep-log"]) - - assert result.exit_code == ExitCode.OK - assert tmp_path.joinpath("output_1.dta").exists() - assert tmp_path.joinpath("output_2.dta").exists() - - # Test that log files with different names are produced. - if sys.platform == "win32": - assert tmp_path.joinpath( - "task_example_py_task_execute_do_file[output_1_dta-stata0].log" - ).exists() - assert tmp_path.joinpath( - "task_example_py_task_execute_do_file[output_2_dta-stata1].log" - ).exists() - else: - assert tmp_path.joinpath("script.log").exists() - - @needs_stata @pytest.mark.end_to_end() def test_parametrize_command_line_options_w_loop(runner, tmp_path):