返回 CodeWhale
test_check_blocking_calls_budget.py
根目录 / scripts / test_check_blocking_calls_budget.py
1 #!/usr/bin/env python3
2 """Hermetic tests for the blocking-calls budget gate (#6149)."""
3
4 from __future__ import annotations
5
6 import importlib.util
7 import sys
8 import tempfile
9 import unittest
10 from pathlib import Path
11
12 ROOT = Path(__file__).resolve().parents[1]
13 SCRIPT = ROOT / "scripts" / "check-blocking-calls-budget.py"
14 SPEC = importlib.util.spec_from_file_location("blocking_calls", SCRIPT)
15 assert SPEC and SPEC.loader
16 mod = importlib.util.module_from_spec(SPEC)
17 sys.modules[SPEC.name] = mod
18 SPEC.loader.exec_module(mod)
19
20
21 def counts(source: str) -> dict[str, int]:
22 with tempfile.TemporaryDirectory() as tmp:
23 victim = Path(tmp) / "victim.rs"
24 victim.write_text(source, encoding="utf-8")
25 return mod.file_counts(victim)
26
27
28 class BlockingCallScopeTests(unittest.TestCase):
29 def test_sleep_in_plain_fn_counts(self) -> None:
30 self.assertEqual(
31 counts("fn wait() {\n std::thread::sleep(std::time::Duration::from_millis(1));\n}\n"),
32 {"thread_sleep": 1},
33 )
34
35 def test_sleep_in_async_fn_counts(self) -> None:
36 self.assertEqual(
37 counts("async fn run() {\n std::thread::sleep(std::time::Duration::from_millis(1));\n}\n"),
38 {"thread_sleep": 1},
39 )
40
41 def test_sleep_in_spawn_blocking_is_exempt(self) -> None:
42 self.assertEqual(
43 counts(
44 "async fn run() {\n"
45 " tokio::task::spawn_blocking(move || {\n"
46 " std::thread::sleep(std::time::Duration::from_millis(1));\n"
47 " });\n"
48 "}\n"
49 ),
50 {},
51 )
52
53 def test_sleep_in_dedicated_thread_is_exempt(self) -> None:
54 self.assertEqual(
55 counts(
56 "fn pump() {\n"
57 " std::thread::Builder::new().spawn(move || {\n"
58 " std::thread::sleep(std::time::Duration::from_millis(5));\n"
59 " });\n"
60 "}\n"
61 ),
62 {},
63 )
64
65 def test_sleep_in_tests_mod_is_exempt(self) -> None:
66 self.assertEqual(
67 counts(
68 "fn prod() {}\n"
69 "#[cfg(test)]\n"
70 "mod tests {\n"
71 " fn probe() { std::thread::sleep(std::time::Duration::from_millis(1)); }\n"
72 "}\n"
73 ),
74 {},
75 )
76
77 def test_sleep_in_cfg_test_fn_is_exempt(self) -> None:
78 self.assertEqual(
79 counts(
80 "#[cfg(test)]\n"
81 "fn helper() { std::thread::sleep(std::time::Duration::from_millis(1)); }\n"
82 ),
83 {},
84 )
85
86 def test_std_fs_call_counts(self) -> None:
87 self.assertEqual(
88 counts("async fn go() {\n let _ = std::fs::read_to_string(p).unwrap();\n}\n"),
89 {"std_fs": 1},
90 )
91
92 def test_std_fs_imports_and_signature_types_do_not_count(self) -> None:
93 self.assertEqual(
94 counts(
95 "use std::fs::File;\n"
96 "use std::fs::OpenOptions;\n"
97 "use std::fs::DirBuilder;\n"
98 "type Handle = std::fs::File;\n"
99 "fn types(_: &std::fs::File, _: std::fs::OpenOptions, "
100 "_: std::fs::DirBuilder) -> Option<std::fs::File> { None }\n"
101 ),
102 {},
103 )
104
105 def test_std_fs_qualified_member_operations_count(self) -> None:
106 for operation in (
107 "std::fs::File::open(path)",
108 "std::fs::File::create(path)",
109 "std::fs::File::options()",
110 "std::fs::OpenOptions::new()",
111 "std::fs::DirBuilder::new()",
112 "std::fs::File :: open(path)",
113 ):
114 with self.subTest(operation=operation):
115 self.assertEqual(
116 counts(f"async fn run() {{ let _ = {operation}; }}\n"),
117 {"std_fs": 1},
118 )
119
120 def test_std_fs_qualified_member_in_spawn_blocking_is_exempt(self) -> None:
121 self.assertEqual(
122 counts(
123 "async fn run() {\n"
124 " tokio::task::spawn_blocking(move || {\n"
125 " let _ = std::fs::File::open(path);\n"
126 " });\n"
127 "}\n"
128 ),
129 {},
130 )
131
132 def test_comment_and_string_literals_do_not_count(self) -> None:
133 self.assertEqual(
134 counts(
135 "fn doc() {\n"
136 " // std::thread::sleep(std::time::Duration::from_millis(1));\n"
137 ' let s = "std::fs::read_to_string(p)";\n'
138 " let t = r#\"std::fs::write(a, b)\"#;\n"
139 "}\n"
140 ),
141 {},
142 )
143
144 def test_tokio_equivalents_do_not_count(self) -> None:
145 self.assertEqual(
146 counts(
147 "async fn go() {\n"
148 " tokio::time::sleep(std::time::Duration::from_millis(1)).await;\n"
149 " let _ = tokio::fs::read_to_string(p).await;\n"
150 "}\n"
151 ),
152 {},
153 )
154
155
156 class CfgTestModuleExclusion(unittest.TestCase):
157 """A file that is wholly a `#[cfg(test)]` module is test code (#6149).
158
159 The per-file scanner only sees test scope declared *inside* a file, so an
160 extracted test suite looked like brand-new unprotected call sites even
161 though nothing moved onto an async path. PR #6096's
162 `session_export_*_tests.rs` reddened `main` this way.
163 """
164
165 def _crates(self, tmp: Path, files: dict[str, str]) -> Path:
166 crates = tmp / "crates" / "demo" / "src"
167 crates.mkdir(parents=True)
168 for name, body in files.items():
169 target = crates / name
170 target.parent.mkdir(parents=True, exist_ok=True)
171 target.write_text(body, encoding="utf-8")
172 return tmp / "crates"
173
174 def test_whole_file_cfg_test_module_is_excluded(self) -> None:
175 with tempfile.TemporaryDirectory() as tmp:
176 root = Path(tmp)
177 crates = self._crates(
178 root,
179 {
180 "lib.rs": "#[cfg(test)]\nmod suite;\n",
181 "suite.rs": "fn helper() { let _ = std::fs::read_to_string(p); }\n",
182 },
183 )
184 original = mod.CRATES
185 try:
186 mod.CRATES = crates
187 excluded = mod.cfg_test_module_files()
188 finally:
189 mod.CRATES = original
190 self.assertIn((crates / "demo" / "src" / "suite.rs").resolve(), excluded)
191
192 def test_plain_mod_declaration_is_not_excluded(self) -> None:
193 with tempfile.TemporaryDirectory() as tmp:
194 root = Path(tmp)
195 crates = self._crates(
196 root,
197 {
198 "lib.rs": "mod production;\n",
199 "production.rs": "fn helper() { let _ = std::fs::read_to_string(p); }\n",
200 },
201 )
202 original = mod.CRATES
203 try:
204 mod.CRATES = crates
205 excluded = mod.cfg_test_module_files()
206 finally:
207 mod.CRATES = original
208 self.assertNotIn(
209 (crates / "demo" / "src" / "production.rs").resolve(), excluded
210 )
211
212 def _excluded(self, root: Path, files: dict[str, str]) -> tuple[Path, set[Path]]:
213 crates = self._crates(root, files)
214 original = mod.CRATES
215 try:
216 mod.CRATES = crates
217 return crates / "demo" / "src", mod.cfg_test_module_files()
218 finally:
219 mod.CRATES = original
220
221 def test_inline_cfg_test_item_include_is_excluded(self) -> None:
222 with tempfile.TemporaryDirectory() as tmp:
223 source, excluded = self._excluded(Path(tmp), {
224 "lib.rs": '#[cfg(test)] mod verification { include!("part.rs"); }',
225 "part.rs": 'fn helper() { std::fs::read("fixture"); }',
226 })
227 self.assertIn((source / "part.rs").resolve(), excluded)
228
229 def test_path_module_and_recursive_includes_inherit_test_only_scope(self) -> None:
230 with tempfile.TemporaryDirectory() as tmp:
231 source, excluded = self._excluded(Path(tmp), {
232 "lib.rs": '#[cfg(test)] #[path = "suite.rs"] mod verification;',
233 "suite.rs": 'include!("pieces/first.rs");',
234 "pieces/first.rs": 'include!(r"second.rs");',
235 "pieces/second.rs": 'fn helper() { std::fs::read("fixture"); }',
236 })
237 for name in ("suite.rs", "pieces/first.rs", "pieces/second.rs"):
238 self.assertIn((source / name).resolve(), excluded)
239
240 def test_test_looking_plain_module_and_filename_are_not_exempt(self) -> None:
241 with tempfile.TemporaryDirectory() as tmp:
242 source, excluded = self._excluded(Path(tmp), {
243 "lib.rs": 'mod tests { include!("test_cases.rs"); }',
244 "test_cases.rs": 'fn helper() { std::fs::read("fixture"); }',
245 })
246 self.assertNotIn((source / "test_cases.rs").resolve(), excluded)
247
248 def test_comments_and_string_include_or_cfg_lookalikes_are_not_exempt(self) -> None:
249 with tempfile.TemporaryDirectory() as tmp:
250 source, excluded = self._excluded(Path(tmp), {
251 "lib.rs": '''// #[cfg(test)] mod commented;
252 /* #[cfg(test)] mod blocked { include!("part.rs"); } */
253 const A: &str = "#[cfg(test)] mod stringed; include!(\\\"part.rs\\\");";
254 const B: &str = r#"#[cfg(test)] mod raw; include!("part.rs");"#;
255 const C: &str = r"backslash \\";
256 include!("production.rs");
257 ''',
258 "commented.rs": '', "stringed.rs": '', "raw.rs": '',
259 "part.rs": '', "production.rs": '',
260 })
261 self.assertFalse(excluded)
262 self.assertNotIn((source / "production.rs").resolve(), excluded)
263
264 def test_production_include_blocks_shared_fragment_and_descendants(self) -> None:
265 with tempfile.TemporaryDirectory() as tmp:
266 source, excluded = self._excluded(Path(tmp), {
267 "lib.rs": 'include!("shared.rs"); #[cfg(test)] mod suite { include!("shared.rs"); }',
268 "shared.rs": 'include!("child.rs");',
269 "child.rs": 'fn helper() { std::fs::read("fixture"); }',
270 })
271 for name in ("shared.rs", "child.rs"):
272 self.assertNotIn((source / name).resolve(), excluded)
273
274 def test_production_path_module_blocks_test_alias(self) -> None:
275 with tempfile.TemporaryDirectory() as tmp:
276 source, excluded = self._excluded(Path(tmp), {
277 "lib.rs": '#[path="shared.rs"] mod production; #[cfg(test)] #[path="shared.rs"] mod suite;',
278 "shared.rs": 'include!("child.rs");',
279 "child.rs": 'fn helper() { std::fs::read("fixture"); }',
280 })
281 self.assertNotIn((source / "shared.rs").resolve(), excluded)
282 self.assertNotIn((source / "child.rs").resolve(), excluded)
283
284 def test_cfg_test_function_include_is_not_a_module_exemption(self) -> None:
285 with tempfile.TemporaryDirectory() as tmp:
286 source, excluded = self._excluded(Path(tmp), {
287 "lib.rs": '#[cfg(test)] fn probe() { include!("part.rs"); }',
288 "part.rs": 'fn helper() { std::fs::read("fixture"); }',
289 })
290 self.assertNotIn((source / "part.rs").resolve(), excluded)
291
292 def test_dynamic_include_and_mixed_cfg_are_not_inferred_as_test_only(self) -> None:
293 with tempfile.TemporaryDirectory() as tmp:
294 source, excluded = self._excluded(Path(tmp), {
295 "lib.rs": '#[cfg(test)] mod suite { include!(concat!("part", ".rs")); }\n#[cfg(any(test, feature="shipping"))] mod shipped { include!("shipping.rs"); }',
296 "part.rs": '', "shipping.rs": '',
297 })
298 self.assertFalse(excluded)
299 self.assertNotIn((source / "part.rs").resolve(), excluded)
300
301 def test_default_crate_roots_and_production_children_stay_counted_when_included_by_tests(self) -> None:
302 for entry in ("lib.rs", "main.rs"):
303 with self.subTest(entry=entry), tempfile.TemporaryDirectory() as tmp:
304 source, excluded = self._excluded(Path(tmp), {
305 "lib.rs": '#[cfg(test)] mod suite { include!("' + entry + '"); }',
306 entry: 'mod production; #[cfg(test)] mod suite { include!("' + entry + '"); }',
307 "production.rs": 'fn run() { std::fs::read("real"); }',
308 })
309 self.assertNotIn((source / entry).resolve(), excluded)
310 self.assertNotIn((source / "production.rs").resolve(), excluded)
311
312 def test_cargo_custom_lib_root_keeps_unguarded_module_and_include_descendants_counted(self) -> None:
313 with tempfile.TemporaryDirectory() as tmp:
314 source, excluded = self._excluded(Path(tmp), {
315 "../Cargo.toml": '[package]\nname="demo"\nversion="0.1.0"\n[lib]\npath="src/custom.rs"\n',
316 "lib.rs": '#[cfg(test)] mod suite { include!("custom.rs"); }',
317 "custom.rs": 'mod production; include!("fragment.rs");',
318 "custom/production.rs": 'fn run() { std::fs::read("real"); }',
319 "fragment.rs": 'fn run() { std::thread::sleep(delay); }',
320 })
321 for name in ("custom.rs", "custom/production.rs", "fragment.rs"):
322 self.assertNotIn((source / name).resolve(), excluded)
323
324 def test_cargo_custom_binary_root_outside_src_stays_counted_when_test_reachable(self) -> None:
325 with tempfile.TemporaryDirectory() as tmp:
326 source, excluded = self._excluded(Path(tmp), {
327 "../Cargo.toml": '[package]\nname="demo"\nversion="0.1.0"\n[[bin]]\nname="launcher"\npath="front/launch.rs"\n',
328 "lib.rs": '#[cfg(test)] mod suite { include!("../front/launch.rs"); }',
329 "../front/launch.rs": 'include!("production.rs");',
330 "../front/production.rs": 'fn run() { std::fs::read("real"); }',
331 })
332 for name in ("../front/launch.rs", "../front/production.rs"):
333 self.assertNotIn((source / name).resolve(), excluded)
334
335 def test_automatic_binary_roots_remain_counted(self) -> None:
336 with tempfile.TemporaryDirectory() as tmp:
337 source, excluded = self._excluded(Path(tmp), {
338 "lib.rs": '#[cfg(test)] mod suite { include!("bin/one.rs"); include!("bin/two/main.rs"); }',
339 "bin/one.rs": 'include!("child.rs");',
340 "bin/two/main.rs": 'include!("../child.rs");',
341 "bin/child.rs": 'fn run() { std::fs::read("real"); }',
342 })
343 for name in ("bin/one.rs", "bin/two/main.rs", "bin/child.rs"):
344 self.assertNotIn((source / name).resolve(), excluded)
345
346 def test_recursive_test_include_cycle_terminates(self) -> None:
347 with tempfile.TemporaryDirectory() as tmp:
348 source, excluded = self._excluded(Path(tmp), {
349 "lib.rs": '#[cfg(test)] mod suite { include!("first.rs"); }',
350 "first.rs": 'include!("second.rs");',
351 "second.rs": 'include!("first.rs");',
352 })
353 self.assertEqual(excluded, {(source / name).resolve() for name in ("first.rs", "second.rs")})
354
355
356 class ExplicitModuleGraphScopeTests(unittest.TestCase):
357 _crates = CfgTestModuleExclusion._crates
358
359 def test_cargo_integration_target_and_declared_helpers_are_test_only(self) -> None:
360 with tempfile.TemporaryDirectory() as tmp:
361 crates = self._crates(Path(tmp), {
362 "../Cargo.toml": '[package]\nname="demo"\nversion="0.1.0"\n',
363 "lib.rs": 'fn shipping() {}',
364 "../tests/scenario.rs": 'mod support; fn probe() { std::fs::read("fixture"); }',
365 "../tests/support/mod.rs": 'fn helper() { std::fs::read("fixture"); }',
366 })
367 package = crates / "demo"
368 test_only, _ = mod.module_file_scopes(package)
369 self.assertIn((package / "tests/scenario.rs").resolve(), test_only)
370 self.assertIn((package / "tests/support/mod.rs").resolve(), test_only)
371 self.assertNotIn((package / "src/lib.rs").resolve(), test_only)
372
373 def test_disabled_automatic_tests_do_not_exempt_an_unclaimed_path(self) -> None:
374 with tempfile.TemporaryDirectory() as tmp:
375 crates = self._crates(Path(tmp), {
376 "../Cargo.toml": '[package]\nname="demo"\nversion="0.1.0"\nautotests=false\n',
377 "lib.rs": 'fn shipping() {}',
378 "../tests/scenario.rs": 'fn run() { std::fs::read("real"); }',
379 })
380 package = crates / "demo"
381 self.assertNotIn((package / "tests/scenario.rs").resolve(), mod.cfg_test_module_files(package))
382
383 def test_explicit_test_target_never_exempts_a_shipping_binary(self) -> None:
384 with tempfile.TemporaryDirectory() as tmp:
385 crates = self._crates(Path(tmp), {
386 "../Cargo.toml": '[package]\nname="demo"\nversion="0.1.0"\n[[bin]]\nname="launch"\npath="front/launch.rs"\n[[test]]\nname="probe"\npath="front/launch.rs"\n',
387 "lib.rs": 'fn shipping() {}',
388 "../front/launch.rs": 'include!("child.rs");',
389 "../front/child.rs": 'fn run() { std::fs::read("real"); }',
390 })
391 package = crates / "demo"
392 test_only, production = mod.module_file_scopes(package)
393 for name in ("front/launch.rs", "front/child.rs"):
394 self.assertNotIn((package / name).resolve(), test_only)
395 self.assertIn((package / name).resolve(), production)
396
397 def test_explicit_test_path_overrides_automatic_name_without_exempting_both(self) -> None:
398 with tempfile.TemporaryDirectory() as tmp:
399 crates = self._crates(Path(tmp), {
400 "../Cargo.toml": '[package]\nname="demo"\nversion="0.1.0"\n[[test]]\nname="scenario"\npath="probes/actual.rs"\n',
401 "lib.rs": 'fn shipping() {}',
402 "../tests/scenario.rs": 'fn unclaimed() {}',
403 "../probes/actual.rs": 'fn actual() {}',
404 })
405 package = crates / "demo"
406 test_only, _ = mod.module_file_scopes(package)
407 self.assertIn((package / "probes/actual.rs").resolve(), test_only)
408 self.assertNotIn((package / "tests/scenario.rs").resolve(), test_only)
409
410 def test_explicit_source_root_does_not_mutate_or_consult_global_crates(self) -> None:
411 with tempfile.TemporaryDirectory() as tmp:
412 source = self._crates(Path(tmp), {
413 "lib.rs": '#[cfg(test)] mod suite { include!("piece.rs"); }',
414 "piece.rs": 'fn probe() {}',
415 }) / "demo" / "src"
416 original = mod.CRATES
417 self.assertEqual(mod.cfg_test_module_files(source), {(source / "piece.rs").resolve()})
418 self.assertEqual(mod.CRATES, original)
419
420 def test_production_conflicts_include_descendants_and_override_test_names(self) -> None:
421 with tempfile.TemporaryDirectory() as tmp:
422 source = self._crates(Path(tmp), {
423 "lib.rs": 'include!("shared_tests.rs"); #[cfg(test)] mod suite { include!("shared_tests.rs"); }',
424 "shared_tests.rs": 'include!("child_test.rs");',
425 "child_test.rs": 'fn run() {}',
426 }) / "demo" / "src"
427 test_only, production = mod.module_file_scopes(source)
428 self.assertEqual(test_only, set())
429 for name in ("lib.rs", "shared_tests.rs", "child_test.rs"):
430 self.assertIn((source / name).resolve(), production)
431
432 def test_custom_root_outside_source_preserves_production_descendants(self) -> None:
433 with tempfile.TemporaryDirectory() as tmp:
434 crates = self._crates(Path(tmp), {
435 "../Cargo.toml": '[package]\nname="demo"\nversion="0.1.0"\n[lib]\npath="entry.rs"\n',
436 "lib.rs": '#[cfg(test)] mod suite { include!("../entry.rs"); }',
437 "../entry.rs": 'include!("src/child_tests.rs");',
438 "child_tests.rs": 'fn run() {}',
439 })
440 source = crates / "demo" / "src"
441 test_only, production = mod.module_file_scopes(source.parent)
442 self.assertNotIn((source / "child_tests.rs").resolve(), test_only)
443 self.assertIn((source / "child_tests.rs").resolve(), production)
444
445
446 if __name__ == "__main__":
447 unittest.main()
448
448 lines PYTHON