Skip to content

Commit 7181497

Browse files
authored
feat: add subdir for pytest param id with config artifacts_use_subdir_for_parametrize (#12)
* feat: add subdir for pytest param id with config `artifacts_use_subdir_for_parametrize` * docs: add config table and exmaples
1 parent f1bed60 commit 7181497

3 files changed

Lines changed: 109 additions & 10 deletions

File tree

README.md

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,60 @@ def test_benchmark(artifacts):
4646
The test case directory is named after the test path, function name, and if any, the test parameter ID.
4747

4848
### Configure
49-
Configurations may be set in `pyproject.toml`, `pytest.ini`, or passed as command line options.
49+
Configurations may be set in `pyproject.toml` or `pytest.ini`. Some options can also be set via CLI (use `pytest --help`)
50+
51+
| Option | Type | Default | Description |
52+
| --- | --- | --- | --- |
53+
| `artifacts_dir` | str | `.artifacts/` | Directory to store test artifacts. Also settable via the `--artifacts-dir` CLI option, which takes precedence over the ini setting. |
54+
| `artifacts_use_subdir_for_parametrize` | bool | `false` | When `True`, parametrized tests get a subdirectory per parameter ID (e.g. `.artifacts/test_foo/param_id/`). When `False`, all parameter variants share the same `.artifacts/test_foo/` directory and overwrite each other. |
5055

5156
```toml
5257
# pyproject.toml
53-
[tool.pytest]
54-
artifacts_dir = .artifacts/
58+
[tool.pytest.ini_options]
59+
artifacts_dir = ".artifacts/"
60+
artifacts_use_subdir_for_parametrize = false
5561
```
5662

5763
```ini
5864
# pytest.ini
5965
[pytest]
6066
artifacts_dir = .artifacts/
67+
artifacts_use_subdir_for_parametrize = false
6168
```
6269

6370
```sh
6471
pytest --artifacts-dir .artifacts/ tests/
6572
```
6673

74+
#### Parametrized test layout
75+
76+
77+
```py
78+
@pytest.mark.parametrize("x", [1, 2])
79+
def test_foo(artifacts, x):
80+
with artifacts.open("out.txt", "w") as f:
81+
f.write(str(x))
82+
```
83+
84+
With `artifacts_use_subdir_for_parametrize = false`:
85+
```
86+
.artifacts/
87+
└── test_foo[1]/
88+
└── out.txt
89+
└── test_foo[2]/
90+
└── out.txt
91+
```
92+
93+
With `artifacts_use_subdir_for_parametrize = true`:
94+
```
95+
.artifacts/
96+
└── test_foo/
97+
├── 1/
98+
│ └── out.txt
99+
└── 2/
100+
└── out.txt
101+
```
102+
67103
## Contributing
68104

69105
Contributions are very welcome.

src/pytest_artifacts/plugin.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ def pytest_addoption(parser):
2323
parser.addini(
2424
"artifacts_dir", "Directory to store test artifacts.", default=".artifacts/"
2525
)
26+
parser.addini(
27+
"artifacts_use_subdir_for_parametrize",
28+
"Whether to use subdirectories for parameterized tests.",
29+
default=False,
30+
type="bool",
31+
)
2632

2733

2834
def pytest_configure(config):
@@ -32,9 +38,16 @@ def pytest_configure(config):
3238

3339
config.artifacts_dir = artifacts_dir
3440

41+
artifacts_use_subdir_for_parametrize = config.getini(
42+
"artifacts_use_subdir_for_parametrize"
43+
)
44+
config.artifacts_use_subdir_for_parametrize = artifacts_use_subdir_for_parametrize
45+
3546

3647
@pytest.fixture
37-
def artifacts(request) -> Generator[ArtifactsRepository, None, None]: # pylint: disable=invalid-name
48+
def artifacts(
49+
request: pytest.FixtureRequest,
50+
) -> Generator[ArtifactsRepository, None, None]: # pylint: disable=invalid-name
3851
"""Provide an artifact repository to store and access test artifacts for the
3952
particular test case.
4053
@@ -50,10 +63,21 @@ def artifacts(request) -> Generator[ArtifactsRepository, None, None]: # pylint:
5063
ArtifactsRepository: The artifacts repository for the specific test
5164
case.
5265
"""
53-
artifacts_dir_for_test_case = (
54-
Path(request.config.artifacts_dir).resolve() / request.node.name
55-
)
56-
with ArtifactsRepository(artifacts_dir_for_test_case) as repo:
66+
base_artifacts_dir = Path(request.config.artifacts_dir).resolve()
67+
68+
if request.config.artifacts_use_subdir_for_parametrize:
69+
try:
70+
test_case_artifacts_dir = (
71+
base_artifacts_dir
72+
/ request.node.originalname
73+
/ request.node.callspec.id
74+
)
75+
except AttributeError:
76+
test_case_artifacts_dir = base_artifacts_dir / request.node.originalname
77+
else:
78+
test_case_artifacts_dir = base_artifacts_dir / request.node.originalname
79+
80+
with ArtifactsRepository(test_case_artifacts_dir) as repo:
5781
yield repo
5882

5983

tests/test_fixtures.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,12 @@
3131
),
3232
],
3333
)
34-
def test_fixture_artifacts_dir(pytester, addopts, ini, expected):
35-
"""Test that the artifacts_dir fixture returns the default value."""
34+
def test_can_configure_artifacts_dir(pytester, addopts, ini, expected):
35+
"""Test that the artifacts_dir fixture can be configured from
36+
* pytest ini file
37+
* command line arguments
38+
* both ini and command line arguments, with command line taking precedence
39+
"""
3640
pytester.makeini(ini)
3741

3842
# create a temporary pytest test module
@@ -55,6 +59,41 @@ def test_sth(request):
5559
assert result.ret == 0
5660

5761

62+
def test_can_configure_artifacts_use_subdir_for_parametrize(pytester):
63+
"""Test that the artifacts_use_subdir_for_parametrize fixture can be configured from
64+
pytest ini file.
65+
"""
66+
pytester.makeini("""
67+
[pytest]
68+
artifacts_use_subdir_for_parametrize = true
69+
""")
70+
71+
# create a temporary pytest test module
72+
pytester.makepyfile("""
73+
import pytest
74+
75+
@pytest.mark.parametrize("text", ["hello", "goodbye"])
76+
def test_sth(text, request, artifacts):
77+
assert request.config.artifacts_use_subdir_for_parametrize is True
78+
79+
with artifacts.open("foobar.txt", mode="wt") as f:
80+
f.write(text)
81+
f.flush()
82+
83+
with artifacts.open("foobar.txt", mode="rt") as f:
84+
assert f.readlines() == [text]
85+
86+
assert artifacts.dir.is_dir() and artifacts.dir.name in ["hello", "goodbye"]
87+
88+
""")
89+
90+
# run pytest with the following cmd args
91+
result = pytester.runpytest("-v")
92+
93+
# make sure that we get a '0' exit code for the testsuite
94+
assert result.ret == 0
95+
96+
5897
def test_fixture_artifacts(pytester):
5998
"""Test that writing to the artifacts creates a directory named after the
6099
test case.

0 commit comments

Comments
 (0)