Skip to content

Commit f3e61e7

Browse files
fix: use synthetic string paths in test_dir.py to avoid Windows path issues
Replace tmp_path-based paths with string paths in TestGetProjectNameFromDir since get_project_name_from_dir() only reads .name and never touches the filesystem. This avoids Windows-specific failures where directory names like "..." are interpreted as parent traversal. Agent-Logs-Url: https://github.com/usethis-python/usethis-python/sessions/c5cf2c22-5f12-46c3-a10e-ee4f3dd165a7 Co-authored-by: nathanjmcdougall <18602289+nathanjmcdougall@users.noreply.github.com>
1 parent d96f071 commit f3e61e7

2 files changed

Lines changed: 49 additions & 15 deletions

File tree

.agents/skills/usethis-python-test/SKILL.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: General guidelines for writing tests in the usethis project, includ
44
compatibility: usethis, Python, pytest
55
license: MIT
66
metadata:
7-
version: "1.5"
7+
version: "1.6"
88
---
99

1010
# Python Test Guidelines
@@ -180,3 +180,39 @@ For a function that sets a value at a key path, the two axes are:
180180
- **Value complexity** — scalar vs. nested dict (e.g. `"pep257"` vs. `{"select": ["A"], "pydocstyle": {"convention": "pep257"}}`)
181181

182182
Single-axis tests cover multiple keys with a scalar value, and a single key with a nested dict value. The cross-axis test uses multiple keys **and** a multiply-nested dict value together.
183+
184+
## Prefer synthetic paths when testing pure path-metadata logic
185+
186+
When the code under test only reads path metadata (`.name`, `.suffix`, `.parent`) and does not access the filesystem, use string paths via `usethis_config.set(project_dir="/fake/dir-name")` instead of creating real directories with `tmp_path` and `mkdir()`.
187+
188+
### Why
189+
190+
Real directory creation couples tests to OS-specific path normalisation rules. On Windows, certain directory names are invalid or silently rewritten:
191+
192+
- `"..."` is interpreted as parent directory traversal (`../..`), causing `FileExistsError`.
193+
- Trailing dots (e.g. `"project."`) are silently stripped.
194+
- Reserved names like `CON`, `NUL`, `PRN` are forbidden.
195+
196+
These restrictions are irrelevant when the function never touches the filesystem — they only introduce cross-platform test failures.
197+
198+
### How
199+
200+
Pass a string path directly to the configuration context manager. The `project_dir` parameter accepts `str`, which is converted to a `Path` internally:
201+
202+
```python
203+
# Correct: synthetic path, no filesystem dependency
204+
def test_leading_dot(self):
205+
with usethis_config.set(project_dir="/fake/.github-private"):
206+
assert get_project_name_from_dir() == "github-private"
207+
```
208+
209+
```python
210+
# Wrong: creates an unnecessary real-path dependency via tmp_path
211+
def test_leading_dot(self, tmp_path: Path):
212+
with usethis_config.set(project_dir=tmp_path / ".github-private"):
213+
assert get_project_name_from_dir() == "github-private"
214+
```
215+
216+
### When real directories are still needed
217+
218+
If the code under test calls filesystem operations (e.g. `is_dir()`, `exists()`, `mkdir()`, reads or writes files), real directories via `tmp_path` are appropriate and necessary.

tests/usethis/_file/test_dir.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,36 @@
1-
from pathlib import Path
2-
31
from usethis._config import usethis_config
42
from usethis._file.dir import get_project_name_from_dir
53

64

75
class TestGetProjectNameFromDir:
8-
def test_simple_name(self, tmp_path: Path):
9-
with usethis_config.set(project_dir=tmp_path / "my_project"):
6+
def test_simple_name(self):
7+
with usethis_config.set(project_dir="/fake/my_project"):
108
assert get_project_name_from_dir() == "my_project"
119

12-
def test_leading_dot(self, tmp_path: Path):
13-
with usethis_config.set(project_dir=tmp_path / ".github-private"):
10+
def test_leading_dot(self):
11+
with usethis_config.set(project_dir="/fake/.github-private"):
1412
assert get_project_name_from_dir() == "github-private"
1513

16-
def test_leading_dots(self, tmp_path: Path):
17-
with usethis_config.set(project_dir=tmp_path / "..hidden"):
14+
def test_leading_dots(self):
15+
with usethis_config.set(project_dir="/fake/..hidden"):
1816
assert get_project_name_from_dir() == "hidden"
1917

2018
def test_trailing_dot(self):
2119
with usethis_config.set(project_dir="/fake/project."):
2220
assert get_project_name_from_dir() == "project"
2321

24-
def test_leading_and_trailing_non_alphanumeric(self, tmp_path: Path):
25-
with usethis_config.set(project_dir=tmp_path / "-_project_-"):
22+
def test_leading_and_trailing_non_alphanumeric(self):
23+
with usethis_config.set(project_dir="/fake/-_project_-"):
2624
assert get_project_name_from_dir() == "project"
2725

2826
def test_only_dots(self):
2927
with usethis_config.set(project_dir="/fake/..."):
3028
assert get_project_name_from_dir() == "hello_world"
3129

32-
def test_no_valid_chars(self, tmp_path: Path):
33-
with usethis_config.set(project_dir=tmp_path / "+"):
30+
def test_no_valid_chars(self):
31+
with usethis_config.set(project_dir="/fake/+"):
3432
assert get_project_name_from_dir() == "hello_world"
3533

36-
def test_drops_invalid_chars(self, tmp_path: Path):
37-
with usethis_config.set(project_dir=tmp_path / "h-e+l.l_o"):
34+
def test_drops_invalid_chars(self):
35+
with usethis_config.set(project_dir="/fake/h-e+l.l_o"):
3836
assert get_project_name_from_dir() == "h-el.l_o"

0 commit comments

Comments
 (0)