|
65 | 65 | CATEGORY_RULE_BY_NAME, |
66 | 66 | CATEGORY_RULES, |
67 | 67 | FULL_SUITE_TRIGGERS, |
| 68 | + FUNC_SCRIPT_DEPENDENCIES, |
68 | 69 | LINT_ONLY_FILES, |
69 | 70 | MINIMAL_PYTEST, |
70 | 71 | ) |
|
75 | 76 | CATEGORY_RULE_BY_NAME, |
76 | 77 | CATEGORY_RULES, |
77 | 78 | FULL_SUITE_TRIGGERS, |
| 79 | + FUNC_SCRIPT_DEPENDENCIES, |
78 | 80 | LINT_ONLY_FILES, |
79 | 81 | MINIMAL_PYTEST, |
80 | 82 | ) |
@@ -332,6 +334,82 @@ def _matches_any(path: str, preds: Sequence[Callable[[str], bool]]) -> bool: |
332 | 334 | return False |
333 | 335 |
|
334 | 336 |
|
| 337 | +def resolve_functional_scripts( |
| 338 | + selected: set[str], |
| 339 | +) -> tuple[list[str], dict[str, set[str]]]: |
| 340 | + """Expand and dependency-order functional scripts. |
| 341 | +
|
| 342 | + Returns: |
| 343 | + ordered |
| 344 | + Selected scripts and their transitive dependencies, with each |
| 345 | + dependency appearing before scripts that depend on it. |
| 346 | + dependency_sources |
| 347 | + Provenance entries for every dependency added during expansion. |
| 348 | + """ |
| 349 | + ordered: list[str] = [] |
| 350 | + dependency_sources: dict[str, set[str]] = defaultdict(set) |
| 351 | + visited: set[str] = set() |
| 352 | + visiting: set[str] = set() |
| 353 | + |
| 354 | + def visit(script: str) -> None: |
| 355 | + if script in visited: |
| 356 | + return |
| 357 | + |
| 358 | + if script in visiting: |
| 359 | + raise ValueError(f"Cyclic functional-script dependency involving {script!r}") |
| 360 | + |
| 361 | + visiting.add(script) |
| 362 | + |
| 363 | + for dependency in FUNC_SCRIPT_DEPENDENCIES.get( |
| 364 | + script, |
| 365 | + (), |
| 366 | + ): |
| 367 | + dependency_sources[dependency].add(f"dependency:{script}") |
| 368 | + visit(dependency) |
| 369 | + |
| 370 | + visiting.remove(script) |
| 371 | + visited.add(script) |
| 372 | + ordered.append(script) |
| 373 | + |
| 374 | + for script in sorted(selected): |
| 375 | + visit(script) |
| 376 | + |
| 377 | + return ordered, dependency_sources |
| 378 | + |
| 379 | + |
| 380 | +def order_functional_scripts( |
| 381 | + selected: set[str], |
| 382 | +) -> list[str]: |
| 383 | + """Expand and order functional scripts after their dependencies.""" |
| 384 | + ordered: list[str] = [] |
| 385 | + visited: set[str] = set() |
| 386 | + visiting: set[str] = set() |
| 387 | + |
| 388 | + def visit(script: str) -> None: |
| 389 | + if script in visited: |
| 390 | + return |
| 391 | + |
| 392 | + if script in visiting: |
| 393 | + raise ValueError(f"Cyclic functional-script dependency involving {script!r}") |
| 394 | + |
| 395 | + visiting.add(script) |
| 396 | + |
| 397 | + for dependency in FUNC_SCRIPT_DEPENDENCIES.get( |
| 398 | + script, |
| 399 | + (), |
| 400 | + ): |
| 401 | + visit(dependency) |
| 402 | + |
| 403 | + visiting.remove(script) |
| 404 | + visited.add(script) |
| 405 | + ordered.append(script) |
| 406 | + |
| 407 | + for script in sorted(selected): |
| 408 | + visit(script) |
| 409 | + |
| 410 | + return ordered |
| 411 | + |
| 412 | + |
335 | 413 | def decide(files: list[str]) -> SelectorResult: |
336 | 414 | reasons: list[str] = [] |
337 | 415 | lane_reasons: dict[str, list[str]] = {} |
@@ -499,13 +577,17 @@ def decide(files: list[str]) -> SelectorResult: |
499 | 577 | fast_reasons.append("fallback_minimal_pytest") |
500 | 578 | lane_reasons["fast"] = fast_reasons |
501 | 579 |
|
| 580 | + ordered_functional_scripts, dependency_sources = resolve_functional_scripts(functional_set) |
| 581 | + for script, srcs in dependency_sources.items(): |
| 582 | + script_sources[script].update(srcs) |
| 583 | + |
502 | 584 | return SelectorResult( |
503 | 585 | lanes=lanes, |
504 | 586 | pytest_paths=sorted(pytest_paths_set), |
505 | | - functional_scripts=sorted(functional_set), |
| 587 | + functional_scripts=ordered_functional_scripts, |
506 | 588 | provenance=SelectionProvenance( |
507 | | - pytest={k: sorted(v) for k, v in sorted(pytest_sources.items())}, |
508 | | - scripts={k: sorted(v) for k, v in sorted(script_sources.items())}, |
| 589 | + pytest={path: sorted(sources) for path, sources in sorted(pytest_sources.items())}, |
| 590 | + scripts={path: sorted(sources) for path, sources in sorted(script_sources.items())}, |
509 | 591 | ), |
510 | 592 | reasons=reasons, |
511 | 593 | changed_files=files, |
|
0 commit comments