62 """True when *node* is a function decorated with pytest.fixture."""
63 if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
65 for decorator
in node.decorator_list:
66 target = decorator.func
if isinstance(decorator, ast.Call)
else decorator
67 if isinstance(target, ast.Attribute)
and target.attr ==
"fixture":
69 if isinstance(target, ast.Name)
and target.id ==
"fixture":
102def _scan_tree(tree: ast.Module, file_str: str) -> list[dict]:
105 findings: list[dict] = []
107 def _error(rule: str, lineno: int, detail: str) ->
None:
108 findings.append(make_violation(
109 rule, file_str,
None,
"error",
110 f
"{detail} (line {lineno}); use monkeypatch or an approved "
111 "fixture (spec-10 section 6)",
114 def _advisory(lineno: int, detail: str) ->
None:
115 findings.append(make_violation(
116 "owned_writes_outside_tmp", file_str,
None,
"advisory",
117 f
"{detail} (line {lineno}); prefer tmp_path-derived targets "
118 "(spec-10 section 6.2 advisory pre-check)",
121 for node
in ast.walk(tree):
122 lineno = getattr(node,
"lineno",
None)
123 if lineno
is None or _in_spans(lineno, spans):
126 if isinstance(node, ast.Call):
130 isinstance(callee, ast.Attribute)
131 and callee.attr ==
"chdir"
132 and isinstance(callee.value, ast.Name)
133 and callee.value.id ==
"os"
135 _error(
"cwd_chdir", lineno,
"raw os.chdir call")
138 isinstance(callee, ast.Attribute)
139 and callee.attr ==
"update"
142 _error(
"env_mutation", lineno,
"raw os.environ.update call")
145 isinstance(callee, ast.Attribute)
146 and callee.attr ==
"putenv"
147 and isinstance(callee.value, ast.Name)
148 and callee.value.id ==
"os"
150 _error(
"env_mutation", lineno,
"raw os.putenv call")
153 isinstance(callee, ast.Name)
154 and callee.id ==
"open"
160 mode = node.args[1].value
161 for kw
in node.keywords:
163 mode = kw.value.value
164 if mode
and any(ch
in mode
for ch
in _WRITE_MODES):
165 _advisory(lineno,
"literal-path write via open()")
168 isinstance(callee, ast.Attribute)
169 and callee.attr
in (
"write_text",
"write_bytes")
170 and isinstance(callee.value, ast.Call)
171 and isinstance(callee.value.func, ast.Name)
172 and callee.value.func.id ==
"Path"
173 and callee.value.args
176 _advisory(lineno,
"literal-path write via Path().write_*")
177 elif isinstance(node, ast.Assign):
178 for target
in node.targets:
180 isinstance(target, ast.Subscript)
183 _error(
"env_mutation", lineno,
184 "raw os.environ[...] assignment")
185 elif isinstance(node, ast.AugAssign):
187 isinstance(node.target, ast.Subscript)
190 _error(
"env_mutation", lineno,
191 "raw os.environ[...] augmented assignment")
192 elif isinstance(node, ast.Delete):
193 for target
in node.targets:
195 isinstance(target, ast.Subscript)
198 _error(
"env_mutation", lineno,
199 "raw del os.environ[...]")
201 _dbg(
"OC_TESTING", func, f
"{file_str}: {len(findings)} finding(s)", 4)