pytest-selective 1.2.0__tar.gz → 1.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pytest_selective-1.2.0/.github/test-lanes.json → pytest_selective-1.4.0/.github/plan-lanes.json +1 -21
- pytest_selective-1.4.0/.github/test-lanes.json +22 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/ci.yml +19 -20
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/pr-title.yml +2 -1
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/public-actions.yml +6 -6
- pytest_selective-1.4.0/.github/workflows/weekly-checkpoint.yml +21 -0
- pytest_selective-1.4.0/.release-please-manifest.json +3 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/AGENTS.md +4 -3
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/CHANGELOG.md +21 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/CONTRIBUTING.md +45 -20
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/PKG-INFO +1 -1
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/action.yml +6 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/actions/plan-lanes/action.yml +8 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/actions/test-impact/action.yml +8 -0
- pytest_selective-1.4.0/contracts/selection/conftest.py.gv-dependency.yaml +5 -0
- pytest_selective-1.4.0/contracts/selection/test_selection_contract.py.gv-dependency.yaml +6 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/ci-toolkit.md +8 -1
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/ci.md +32 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/configuration.md +8 -0
- pytest_selective-1.4.0/documentation/case-study.md +293 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/mkdocs.yml +14 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/pyproject.toml +1 -1
- pytest_selective-1.4.0/scripts/check_lane_routing.py +145 -0
- pytest_selective-1.4.0/scripts/ci_file_config.py +18 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/smoke_distribution.py +2 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/skills/pytest-selective/SKILL.md +71 -18
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/ci.py +39 -12
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/git_operations.py +23 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_audit_declarations.py +13 -2
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_cli.py +29 -4
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_graph.py +47 -14
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/local.py +3 -5
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_ci_admission.py +2 -1
- pytest_selective-1.4.0/tests/test_ci_admission.py.gv-dependency.yaml +5 -0
- pytest_selective-1.4.0/tests/test_release_workflow.py.gv-dependency.yaml +5 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_repository_ci.py +88 -72
- pytest_selective-1.4.0/tests/test_repository_ci.py.gv-dependency.yaml +7 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_ci.py +76 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_impact_audit.py +7 -1
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_impact_audit_declarations.py +10 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_impact_graph.py +83 -0
- pytest_selective-1.4.0/tests/testing/test_pull_request_merge_base.py +83 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_working_tree.py +10 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/uv.lock +1 -1
- pytest_selective-1.2.0/.release-please-manifest.json +0 -3
- pytest_selective-1.2.0/scripts/ci_file_config.py +0 -13
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.dry-lint/baseline.json +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.dry-lint/progress.json +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.dry-lint.toml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.gitattributes +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/dependabot.yml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/validation-inputs.json +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/docs.yml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/dry-lint-audit.yml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/dry-lint-judge.yml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/dry-lint.yml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/observe-ci.yml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/release-please.yml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/release.yml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/windows-performance.yml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.gitignore +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.pre-commit-config.yaml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.readthedocs.yaml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/CLAUDE.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/LICENSE +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/PROVENANCE.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/README.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/RELEASING.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/actions/README.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/actions/pytest-expansion/action.yml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/actions/validation-gate/action.yml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/agentsync.toml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/contracts/selection/README.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/contracts/selection/conftest.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/contracts/selection/quint-connect.json +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/contracts/selection/quint.ini +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/contracts/selection/runtime_deps.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/contracts/selection/selection.qnt +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/contracts/selection/selection_driver.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/contracts/selection/test_selection_contract.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/README.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/actions.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/agents.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/assets/banner.png +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/assets/banner.svg +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/assets/brand.json +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/assets/icon.png +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/assets/logo.svg +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/assets/raster-sources.json +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/assets/social-preview.png +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/assets/social-preview.svg +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-due-ci.json +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-due-docs-ci.json +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-due-docs-widget.json +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-due-widget.json +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-input-replays.json +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-obligation-docs.json +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-obligation.json +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-routing.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-selective-docs.json +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/obligation-baseline.json +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after-3.11.xml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after-3.14.xml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after.json +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control-3.11.xml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control-3.14.xml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control.json +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-earlier.json +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/windows-runtime.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/comparison-pytest-impacted.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/evidence.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/local-development.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/pytest-hygiene.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/selection-health.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/shared-plan-execution.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/tutorial.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/tutorials/adoption-checklist.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/tutorials/sidecar-dependencies.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/tutorials/skip-unrelated-ci.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/actions-directory-test-plan.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/codex-selector-correctness-performance-test-plan.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/codex-windows-git-performance-test-plan.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/graph-based-inputs-plan.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/impacted-improvements-test-plan.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/move-action-implementations-test-plan.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/public-action-onboarding-test-plan.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/ratchet-duplicates-zero-test-plan.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/rerun-selection-test-plan.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/assets/routing-comparison.svg +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/check_site.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/ecosystem.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/hooks.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/index.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/llms.txt +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/overrides/main.html +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/publishing.md +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/requirements.in +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/requirements.txt +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/release-please-config.json +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/benchmark_graph.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/benchmark_parsing.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/benchmark_rust.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/check_docs.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/check_metadata.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/compare_impacted.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/measure_ci_runs.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/public_actions.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/render_brand.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/run_ci_tests.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/tutorial.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/skills/pytest-selective/agents/openai.yaml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/__init__.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/__main__.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/ast_imports.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/checkpoint.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/execution.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/gate.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/hygiene.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_audit.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_audit_plugin.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_declarations.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_explanation.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_first_party.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_models.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_name_scope.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_selection.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/input_policy.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/parse_batch.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/plugin.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/py.typed +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/pytest_expansion.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/pytest_plugins.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/pytest_sharding.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/test_inventory.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/typecheck_scope.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/working_tree.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/__init__.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_checkpoint_policy.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_ci_measurements.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_ci_test_execution.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_distribution_metadata.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_hygiene.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_public_actions.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_pytest_expansion.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_release_workflow.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_repository_hygiene.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_repository_hygiene.py.gv-dependency.yaml +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_tutorial.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/__init__.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/git_repo.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_ast_imports.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_gate.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_git_repo.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_global_inputs.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_impact_explanation.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_impact_first_party.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_impact_name_scope.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_parse_batch.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_portability.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_pytest_plugin.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_pytest_plugins.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_pytest_sharding.py +0 -0
- {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_typecheck_scope.py +0 -0
pytest_selective-1.2.0/.github/test-lanes.json → pytest_selective-1.4.0/.github/plan-lanes.json
RENAMED
|
@@ -25,27 +25,6 @@
|
|
|
25
25
|
"src/**",
|
|
26
26
|
"tests/**"
|
|
27
27
|
]
|
|
28
|
-
},
|
|
29
|
-
"contracts": {
|
|
30
|
-
"roots": [
|
|
31
|
-
"contracts"
|
|
32
|
-
],
|
|
33
|
-
"inputs": [
|
|
34
|
-
"src/**",
|
|
35
|
-
"contracts/**",
|
|
36
|
-
"tests/testing/git_repo.py"
|
|
37
|
-
]
|
|
38
|
-
},
|
|
39
|
-
"package": {
|
|
40
|
-
"inputs": [
|
|
41
|
-
"src/**",
|
|
42
|
-
"pyproject.toml",
|
|
43
|
-
"uv.lock",
|
|
44
|
-
"README.md",
|
|
45
|
-
"LICENSE",
|
|
46
|
-
"scripts/check_metadata.py",
|
|
47
|
-
"scripts/smoke_distribution.py"
|
|
48
|
-
]
|
|
49
28
|
}
|
|
50
29
|
},
|
|
51
30
|
"global_inputs": [
|
|
@@ -56,6 +35,7 @@
|
|
|
56
35
|
"pyproject.toml",
|
|
57
36
|
"**/uv.lock",
|
|
58
37
|
"uv.lock",
|
|
38
|
+
".github/plan-lanes.json",
|
|
59
39
|
".github/test-lanes.json",
|
|
60
40
|
".github/workflows/ci.yml",
|
|
61
41
|
".github/workflows/release.yml",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "workflow",
|
|
3
|
+
"lanes": {
|
|
4
|
+
"tests": {
|
|
5
|
+
"roots": ["tests"]
|
|
6
|
+
}
|
|
7
|
+
},
|
|
8
|
+
"rules": [
|
|
9
|
+
{
|
|
10
|
+
"name": "python-environment",
|
|
11
|
+
"when": ["pyproject.toml", "**/pyproject.toml", "uv.lock", "**/uv.lock", ".python-version"],
|
|
12
|
+
"select": ["all"],
|
|
13
|
+
"reason": "the locked test environment; documentation/requirements*.txt belongs to Read the Docs"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "distribution-smoke",
|
|
17
|
+
"when": ["scripts/smoke_distribution.py"],
|
|
18
|
+
"select": ["lane:tests"],
|
|
19
|
+
"reason": "the distributions job runs the smoke only when the tests lane is planned"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -6,8 +6,6 @@ on:
|
|
|
6
6
|
types: [opened, synchronize, reopened]
|
|
7
7
|
workflow_dispatch:
|
|
8
8
|
workflow_call:
|
|
9
|
-
schedule:
|
|
10
|
-
- cron: '13 3 * * 1'
|
|
11
9
|
permissions:
|
|
12
10
|
contents: read
|
|
13
11
|
concurrency:
|
|
@@ -15,6 +13,9 @@ concurrency:
|
|
|
15
13
|
group: ci-${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.run_attempt == 1 && github.event.pull_request.number || github.run_id }}
|
|
16
14
|
cancel-in-progress: ${{ github.event_name == 'pull_request' && github.run_attempt == 1 }}
|
|
17
15
|
jobs:
|
|
16
|
+
# Plans whole lanes before any runner starts: each lane is the name a job's condition reads, and a change selects it
|
|
17
|
+
# through the tests, scripts and actions that job runs. `ci-esta lanes` shows the lanes and why; .github/test-lanes.json
|
|
18
|
+
# holds only what the workflow cannot show.
|
|
18
19
|
select-tests:
|
|
19
20
|
runs-on: ubuntu-latest
|
|
20
21
|
permissions:
|
|
@@ -33,7 +34,7 @@ jobs:
|
|
|
33
34
|
CONTRACT_LANE_JOBS: '{"selection":"Selection contract (Quint)"}'
|
|
34
35
|
outputs:
|
|
35
36
|
# Planned lanes after reuse: a reusing lane is false only when every one of its jobs is proven by a receipt.
|
|
36
|
-
lanes: ${{ steps.
|
|
37
|
+
lanes: ${{ steps.contract-evidence.outputs.admitted-lanes || steps.plan.outputs.lanes }}
|
|
37
38
|
# The plan before reuse, for steps that follow changed runtime inputs rather than test execution.
|
|
38
39
|
planned: ${{ steps.plan.outputs.lanes }}
|
|
39
40
|
mode: ${{ steps.files.outputs.mode }}
|
|
@@ -51,9 +52,10 @@ jobs:
|
|
|
51
52
|
if: github.event_name == 'pull_request'
|
|
52
53
|
run: 'true'
|
|
53
54
|
- id: plan
|
|
54
|
-
uses:
|
|
55
|
+
uses: gigaverse-app/ci-esta/actions/plan-work@v1
|
|
55
56
|
with:
|
|
56
57
|
config: .github/test-lanes.json
|
|
58
|
+
# File selection has no workflow graph, so it reads the complete lane file public-actions.yml plans with.
|
|
57
59
|
- name: Share the repository graph and execution inputs with file selection
|
|
58
60
|
env:
|
|
59
61
|
PYTHONPATH: src
|
|
@@ -84,6 +86,8 @@ jobs:
|
|
|
84
86
|
input-paths: ${{ steps.inputs.outputs.tests }}
|
|
85
87
|
reuse-after-merge: 'true'
|
|
86
88
|
reuse-version-only-release: 'true'
|
|
89
|
+
planned-lanes: ${{ steps.plan.outputs.lanes }}
|
|
90
|
+
lane-keys: '{"tests":["py311-ubuntu","py311-windows","py311-macos","py314-ubuntu","py314-windows","py314-macos","minimum"]}'
|
|
87
91
|
- id: contract-evidence
|
|
88
92
|
if: >-
|
|
89
93
|
${{ (github.event_name == 'pull_request' || github.event_name == 'push') &&
|
|
@@ -95,21 +99,9 @@ jobs:
|
|
|
95
99
|
input-paths: ${{ steps.inputs.outputs.contracts }}
|
|
96
100
|
reuse-after-merge: 'true'
|
|
97
101
|
reuse-version-only-release: 'true'
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
PLANNED_LANES: ${{ steps.plan.outputs.lanes }}
|
|
102
|
-
REUSED: ${{ steps.evidence.outputs.reused || '{}' }}
|
|
103
|
-
CONTRACT_REUSED: ${{ steps.contract-evidence.outputs.reused || '{}' }}
|
|
104
|
-
run: |
|
|
105
|
-
admitted=$(jq -c \
|
|
106
|
-
--argjson tests "$TEST_LANE_JOBS" --argjson reused "$REUSED" \
|
|
107
|
-
--argjson contracts "$CONTRACT_LANE_JOBS" --argjson contract_reused "$CONTRACT_REUSED" '
|
|
108
|
-
def proven($jobs; $proof): [$jobs | keys[] | $proof[.] == true] | all;
|
|
109
|
-
(if proven($tests; $reused) then .tests = false else . end)
|
|
110
|
-
| (if proven($contracts; $contract_reused) then .contracts = false else . end)' <<<"$PLANNED_LANES")
|
|
111
|
-
echo "reused=$REUSED contracts=$CONTRACT_REUSED"
|
|
112
|
-
echo "lanes=$admitted" | tee -a "$GITHUB_OUTPUT"
|
|
102
|
+
# Chained: the tests step already admitted its lane; this step admits the contracts lane on top.
|
|
103
|
+
planned-lanes: ${{ steps.evidence.outputs.admitted-lanes }}
|
|
104
|
+
lane-keys: '{"contracts":["selection"]}'
|
|
113
105
|
quality:
|
|
114
106
|
runs-on: ubuntu-latest
|
|
115
107
|
steps:
|
|
@@ -121,11 +113,18 @@ jobs:
|
|
|
121
113
|
- run: uv run --locked pre-commit run --all-files
|
|
122
114
|
env:
|
|
123
115
|
SKIP: dry-lint
|
|
116
|
+
# Each lane-gated job's condition, the gate's lane-jobs and the lanes the workflow defines must agree.
|
|
124
117
|
- name: Check aggregate gate against the execution lanes
|
|
125
118
|
run: >-
|
|
126
119
|
uv run --no-project --python 3.14
|
|
127
|
-
--with "ci-esta>=1.
|
|
120
|
+
--with "ci-esta[selection]>=1.13.0,<2.0.0"
|
|
128
121
|
ci-esta check-workflow --workflow .github/workflows/ci.yml --lanes .github/test-lanes.json
|
|
122
|
+
# Plans one-file changes with CI-esta against real commits and fails on any lane a change should not run.
|
|
123
|
+
- name: Check the lane routing
|
|
124
|
+
run: >-
|
|
125
|
+
uv run --no-project --python 3.14
|
|
126
|
+
--with "ci-esta[selection]>=1.13.0,<2.0.0"
|
|
127
|
+
python scripts/check_lane_routing.py
|
|
129
128
|
- name: Validate GitHub Actions
|
|
130
129
|
run: docker run --rm -v "$PWD:/repo" -w /repo rhysd/actionlint:1.7.7 -color
|
|
131
130
|
tests:
|
|
@@ -2,7 +2,8 @@ name: PR title
|
|
|
2
2
|
|
|
3
3
|
# Squash merges use this checked title as the Conventional Commit header.
|
|
4
4
|
on:
|
|
5
|
-
|
|
5
|
+
# Metadata reads need no privileged target event or repository secrets.
|
|
6
|
+
pull_request:
|
|
6
7
|
types: [opened, edited, synchronize, reopened, ready_for_review]
|
|
7
8
|
|
|
8
9
|
permissions:
|
|
@@ -4,8 +4,6 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
branches: [main]
|
|
6
6
|
workflow_dispatch:
|
|
7
|
-
schedule:
|
|
8
|
-
- cron: '33 3 * * 1'
|
|
9
7
|
permissions:
|
|
10
8
|
contents: read
|
|
11
9
|
jobs:
|
|
@@ -17,10 +15,12 @@ jobs:
|
|
|
17
15
|
- uses: actions/checkout@v7
|
|
18
16
|
with:
|
|
19
17
|
fetch-depth: 0
|
|
18
|
+
# This workflow smokes the repository's own actions, so its planner is plan-lanes itself, run selectively on every
|
|
19
|
+
# change against .github/plan-lanes.json; CI's lanes come from CI-esta's plan-work.
|
|
20
20
|
- id: select
|
|
21
21
|
uses: ./actions/plan-lanes
|
|
22
22
|
with:
|
|
23
|
-
config: .github/
|
|
23
|
+
config: .github/plan-lanes.json
|
|
24
24
|
artifact-name: public-action-selection
|
|
25
25
|
public-actions:
|
|
26
26
|
needs: select-tests
|
|
@@ -37,7 +37,7 @@ jobs:
|
|
|
37
37
|
python - <<'PY'
|
|
38
38
|
import json
|
|
39
39
|
from pathlib import Path
|
|
40
|
-
graph = json.loads(Path('.github/
|
|
40
|
+
graph = json.loads(Path('.github/plan-lanes.json').read_text())['graph']
|
|
41
41
|
Path('smoke-impact.json').write_text(json.dumps(graph))
|
|
42
42
|
PY
|
|
43
43
|
- uses: ./
|
|
@@ -59,7 +59,7 @@ jobs:
|
|
|
59
59
|
- uses: ./actions/plan-lanes
|
|
60
60
|
id: lanes
|
|
61
61
|
with:
|
|
62
|
-
config: .github/
|
|
62
|
+
config: .github/plan-lanes.json
|
|
63
63
|
base: ${{ github.sha }}
|
|
64
64
|
full: 'true'
|
|
65
65
|
artifact-name: ''
|
|
@@ -70,7 +70,7 @@ jobs:
|
|
|
70
70
|
python - <<'PY'
|
|
71
71
|
import json, os
|
|
72
72
|
# A full checkpoint admits every configured lane.
|
|
73
|
-
configured = json.load(open('.github/
|
|
73
|
+
configured = json.load(open('.github/plan-lanes.json', encoding='utf-8'))['lanes']
|
|
74
74
|
assert json.loads(os.environ['LANES']) == dict.fromkeys(configured, True)
|
|
75
75
|
PY
|
|
76
76
|
- name: Require successful planning after fallback execution
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: Weekly full checkpoint
|
|
2
|
+
'on':
|
|
3
|
+
schedule:
|
|
4
|
+
- cron: 13 3 * * 1
|
|
5
|
+
workflow_dispatch: null
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read
|
|
8
|
+
actions: write
|
|
9
|
+
concurrency:
|
|
10
|
+
group: weekly-full-checkpoint
|
|
11
|
+
cancel-in-progress: false
|
|
12
|
+
jobs:
|
|
13
|
+
dispatch:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
timeout-minutes: 5
|
|
16
|
+
steps:
|
|
17
|
+
- uses: gigaverse-app/ci-esta/actions/weekly-checkpoint@v1
|
|
18
|
+
with:
|
|
19
|
+
workflows: '["ci.yml", "public-actions.yml"]'
|
|
20
|
+
ref: ''
|
|
21
|
+
workflow-inputs: '{}'
|
|
@@ -19,9 +19,10 @@ on pytest-selective itself.
|
|
|
19
19
|
- Planning supports committed revisions and explicit working-tree snapshots. Preserve full execution on
|
|
20
20
|
operational uncertainty and diagnostics for invalid declarations. An empty
|
|
21
21
|
selection is not an empty pytest argument list.
|
|
22
|
-
- Select consumer routing regressions by their declared execution/configuration inputs.
|
|
23
|
-
`.github/test-lanes.json`
|
|
24
|
-
|
|
22
|
+
- Select consumer routing regressions by their declared execution/configuration inputs. Declare a
|
|
23
|
+
file a test or CI script reads by path next to its reader; `.github/test-lanes.json` holds only
|
|
24
|
+
what CI-esta cannot see in the workflow, and `scripts/check_lane_routing.py` pins the lanes. Preserve
|
|
25
|
+
explicit full/manual checkpoints and the `test:full` override.
|
|
25
26
|
- The README's performance numbers are scenario-specific observations. Keep
|
|
26
27
|
claims linked to [raw evidence](docs/benchmarks/2026-10-22-routing.md); distinguish
|
|
27
28
|
runner-seconds, elapsed time and billing. Skill/example edits need usable links
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.0](https://github.com/gigaverse-app/pytest-selective/compare/v1.3.0...v1.4.0) (2026-11-09)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **lanes:** partition test roots by filename patterns ([27c10ec](https://github.com/gigaverse-app/pytest-selective/commit/27c10ec608f052a36f58d9b10730048ab9d38598))
|
|
9
|
+
|
|
10
|
+
## [1.3.0](https://github.com/gigaverse-app/pytest-selective/compare/v1.2.0...v1.3.0) (2026-11-09)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **audit:** show how many tests each written declaration selects ([#124](https://github.com/gigaverse-app/pytest-selective/issues/124)) ([b660301](https://github.com/gigaverse-app/pytest-selective/commit/b660301be82aefb04fdc79202dc83067c28d7eb1))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug fixes
|
|
19
|
+
|
|
20
|
+
* agent setup round 3, from a third adoption trial ([#127](https://github.com/gigaverse-app/pytest-selective/issues/127)) ([a82bea9](https://github.com/gigaverse-app/pytest-selective/commit/a82bea9f607abcb3ed68009571202ad8b7726c7e))
|
|
21
|
+
* **selection:** keep every module a pytest11 entry-point name maps to ([#122](https://github.com/gigaverse-app/pytest-selective/issues/122)) ([acb6649](https://github.com/gigaverse-app/pytest-selective/commit/acb66492c6440621387e0506c1d0071a5a48979c))
|
|
22
|
+
* **selection:** plan a pull request against its merge commit's actual base ([#119](https://github.com/gigaverse-app/pytest-selective/issues/119)) ([276cec8](https://github.com/gigaverse-app/pytest-selective/commit/276cec84f65c889a1820fd5a001a3a4aca8b7e0d))
|
|
23
|
+
|
|
3
24
|
## [1.2.0](https://github.com/gigaverse-app/pytest-selective/compare/v1.1.2...v1.2.0) (2026-11-09)
|
|
4
25
|
|
|
5
26
|
|
|
@@ -98,24 +98,44 @@ limitations. The pull request template provides a short checklist. See
|
|
|
98
98
|
|
|
99
99
|
## CI lane selection
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
CI plans whole lanes with [CI-esta](https://github.com/gigaverse-app/ci-esta)'s
|
|
102
|
+
`plan-work` before any runner starts: `tests` (the Python matrix and
|
|
103
|
+
`pytest-minimum`), `contracts` (the Quint selection contract) and `package` (the
|
|
104
|
+
distribution build), the names the jobs' conditions read. A change selects a lane
|
|
105
|
+
through what its jobs run: the tests that import it, the files tests declare they
|
|
106
|
+
read, and the scripts and actions each job executes. Documentation-only PRs keep
|
|
102
107
|
quality checks (including Markdown links) and the documentation build, while
|
|
103
|
-
skipping every
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
108
|
+
skipping every lane. A README or LICENSE change runs the `package` lane, because
|
|
109
|
+
both distributions embed them; a README change also runs `contracts`, whose job
|
|
110
|
+
checks the README's contract report, and the one test whose metadata check reads it. `pyproject.toml` and `uv.lock` run every
|
|
111
|
+
lane. `ci-esta lanes --base origin/main` shows what a change would run and why.
|
|
112
|
+
[.github/test-lanes.json](.github/test-lanes.json) extends the workflow and holds
|
|
113
|
+
only what it cannot show: the `tests` lane's roots (its jobs run `pytest-selective
|
|
114
|
+
execute-plan`, which CI-esta does not read as a pytest command), a
|
|
115
|
+
`python-environment` rule that leaves the documentation site's requirements out,
|
|
116
|
+
and a rule that plans the tests lane when the installed-distribution smoke script
|
|
117
|
+
changes, because `distributions` smokes only when that lane is planned. When a test
|
|
118
|
+
or a script starts reading a file by path, declare it next to the reader (a
|
|
119
|
+
`<test>.gv-dependency.yaml` sidecar, which `pytest-selective audit
|
|
120
|
+
--write-declarations` writes, or a `# gv:test-dependency: PATH` comment).
|
|
121
|
+
`quality` runs [scripts/check_lane_routing.py](scripts/check_lane_routing.py),
|
|
122
|
+
which plans one-file changes with CI-esta against real Git commits, including a
|
|
123
|
+
full checkpoint, and `ci-esta check-workflow`, which keeps the job conditions, the
|
|
124
|
+
gate and the lane file in agreement. Manual/called/release runs and the
|
|
125
|
+
`test:full` label run everything; a failed planner also runs all tests.
|
|
126
|
+
Installed-distribution smoke checks follow the tests lane's plan; README-only
|
|
127
|
+
changes skip them while still verifying the README embedded in both release
|
|
128
|
+
archives.
|
|
129
|
+
|
|
130
|
+
Within an admitted lane, the repository's public file-selection action selects
|
|
131
|
+
affected test files once; the platform matrix and minimum-pytest job execute that
|
|
132
|
+
same plan. CI-esta receives both lane and file explanations, learns JUnit timings,
|
|
133
|
+
and validates the aggregate gate. File selection has no workflow graph, so it reads
|
|
134
|
+
[.github/plan-lanes.json](.github/plan-lanes.json), a complete lane file: its graph
|
|
135
|
+
plus the execution inputs that run every test file (scripts, workflows, actions,
|
|
136
|
+
dependency and pytest policy). `public-actions.yml` plans with the same file through
|
|
137
|
+
this repository's own `plan-lanes` action, selectively on every change, and smokes
|
|
138
|
+
its full checkpoint. Declare subprocess
|
|
119
139
|
and non-import dependencies at their readers. Full plans collect whole-suite
|
|
120
140
|
coverage; scoped plans produce JUnit timing evidence without presenting partial
|
|
121
141
|
coverage as a whole-suite result. The public [shared-plan executor](docs/shared-plan-execution.md)
|
|
@@ -199,14 +219,19 @@ history or failed planning must execute the full affected lanes and fail the
|
|
|
199
219
|
required planning gate. Ordinary PRs and pushes use selection; manual/release/scheduled/full overrides
|
|
200
220
|
remain complete checkpoints.
|
|
201
221
|
|
|
202
|
-
CI and public-action smoke
|
|
222
|
+
The **Weekly full checkpoint** workflow dispatches CI and public-action smoke
|
|
223
|
+
every Monday using [CI-esta's shared action](https://github.com/gigaverse-app/ci-esta/blob/main/docs/weekly-checkpoints.md).
|
|
224
|
+
Its small wrapper is generated by `ci-esta checkpoint init`; the source guide
|
|
225
|
+
documents schedule, workflow, ref and input overrides. Ordinary runs keep
|
|
203
226
|
the shared affected-file plan across all six OS/Python combinations and minimum
|
|
204
227
|
pytest. `tests/test_tutorial.py` owns the end-to-end tutorial execution and follows
|
|
205
228
|
that plan; there is no second tutorial invocation in each matrix job. JUnit retains
|
|
206
229
|
its test result and timing, and full plans also upload whole-suite coverage.
|
|
207
230
|
|
|
208
|
-
To run
|
|
209
|
-
and choose **Run workflow
|
|
231
|
+
To run both complete checkpoints after a CI change, select **Weekly full checkpoint**
|
|
232
|
+
in GitHub Actions and choose **Run workflow**, then inspect the dispatched CI and
|
|
233
|
+
public-action results. A green dispatcher means both requests were accepted.
|
|
234
|
+
You can also dispatch either target directly. Retrying an ordinary PR run preserves its selection;
|
|
210
235
|
use the manual checkpoint when you need full-suite coverage evidence.
|
|
211
236
|
|
|
212
237
|
## Duplication
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pytest-selective
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Explainable Python test impact analysis: select affected pytest files from Git changes
|
|
5
5
|
Project-URL: Homepage, https://github.com/gigaverse-app/pytest-selective
|
|
6
6
|
Project-URL: Documentation, https://pytest-selective.readthedocs.io/en/latest/
|
|
@@ -14,6 +14,11 @@ inputs:
|
|
|
14
14
|
head:
|
|
15
15
|
description: Exact checked-out commit.
|
|
16
16
|
default: HEAD
|
|
17
|
+
pr-head:
|
|
18
|
+
description: The pull request's head. When the checkout is GitHub's merge of it (refs/pull/N/merge), changes are measured
|
|
19
|
+
from that merge's base-branch parent, so commits that landed on the base branch after `base` are not counted as the
|
|
20
|
+
PR's. Empty (any other event) keeps `base`.
|
|
21
|
+
default: ${{ github.event.pull_request.head.sha }}
|
|
17
22
|
full-roots:
|
|
18
23
|
description: Space-separated complete test roots used if planning cannot prove a narrower scope.
|
|
19
24
|
required: true
|
|
@@ -100,6 +105,7 @@ runs:
|
|
|
100
105
|
config: ${{ inputs.config }}
|
|
101
106
|
base: ${{ inputs.base }}
|
|
102
107
|
head: ${{ inputs.head }}
|
|
108
|
+
pr-head: ${{ inputs.pr-head }}
|
|
103
109
|
full-roots: ${{ inputs.full-roots }}
|
|
104
110
|
full: ${{ inputs.full }}
|
|
105
111
|
extra-files: ${{ inputs.extra-files }}
|
|
@@ -7,6 +7,12 @@ inputs:
|
|
|
7
7
|
base:
|
|
8
8
|
description: Base commit containing the complete change set.
|
|
9
9
|
default: ${{ github.event.pull_request.base.sha || github.event.before }}
|
|
10
|
+
pr-head:
|
|
11
|
+
description: >-
|
|
12
|
+
The pull request's head. When the checkout is GitHub's merge of it (refs/pull/N/merge), changes are measured from
|
|
13
|
+
that merge's base-branch parent, so commits that landed on the base branch after `base` are not counted as the PR's.
|
|
14
|
+
Empty (any other event) keeps `base`.
|
|
15
|
+
default: ${{ github.event.pull_request.head.sha }}
|
|
10
16
|
full:
|
|
11
17
|
description: 'auto preserves selection on PR/push reruns and runs full for manual/scheduled/release checkpoints or test:full. Override with true or false.'
|
|
12
18
|
default: auto
|
|
@@ -40,10 +46,12 @@ runs:
|
|
|
40
46
|
SELECTIVE_CONFIG: ${{ inputs.config }}
|
|
41
47
|
SELECTIVE_BASE: ${{ inputs.base }}
|
|
42
48
|
SELECTIVE_FULL: ${{ inputs.full }}
|
|
49
|
+
SELECTIVE_PR_HEAD: ${{ inputs.pr-head }}
|
|
43
50
|
run: |
|
|
44
51
|
export PYTHONPATH="$SELECTIVE_ACTION/../../src${PYTHONPATH:+:$PYTHONPATH}"
|
|
45
52
|
args=(--config "$SELECTIVE_CONFIG" --base "$SELECTIVE_BASE" --github-output "$GITHUB_OUTPUT")
|
|
46
53
|
args+=(--full "$SELECTIVE_FULL")
|
|
54
|
+
if [ -n "$SELECTIVE_PR_HEAD" ]; then args+=(--pr-head "$SELECTIVE_PR_HEAD"); fi
|
|
47
55
|
uv run --no-project --python 3.14 --with pydantic==2.13.4 --with pyyaml==6.0.3 \
|
|
48
56
|
python -m pytest_selective.ci "${args[@]}" | tee test-lanes.json
|
|
49
57
|
{
|
|
@@ -13,6 +13,12 @@ inputs:
|
|
|
13
13
|
head:
|
|
14
14
|
description: Exact checked-out commit.
|
|
15
15
|
default: HEAD
|
|
16
|
+
pr-head:
|
|
17
|
+
description: >-
|
|
18
|
+
The pull request's head. When the checkout is GitHub's merge of it (refs/pull/N/merge), changes are measured from
|
|
19
|
+
that merge's base-branch parent, so commits that landed on the base branch after `base` are not counted as the PR's.
|
|
20
|
+
Empty (any other event) keeps `base`.
|
|
21
|
+
default: ${{ github.event.pull_request.head.sha }}
|
|
16
22
|
full-roots:
|
|
17
23
|
description: Space-separated complete test roots used if planning cannot prove a narrower scope.
|
|
18
24
|
required: true
|
|
@@ -114,6 +120,7 @@ runs:
|
|
|
114
120
|
IMPACT_CONFIG: ${{ inputs.config }}
|
|
115
121
|
IMPACT_BASE: ${{ inputs.base }}
|
|
116
122
|
IMPACT_HEAD: ${{ inputs.head }}
|
|
123
|
+
IMPACT_PR_HEAD: ${{ inputs.pr-head }}
|
|
117
124
|
IMPACT_FULL: ${{ inputs.full }}
|
|
118
125
|
IMPACT_EXTRA: ${{ inputs.extra-files }}
|
|
119
126
|
IMPACT_ALTERNATE_BASE: ${{ inputs.alternate-base }}
|
|
@@ -147,6 +154,7 @@ runs:
|
|
|
147
154
|
# A reused workspace must not upload a previous plan's explanation.
|
|
148
155
|
rm -f test-impact-explanation.json
|
|
149
156
|
args+=(--full "$IMPACT_FULL")
|
|
157
|
+
if [ -n "${IMPACT_PR_HEAD:-}" ]; then args+=(--pr-head "$IMPACT_PR_HEAD"); fi
|
|
150
158
|
if [ -n "$IMPACT_EXTRA" ]; then args+=(--extra-files "$IMPACT_EXTRA"); fi
|
|
151
159
|
if [ -n "${IMPACT_GRAPH_BASE:-}" ]; then args+=(--graph-base "$IMPACT_GRAPH_BASE"); fi
|
|
152
160
|
# Optional: an unfetchable alternate only forgoes a smaller selection.
|
|
@@ -36,7 +36,14 @@ PR, on a version-only release PR, and on a PR push that changes nothing the
|
|
|
36
36
|
tests read. A `PR evidence #N head SHA` step ties each PR run to its pull
|
|
37
37
|
request, because GitHub often leaves that link out. The distribution smoke test
|
|
38
38
|
still follows the plan before reuse (`planned`), and partial proof runs the
|
|
39
|
-
whole lane.
|
|
39
|
+
whole lane. The contract lane works the same way with its own receipt and inputs
|
|
40
|
+
(including `README.md`, which its README check reads).
|
|
41
|
+
|
|
42
|
+
CI-esta decides admission: each evidence step gets the planned lanes and
|
|
43
|
+
`lane-keys`, the keys of every job a lane gates, and reports `admitted-lanes`.
|
|
44
|
+
The contract step chains on the tests step's result. `ci-esta check-workflow`
|
|
45
|
+
then proves `lane-keys` name every job each lane gates, matrix cells included,
|
|
46
|
+
so a newly added cell can never be skipped as proven.
|
|
40
47
|
|
|
41
48
|
The unconditional CI job runs `ci-esta check-workflow` against this workflow and
|
|
42
49
|
lane configuration, so renamed lanes or omitted metadata prerequisites cannot
|
|
@@ -64,6 +64,14 @@ controls the test interpreter. `full-roots` must contain the complete roots, not
|
|
|
64
64
|
the previous selection. `base` defaults to the PR base or the push's previous
|
|
65
65
|
commit. Override it only when you have validated a different comparison base.
|
|
66
66
|
|
|
67
|
+
On a pull request, GitHub checks out a merge of the PR head into the base
|
|
68
|
+
branch's current tip, which can be newer than the PR's recorded base. Both
|
|
69
|
+
actions pass the PR head (`pr-head`) so the planner measures changes from that
|
|
70
|
+
merge's base-branch parent. Commits that landed on the base branch after the PR
|
|
71
|
+
was opened, such as a release's version bump, are then not counted as the PR's
|
|
72
|
+
own. A shallow checkout or any other event keeps `base`. Check out with
|
|
73
|
+
`fetch-depth: 0` to get the narrower plan.
|
|
74
|
+
|
|
67
75
|
Both file and lane actions default to `full: auto`:
|
|
68
76
|
|
|
69
77
|
| Trigger | Default |
|
|
@@ -277,3 +285,27 @@ The action also uploads the report as the `test-lane-selection` artifact; set
|
|
|
277
285
|
pull-request sticky's lane section: which lanes ran and why, and the runner time
|
|
278
286
|
the skipped jobs took on the default branch's last full run. See
|
|
279
287
|
[how the two fit together in a Python project](https://github.com/gigaverse-app/ci-esta/blob/main/docs/integrations.md#getting-the-most-from-both-in-a-python-project).
|
|
288
|
+
|
|
289
|
+
## Split whole lanes by test filename
|
|
290
|
+
|
|
291
|
+
Lanes sharing a directory can partition its tests with `test_patterns` and
|
|
292
|
+
`exclude_patterns`. Both match repository-relative POSIX paths using the same
|
|
293
|
+
globs as lane inputs; `*` also matches path separators. Roots still limit the
|
|
294
|
+
search. The defaults include every test under the roots and exclude none.
|
|
295
|
+
|
|
296
|
+
```json
|
|
297
|
+
{
|
|
298
|
+
"unit": {"roots": ["tests"], "exclude_patterns": ["*_integration.py"]},
|
|
299
|
+
"integration": {"roots": ["tests"], "test_patterns": ["*_integration.py"]}
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Put these entries under `lanes` in the lane configuration. A source dependency
|
|
304
|
+
still selects every lane containing an affected test. Deleted tests use their
|
|
305
|
+
base-revision membership; an affected test claimed by no lane causes a full
|
|
306
|
+
fallback. Explicit inputs and full checkpoints are unaffected by these filters.
|
|
307
|
+
|
|
308
|
+
Same-named modules in independent module roots are unrelated unless an import
|
|
309
|
+
connects them. An actual ambiguous bare import conservatively retains every
|
|
310
|
+
possible target. Use qualified imports to make independent demo modules
|
|
311
|
+
unambiguous; selection does not guess a runtime import order.
|
|
@@ -22,6 +22,11 @@ select. The default filenames are `test_*.py`, `*_test.py`, `*_tests.py`, and
|
|
|
22
22
|
`tests.py`; configure
|
|
23
23
|
`test_patterns` for a different collection policy.
|
|
24
24
|
|
|
25
|
+
Every `--config` (`affected`, `explain`, `fingerprints`, `audit`, `run` and the rest) also accepts a
|
|
26
|
+
[lane config](ci.md) such as `.github/test-lanes.json`: it uses the lane config's `graph`, with its
|
|
27
|
+
top-level `global_inputs` when declared. Pass the lane config itself rather than a copy of its graph,
|
|
28
|
+
so a later `graph.pytest_policy` refresh is always the one in use.
|
|
29
|
+
|
|
25
30
|
## Review and record collection policy
|
|
26
31
|
|
|
27
32
|
If your repository has `pyproject.toml`, `pytest.ini`, a `tox.ini` with a `[pytest]`
|
|
@@ -61,6 +66,9 @@ fixture example, directory manifests, dependency comments, and dynamic Python in
|
|
|
61
66
|
|
|
62
67
|
A read that names a tracked file by literal path, such as `open("data/prices.json")` or
|
|
63
68
|
`(Path(__file__).parent / "prices.json").read_text()`, is followed automatically as a content read.
|
|
69
|
+
A string that only names a tracked file, such as a routing test's `("bench.py", ...)` row, adds no
|
|
70
|
+
edge: a dotted string is followed as a possible module name (a patch target, a plugin) only when it
|
|
71
|
+
names no tracked file, from the repository root or beside its reader.
|
|
64
72
|
For a path that is computed, or built from several parts, add a reader-owned comment:
|
|
65
73
|
|
|
66
74
|
```python
|