Skip to content

Commit 14bc796

Browse files
authored
Add snapshot tests. (#475)
1 parent 1970445 commit 14bc796

File tree

11 files changed

+235
-75
lines changed

11 files changed

+235
-75
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ repos:
1616
- id: no-commit-to-branch
1717
args: [--branch, main]
1818
- id: trailing-whitespace
19+
exclude: (__snapshots__)
1920
- repo: https://github.com/pre-commit/pygrep-hooks
2021
rev: v1.10.0
2122
hooks:

environment.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ dependencies:
3535
- pytest-cov
3636
- pytest-env
3737
- pytest-xdist
38+
- syrupy
3839
- tabulate
3940
- tox
4041

src/_pytask/dag.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ def _check_if_root_nodes_are_available(dag: nx.DiGraph, paths: Sequence[Path]) -
234234
try:
235235
node_exists = dag.nodes[node]["node"].state()
236236
except Exception as e: # noqa: BLE001
237-
msg = _format_exception_from_failed_node_state(node, dag)
237+
msg = _format_exception_from_failed_node_state(node, dag, paths)
238238
raise ResolvingDependenciesError(msg) from e
239239
if not node_exists:
240240
missing_root_nodes.append(node)
@@ -256,13 +256,13 @@ def _check_if_root_nodes_are_available(dag: nx.DiGraph, paths: Sequence[Path]) -
256256

257257

258258
def _format_exception_from_failed_node_state(
259-
node_signature: str, dag: nx.DiGraph
259+
node_signature: str, dag: nx.DiGraph, paths: Sequence[Path]
260260
) -> str:
261261
"""Format message when ``node.state()`` threw an exception."""
262262
tasks = [dag.nodes[i]["task"] for i in dag.successors(node_signature)]
263263
names = [task.name for task in tasks]
264264
successors = ", ".join([f"{name!r}" for name in names])
265-
node_name = dag.nodes[node_signature]["node"].name
265+
node_name = format_node_name(dag.nodes[node_signature]["node"], paths).plain
266266
return (
267267
f"While checking whether dependency {node_name!r} from task(s) "
268268
f"{successors} exists, an error was raised."
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# serializer version: 1
2+
# name: test_more_nested_pytree_and_python_node_as_return
3+
'''
4+
───────────────────────────── Start pytask session ─────────────────────────────
5+
Platform: <platform> -- Python <version>, pytask <version>, pluggy <version>
6+
Root: <path>
7+
Collected 1 task.
8+
9+
Collected tasks:
10+
└── 🐍 <Module test_more_nested_pytree_and_py0/task_module.py>
11+
└── 📝 <Function task_module.py::task_example>
12+
├── 📄 <Product
13+
│ test_more_nested_pytree_and_py0/task_module.py::task_example::return
14+
│ ::0>
15+
├── 📄 <Product
16+
│ test_more_nested_pytree_and_py0/task_module.py::task_example::return
17+
│ ::1-0>
18+
├── 📄 <Product
19+
│ test_more_nested_pytree_and_py0/task_module.py::task_example::return
20+
│ ::1-1>
21+
└── 📄 <Product
22+
test_more_nested_pytree_and_py0/task_module.py::task_example::return
23+
::2>
24+
25+
────────────────────────────────────────────────────────────────────────────────
26+
'''
27+
# ---

tests/__snapshots__/test_dag.ambr

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# serializer version: 1
2+
# name: test_check_if_root_nodes_are_available
3+
'''
4+
───────────────────────────── Start pytask session ─────────────────────────────
5+
Platform: <platform> -- Python <version>, pytask <version>, pluggy <version>
6+
Root: <path>
7+
Collected 1 task.
8+
9+
──────────────────── Failures during resolving dependencies ────────────────────
10+
11+
ResolvingDependenciesError: Some dependencies do not exist or are not produced
12+
by any task. See the following tree which shows which dependencies are missing
13+
for which tasks.
14+
15+
Missing dependencies:
16+
└── 📄 test_check_if_root_nodes_are_a0/in.txt
17+
└── 📝 task_d.py::task_d
18+
19+
20+
(Hint: Your file-system is case-sensitive. Check the paths' capitalization
21+
carefully.)
22+
23+
────────────────────────────────────────────────────────────────────────────────
24+
'''
25+
# ---
26+
# name: test_check_if_root_nodes_are_available_w_name
27+
'''
28+
───────────────────────────── Start pytask session ─────────────────────────────
29+
Platform: <platform> -- Python <version>, pytask <version>, pluggy <version>
30+
Root: <path>
31+
Collected 1 task.
32+
33+
──────────────────── Failures during resolving dependencies ────────────────────
34+
35+
ResolvingDependenciesError: Some dependencies do not exist or are not produced
36+
by any task. See the following tree which shows which dependencies are missing
37+
for which tasks.
38+
39+
Missing dependencies:
40+
└── 📄 input1
41+
└── 📝 task_e.py::task_e
42+
43+
44+
(Hint: Your file-system is case-sensitive. Check the paths' capitalization
45+
carefully.)
46+
47+
────────────────────────────────────────────────────────────────────────────────
48+
'''
49+
# ---
50+
# name: test_check_if_root_nodes_are_available_with_separate_build_folder
51+
'''
52+
───────────────────────────── Start pytask session ─────────────────────────────
53+
Platform: <platform> -- Python <version>, pytask <version>, pluggy <version>
54+
Root: <path>
55+
Collected 1 task.
56+
57+
──────────────────── Failures during resolving dependencies ────────────────────
58+
59+
ResolvingDependenciesError: Some dependencies do not exist or are not produced
60+
by any task. See the following tree which shows which dependencies are missing
61+
for which tasks.
62+
63+
Missing dependencies:
64+
└── 📄 test_check_if_root_nodes_are_a2/bld/in.txt
65+
└── 📝 task_d.py::task_d
66+
67+
68+
(Hint: Your file-system is case-sensitive. Check the paths' capitalization
69+
carefully.)
70+
71+
────────────────────────────────────────────────────────────────────────────────
72+
'''
73+
# ---
74+
# name: test_cycle_in_dag
75+
'''
76+
───────────────────────────── Start pytask session ─────────────────────────────
77+
Platform: <platform> -- Python <version>, pytask <version>, pluggy <version>
78+
Root: <path>
79+
Collected 2 tasks.
80+
81+
──────────────────── Failures during resolving dependencies ────────────────────
82+
83+
ResolvingDependenciesError: The DAG contains cycles which means a dependency is
84+
directly or indirectly a product of the same task. See the following the path of
85+
nodes in the graph which forms the cycle.
86+
87+
task_module.py::task_1
88+
89+
test_cycle_in_dag0/out_1.txt
90+
91+
task_module.py::task_2
92+
93+
test_cycle_in_dag0/out_2.txt
94+
95+
task_module.py::task_1
96+
97+
────────────────────────────────────────────────────────────────────────────────
98+
'''
99+
# ---
100+
# name: test_error_when_node_state_throws_error
101+
'''
102+
───────────────────────────── Start pytask session ─────────────────────────────
103+
Platform: <platform> -- Python <version>, pytask <version>, pluggy <version>
104+
Root: <path>
105+
Collected 1 task.
106+
107+
──────────────────── Failures during resolving dependencies ────────────────────
108+
109+
TypeError: unhashable type: 'dict'
110+
111+
The above exception was the direct cause of the following exception:
112+
113+
ResolvingDependenciesError: While checking whether dependency
114+
'test_error_when_node_state_thr0/task_example.py::task_example::a' from task(s)
115+
'task_example.py::task_example' exists, an error was raised.
116+
117+
────────────────────────────────────────────────────────────────────────────────
118+
'''
119+
# ---
120+
# name: test_two_tasks_have_the_same_product
121+
'''
122+
───────────────────────────── Start pytask session ─────────────────────────────
123+
Platform: <platform> -- Python <version>, pytask <version>, pluggy <version>
124+
Root: <path>
125+
Collected 2 tasks.
126+
127+
──────────────────── Failures during resolving dependencies ────────────────────
128+
129+
ResolvingDependenciesError: There are some tasks which produce the same output.
130+
See the following tree which shows which products are produced by multiple
131+
tasks.
132+
133+
Products from multiple tasks:
134+
└── 📄 test_two_tasks_have_the_same_p0/out.txt
135+
├── 📝 task_d.py::task_1
136+
└── 📝 task_d.py::task_2
137+
138+
139+
────────────────────────────────────────────────────────────────────────────────
140+
'''
141+
# ---

tests/conftest.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,54 @@
11
from __future__ import annotations
22

3+
import re
34
import sys
45
from contextlib import contextmanager
56
from pathlib import Path
7+
from typing import Any
68
from typing import Callable
79

810
import pytest
911
from click.testing import CliRunner
12+
from packaging import version
13+
from pytask import console
1014

1115

1216
@pytest.fixture(autouse=True)
1317
def _add_objects_to_doctest_namespace(doctest_namespace):
1418
doctest_namespace["Path"] = Path
1519

1620

21+
@pytest.fixture(autouse=True, scope="session")
22+
def _path_for_snapshots():
23+
console.width = 80
24+
25+
26+
def _remove_variable_info_from_output(data: str, path: Any) -> str: # noqa: ARG001
27+
lines = data.splitlines()
28+
29+
# Remove dynamic versions.
30+
index_root = next(i for i, line in enumerate(lines) if line.startswith("Root:"))
31+
new_info_line = "".join(lines[1:index_root])
32+
for platform in ("linux", "win32", "darwin"):
33+
new_info_line = new_info_line.replace(platform, "<platform>")
34+
pattern = re.compile(version.VERSION_PATTERN, flags=re.IGNORECASE | re.VERBOSE)
35+
new_info_line = re.sub(pattern=pattern, repl="<version>", string=new_info_line)
36+
37+
# Remove dynamic root path
38+
index_collected = next(
39+
i for i, line in enumerate(lines) if line.startswith("Collected")
40+
)
41+
new_root_line = "Root: <path>"
42+
43+
new_lines = [lines[0], new_info_line, new_root_line, *lines[index_collected:]]
44+
return "\n".join(new_lines)
45+
46+
47+
@pytest.fixture()
48+
def snapshot_cli(snapshot):
49+
return snapshot.with_defaults(matcher=_remove_variable_info_from_output)
50+
51+
1752
class SysPathsSnapshot:
1853
"""A snapshot for sys.path."""
1954

tests/test_collect.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -455,8 +455,6 @@ def task_write_text(depends_on, produces):
455455

456456
result = runner.invoke(cli, [tmp_path.as_posix()])
457457
assert "FutureWarning" in result.output
458-
assert "Using strings to specify a dependency" in result.output
459-
assert "Using strings to specify a product" in result.output
460458

461459

462460
@pytest.mark.end_to_end()

tests/test_collect_command.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import os
44
import pickle
5+
import sys
56
import textwrap
67
from pathlib import Path
78

@@ -610,7 +611,7 @@ def task_example(
610611

611612

612613
@pytest.mark.end_to_end()
613-
def test_more_nested_pytree_and_python_node_as_return(runner, tmp_path):
614+
def test_more_nested_pytree_and_python_node_as_return(runner, snapshot_cli, tmp_path):
614615
source = """
615616
from pathlib import Path
616617
from typing import Any
@@ -630,8 +631,5 @@ def task_example() -> Annotated[Dict[str, str], nodes]:
630631
tmp_path.joinpath("task_module.py").write_text(textwrap.dedent(source))
631632
result = runner.invoke(cli, ["collect", "--nodes", tmp_path.as_posix()])
632633
assert result.exit_code == ExitCode.OK
633-
output = result.output.replace(" ", "").replace("\n", "").replace("│", "")
634-
assert "return::0" in output
635-
assert "return::1-0" in output
636-
assert "return::1-1" in output
637-
assert "return::2" in output
634+
if sys.platform != "win32":
635+
assert result.output == snapshot_cli()

0 commit comments

Comments
 (0)