def test_parallel_execution_speedup(tmp_path, parallel_backend): source = """ import pytask import time @pytask.mark.produces("out_1.txt") def task_1(produces): time.sleep(5) produces.write_text("1") @pytask.mark.produces("out_2.txt") def task_2(produces): time.sleep(5) produces.write_text("2") """ tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(source)) session = main({"paths": tmp_path}) assert session.exit_code == 0 assert session.execution_end - session.execution_start > 10 tmp_path.joinpath("out_1.txt").unlink() tmp_path.joinpath("out_2.txt").unlink() session = main({ "paths": tmp_path, "n_workers": 2, "parallel_backend": parallel_backend }) assert session.exit_code == 0 assert session.execution_end - session.execution_start < 10
def test_multiple_runs_with_persist(tmp_path): """Perform multiple consecutive runs and check intermediate outcomes with persist. 1. The product is missing which should result in a normal execution of the task. 2. Change the product, check that run is successful and state in database has changed. 3. Run the task another time. Now, the task is skipped successfully. """ source = """ import pytask @pytask.mark.persist @pytask.mark.depends_on("in.txt") @pytask.mark.produces("out.txt") def task_dummy(depends_on, produces): produces.write_text(depends_on.read_text()) """ tmp_path.joinpath("task_module.py").write_text(textwrap.dedent(source)) tmp_path.joinpath("in.txt").write_text("I'm not the reason you care.") session = main({"paths": tmp_path}) assert session.exit_code == ExitCode.OK assert len(session.execution_reports) == 1 assert session.execution_reports[0].outcome == TaskOutcome.SUCCESS assert session.execution_reports[0].exc_info is None assert tmp_path.joinpath("out.txt").exists() tmp_path.joinpath("out.txt").write_text("Never again in despair.") session = main({"paths": tmp_path}) assert session.exit_code == ExitCode.OK assert len(session.execution_reports) == 1 assert session.execution_reports[0].outcome == TaskOutcome.PERSISTENCE assert isinstance(session.execution_reports[0].exc_info[1], Persisted) with orm.db_session: create_database("sqlite", tmp_path.joinpath(".pytask.sqlite3").as_posix(), True, False) task_id = tmp_path.joinpath( "task_module.py").as_posix() + "::task_dummy" node_id = tmp_path.joinpath("out.txt").as_posix() state = State[task_id, node_id].state assert float(state) == tmp_path.joinpath("out.txt").stat().st_mtime session = main({"paths": tmp_path}) assert session.exit_code == ExitCode.OK assert len(session.execution_reports) == 1 assert session.execution_reports[0].outcome == TaskOutcome.SKIP_UNCHANGED assert isinstance(session.execution_reports[0].exc_info[1], SkippedUnchanged)
def test_skip_unchanged(tmp_path): source = """ def task_dummy(): pass """ tmp_path.joinpath("task_module.py").write_text(textwrap.dedent(source)) session = main({"paths": tmp_path}) assert session.execution_reports[0].outcome == TaskOutcome.SUCCESS session = main({"paths": tmp_path}) assert isinstance(session.execution_reports[0].exc_info[1], SkippedUnchanged)
def test_duration_is_stored_in_task(tmp_path): source = """ import time def task_example(): time.sleep(2) """ tmp_path.joinpath("task_example.py").write_text(textwrap.dedent(source)) session = main({"paths": tmp_path}) assert session.exit_code == ExitCode.OK assert len(session.tasks) == 1 task = session.tasks[0] duration = task.attributes["duration"] assert duration[1] - duration[0] > 2 with orm.db_session: create_database("sqlite", tmp_path.joinpath(".pytask.sqlite3").as_posix(), True, False) task_name = tmp_path.joinpath( "task_example.py").as_posix() + "::task_example" runtime = Runtime[task_name] assert runtime.duration > 2
def test_if_skipif_decorator_is_applied_skipping(tmp_path): source = """ import pytask @pytask.mark.skipif(condition=True, reason="bla") @pytask.mark.produces("out.txt") def task_first(): assert False @pytask.mark.depends_on("out.txt") def task_second(): assert False """ tmp_path.joinpath("task_module.py").write_text(textwrap.dedent(source)) session = main({"paths": tmp_path}) node = session.collection_reports[0].node assert len(node.markers) == 1 assert node.markers[0].name == "skipif" assert node.markers[0].args == () assert node.markers[0].kwargs == {"condition": True, "reason": "bla"} assert session.execution_reports[0].outcome == TaskOutcome.SKIP assert isinstance(session.execution_reports[0].exc_info[1], Skipped) assert session.execution_reports[1].outcome == TaskOutcome.SKIP assert isinstance(session.execution_reports[1].exc_info[1], Skipped) assert session.execution_reports[0].exc_info[1].args[0] == "bla"
def test_if_skipif_decorator_is_applied_execute(tmp_path): source = """ import pytask @pytask.mark.skipif(False, reason="bla") @pytask.mark.produces("out.txt") def task_first(produces): with open(produces, "w") as f: f.write("hello world.") @pytask.mark.depends_on("out.txt") def task_second(): pass """ tmp_path.joinpath("task_module.py").write_text(textwrap.dedent(source)) session = main({"paths": tmp_path}) node = session.collection_reports[0].node assert len(node.markers) == 1 assert node.markers[0].name == "skipif" assert node.markers[0].args == (False,) assert node.markers[0].kwargs == {"reason": "bla"} assert session.execution_reports[0].outcome == TaskOutcome.SUCCESS assert session.execution_reports[0].exc_info is None assert session.execution_reports[1].outcome == TaskOutcome.SUCCESS assert session.execution_reports[1].exc_info is None
def test_keyword_option_custom(tmp_path, expr: str, expected_passed: str) -> None: tmp_path.joinpath("task_module.py").write_text( textwrap.dedent( """ def task_interface(): pass def task_nointer(): pass def task_pass(): pass def task_no_1(): pass def task_no_2(): pass """ ) ) session = main({"paths": tmp_path, "expression": expr}) assert session.exit_code == ExitCode.OK tasks_that_run = [ report.task.name.rsplit("::")[1] for report in session.execution_reports if not report.exc_info ] assert set(tasks_that_run) == set(expected_passed)
def test_parametrized_execution_of_do_file(tmp_path): task_source = """ import pytask @pytask.mark.stata @pytask.mark.parametrize("depends_on, produces", [ ("script_1.do", "0.dta"), ("script_2.do", "1.dta"), ]) def task_run_do_file(): pass """ tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(task_source)) for name, out in [ ("script_1.do", "0"), ("script_2.do", "1"), ]: do_file = f""" sysuse auto, clear save {out} """ tmp_path.joinpath(name).write_text(textwrap.dedent(do_file)) session = main({"paths": tmp_path}) assert session.exit_code == 0 assert tmp_path.joinpath("0.dta").exists() assert tmp_path.joinpath("1.dta").exists()
def test_run_do_file(tmp_path): task_source = """ import pytask @pytask.mark.stata @pytask.mark.depends_on("script.do") @pytask.mark.produces("auto.dta") def task_run_do_file(): pass """ tmp_path.joinpath("task_dummy.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)) session = main({"paths": tmp_path, "stata_keep_log": True}) assert session.exit_code == 0 assert tmp_path.joinpath("auto.dta").exists() if sys.platform == "win32": assert tmp_path.joinpath("task_dummy_py_task_run_do_file.log").exists() else: assert tmp_path.joinpath("script.log").exists()
def test_if_skipif_decorator_is_applied_any_condition_matches(tmp_path): """Any condition of skipif has to be True and only their message is shown.""" source = """ import pytask @pytask.mark.skipif(condition=False, reason="I am fine") @pytask.mark.skipif(condition=True, reason="No, I am not.") @pytask.mark.produces("out.txt") def task_first(): assert False @pytask.mark.depends_on("out.txt") def task_second(): assert False """ tmp_path.joinpath("task_module.py").write_text(textwrap.dedent(source)) session = main({"paths": tmp_path}) node = session.collection_reports[0].node assert len(node.markers) == 2 assert node.markers[0].name == "skipif" assert node.markers[0].args == () assert node.markers[0].kwargs == {"condition": True, "reason": "No, I am not."} assert node.markers[1].name == "skipif" assert node.markers[1].args == () assert node.markers[1].kwargs == {"condition": False, "reason": "I am fine"} assert session.execution_reports[0].outcome == TaskOutcome.SKIP assert isinstance(session.execution_reports[0].exc_info[1], Skipped) assert session.execution_reports[1].outcome == TaskOutcome.SKIP assert isinstance(session.execution_reports[1].exc_info[1], Skipped) assert session.execution_reports[0].exc_info[1].args[0] == "No, I am not."
def test_collect_same_test_different_ways(tmp_path, path_extension): tmp_path.joinpath("task_module.py").write_text("def task_passes(): pass") session = main({"paths": tmp_path.joinpath(path_extension)}) assert session.exit_code == ExitCode.OK assert len(session.tasks) == 1
def test_preserve_input_for_dependencies_and_products(tmp_path, input_type): """Input type for dependencies and products is preserved.""" path = tmp_path.joinpath("in.txt") input_ = {0: path.as_posix()} if input_type == "dict" else [path.as_posix()] path.touch() path = tmp_path.joinpath("out.txt") output = {0: path.as_posix()} if input_type == "dict" else [path.as_posix()] source = f""" import pytask from pathlib import Path @pytask.mark.depends_on({input_}) @pytask.mark.produces({output}) def task_example(depends_on, produces): for nodes in [depends_on, produces]: assert isinstance(nodes, dict) assert len(nodes) == 1 assert 0 in nodes produces[0].touch() """ tmp_path.joinpath("task_module.py").write_text(textwrap.dedent(source)) session = main({"paths": tmp_path}) assert session.exit_code == ExitCode.OK
def test_ignore_default_paths(tmp_path, ignored_folder): folder = ignored_folder.split("/*")[0] tmp_path.joinpath(folder).mkdir() tmp_path.joinpath(folder, "task_module.py").write_text("def task_d(): pass") session = main({"paths": tmp_path}) assert session.exit_code == ExitCode.OK assert len(session.tasks) == 0
def test_marker_names_toml(tmp_path, marker_name): toml = f""" [tool.pytask.ini_options.markers] markers = ['{marker_name}'] """ tmp_path.joinpath("pyproject.toml").write_text(textwrap.dedent(toml)) session = main({"paths": tmp_path, "markers": True}) assert session.exit_code == ExitCode.CONFIGURATION_FAILED
def test_ignore_paths(tmp_path, config_path, ignore, new_line): tmp_path.joinpath("task_module.py").write_text("def task_example(): pass") entry = f"ignore =\n\t{ignore}" if new_line else f"ignore = {ignore}" config = f"[pytask]\n{entry}" if ignore else "[pytask]" tmp_path.joinpath(config_path).write_text(config) session = main({"paths": tmp_path}) assert session.exit_code == ExitCode.OK assert len(session.tasks) == 0 if ignore else len(session.tasks) == 1
def test_marker_names(tmp_path, marker_name, config_name): ini = f""" [pytask] markers = {marker_name} """ tmp_path.joinpath(config_name).write_text(textwrap.dedent(ini)) session = main({"paths": tmp_path, "markers": True}) assert session.exit_code == ExitCode.CONFIGURATION_FAILED
def test_ignore_paths_toml(tmp_path, ignore, new_line): tmp_path.joinpath("task_module.py").write_text("def task_example(): pass") entry = f"ignore = ['{ignore}']" if new_line else f"ignore = '{ignore}'" config = ( f"[tool.pytask.ini_options]\n{entry}" if ignore else "[tool.pytask.ini_options]" ) tmp_path.joinpath("pyproject.toml").write_text(config) session = main({"paths": tmp_path}) assert session.exit_code == ExitCode.OK assert len(session.tasks) == 0 if ignore else len(session.tasks) == 1
def test_execution_stops_after_n_failures(tmp_path, n_failures): source = """ def task_1(): raise Exception def task_2(): raise Exception def task_3(): raise Exception """ tmp_path.joinpath("task_module.py").write_text(textwrap.dedent(source)) session = main({"paths": tmp_path, "max_failures": n_failures}) assert len(session.tasks) == 3 assert len(session.execution_reports) == n_failures
def test_collect_files_w_custom_file_name_pattern_toml( tmp_path, task_files, pattern, expected_collected_tasks): tmp_path.joinpath("pyproject.toml").write_text( f"[tool.pytask.ini_options]\ntask_files = {pattern}") for file in task_files: tmp_path.joinpath(file).write_text("def task_example(): pass") session = main({"paths": tmp_path}) assert session.exit_code == ExitCode.OK assert len(session.tasks) == expected_collected_tasks
def test_skip_unchanged_w_dependencies_and_products(tmp_path): source = """ import pytask @pytask.mark.depends_on("in.txt") @pytask.mark.produces("out.txt") def task_dummy(depends_on, produces): produces.write_text(depends_on.read_text()) """ tmp_path.joinpath("task_module.py").write_text(textwrap.dedent(source)) tmp_path.joinpath("in.txt").write_text("Original content of in.txt.") session = main({"paths": tmp_path}) assert session.execution_reports[0].outcome == TaskOutcome.SUCCESS assert tmp_path.joinpath("out.txt").read_text() == "Original content of in.txt." session = main({"paths": tmp_path}) assert session.execution_reports[0].outcome == TaskOutcome.SKIP_UNCHANGED assert isinstance(session.execution_reports[0].exc_info[1], SkippedUnchanged) assert tmp_path.joinpath("out.txt").read_text() == "Original content of in.txt."
def test_keyword_option_wrong_arguments( tmp_path, capsys, option: str, expr: str, expected_error: str ) -> None: tmp_path.joinpath("task_module.py").write_text( textwrap.dedent("def task_func(arg): pass") ) session = main({"paths": tmp_path, option: expr}) assert session.exit_code == ExitCode.RESOLVING_DEPENDENCIES_FAILED captured = capsys.readouterr() assert expected_error in captured.out.replace( "\n", " " ) or expected_error in captured.out.replace("\n", "")
def test_parse_markers_toml(tmp_path): toml = """ [tool.pytask.ini_options.markers] a1 = "this is a webtest marker" a2 = "this is a smoke marker" """ tmp_path.joinpath("pyproject.toml").write_text(textwrap.dedent(toml)) session = main({"paths": tmp_path}) assert session.exit_code == ExitCode.OK assert "a1" in session.config["markers"] assert "a2" in session.config["markers"]
def test_collect_files_w_custom_file_name_pattern(tmp_path, config_name, task_files, pattern, expected_collected_tasks): tmp_path.joinpath(config_name).write_text( f"[pytask]\ntask_files = {pattern}") for file in task_files: tmp_path.joinpath(file).write_text("def task_example(): pass") session = main({"paths": tmp_path}) assert session.exit_code == ExitCode.OK assert len(session.tasks) == expected_collected_tasks
def test_parametrize_with_single_dict(tmp_path): source = """ import pytask @pytask.mark.parametrize('i', [{"a": 1}, {"a": 1.0}]) def task_write_numbers_to_file(i): assert i["a"] == 1 """ tmp_path.joinpath("task_module.py").write_text(textwrap.dedent(source)) session = main({"paths": tmp_path}) assert session.exit_code == ExitCode.OK
def test_raise_error_if_parametrization_produces_non_unique_tasks(tmp_path): tmp_path.joinpath("task_module.py").write_text( textwrap.dedent(""" import pytask @pytask.mark.parametrize('i', [0, 0]) def task_func(i): pass """)) session = main({"paths": tmp_path}) assert session.exit_code == ExitCode.COLLECTION_FAILED assert isinstance(session.collection_reports[0].exc_info[1], ValueError)
def test_raise_error_for_irregular_ids(tmp_path, ids): tmp_path.joinpath("task_module.py").write_text( textwrap.dedent(f""" import pytask @pytask.mark.parametrize('i', range(2), ids={ids}) def task_func(): pass """)) session = main({"paths": tmp_path}) assert session.exit_code == ExitCode.COLLECTION_FAILED assert isinstance(session.collection_reports[0].exc_info[1], ValueError)
def test_debug_pytask(capsys, tmp_path): session = main({"paths": tmp_path, "debug_pytask": True}) assert session.exit_code == ExitCode.OK captured = capsys.readouterr() # The first hooks which will be displayed. assert "pytask_post_parse [hook]" in captured.out assert "finish pytask_post_parse --> [] [hook]" in captured.out # The last hooks which will be displayed. assert "finish pytask_execute_log_end --> [True] [hook]" in captured.out assert "finish pytask_execute --> None [hook]" in captured.out
def test_pass_config_to_cli_toml(tmp_path): config = """ [tool.pytask.ini_options] markers = {"elton" = "Can you feel the love tonight?"} """ tmp_path.joinpath("pyproject.toml").write_text(textwrap.dedent(config)) session = main({ "config": tmp_path.joinpath("pyproject.toml"), "paths": tmp_path }) assert session.exit_code == ExitCode.OK assert "elton" in session.config["markers"]
def test_raise_error_if_function_does_not_use_parametrized_arguments(tmp_path): source = """ import pytask @pytask.mark.parametrize('i', range(2)) def task_func(): pass """ tmp_path.joinpath("task_module.py").write_text(textwrap.dedent(source)) session = main({"paths": tmp_path}) assert session.exit_code == ExitCode.FAILED assert isinstance(session.execution_reports[0].exc_info[1], TypeError) assert isinstance(session.execution_reports[1].exc_info[1], TypeError)
def test_parametrize_w_ids(tmp_path, arg_values, ids): tmp_path.joinpath("task_module.py").write_text( textwrap.dedent(f""" import pytask @pytask.mark.parametrize('i', {arg_values}, ids={ids}) def task_func(i): pass """)) session = main({"paths": tmp_path}) assert session.exit_code == ExitCode.OK for task, id_ in zip(session.tasks, ids): assert id_ in task.name