pytest-selective 1.1.2__tar.gz → 1.3.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.1.2 → pytest_selective-1.3.0}/.github/workflows/ci.yml +4 -5
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/pr-title.yml +2 -1
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/public-actions.yml +0 -2
- pytest_selective-1.3.0/.github/workflows/weekly-checkpoint.yml +21 -0
- pytest_selective-1.3.0/.release-please-manifest.json +3 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/CHANGELOG.md +21 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/CONTRIBUTING.md +13 -6
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/PKG-INFO +2 -2
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/README.md +1 -1
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/action.yml +6 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/actions/plan-lanes/action.yml +8 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/actions/test-impact/action.yml +8 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/contracts/selection/conftest.py +2 -1
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/contracts/selection/runtime_deps.py +4 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/ci.md +8 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/configuration.md +52 -1
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/selection-health.md +2 -1
- pytest_selective-1.3.0/docs/shared-plan-execution.md +43 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/pyproject.toml +1 -1
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/ci_file_config.py +1 -8
- pytest_selective-1.3.0/scripts/run_ci_tests.py +8 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/skills/pytest-selective/SKILL.md +71 -18
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/ci.py +32 -1
- pytest_selective-1.1.2/scripts/run_ci_tests.py → pytest_selective-1.3.0/src/pytest_selective/execution.py +18 -3
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/git_operations.py +23 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_audit.py +9 -4
- pytest_selective-1.3.0/src/pytest_selective/impact_audit_declarations.py +159 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_cli.py +86 -6
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_graph.py +170 -24
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/local.py +3 -5
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_ci_admission.py +1 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_ci_test_execution.py +5 -5
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_repository_ci.py +40 -4
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_impact_audit.py +40 -0
- pytest_selective-1.3.0/tests/testing/test_impact_audit_declarations.py +124 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_impact_graph.py +152 -0
- pytest_selective-1.3.0/tests/testing/test_pull_request_merge_base.py +83 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_working_tree.py +10 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/uv.lock +1 -1
- pytest_selective-1.1.2/.release-please-manifest.json +0 -3
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.dry-lint/baseline.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.dry-lint/progress.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.dry-lint.toml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.gitattributes +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/dependabot.yml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/test-lanes.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/validation-inputs.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/docs.yml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/dry-lint-audit.yml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/dry-lint-judge.yml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/dry-lint.yml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/observe-ci.yml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/release-please.yml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/release.yml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/windows-performance.yml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.gitignore +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.pre-commit-config.yaml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.readthedocs.yaml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/AGENTS.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/CLAUDE.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/LICENSE +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/PROVENANCE.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/RELEASING.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/actions/README.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/actions/pytest-expansion/action.yml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/actions/validation-gate/action.yml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/agentsync.toml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/contracts/selection/README.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/contracts/selection/quint-connect.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/contracts/selection/quint.ini +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/contracts/selection/selection.qnt +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/contracts/selection/selection_driver.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/contracts/selection/test_selection_contract.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/README.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/actions.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/agents.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/assets/banner.png +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/assets/banner.svg +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/assets/brand.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/assets/icon.png +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/assets/logo.svg +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/assets/raster-sources.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/assets/social-preview.png +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/assets/social-preview.svg +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22-due-ci.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22-due-docs-ci.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22-due-docs-widget.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22-due-widget.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22-input-replays.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22-obligation-docs.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22-obligation.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22-routing.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22-selective-docs.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-23-windows-saleor/obligation-baseline.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after-3.11.xml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after-3.14.xml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control-3.11.xml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control-3.14.xml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-earlier.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-23-windows-saleor.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/windows-runtime.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/ci-toolkit.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/comparison-pytest-impacted.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/evidence.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/local-development.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/pytest-hygiene.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/tutorial.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/tutorials/adoption-checklist.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/tutorials/sidecar-dependencies.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/tutorials/skip-unrelated-ci.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/work-in-progress/tickets/actions-directory-test-plan.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/work-in-progress/tickets/codex-selector-correctness-performance-test-plan.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/work-in-progress/tickets/codex-windows-git-performance-test-plan.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/work-in-progress/tickets/graph-based-inputs-plan.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/work-in-progress/tickets/impacted-improvements-test-plan.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/work-in-progress/tickets/move-action-implementations-test-plan.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/work-in-progress/tickets/public-action-onboarding-test-plan.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/work-in-progress/tickets/ratchet-duplicates-zero-test-plan.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/work-in-progress/tickets/rerun-selection-test-plan.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/assets/routing-comparison.svg +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/check_site.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/ecosystem.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/hooks.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/index.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/llms.txt +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/overrides/main.html +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/publishing.md +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/requirements.in +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/requirements.txt +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/mkdocs.yml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/release-please-config.json +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/benchmark_graph.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/benchmark_parsing.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/benchmark_rust.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/check_docs.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/check_metadata.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/compare_impacted.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/measure_ci_runs.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/public_actions.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/render_brand.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/smoke_distribution.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/tutorial.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/skills/pytest-selective/agents/openai.yaml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/__init__.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/__main__.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/ast_imports.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/checkpoint.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/gate.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/hygiene.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_audit_plugin.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_declarations.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_explanation.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_first_party.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_models.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_name_scope.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_selection.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/input_policy.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/parse_batch.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/plugin.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/py.typed +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/pytest_expansion.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/pytest_plugins.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/pytest_sharding.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/test_inventory.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/typecheck_scope.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/working_tree.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/__init__.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_checkpoint_policy.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_ci_measurements.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_distribution_metadata.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_hygiene.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_public_actions.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_pytest_expansion.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_release_workflow.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_repository_hygiene.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_repository_hygiene.py.gv-dependency.yaml +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_tutorial.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/__init__.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/git_repo.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_ast_imports.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_ci.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_gate.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_git_repo.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_global_inputs.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_impact_explanation.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_impact_first_party.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_impact_name_scope.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_parse_batch.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_portability.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_pytest_plugin.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_pytest_plugins.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_pytest_sharding.py +0 -0
- {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_typecheck_scope.py +0 -0
|
@@ -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:
|
|
@@ -149,12 +147,13 @@ jobs:
|
|
|
149
147
|
- name: Record coverage runtime
|
|
150
148
|
run: uv run --locked python -m coverage --version
|
|
151
149
|
- name: Execute the shared affected-file plan
|
|
152
|
-
run: uv run --locked
|
|
150
|
+
run: uv run --locked pytest-selective execute-plan -- -q -n 4 --dist=worksteal --durations=20 --junitxml=test-timings.xml
|
|
153
151
|
env:
|
|
154
152
|
CI_PLANNER_RESULT: ${{ needs.select-tests.result }}
|
|
155
153
|
CI_TEST_MODE: ${{ needs.select-tests.outputs.mode }}
|
|
156
154
|
CI_TEST_FILES: ${{ needs.select-tests.outputs.files }}
|
|
157
|
-
|
|
155
|
+
# Full coverage on each OS; 3.11 retains the same compatibility tests without duplicate instrumentation.
|
|
156
|
+
CI_COVERAGE: ${{ matrix.python == '3.14' }}
|
|
158
157
|
# Keep temporary Git repositories beside the checkout on the work disk.
|
|
159
158
|
TMP: ${{ runner.temp }}
|
|
160
159
|
TEMP: ${{ runner.temp }}
|
|
@@ -186,7 +185,7 @@ jobs:
|
|
|
186
185
|
python-version: '3.11'
|
|
187
186
|
- run: uv pip install 'pytest==8.0.0'
|
|
188
187
|
- name: Execute the same file plan on minimum pytest
|
|
189
|
-
run: uv run --no-sync
|
|
188
|
+
run: uv run --no-sync pytest-selective execute-plan -- -q -n 4 --dist=worksteal --durations=20
|
|
190
189
|
env:
|
|
191
190
|
CI_PLANNER_RESULT: ${{ needs.select-tests.result }}
|
|
192
191
|
CI_TEST_MODE: ${{ needs.select-tests.outputs.mode }}
|
|
@@ -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:
|
|
@@ -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: '{}'
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.3.0](https://github.com/gigaverse-app/pytest-selective/compare/v1.2.0...v1.3.0) (2026-11-09)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **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))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug fixes
|
|
12
|
+
|
|
13
|
+
* 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))
|
|
14
|
+
* **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))
|
|
15
|
+
* **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))
|
|
16
|
+
|
|
17
|
+
## [1.2.0](https://github.com/gigaverse-app/pytest-selective/compare/v1.1.2...v1.2.0) (2026-11-09)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* **ci:** share plan execution and reduce matrix instrumentation ([1fe0f36](https://github.com/gigaverse-app/pytest-selective/commit/1fe0f36463d4354a99ea3f45406825fa728a12c2))
|
|
23
|
+
|
|
3
24
|
## [1.1.2](https://github.com/gigaverse-app/pytest-selective/compare/v1.1.1...v1.1.2) (2026-11-09)
|
|
4
25
|
|
|
5
26
|
|
|
@@ -79,7 +79,9 @@ split indexes and linked worktrees without modifying the user's index or HEAD.
|
|
|
79
79
|
CI tests Python 3.11 and 3.14 on Linux, Windows, and macOS. A separate job checks
|
|
80
80
|
pytest 8.0.0 on Python 3.11, the plugin's lower bound. It also checks hooks, builds both distribution
|
|
81
81
|
formats, validates PyPI metadata, and installs each distribution in an isolated
|
|
82
|
-
environment on both Python versions.
|
|
82
|
+
environment on both Python versions. JUnit retains results and timings on every platform and Python version.
|
|
83
|
+
Full runs collect coverage on Python 3.14 on all three operating systems; Python 3.11 runs the same compatibility
|
|
84
|
+
tests without a duplicate instrumentation pass. Each runner uses four work-stealing workers.
|
|
83
85
|
|
|
84
86
|
Use `uv lock --upgrade-package NAME` for a targeted dependency update and run the
|
|
85
87
|
affected checks. Dependabot proposes weekly uv and GitHub Actions updates.
|
|
@@ -116,8 +118,8 @@ File planning derives its graph from that same lane config and merges its declar
|
|
|
116
118
|
global execution inputs with the graph's environment inputs. Declare subprocess
|
|
117
119
|
and non-import dependencies at their readers. Full plans collect whole-suite
|
|
118
120
|
coverage; scoped plans produce JUnit timing evidence without presenting partial
|
|
119
|
-
coverage as a whole-suite result. The
|
|
120
|
-
|
|
121
|
+
coverage as a whole-suite result. The public [shared-plan executor](docs/shared-plan-execution.md)
|
|
122
|
+
consumes the action's JSON file list without implementing another selector.
|
|
121
123
|
The outer pytest process disables the selective plugin to avoid a second planner
|
|
122
124
|
and a hidden dependency of every test on plugin startup. Plugin integration tests
|
|
123
125
|
still load it normally in subprocesses and declare that dependency explicitly.
|
|
@@ -197,14 +199,19 @@ history or failed planning must execute the full affected lanes and fail the
|
|
|
197
199
|
required planning gate. Ordinary PRs and pushes use selection; manual/release/scheduled/full overrides
|
|
198
200
|
remain complete checkpoints.
|
|
199
201
|
|
|
200
|
-
CI and public-action smoke
|
|
202
|
+
The **Weekly full checkpoint** workflow dispatches CI and public-action smoke
|
|
203
|
+
every Monday using [CI-esta's shared action](https://github.com/gigaverse-app/ci-esta/blob/main/docs/weekly-checkpoints.md).
|
|
204
|
+
Its small wrapper is generated by `ci-esta checkpoint init`; the source guide
|
|
205
|
+
documents schedule, workflow, ref and input overrides. Ordinary runs keep
|
|
201
206
|
the shared affected-file plan across all six OS/Python combinations and minimum
|
|
202
207
|
pytest. `tests/test_tutorial.py` owns the end-to-end tutorial execution and follows
|
|
203
208
|
that plan; there is no second tutorial invocation in each matrix job. JUnit retains
|
|
204
209
|
its test result and timing, and full plans also upload whole-suite coverage.
|
|
205
210
|
|
|
206
|
-
To run
|
|
207
|
-
and choose **Run workflow
|
|
211
|
+
To run both complete checkpoints after a CI change, select **Weekly full checkpoint**
|
|
212
|
+
in GitHub Actions and choose **Run workflow**, then inspect the dispatched CI and
|
|
213
|
+
public-action results. A green dispatcher means both requests were accepted.
|
|
214
|
+
You can also dispatch either target directly. Retrying an ordinary PR run preserves its selection;
|
|
208
215
|
use the manual checkpoint when you need full-suite coverage evidence.
|
|
209
216
|
|
|
210
217
|
## Duplication
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pytest-selective
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.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/
|
|
@@ -237,7 +237,7 @@ skip pytest, because invoking pytest without paths runs the whole suite.
|
|
|
237
237
|
| --- | --- |
|
|
238
238
|
| A transitive dependency | Import chains and package initialization |
|
|
239
239
|
| A deleted or renamed module | Dependencies in both the base and head trees |
|
|
240
|
-
| Fixtures or startup code | Ancestor conftests, plugins named by `-p` in `addopts`, and configured global modules |
|
|
240
|
+
| Fixtures or startup code | Ancestor conftests, plugins named by `-p` in `addopts`, the project's own `pytest11` entry points, and configured global modules |
|
|
241
241
|
| Templates, schemas, or data files | [Reader-owned declarations and YAML sidecars](docs/tutorials/sidecar-dependencies.md) |
|
|
242
242
|
| A surprisingly large selection | Dependency paths, startup-floor and blast-radius explanations |
|
|
243
243
|
| A large CI suite | Cached parsing, graph reuse, and duration-based shard planning |
|
|
@@ -206,7 +206,7 @@ skip pytest, because invoking pytest without paths runs the whole suite.
|
|
|
206
206
|
| --- | --- |
|
|
207
207
|
| A transitive dependency | Import chains and package initialization |
|
|
208
208
|
| A deleted or renamed module | Dependencies in both the base and head trees |
|
|
209
|
-
| Fixtures or startup code | Ancestor conftests, plugins named by `-p` in `addopts`, and configured global modules |
|
|
209
|
+
| Fixtures or startup code | Ancestor conftests, plugins named by `-p` in `addopts`, the project's own `pytest11` entry points, and configured global modules |
|
|
210
210
|
| Templates, schemas, or data files | [Reader-owned declarations and YAML sidecars](docs/tutorials/sidecar-dependencies.md) |
|
|
211
211
|
| A surprisingly large selection | Dependency paths, startup-floor and blast-radius explanations |
|
|
212
212
|
| A large CI suite | Cached parsing, graph reuse, and duration-based shard planning |
|
|
@@ -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.
|
|
@@ -305,7 +305,8 @@ class RepositoryDriver(SelectionDriver):
|
|
|
305
305
|
changed = set(git_text(self.root, "diff", "--name-only", "--no-renames", base, self.head).splitlines())
|
|
306
306
|
tests = self._tests(self.head)
|
|
307
307
|
at_head = self._runtime(self.head, tests)
|
|
308
|
-
|
|
308
|
+
base_tests = set(self._tests(base))
|
|
309
|
+
at_base = self._runtime(base, [test for test in tests if test in base_tests])
|
|
309
310
|
needed = {test for test in tests if (at_head[test] | at_base.get(test, frozenset())) & changed}
|
|
310
311
|
return {NAMES[test] for test in needed} - set(selection["selected"])
|
|
311
312
|
|
|
@@ -9,12 +9,16 @@ Modules and sys.path are restored between test files, so each file's dependencie
|
|
|
9
9
|
import json
|
|
10
10
|
import os
|
|
11
11
|
import sys
|
|
12
|
+
from functools import cache
|
|
12
13
|
from pathlib import Path
|
|
13
14
|
|
|
14
15
|
import pytest
|
|
15
16
|
|
|
16
17
|
|
|
18
|
+
@cache
|
|
17
19
|
def _under(root: Path, path: str | None) -> str | None:
|
|
20
|
+
# Each process inspects one immutable exported tree. Cache path resolution,
|
|
21
|
+
# never the per-test import/open observations, which remain isolated below.
|
|
18
22
|
if not path:
|
|
19
23
|
return None
|
|
20
24
|
try:
|
|
@@ -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 |
|
|
@@ -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
|
|
@@ -97,7 +105,7 @@ patterns to your actual inventory. Central-rule reader patterns use shell-style
|
|
|
97
105
|
matching: here `*` also covers nested paths under `tests/`. Common lockfiles, dependency manifests and pytest configuration already belong
|
|
98
106
|
to the default `global_inputs`. Keep those defaults unless you have reviewed a
|
|
99
107
|
narrower environment contract; these reader-specific rules are for additional
|
|
100
|
-
inputs. Other non-Python files are not automatically global inputs. Plugins named by `-p NAME` in a pytest configuration's `addopts` are global modules automatically
|
|
108
|
+
inputs. Other non-Python files are not automatically global inputs. Plugins named by `-p NAME` in a pytest configuration's `addopts` are global modules automatically, and so are the project's own `pytest11` entry points (`[project.entry-points.pytest11]` or `[tool.poetry.plugins.pytest11]` in any tracked `pyproject.toml`) whose module is a tracked file. An entry point stays global unless every pytest configuration disables it, with `-p no:NAME` (`NAME` is the entry point's name) or `--disable-plugin-autoload` in `addopts`. Plugins passed only on the command line or in `PYTEST_ADDOPTS`, and `PYTEST_DISABLE_PLUGIN_AUTOLOAD`, are not visible to the planner. Use `global_modules` for those and other modules loaded by every test and
|
|
101
109
|
`always_run` for explicit canary test paths. Retain full-suite checkpoints.
|
|
102
110
|
|
|
103
111
|
## Explain and audit
|
|
@@ -121,6 +129,49 @@ pytest-selective audit --config .github/test-impact.json --records impact-audit
|
|
|
121
129
|
Audit results cover executed paths, not every possible execution. Declare missing
|
|
122
130
|
edges at their readers. See [evidence and limitations](evidence.md).
|
|
123
131
|
|
|
132
|
+
### Write the missing declarations
|
|
133
|
+
|
|
134
|
+
`audit --write-declarations` writes a rule for every undeclared read into the
|
|
135
|
+
reading test's sidecar, `<test>.gv-dependency.yaml`, creating it or appending to
|
|
136
|
+
the one it already has:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
pytest-selective audit --config .github/test-impact.json --records impact-audit --write-declarations
|
|
140
|
+
pytest-selective audit --config .github/test-impact.json --records impact-audit --working-tree # confirms nothing is left before you commit
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```yaml
|
|
144
|
+
dependencies:
|
|
145
|
+
- inputs:
|
|
146
|
+
- "//examples/backgrounds/**"
|
|
147
|
+
- "//examples/gold.toml"
|
|
148
|
+
kind: content
|
|
149
|
+
reason: "Read at runtime; recorded by pytest-selective audit --write-declarations."
|
|
150
|
+
- inputs:
|
|
151
|
+
- "//scripts/replay_coverage.py"
|
|
152
|
+
kind: import
|
|
153
|
+
reason: "Loaded or run outside the test's imports; recorded by pytest-selective audit --write-declarations."
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
- A Python input becomes an `import` rule: a test that runs a script in a
|
|
157
|
+
subprocess also depends on what the script imports. Any other input becomes a
|
|
158
|
+
`content` rule. Review the kind where the test only reads Python as text.
|
|
159
|
+
- Several reads in one directory become `<directory>/**` only when the test read
|
|
160
|
+
every tracked file under it, so the glob claims nothing the run did not see.
|
|
161
|
+
It also covers files added there later.
|
|
162
|
+
- Every pattern must match a tracked file at the audited revision. A path the
|
|
163
|
+
pattern language cannot express (for example one containing `[`) is printed as
|
|
164
|
+
`not declared` and the command exits 2.
|
|
165
|
+
- Appending keeps an existing sidecar's text and comments. When the result would
|
|
166
|
+
not parse to exactly the old rules plus the new ones, the file is left alone
|
|
167
|
+
and the rules are printed for you to add by hand.
|
|
168
|
+
|
|
169
|
+
The command prints each sidecar and pattern it wrote and exits 0 when every
|
|
170
|
+
undeclared read was declared. The files are in the working tree, so the plain
|
|
171
|
+
audit (which reads `--head`) still reports them until you commit; use
|
|
172
|
+
`--working-tree` to check first. Prefer a hand-written comment or directory
|
|
173
|
+
manifest where it reads better; the writer only removes the transcription step.
|
|
174
|
+
|
|
124
175
|
Audit discovery uses the requested repository even when a calling Git hook exports
|
|
125
176
|
location variables for another checkout. It shares repository isolation with the
|
|
126
177
|
planner and expansion guard.
|
|
@@ -30,7 +30,8 @@ uv run pytest-selective audit --config .github/test-impact.json --records impact
|
|
|
30
30
|
The recorder observes real file reads in the tests you execute. The audit can
|
|
31
31
|
expose a JSON fixture, template or schema that the graph did not declare. Add an
|
|
32
32
|
edge at the reader using a dependency comment or YAML sidecar, then recheck the
|
|
33
|
-
route.
|
|
33
|
+
route. `audit --write-declarations` writes those sidecars for you; see
|
|
34
|
+
[write the missing declarations](configuration.md#write-the-missing-declarations). [Work through the sidecar tutorial](tutorials/sidecar-dependencies.md), or consult
|
|
34
35
|
[declaration syntax](configuration.md#declare-inputs-that-imports-cannot-describe).
|
|
35
36
|
The audit covers observed paths; keep representative full runs for paths it has
|
|
36
37
|
not seen, and explicitly model dynamic imports and external inputs.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Plan once, execute across compatibility lanes
|
|
2
|
+
|
|
3
|
+
Use `pytest-selective execute-plan` to run a file plan emitted by the public
|
|
4
|
+
test-impact action. Every matrix job consumes the same plan; it does not need
|
|
5
|
+
Git history or a second dependency analysis.
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
- run: uv run --locked pytest-selective execute-plan -- -q -n 4 --dist=worksteal
|
|
9
|
+
env:
|
|
10
|
+
CI_PLANNER_RESULT: ${{ needs.plan.result }}
|
|
11
|
+
CI_TEST_MODE: ${{ needs.plan.outputs.mode }}
|
|
12
|
+
CI_TEST_FILES: ${{ needs.plan.outputs.files }}
|
|
13
|
+
CI_COVERAGE: 'true'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`files` must be the action's `test_roots_json` output. A successful `none` plan
|
|
17
|
+
does not start pytest. Failed planning, malformed JSON, unknown modes or an
|
|
18
|
+
empty scoped selection execute the complete roots. Roots default to `tests`;
|
|
19
|
+
override with `--full-roots tests/unit tests/integration -- ...`. Paths with
|
|
20
|
+
spaces remain single arguments, and pytest receives its normal exit status.
|
|
21
|
+
|
|
22
|
+
`CI_COVERAGE=true` adds `--cov --cov-report=term-missing --cov-report=xml` only
|
|
23
|
+
for full execution. The environment must have pytest-cov installed. Keep
|
|
24
|
+
coverage thresholds in your project configuration; do not enforce a full-suite
|
|
25
|
+
threshold on selected files. The outer execution disables the selective plugin
|
|
26
|
+
because planning already happened. Integration tests can still load it explicitly.
|
|
27
|
+
|
|
28
|
+
When lane and file selection share a graph, derive the file config:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
pytest-selective file-config --lanes .github/test-lanes.json --output /tmp/test-impact.json
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This combines the graph's global inputs with the lane config's explicit global
|
|
35
|
+
inputs. Declare `global_inputs` before using it. Lane-specific `inputs` remain
|
|
36
|
+
lane admission rules; they do not automatically make every test file affected.
|
|
37
|
+
Declare non-import dependencies in the graph as usual.
|
|
38
|
+
|
|
39
|
+
pytest-selective's own CI uses four work-stealing workers on each existing
|
|
40
|
+
runner. Its full checkpoints retain all Python 3.11 and 3.14 compatibility tests
|
|
41
|
+
on Linux, Windows and macOS. Coverage runs on Python 3.14 on all three systems,
|
|
42
|
+
avoiding a second instrumentation pass on Python 3.11. The minimum-pytest lane,
|
|
43
|
+
Quint contract and installed-distribution smoke remain independent checks.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pytest-selective"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.3.0"
|
|
8
8
|
description = "Explainable Python test impact analysis: select affected pytest files from Git changes"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -4,15 +4,8 @@ import sys
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
6
|
from pytest_selective.ci import LaneConfig
|
|
7
|
-
from pytest_selective.impact_models import ImpactConfig
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
def file_config(lanes: LaneConfig) -> ImpactConfig:
|
|
11
|
-
if lanes.global_inputs is None:
|
|
12
|
-
raise ValueError("This repository must declare its shared CI execution inputs")
|
|
13
|
-
return lanes.graph.model_copy(
|
|
14
|
-
update={"global_inputs": sorted(set(lanes.graph.global_inputs) | set(lanes.global_inputs))}
|
|
15
|
-
)
|
|
8
|
+
file_config = LaneConfig.file_config
|
|
16
9
|
|
|
17
10
|
|
|
18
11
|
if __name__ == "__main__":
|