|
16 | 16 | ) |
17 | 17 |
|
18 | 18 |
|
19 | | -def _to_pyargs_nodeid(file_path: str, func_name: str) -> str | None: |
20 | | - """Attempt to build a pytest nodeid suitable for `pytest <nodeid>`. |
21 | | -
|
22 | | - Preference order: |
23 | | - 1) Dotted package module path with double-colon: pkg.subpkg.module::func |
24 | | - 2) Filesystem path with double-colon: path/to/module.py::func |
25 | | -
|
26 | | - Returns dotted form when package root can be inferred (directory chain with __init__.py |
27 | | - leading up to a directory contained in sys.path). Returns None if no reasonable |
28 | | - nodeid can be created (should be rare). |
29 | | - """ |
30 | | - try: |
31 | | - abs_path = os.path.abspath(file_path) |
32 | | - dir_path, filename = os.path.split(abs_path) |
33 | | - module_base, ext = os.path.splitext(filename) |
34 | | - if ext != ".py": |
35 | | - # Not a python file |
36 | | - return None |
37 | | - |
38 | | - # Walk up while packages have __init__.py |
39 | | - segments: list[str] = [module_base] |
40 | | - current = dir_path |
41 | | - package_root = None |
42 | | - while True: |
43 | | - if os.path.isfile(os.path.join(current, "__init__.py")): |
44 | | - segments.insert(0, os.path.basename(current)) |
45 | | - parent = os.path.dirname(current) |
46 | | - # Stop if parent is not within current sys.path import roots |
47 | | - if parent == current: |
48 | | - break |
49 | | - current = parent |
50 | | - else: |
51 | | - package_root = current |
52 | | - break |
53 | | - |
54 | | - # If we found a package chain, check that the package_root is importable (in sys.path) |
55 | | - if package_root and any( |
56 | | - os.path.abspath(sp).rstrip(os.sep) == os.path.abspath(package_root).rstrip(os.sep) for sp in sys.path |
57 | | - ): |
58 | | - dotted = ".".join(segments) |
59 | | - return f"{dotted}::{func_name}" |
60 | | - |
61 | | - # Do not emit a dotted top-level module for non-packages; prefer path-based nodeid |
62 | | - |
63 | | - # Fallback to relative path (if under cwd) or absolute path |
64 | | - cwd = os.getcwd() |
65 | | - try: |
66 | | - rel = os.path.relpath(abs_path, cwd) |
67 | | - except Exception: |
68 | | - rel = abs_path |
69 | | - return f"{rel}::{func_name}" |
70 | | - except Exception: |
71 | | - return None |
72 | | - |
73 | | - |
74 | 19 | def _parse_entry(entry: str, cwd: str) -> tuple[str, str]: |
75 | 20 | # Accept module::function, path::function, or legacy module:function |
76 | 21 | entry = entry.strip() |
|
0 commit comments