pytest-selective 1.1.0__tar.gz → 1.1.2__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/.github/validation-inputs.json +26 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.github/workflows/ci.yml +32 -7
- pytest_selective-1.1.2/.github/workflows/dry-lint-audit.yml +23 -0
- pytest_selective-1.1.2/.release-please-manifest.json +3 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/CHANGELOG.md +14 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/CONTRIBUTING.md +4 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/PKG-INFO +19 -9
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/README.md +18 -8
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/actions/plan-lanes/action.yml +4 -2
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/contracts/selection/conftest.py +11 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/contracts/selection/quint-connect.json +14 -1
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/contracts/selection/selection.qnt +30 -3
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/contracts/selection/selection_driver.py +6 -1
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/actions.md +4 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/ci.md +3 -4
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/configuration.md +12 -5
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/pyproject.toml +1 -1
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/scripts/measure_ci_runs.py +4 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/skills/pytest-selective/SKILL.md +137 -81
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/impact_graph.py +39 -2
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/impact_models.py +23 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/test_public_actions.py +8 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/testing/test_impact_audit.py +8 -6
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/testing/test_impact_graph.py +81 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/uv.lock +1 -1
- pytest_selective-1.1.0/.github/validation-inputs.json +0 -11
- pytest_selective-1.1.0/.github/workflows/dry-lint-audit.yml +0 -91
- pytest_selective-1.1.0/.release-please-manifest.json +0 -3
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.dry-lint/baseline.json +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.dry-lint/progress.json +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.dry-lint.toml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.gitattributes +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.github/dependabot.yml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.github/test-lanes.json +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.github/workflows/docs.yml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.github/workflows/dry-lint-judge.yml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.github/workflows/dry-lint.yml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.github/workflows/observe-ci.yml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.github/workflows/pr-title.yml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.github/workflows/public-actions.yml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.github/workflows/release-please.yml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.github/workflows/release.yml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.github/workflows/windows-performance.yml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.gitignore +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.pre-commit-config.yaml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/.readthedocs.yaml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/AGENTS.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/CLAUDE.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/LICENSE +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/PROVENANCE.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/RELEASING.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/action.yml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/actions/README.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/actions/pytest-expansion/action.yml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/actions/test-impact/action.yml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/actions/validation-gate/action.yml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/agentsync.toml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/contracts/selection/README.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/contracts/selection/quint.ini +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/contracts/selection/runtime_deps.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/contracts/selection/test_selection_contract.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/README.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/agents.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/assets/banner.png +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/assets/banner.svg +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/assets/brand.json +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/assets/icon.png +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/assets/logo.svg +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/assets/raster-sources.json +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/assets/social-preview.png +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/assets/social-preview.svg +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/2026-10-22-due-ci.json +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/2026-10-22-due-docs-ci.json +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/2026-10-22-due-docs-widget.json +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/2026-10-22-due-widget.json +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/2026-10-22-input-replays.json +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/2026-10-22-obligation-docs.json +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/2026-10-22-obligation.json +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/2026-10-22-routing.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/2026-10-22-selective-docs.json +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/2026-10-22.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/2026-10-23-windows-saleor/obligation-baseline.json +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/2026-10-23-windows-saleor/selective-after-3.11.xml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/2026-10-23-windows-saleor/selective-after-3.14.xml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/2026-10-23-windows-saleor/selective-after.json +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/2026-10-23-windows-saleor/selective-control-3.11.xml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/2026-10-23-windows-saleor/selective-control-3.14.xml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/2026-10-23-windows-saleor/selective-control.json +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/2026-10-23-windows-saleor/selective-earlier.json +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/2026-10-23-windows-saleor.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/benchmarks/windows-runtime.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/ci-toolkit.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/comparison-pytest-impacted.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/evidence.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/local-development.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/pytest-hygiene.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/selection-health.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/tutorial.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/tutorials/adoption-checklist.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/tutorials/sidecar-dependencies.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/tutorials/skip-unrelated-ci.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/work-in-progress/tickets/actions-directory-test-plan.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/work-in-progress/tickets/codex-selector-correctness-performance-test-plan.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/work-in-progress/tickets/codex-windows-git-performance-test-plan.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/work-in-progress/tickets/graph-based-inputs-plan.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/work-in-progress/tickets/impacted-improvements-test-plan.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/work-in-progress/tickets/move-action-implementations-test-plan.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/work-in-progress/tickets/public-action-onboarding-test-plan.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/work-in-progress/tickets/ratchet-duplicates-zero-test-plan.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/docs/work-in-progress/tickets/rerun-selection-test-plan.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/documentation/assets/routing-comparison.svg +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/documentation/check_site.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/documentation/ecosystem.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/documentation/hooks.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/documentation/index.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/documentation/llms.txt +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/documentation/overrides/main.html +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/documentation/publishing.md +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/documentation/requirements.in +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/documentation/requirements.txt +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/mkdocs.yml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/release-please-config.json +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/scripts/benchmark_graph.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/scripts/benchmark_parsing.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/scripts/benchmark_rust.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/scripts/check_docs.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/scripts/check_metadata.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/scripts/ci_file_config.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/scripts/compare_impacted.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/scripts/public_actions.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/scripts/render_brand.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/scripts/run_ci_tests.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/scripts/smoke_distribution.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/scripts/tutorial.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/skills/pytest-selective/agents/openai.yaml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/__init__.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/__main__.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/ast_imports.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/checkpoint.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/ci.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/gate.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/git_operations.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/hygiene.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/impact_audit.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/impact_audit_plugin.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/impact_cli.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/impact_declarations.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/impact_explanation.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/impact_first_party.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/impact_name_scope.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/impact_selection.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/input_policy.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/local.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/parse_batch.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/plugin.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/py.typed +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/pytest_expansion.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/pytest_plugins.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/pytest_sharding.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/test_inventory.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/typecheck_scope.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/src/pytest_selective/working_tree.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/__init__.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/test_checkpoint_policy.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/test_ci_admission.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/test_ci_measurements.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/test_ci_test_execution.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/test_distribution_metadata.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/test_hygiene.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/test_pytest_expansion.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/test_release_workflow.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/test_repository_ci.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/test_repository_hygiene.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/test_repository_hygiene.py.gv-dependency.yaml +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/test_tutorial.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/testing/__init__.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/testing/git_repo.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/testing/test_ast_imports.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/testing/test_ci.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/testing/test_gate.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/testing/test_git_repo.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/testing/test_global_inputs.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/testing/test_impact_explanation.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/testing/test_impact_first_party.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/testing/test_impact_name_scope.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/testing/test_parse_batch.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/testing/test_portability.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/testing/test_pytest_plugin.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/testing/test_pytest_plugins.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/testing/test_pytest_sharding.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/testing/test_typecheck_scope.py +0 -0
- {pytest_selective-1.1.0 → pytest_selective-1.1.2}/tests/testing/test_working_tree.py +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tests": [
|
|
3
|
+
".github",
|
|
4
|
+
"src",
|
|
5
|
+
"tests",
|
|
6
|
+
"scripts",
|
|
7
|
+
"actions",
|
|
8
|
+
"action.yml",
|
|
9
|
+
"pyproject.toml",
|
|
10
|
+
"uv.lock",
|
|
11
|
+
".pre-commit-config.yaml"
|
|
12
|
+
],
|
|
13
|
+
"contracts": [
|
|
14
|
+
".github",
|
|
15
|
+
"src",
|
|
16
|
+
"contracts",
|
|
17
|
+
"tests/testing/git_repo.py",
|
|
18
|
+
"scripts",
|
|
19
|
+
"actions",
|
|
20
|
+
"action.yml",
|
|
21
|
+
"pyproject.toml",
|
|
22
|
+
"uv.lock",
|
|
23
|
+
".pre-commit-config.yaml",
|
|
24
|
+
"README.md"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
@@ -29,8 +29,10 @@ jobs:
|
|
|
29
29
|
"py311-macos":"tests (3.11, macos-latest)","py314-ubuntu":"tests (3.14, ubuntu-latest)",
|
|
30
30
|
"py314-windows":"tests (3.14, windows-latest)","py314-macos":"tests (3.14, macos-latest)",
|
|
31
31
|
"minimum":"pytest-minimum"}
|
|
32
|
+
# The contracts lane's one job. It reads README.md, so it keeps its own inputs and receipt.
|
|
33
|
+
CONTRACT_LANE_JOBS: '{"selection":"Selection contract (Quint)"}'
|
|
32
34
|
outputs:
|
|
33
|
-
# Planned lanes after reuse:
|
|
35
|
+
# Planned lanes after reuse: a reusing lane is false only when every one of its jobs is proven by a receipt.
|
|
34
36
|
lanes: ${{ steps.admit.outputs.lanes }}
|
|
35
37
|
# The plan before reuse, for steps that follow changed runtime inputs rather than test execution.
|
|
36
38
|
planned: ${{ steps.plan.outputs.lanes }}
|
|
@@ -38,6 +40,7 @@ jobs:
|
|
|
38
40
|
files: ${{ steps.files.outputs.test_roots_json }}
|
|
39
41
|
receipt: ${{ steps.evidence.outputs.receipt }}
|
|
40
42
|
reused: ${{ steps.evidence.outputs.reused }}
|
|
43
|
+
contract-receipt: ${{ steps.contract-evidence.outputs.receipt }}
|
|
41
44
|
steps:
|
|
42
45
|
- uses: actions/checkout@v7
|
|
43
46
|
with:
|
|
@@ -62,9 +65,11 @@ jobs:
|
|
|
62
65
|
with:
|
|
63
66
|
config: ${{ runner.temp }}/test-impact.json
|
|
64
67
|
full-roots: tests
|
|
65
|
-
# Everything
|
|
68
|
+
# Everything each reusing lane executes or reads, bound into its receipts.
|
|
66
69
|
- id: inputs
|
|
67
|
-
run:
|
|
70
|
+
run: |
|
|
71
|
+
echo "tests=$(jq -c .tests .github/validation-inputs.json)" >> "$GITHUB_OUTPUT"
|
|
72
|
+
echo "contracts=$(jq -c .contracts .github/validation-inputs.json)" >> "$GITHUB_OUTPUT"
|
|
68
73
|
# CI-esta reuses a passing first attempt of the same job when its inputs and selected files are identical:
|
|
69
74
|
# after a squash merge, on a version-only release PR, and on a PR push that changes nothing the tests read.
|
|
70
75
|
# Release (workflow_call), schedules, manual runs and the test:full label always execute.
|
|
@@ -76,17 +81,34 @@ jobs:
|
|
|
76
81
|
with:
|
|
77
82
|
lanes: ${{ env.TEST_LANE_JOBS }}
|
|
78
83
|
execution-context: ${{ steps.files.outputs.mode }} ${{ steps.files.outputs.test_roots_json }}
|
|
79
|
-
input-paths: ${{ steps.inputs.outputs.
|
|
84
|
+
input-paths: ${{ steps.inputs.outputs.tests }}
|
|
85
|
+
reuse-after-merge: 'true'
|
|
86
|
+
reuse-version-only-release: 'true'
|
|
87
|
+
- id: contract-evidence
|
|
88
|
+
if: >-
|
|
89
|
+
${{ (github.event_name == 'pull_request' || github.event_name == 'push') &&
|
|
90
|
+
!contains(github.event.pull_request.labels.*.name, 'test:full') }}
|
|
91
|
+
uses: gigaverse-app/ci-esta/actions/resolve-lane-evidence@v1
|
|
92
|
+
with:
|
|
93
|
+
lanes: ${{ env.CONTRACT_LANE_JOBS }}
|
|
94
|
+
execution-context: selection contract
|
|
95
|
+
input-paths: ${{ steps.inputs.outputs.contracts }}
|
|
80
96
|
reuse-after-merge: 'true'
|
|
81
97
|
reuse-version-only-release: 'true'
|
|
82
98
|
- id: admit
|
|
83
|
-
name: Admit
|
|
99
|
+
name: Admit each reusing lane unless every job is proven
|
|
84
100
|
env:
|
|
85
101
|
PLANNED_LANES: ${{ steps.plan.outputs.lanes }}
|
|
86
102
|
REUSED: ${{ steps.evidence.outputs.reused || '{}' }}
|
|
103
|
+
CONTRACT_REUSED: ${{ steps.contract-evidence.outputs.reused || '{}' }}
|
|
87
104
|
run: |
|
|
88
|
-
admitted=$(jq -c
|
|
89
|
-
|
|
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"
|
|
90
112
|
echo "lanes=$admitted" | tee -a "$GITHUB_OUTPUT"
|
|
91
113
|
quality:
|
|
92
114
|
runs-on: ubuntu-latest
|
|
@@ -231,6 +253,9 @@ jobs:
|
|
|
231
253
|
if: ${{ always() }}
|
|
232
254
|
with:
|
|
233
255
|
readme-mode: 'off'
|
|
256
|
+
- name: ${{ needs.select-tests.outputs.contract-receipt || 'No execution receipt' }}
|
|
257
|
+
if: ${{ needs.select-tests.result == 'success' && needs.select-tests.outputs.contract-receipt != '' }}
|
|
258
|
+
run: echo 'Recording a completed contract run for this tested tree.'
|
|
234
259
|
|
|
235
260
|
validation-gate:
|
|
236
261
|
name: Validation gate
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: dry-lint audit
|
|
2
|
+
# Weekly, or dispatched after merging a cleanup: dry-lint's own audit workflow sweeps the repository against the
|
|
3
|
+
# accepted backlog and opens or updates one dry-lint/ratchet pull request recording what was cleaned. Its steps ship
|
|
4
|
+
# with dry-lint releases; this file only says when it runs and with which credentials.
|
|
5
|
+
on:
|
|
6
|
+
schedule:
|
|
7
|
+
- cron: '23 6 * * 1'
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
# dry-lint.yml calls this file on a manual dispatch, to exercise the audit with the rest of the checks.
|
|
10
|
+
workflow_call:
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
jobs:
|
|
14
|
+
audit:
|
|
15
|
+
uses: gigaverse-app/dry-lint/.github/workflows/audit.yml@v1
|
|
16
|
+
with:
|
|
17
|
+
python-version: '3.13'
|
|
18
|
+
budget-usd: '5'
|
|
19
|
+
plugins: anthropic==2.4.0
|
|
20
|
+
secrets:
|
|
21
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
22
|
+
# The organization credential makes the ratchet pull request run ordinary CI.
|
|
23
|
+
DRY_LINT_RATCHET_TOKEN: ${{ secrets.GV_CI_CROSS_REPO_TOKEN }}
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.1.2](https://github.com/gigaverse-app/pytest-selective/compare/v1.1.1...v1.1.2) (2026-11-09)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug fixes
|
|
7
|
+
|
|
8
|
+
* agent setup round 2, from a second adoption trial ([#114](https://github.com/gigaverse-app/pytest-selective/issues/114)) ([45d4afe](https://github.com/gigaverse-app/pytest-selective/commit/45d4afe76cde9f6093519d97000244c6c06b157d))
|
|
9
|
+
|
|
10
|
+
## [1.1.1](https://github.com/gigaverse-app/pytest-selective/compare/v1.1.0...v1.1.1) (2026-11-09)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug fixes
|
|
14
|
+
|
|
15
|
+
* **selection:** follow file reads that name a tracked file by literal path ([#106](https://github.com/gigaverse-app/pytest-selective/issues/106)) ([5c534c4](https://github.com/gigaverse-app/pytest-selective/commit/5c534c48d3d098fb0e659973e2e40b410198af19))
|
|
16
|
+
|
|
3
17
|
## [1.1.0](https://github.com/gigaverse-app/pytest-selective/compare/v1.0.0...v1.1.0) (2026-11-09)
|
|
4
18
|
|
|
5
19
|
|
|
@@ -203,6 +203,10 @@ pytest. `tests/test_tutorial.py` owns the end-to-end tutorial execution and foll
|
|
|
203
203
|
that plan; there is no second tutorial invocation in each matrix job. JUnit retains
|
|
204
204
|
its test result and timing, and full plans also upload whole-suite coverage.
|
|
205
205
|
|
|
206
|
+
To run a complete checkpoint after a CI change, select **CI** in GitHub Actions
|
|
207
|
+
and choose **Run workflow**. Retrying an ordinary PR run preserves its selection;
|
|
208
|
+
use the manual checkpoint when you need full-suite coverage evidence.
|
|
209
|
+
|
|
206
210
|
## Duplication
|
|
207
211
|
|
|
208
212
|
`uv run pre-commit install` installs both the staged dry-lint check and the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pytest-selective
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
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/
|
|
@@ -60,9 +60,9 @@ the affected test files, and explains the result. Use it directly as a pytest
|
|
|
60
60
|
plugin, or plan before collection and service startup with the CLI and CI actions.
|
|
61
61
|
|
|
62
62
|
Built from the selector used in Gigaverse's backend, AI, and shared Python
|
|
63
|
-
infrastructure.
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
infrastructure. Use the actions at the moving `@v1` tag, which follows the newest
|
|
64
|
+
release, and the package at `pytest-selective>=X.Y.Z,<2`. Where your tooling rejects
|
|
65
|
+
tags, pin both to one release instead: `@<sha> # vX.Y.Z` and `pytest-selective==X.Y.Z`.
|
|
66
66
|
[Provenance and extraction scope](https://github.com/gigaverse-app/pytest-selective/blob/main/PROVENANCE.md).
|
|
67
67
|
|
|
68
68
|
[Quick tutorial](https://github.com/gigaverse-app/pytest-selective/blob/main/docs/tutorial.md) ·
|
|
@@ -279,17 +279,18 @@ change that runs the contract.
|
|
|
279
279
|
<!-- pytest-quint-connect:contracts selection -->
|
|
280
280
|
<a href="https://github.com/gigaverse-app/pytest-quint-connect#impact"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pytest-quint-connect-counter.gigaverse-oss.workers.dev/v1/sign.svg?key=f8e41d50ca4018845c305f2f0dbd1cfd6d9b281e53b352490af53154a54e6265&name=gigaverse-app%2Fpytest-selective&theme=dark"><img alt="What pytest-quint-connect's contracts caught in gigaverse-app/pytest-selective's pull requests" src="https://pytest-quint-connect-counter.gigaverse-oss.workers.dev/v1/sign.svg?key=f8e41d50ca4018845c305f2f0dbd1cfd6d9b281e53b352490af53154a54e6265&name=gigaverse-app%2Fpytest-selective" width="100%"></picture></a>
|
|
281
281
|
|
|
282
|
-
<a href="https://github.com/gigaverse-app/pytest-quint-connect" title="Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug"><img alt="Quint contracts: 1 contract passed ·
|
|
282
|
+
<a href="https://github.com/gigaverse-app/pytest-quint-connect" title="Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug"><img alt="Quint contracts: 1 contract passed · 49 histories · 14/14 planted bugs caught" src="https://img.shields.io/badge/Quint_contracts-1_contract_passed_%C2%B7_49_histories_%C2%B7_14%2F14_planted_bugs_caught-brightgreen"></a>
|
|
283
283
|
|
|
284
284
|
| | Contract | Cases passed | [Planted bugs caught](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/why.md#proof-that-the-tests-can-fail "Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug") |
|
|
285
285
|
| :-: | :-- | --: | --: |
|
|
286
|
-
| ✅ | `selection` `test_selection_contract.py::test_selection`<br><sub>
|
|
286
|
+
| ✅ | `selection` `test_selection_contract.py::test_selection`<br><sub>49 histories (25 witnesses, 24 samples, seed 11) · every action covered · Quint 0.33.1</sub> | 64/64 | 14/14 |
|
|
287
287
|
|
|
288
288
|
**Bugs this contract found**
|
|
289
289
|
|
|
290
|
-
1. **A test that does `from pkg import *` was not selected when a submodule pkg lists in __all__ changed.** Fixed in #94 · found by the runtime check (pytest loaded the changed file, the planner skipped the test), after an adversarial review reported it; witness starImportFollowsAllTest · regression test `tests/testing/test_impact_graph.py::test_star_import_reaches_the_submodules_its_package_lists_in_all`. 1 of
|
|
291
|
-
1. **Editing a plugin loaded by `addopts = -p NAME` selected no test, though it is loaded into every session.** Fixed in #95 · found by the runtime check (pytest loaded the changed file, the planner skipped the test), after an adversarial review reported it; witness commandLinePluginSelectsEveryTestTest and random histories · regression test `tests/testing/test_impact_graph.py::test_command_line_plugin_in_addopts_reaches_every_test`.
|
|
292
|
-
1. **A bare `import helpers` resolved by pytest's default import mode was not followed, so editing tests/helpers.py selected nothing.** Fixed in #96 · found by the runtime check (pytest loaded the changed file, the planner skipped the test), after an adversarial review reported it; witness bareTestImportFollowsPrependModeTest · regression test `tests/testing/test_impact_graph.py::test_bare_import_resolves_from_the_importers_prepend_mode_base_directory`. 1 of
|
|
290
|
+
1. **A test that does `from pkg import *` was not selected when a submodule pkg lists in __all__ changed.** Fixed in #94 · found by the runtime check (pytest loaded the changed file, the planner skipped the test), after an adversarial review reported it; witness starImportFollowsAllTest · regression test `tests/testing/test_impact_graph.py::test_star_import_reaches_the_submodules_its_package_lists_in_all`. 1 of 49 histories diverge when [planted bug](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/why.md#proof-that-the-tests-can-fail "Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug") `star-exports-ignored` (Reintroduces the star-import bug: from pkg import * no longer reaches the submodules pkg lists in __all__) reintroduces it; first caught by `witness-starImportFollowsAllTest` at state 2, after `edit(file='alpha', target='')`.
|
|
291
|
+
1. **Editing a plugin loaded by `addopts = -p NAME` selected no test, though it is loaded into every session.** Fixed in #95 · found by the runtime check (pytest loaded the changed file, the planner skipped the test), after an adversarial review reported it; witness commandLinePluginSelectsEveryTestTest and random histories · regression test `tests/testing/test_impact_graph.py::test_command_line_plugin_in_addopts_reaches_every_test`. 2 of 49 histories diverge when [planted bug](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/why.md#proof-that-the-tests-can-fail "Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug") `addopts-plugins-ignored` (Reintroduces the plugin bug: a plugin named by -p in addopts is no longer a dependency of every test) reintroduces it; first caught by `witness-commandLinePluginSelectsEveryTestTest` at state 1, after `edit(file='plugin', target='')`.
|
|
292
|
+
1. **A bare `import helpers` resolved by pytest's default import mode was not followed, so editing tests/helpers.py selected nothing.** Fixed in #96 · found by the runtime check (pytest loaded the changed file, the planner skipped the test), after an adversarial review reported it; witness bareTestImportFollowsPrependModeTest · regression test `tests/testing/test_impact_graph.py::test_bare_import_resolves_from_the_importers_prepend_mode_base_directory`. 1 of 49 histories diverge when [planted bug](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/why.md#proof-that-the-tests-can-fail "Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug") `prepend-roots-ignored` (Reintroduces the import-mode bug: imports no longer resolve from the directories pytest prepends to sys.path) reintroduces it; first caught by `witness-bareTestImportFollowsPrependModeTest` at state 2, after `edit(file='helper', target='')`.
|
|
293
|
+
1. **A test that read a tracked file with open() and a literal path was not selected when that file changed.** Fixed in #106 · found by the runtime check (pytest opened the changed file, the planner skipped the test), after an adversarial review reported it; witness literalPathReadTest · regression test `tests/testing/test_impact_graph.py::test_reading_a_tracked_file_by_literal_path_selects_the_reader`. 1 of 49 histories diverge when [planted bug](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/why.md#proof-that-the-tests-can-fail "Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug") `literal-reads-ignored` (Reintroduces the literal-read bug: open('data/table.json') no longer makes the reader depend on that file) reintroduces it; first caught by `witness-literalPathReadTest` at state 2, after `edit(file='table', target='')`.
|
|
293
294
|
|
|
294
295
|
**[Planted bugs](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/why.md#proof-that-the-tests-can-fail "Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug") this contract catches**
|
|
295
296
|
|
|
@@ -369,5 +370,14 @@ records progress against the accepted backlog. [Setup and cleanup workflow](CONT
|
|
|
369
370
|
<!-- dry-lint-progress:start -->
|
|
370
371
|
<a href="https://github.com/gigaverse-app/dry-lint"><picture><source media="(prefers-color-scheme: dark)" srcset="https://dry-lint-counter.gigaverse-oss.workers.dev/v1/sign.svg?theme=dark&key=f8e41d50ca4018845c305f2f0dbd1cfd6d9b281e53b352490af53154a54e6265&name=gigaverse-app%2Fpytest-selective&cleaned=9&remaining=0&dismissed=0"><img alt="dry-lint in gigaverse-app/pytest-selective: new duplicates kept out of its pull requests, 9 old duplicates cleaned up, 0 remaining" src="https://dry-lint-counter.gigaverse-oss.workers.dev/v1/sign.svg?key=f8e41d50ca4018845c305f2f0dbd1cfd6d9b281e53b352490af53154a54e6265&name=gigaverse-app%2Fpytest-selective&cleaned=9&remaining=0&dismissed=0" width="100%"></picture></a>
|
|
371
372
|
|
|
373
|
+
**Reviewed duplication debt** · Counts from the committed baselines.
|
|
374
|
+
|
|
375
|
+
| Accepted pairs | Remaining | Retired | Dismissed |
|
|
376
|
+
| :-- | --: | --: | --: |
|
|
377
|
+
| Functions | 0 | 9 | 0 |
|
|
378
|
+
| Models | Not initialized | — | — |
|
|
379
|
+
|
|
372
380
|
[Accepted backlog](.dry-lint/baseline.json) · [Weekly audit](https://github.com/gigaverse-app/pytest-selective/actions/workflows/dry-lint-audit.yml)
|
|
381
|
+
|
|
382
|
+
Retired means a side was removed, or (functions only) the judge now finds the pair separate. It is not a count of refactors. Dismissals stay separate; an uninitialized model baseline is unknown.
|
|
373
383
|
<!-- dry-lint-progress:end -->
|
|
@@ -29,9 +29,9 @@ the affected test files, and explains the result. Use it directly as a pytest
|
|
|
29
29
|
plugin, or plan before collection and service startup with the CLI and CI actions.
|
|
30
30
|
|
|
31
31
|
Built from the selector used in Gigaverse's backend, AI, and shared Python
|
|
32
|
-
infrastructure.
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
infrastructure. Use the actions at the moving `@v1` tag, which follows the newest
|
|
33
|
+
release, and the package at `pytest-selective>=X.Y.Z,<2`. Where your tooling rejects
|
|
34
|
+
tags, pin both to one release instead: `@<sha> # vX.Y.Z` and `pytest-selective==X.Y.Z`.
|
|
35
35
|
[Provenance and extraction scope](https://github.com/gigaverse-app/pytest-selective/blob/main/PROVENANCE.md).
|
|
36
36
|
|
|
37
37
|
[Quick tutorial](https://github.com/gigaverse-app/pytest-selective/blob/main/docs/tutorial.md) ·
|
|
@@ -248,17 +248,18 @@ change that runs the contract.
|
|
|
248
248
|
<!-- pytest-quint-connect:contracts selection -->
|
|
249
249
|
<a href="https://github.com/gigaverse-app/pytest-quint-connect#impact"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pytest-quint-connect-counter.gigaverse-oss.workers.dev/v1/sign.svg?key=f8e41d50ca4018845c305f2f0dbd1cfd6d9b281e53b352490af53154a54e6265&name=gigaverse-app%2Fpytest-selective&theme=dark"><img alt="What pytest-quint-connect's contracts caught in gigaverse-app/pytest-selective's pull requests" src="https://pytest-quint-connect-counter.gigaverse-oss.workers.dev/v1/sign.svg?key=f8e41d50ca4018845c305f2f0dbd1cfd6d9b281e53b352490af53154a54e6265&name=gigaverse-app%2Fpytest-selective" width="100%"></picture></a>
|
|
250
250
|
|
|
251
|
-
<a href="https://github.com/gigaverse-app/pytest-quint-connect" title="Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug"><img alt="Quint contracts: 1 contract passed ·
|
|
251
|
+
<a href="https://github.com/gigaverse-app/pytest-quint-connect" title="Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug"><img alt="Quint contracts: 1 contract passed · 49 histories · 14/14 planted bugs caught" src="https://img.shields.io/badge/Quint_contracts-1_contract_passed_%C2%B7_49_histories_%C2%B7_14%2F14_planted_bugs_caught-brightgreen"></a>
|
|
252
252
|
|
|
253
253
|
| | Contract | Cases passed | [Planted bugs caught](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/why.md#proof-that-the-tests-can-fail "Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug") |
|
|
254
254
|
| :-: | :-- | --: | --: |
|
|
255
|
-
| ✅ | `selection` `test_selection_contract.py::test_selection`<br><sub>
|
|
255
|
+
| ✅ | `selection` `test_selection_contract.py::test_selection`<br><sub>49 histories (25 witnesses, 24 samples, seed 11) · every action covered · Quint 0.33.1</sub> | 64/64 | 14/14 |
|
|
256
256
|
|
|
257
257
|
**Bugs this contract found**
|
|
258
258
|
|
|
259
|
-
1. **A test that does `from pkg import *` was not selected when a submodule pkg lists in __all__ changed.** Fixed in #94 · found by the runtime check (pytest loaded the changed file, the planner skipped the test), after an adversarial review reported it; witness starImportFollowsAllTest · regression test `tests/testing/test_impact_graph.py::test_star_import_reaches_the_submodules_its_package_lists_in_all`. 1 of
|
|
260
|
-
1. **Editing a plugin loaded by `addopts = -p NAME` selected no test, though it is loaded into every session.** Fixed in #95 · found by the runtime check (pytest loaded the changed file, the planner skipped the test), after an adversarial review reported it; witness commandLinePluginSelectsEveryTestTest and random histories · regression test `tests/testing/test_impact_graph.py::test_command_line_plugin_in_addopts_reaches_every_test`.
|
|
261
|
-
1. **A bare `import helpers` resolved by pytest's default import mode was not followed, so editing tests/helpers.py selected nothing.** Fixed in #96 · found by the runtime check (pytest loaded the changed file, the planner skipped the test), after an adversarial review reported it; witness bareTestImportFollowsPrependModeTest · regression test `tests/testing/test_impact_graph.py::test_bare_import_resolves_from_the_importers_prepend_mode_base_directory`. 1 of
|
|
259
|
+
1. **A test that does `from pkg import *` was not selected when a submodule pkg lists in __all__ changed.** Fixed in #94 · found by the runtime check (pytest loaded the changed file, the planner skipped the test), after an adversarial review reported it; witness starImportFollowsAllTest · regression test `tests/testing/test_impact_graph.py::test_star_import_reaches_the_submodules_its_package_lists_in_all`. 1 of 49 histories diverge when [planted bug](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/why.md#proof-that-the-tests-can-fail "Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug") `star-exports-ignored` (Reintroduces the star-import bug: from pkg import * no longer reaches the submodules pkg lists in __all__) reintroduces it; first caught by `witness-starImportFollowsAllTest` at state 2, after `edit(file='alpha', target='')`.
|
|
260
|
+
1. **Editing a plugin loaded by `addopts = -p NAME` selected no test, though it is loaded into every session.** Fixed in #95 · found by the runtime check (pytest loaded the changed file, the planner skipped the test), after an adversarial review reported it; witness commandLinePluginSelectsEveryTestTest and random histories · regression test `tests/testing/test_impact_graph.py::test_command_line_plugin_in_addopts_reaches_every_test`. 2 of 49 histories diverge when [planted bug](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/why.md#proof-that-the-tests-can-fail "Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug") `addopts-plugins-ignored` (Reintroduces the plugin bug: a plugin named by -p in addopts is no longer a dependency of every test) reintroduces it; first caught by `witness-commandLinePluginSelectsEveryTestTest` at state 1, after `edit(file='plugin', target='')`.
|
|
261
|
+
1. **A bare `import helpers` resolved by pytest's default import mode was not followed, so editing tests/helpers.py selected nothing.** Fixed in #96 · found by the runtime check (pytest loaded the changed file, the planner skipped the test), after an adversarial review reported it; witness bareTestImportFollowsPrependModeTest · regression test `tests/testing/test_impact_graph.py::test_bare_import_resolves_from_the_importers_prepend_mode_base_directory`. 1 of 49 histories diverge when [planted bug](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/why.md#proof-that-the-tests-can-fail "Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug") `prepend-roots-ignored` (Reintroduces the import-mode bug: imports no longer resolve from the directories pytest prepends to sys.path) reintroduces it; first caught by `witness-bareTestImportFollowsPrependModeTest` at state 2, after `edit(file='helper', target='')`.
|
|
262
|
+
1. **A test that read a tracked file with open() and a literal path was not selected when that file changed.** Fixed in #106 · found by the runtime check (pytest opened the changed file, the planner skipped the test), after an adversarial review reported it; witness literalPathReadTest · regression test `tests/testing/test_impact_graph.py::test_reading_a_tracked_file_by_literal_path_selects_the_reader`. 1 of 49 histories diverge when [planted bug](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/why.md#proof-that-the-tests-can-fail "Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug") `literal-reads-ignored` (Reintroduces the literal-read bug: open('data/table.json') no longer makes the reader depend on that file) reintroduces it; first caught by `witness-literalPathReadTest` at state 2, after `edit(file='table', target='')`.
|
|
262
263
|
|
|
263
264
|
**[Planted bugs](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/why.md#proof-that-the-tests-can-fail "Mutants: deliberately broken copies of the code. Each must make some history diverge, proving the contract would notice that kind of bug") this contract catches**
|
|
264
265
|
|
|
@@ -338,5 +339,14 @@ records progress against the accepted backlog. [Setup and cleanup workflow](CONT
|
|
|
338
339
|
<!-- dry-lint-progress:start -->
|
|
339
340
|
<a href="https://github.com/gigaverse-app/dry-lint"><picture><source media="(prefers-color-scheme: dark)" srcset="https://dry-lint-counter.gigaverse-oss.workers.dev/v1/sign.svg?theme=dark&key=f8e41d50ca4018845c305f2f0dbd1cfd6d9b281e53b352490af53154a54e6265&name=gigaverse-app%2Fpytest-selective&cleaned=9&remaining=0&dismissed=0"><img alt="dry-lint in gigaverse-app/pytest-selective: new duplicates kept out of its pull requests, 9 old duplicates cleaned up, 0 remaining" src="https://dry-lint-counter.gigaverse-oss.workers.dev/v1/sign.svg?key=f8e41d50ca4018845c305f2f0dbd1cfd6d9b281e53b352490af53154a54e6265&name=gigaverse-app%2Fpytest-selective&cleaned=9&remaining=0&dismissed=0" width="100%"></picture></a>
|
|
340
341
|
|
|
342
|
+
**Reviewed duplication debt** · Counts from the committed baselines.
|
|
343
|
+
|
|
344
|
+
| Accepted pairs | Remaining | Retired | Dismissed |
|
|
345
|
+
| :-- | --: | --: | --: |
|
|
346
|
+
| Functions | 0 | 9 | 0 |
|
|
347
|
+
| Models | Not initialized | — | — |
|
|
348
|
+
|
|
341
349
|
[Accepted backlog](.dry-lint/baseline.json) · [Weekly audit](https://github.com/gigaverse-app/pytest-selective/actions/workflows/dry-lint-audit.yml)
|
|
350
|
+
|
|
351
|
+
Retired means a side was removed, or (functions only) the judge now finds the pair separate. It is not a count of refactors. Dismissals stay separate; an uninitialized model baseline is unknown.
|
|
342
352
|
<!-- dry-lint-progress:end -->
|
|
@@ -12,8 +12,10 @@ inputs:
|
|
|
12
12
|
default: auto
|
|
13
13
|
artifact-name:
|
|
14
14
|
description: >
|
|
15
|
-
Upload the lane plan under this name, where
|
|
16
|
-
the sticky's lane section.
|
|
15
|
+
Upload the lane plan (test-lanes.json) as an artifact under this name, where
|
|
16
|
+
CI-esta's report renders it as the sticky's lane section. Give each planner job
|
|
17
|
+
in one workflow run its own name. Empty skips the upload; planning and the
|
|
18
|
+
outputs are unchanged.
|
|
17
19
|
default: test-lane-selection
|
|
18
20
|
outputs:
|
|
19
21
|
lanes:
|
|
@@ -193,6 +193,9 @@ MUTANTS: dict[str, Callable[[], list[tuple[str, object]]]] = {
|
|
|
193
193
|
],
|
|
194
194
|
"addopts-plugins-ignored": lambda: [("pytest_selective.impact_graph.command_line_plugins", lambda policies: set())],
|
|
195
195
|
"prepend-roots-ignored": lambda: [("pytest_selective.impact_graph.prepend_roots", lambda python_files, config: [])],
|
|
196
|
+
"literal-reads-ignored": lambda: [
|
|
197
|
+
("pytest_selective.impact_graph.literal_read_targets", lambda reader, literal, files: set())
|
|
198
|
+
],
|
|
196
199
|
}
|
|
197
200
|
|
|
198
201
|
|
|
@@ -210,6 +213,7 @@ class RepositoryDriver(SelectionDriver):
|
|
|
210
213
|
self.imports: dict[str, set[str]] = {}
|
|
211
214
|
self.declared: dict[str, set[str]] = {}
|
|
212
215
|
self.broken: set[str] = set()
|
|
216
|
+
self.reads: set[str] = set()
|
|
213
217
|
self.versions = dict.fromkeys(PATHS, 0)
|
|
214
218
|
self.requirements = 0
|
|
215
219
|
self.config = CONFIG
|
|
@@ -231,6 +235,8 @@ class RepositoryDriver(SelectionDriver):
|
|
|
231
235
|
lines += [f"# gv:test-dependency: {PATHS[target]}" for target in sorted(self.declared.get(name, ()))]
|
|
232
236
|
lines += [IMPORT_LINES[target] for target in sorted(self.imports.get(name, ()))]
|
|
233
237
|
lines.append(f"VERSION = {version}")
|
|
238
|
+
if name in self.reads:
|
|
239
|
+
lines.append(f'TABLE = open("{PATHS["table"]}", encoding="utf-8").read()')
|
|
234
240
|
if name in self.broken:
|
|
235
241
|
lines.append("def broken(:")
|
|
236
242
|
write(self.root, PATHS[name], "\n".join(lines) + "\n")
|
|
@@ -334,6 +340,7 @@ class RepositoryDriver(SelectionDriver):
|
|
|
334
340
|
self.imports.pop(file, None)
|
|
335
341
|
self.declared.pop(file, None)
|
|
336
342
|
self.broken.discard(file)
|
|
343
|
+
self.reads.discard(file)
|
|
337
344
|
(self.root / PATHS[file]).unlink()
|
|
338
345
|
if file in CONTENT_SIDECARS:
|
|
339
346
|
(self.root / CONTENT_SIDECARS[file][0]).unlink()
|
|
@@ -360,6 +367,10 @@ class RepositoryDriver(SelectionDriver):
|
|
|
360
367
|
self.broken.discard(file)
|
|
361
368
|
self._commit(file)
|
|
362
369
|
|
|
370
|
+
def read_table(self, file: str) -> None:
|
|
371
|
+
self.reads.add(file)
|
|
372
|
+
self._commit(file)
|
|
373
|
+
|
|
363
374
|
def touch_global(self) -> None:
|
|
364
375
|
self.requirements += 1
|
|
365
376
|
self._write_requirements()
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"toggleConfig": "flip test_roots between tests and tests/sub, or add/remove data/table.json as a global input, then plan the last commit again",
|
|
21
21
|
"restart": "close the graph cache (it prunes on close), reopen it, then plan the last commit again",
|
|
22
22
|
"corrupt": "empty every stored graph's edges without updating its checksum, then plan the last commit again",
|
|
23
|
+
"readTable": "make a test read data/table.json with open() and a literal path at import time, commit, then select",
|
|
23
24
|
"inspect": "read the last selections without committing"
|
|
24
25
|
},
|
|
25
26
|
"witnesses": [
|
|
@@ -46,6 +47,7 @@
|
|
|
46
47
|
"commandLinePluginSelectsEveryTestTest",
|
|
47
48
|
"bareTestImportFollowsPrependModeTest",
|
|
48
49
|
"packageInitEditSelectsItsImportersTest",
|
|
50
|
+
"literalPathReadTest",
|
|
49
51
|
"sinceStartSpansCommitsTest"
|
|
50
52
|
],
|
|
51
53
|
"mutants": [
|
|
@@ -100,6 +102,10 @@
|
|
|
100
102
|
{
|
|
101
103
|
"name": "prepend-roots-ignored",
|
|
102
104
|
"description": "Reintroduces the import-mode bug: imports no longer resolve from the directories pytest prepends to sys.path."
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"name": "literal-reads-ignored",
|
|
108
|
+
"description": "Reintroduces the literal-read bug: open('data/table.json') no longer makes the reader depend on that file."
|
|
103
109
|
}
|
|
104
110
|
],
|
|
105
111
|
"bounds": {"seed": 11, "max_samples": 800, "max_steps": 8, "n_traces": 24},
|
|
@@ -116,7 +122,7 @@
|
|
|
116
122
|
},
|
|
117
123
|
"limits": [
|
|
118
124
|
"One repository: three modules and their package initializer (with __all__), a test-root helper, a command-line plugin loaded by addopts -p, a root and a nested conftest, three tests, one data file and one content-kind sidecar; at most 8 operations per sampled history.",
|
|
119
|
-
"Import spellings are import pkg.x, import tests.helpers, from pkg import * and a prepend-mode bare import helpers. Other forms (relative and dynamic imports, module strings,
|
|
125
|
+
"Import spellings are import pkg.x, import tests.helpers, from pkg import * and a prepend-mode bare import helpers. Tests may also read data/table.json with open() and a literal repository path. Other forms (relative and dynamic imports, module strings, computed paths, symlinks) are not generated.",
|
|
120
126
|
"The runtime check records imports and opened files during pytest's collection of each test file, with plugin autoloading off and the default prepend import mode. It is a lower bound: a test that loads a changed file must be selected, but selecting more is not a failure. It is skipped for mutant cases, which the model comparison judges.",
|
|
121
127
|
"First-party pins, name-scoped modules, deferred strings and Django URLconf policy are outside the model. The graph cache is exercised through configuration edits, restarts and damaged entries; only answers are compared.",
|
|
122
128
|
"Each step is a real Git commit or a re-plan of the last one, selected by affected() through one GraphCache per history; the driver's mapping of paths to file names is trusted."
|
|
@@ -142,6 +148,13 @@
|
|
|
142
148
|
"found_by": "the runtime check (pytest loaded the changed file, the planner skipped the test), after an adversarial review reported it; witness bareTestImportFollowsPrependModeTest",
|
|
143
149
|
"regression_test": "tests/testing/test_impact_graph.py::test_bare_import_resolves_from_the_importers_prepend_mode_base_directory",
|
|
144
150
|
"mutant": "prepend-roots-ignored"
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"summary": "A test that read a tracked file with open() and a literal path was not selected when that file changed.",
|
|
154
|
+
"fixed_in": "#106",
|
|
155
|
+
"found_by": "the runtime check (pytest opened the changed file, the planner skipped the test), after an adversarial review reported it; witness literalPathReadTest",
|
|
156
|
+
"regression_test": "tests/testing/test_impact_graph.py::test_reading_a_tracked_file_by_literal_path_selects_the_reader",
|
|
157
|
+
"mutant": "literal-reads-ignored"
|
|
145
158
|
}
|
|
146
159
|
]
|
|
147
160
|
}
|
|
@@ -25,8 +25,17 @@ module selection {
|
|
|
25
25
|
versions: str -> int,
|
|
26
26
|
broken: Set[str],
|
|
27
27
|
requirements: int,
|
|
28
|
+
// Tests that read data/table.json with open() and a literal path, at import time.
|
|
29
|
+
reads: Set[str],
|
|
30
|
+
}
|
|
31
|
+
type Content = {
|
|
32
|
+
present: bool,
|
|
33
|
+
imports: Set[(str, str)],
|
|
34
|
+
declared: Set[(str, str)],
|
|
35
|
+
version: int,
|
|
36
|
+
broken: bool,
|
|
37
|
+
reads: bool,
|
|
28
38
|
}
|
|
29
|
-
type Content = { present: bool, imports: Set[(str, str)], declared: Set[(str, str)], version: int, broken: bool }
|
|
30
39
|
// narrow: test_roots is tests/sub instead of tests. tableGlobal: data/table.json is a global input.
|
|
31
40
|
type Config = { narrow: bool, tableGlobal: bool }
|
|
32
41
|
|
|
@@ -83,8 +92,9 @@ module selection {
|
|
|
83
92
|
declared: t.declared.filter(d => d._1 == f),
|
|
84
93
|
version: t.versions.get(f),
|
|
85
94
|
broken: t.broken.contains(f),
|
|
95
|
+
reads: t.reads.contains(f),
|
|
86
96
|
}
|
|
87
|
-
else { present: false, imports: Set(), declared: Set(), version: 0, broken: false }
|
|
97
|
+
else { present: false, imports: Set(), declared: Set(), version: 0, broken: false, reads: false }
|
|
88
98
|
|
|
89
99
|
// The files collected as tests under a configuration.
|
|
90
100
|
pure def testsOf(c: Config): Set[str] = if (c.narrow) Set("test_sub") else TESTS
|
|
@@ -113,7 +123,9 @@ module selection {
|
|
|
113
123
|
val changed = FILES.filter(f => content(before, f) != content(after, f))
|
|
114
124
|
val global = before.requirements != after.requirements or (c.tableGlobal and changed.contains("table"))
|
|
115
125
|
val tests = testsOf(c).intersect(after.present)
|
|
116
|
-
|
|
126
|
+
// A literal path read is a content read, like a kind: content sidecar.
|
|
127
|
+
val literal = before.reads.union(after.reads).map(f => (f, "table"))
|
|
128
|
+
val readers = CONTENT_READS.union(literal)
|
|
117
129
|
.filter(r => changed.contains(r._2) and (before.present.contains(r._1) or after.present.contains(r._1)))
|
|
118
130
|
.map(r => r._1)
|
|
119
131
|
val chosen = if (global) tests
|
|
@@ -167,6 +179,7 @@ module selection {
|
|
|
167
179
|
versions: FILES.mapBy(_ => 0),
|
|
168
180
|
broken: Set(),
|
|
169
181
|
requirements: 0,
|
|
182
|
+
reads: Set(),
|
|
170
183
|
}
|
|
171
184
|
|
|
172
185
|
action init = all {
|
|
@@ -212,6 +225,7 @@ module selection {
|
|
|
212
225
|
present: tree.present.exclude(Set(file)),
|
|
213
226
|
declared: tree.declared.filter(d => d._1 != file),
|
|
214
227
|
broken: tree.broken.exclude(Set(file)),
|
|
228
|
+
reads: tree.reads.exclude(Set(file)),
|
|
215
229
|
}),
|
|
216
230
|
}
|
|
217
231
|
|
|
@@ -248,6 +262,14 @@ module selection {
|
|
|
248
262
|
commit("repair", file, "", { ...tree, broken: tree.broken.exclude(Set(file)) }),
|
|
249
263
|
}
|
|
250
264
|
|
|
265
|
+
// Make a test read data/table.json with open() and a literal path, without declaring it.
|
|
266
|
+
action readTable(file: str): bool = all {
|
|
267
|
+
TESTS.contains(file),
|
|
268
|
+
tree.present.contains(file),
|
|
269
|
+
not(tree.reads.contains(file)),
|
|
270
|
+
commit("readTable", file, "", { ...tree, reads: tree.reads.union(Set(file)) }),
|
|
271
|
+
}
|
|
272
|
+
|
|
251
273
|
action touchGlobal = commit("touchGlobal", "", "", { ...tree, requirements: tree.requirements + 1 })
|
|
252
274
|
|
|
253
275
|
// Flip one configuration option, then plan the last commit again.
|
|
@@ -288,6 +310,7 @@ module selection {
|
|
|
288
310
|
undeclare(file, target),
|
|
289
311
|
breakSyntax(file),
|
|
290
312
|
repair(file),
|
|
313
|
+
readTable(file),
|
|
291
314
|
touchGlobal,
|
|
292
315
|
toggleConfig(option),
|
|
293
316
|
restart,
|
|
@@ -380,6 +403,10 @@ module selection {
|
|
|
380
403
|
run packageInitEditSelectsItsImportersTest = init.then(edit(INIT))
|
|
381
404
|
.then(all { assert(observed == view("scoped", Set("test_one", "test_two"))), inspect })
|
|
382
405
|
|
|
406
|
+
// test_one now reads the table by literal path; the other two already depended on it.
|
|
407
|
+
run literalPathReadTest = init.then(readTable("test_one")).then(edit("table"))
|
|
408
|
+
.then(all { assert(observed == view("full", TESTS)), inspect })
|
|
409
|
+
|
|
383
410
|
run sinceStartSpansCommitsTest = init.then(edit("beta")).then(edit("table"))
|
|
384
411
|
.then(all {
|
|
385
412
|
assert(observed == { mode: "scoped", selected: Set("test_two", "test_sub"), sinceStart: { mode: "full", selected: TESTS }, missed: Set() }),
|
|
@@ -28,7 +28,7 @@ class SelectionDriver(ActionDriver):
|
|
|
28
28
|
"""Subclass this, implement each action against the real code, and read state back in observe()."""
|
|
29
29
|
|
|
30
30
|
__quint_contract__ = "selection"
|
|
31
|
-
__quint_fingerprint__ = "
|
|
31
|
+
__quint_fingerprint__ = "40f9e4a52a1f7fce52f35f086d3825648232670ff6aca12e7d8001fa80cb91a2"
|
|
32
32
|
__quint_codegen_command__ = "pytest-quint-connect codegen quint-connect.json -o selection_driver.py"
|
|
33
33
|
__quint_actions__ = {
|
|
34
34
|
"init": ("init", ()),
|
|
@@ -45,6 +45,7 @@ class SelectionDriver(ActionDriver):
|
|
|
45
45
|
"toggleConfig": ("toggle_config", (("option", "file"),)),
|
|
46
46
|
"restart": ("restart", ()),
|
|
47
47
|
"corrupt": ("corrupt", ()),
|
|
48
|
+
"readTable": ("read_table", (("file", "file"),)),
|
|
48
49
|
"inspect": ("inspect", ()),
|
|
49
50
|
}
|
|
50
51
|
|
|
@@ -104,6 +105,10 @@ class SelectionDriver(ActionDriver):
|
|
|
104
105
|
def corrupt(self) -> None:
|
|
105
106
|
"""empty every stored graph's edges without updating its checksum, then plan the last commit again"""
|
|
106
107
|
|
|
108
|
+
@abstractmethod
|
|
109
|
+
def read_table(self, file: str) -> None:
|
|
110
|
+
"""make a test read data/table.json with open() and a literal path at import time, commit, then select"""
|
|
111
|
+
|
|
107
112
|
@abstractmethod
|
|
108
113
|
def inspect(self) -> None:
|
|
109
114
|
"""read the last selections without committing"""
|
|
@@ -28,6 +28,10 @@ supported on GitHub.com with runner 2.336.0 or newer. Individual `actions/NAME`
|
|
|
28
28
|
entry points call the implementations directly and do not require that syntax.
|
|
29
29
|
[GitHub reference documentation](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax).
|
|
30
30
|
|
|
31
|
+
`plan-lanes` uploads its plan as the `test-lane-selection` artifact by default. Set
|
|
32
|
+
`artifact-name` to rename it, which a second planner job in the same workflow run needs, or to
|
|
33
|
+
`''` to skip the upload.
|
|
34
|
+
|
|
31
35
|
## Action reference
|
|
32
36
|
|
|
33
37
|
Each link below is the public action's complete input/output contract.
|
|
@@ -253,11 +253,10 @@ job per workflow the required check and run
|
|
|
253
253
|
gated job to its lanes. It fails unless each prerequisite succeeded or was skipped while
|
|
254
254
|
the planner succeeded and every mapped lane is `false`; a failed planner fails it.
|
|
255
255
|
|
|
256
|
-
|
|
257
|
-
skipping it skips publishing.
|
|
258
|
-
(`github.event_name != 'pull_request' || ...`), or plan inside the job with
|
|
256
|
+
Never gate a job that a publish or deploy job `needs:`, directly or transitively:
|
|
257
|
+
skipping it skips publishing. Leave it ungated, or plan inside the job with
|
|
259
258
|
`continue-on-error: true` and admit only its expensive steps, as the `distributions`
|
|
260
|
-
job in this repository's CI does. A branch's first push has no previous commit, so
|
|
259
|
+
job in this repository's CI does. Gate every other job that installs, builds or tests. A branch's first push has no previous commit, so
|
|
261
260
|
`on: push` for every branch runs every lane once per new branch; prefer pull-request
|
|
262
261
|
triggers plus pushes to the default branch.
|
|
263
262
|
The default event policy handles manual/release checkpoints, schedules and
|
|
@@ -24,9 +24,10 @@ select. The default filenames are `test_*.py`, `*_test.py`, `*_tests.py`, and
|
|
|
24
24
|
|
|
25
25
|
## Review and record collection policy
|
|
26
26
|
|
|
27
|
-
If your repository has `pyproject.toml`, `pytest.ini`, `tox.ini
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
If your repository has `pyproject.toml`, `pytest.ini`, a `tox.ini` with a `[pytest]`
|
|
28
|
+
section, or a `setup.cfg` with a `[tool:pytest]` section, record its committed
|
|
29
|
+
pytest-policy fingerprints. A `pyproject.toml` or `pytest.ini` with no pytest settings
|
|
30
|
+
still has a fingerprint, because pytest can still choose it. This review prevents a new
|
|
30
31
|
collection rule from silently invalidating the inventory.
|
|
31
32
|
|
|
32
33
|
```bash
|
|
@@ -58,7 +59,9 @@ runs the complete configured roots.
|
|
|
58
59
|
Start with the [sidecar tutorial](tutorials/sidecar-dependencies.md) for a runnable
|
|
59
60
|
fixture example, directory manifests, dependency comments, and dynamic Python inputs.
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
A read that names a tracked file by literal path, such as `open("data/prices.json")` or
|
|
63
|
+
`(Path(__file__).parent / "prices.json").read_text()`, is followed automatically as a content read.
|
|
64
|
+
For a path that is computed, or built from several parts, add a reader-owned comment:
|
|
62
65
|
|
|
63
66
|
```python
|
|
64
67
|
# gv:test-dependency: tests/data/prices.json
|
|
@@ -133,7 +136,11 @@ Unsupported checker patterns retain the full-check fallback.
|
|
|
133
136
|
|
|
134
137
|
The policy fingerprints cover conventional pytest configuration names, including
|
|
135
138
|
`pytest.toml`, `.pytest.toml`, `.pytest.ini` and nested candidates. Empty candidates
|
|
136
|
-
are included because their presence can change pytest's root and precedence.
|
|
139
|
+
are included because their presence can change pytest's root and precedence. A
|
|
140
|
+
`tox.ini` without `[pytest]` and a `setup.cfg` without `[tool:pytest]` are not: pytest
|
|
141
|
+
skips them. Adding that section later changes the fingerprints and needs a review.
|
|
142
|
+
Releases up to 1.1.1 recorded such a file as the hash of an empty policy; that entry
|
|
143
|
+
is still accepted and needs no refresh.
|
|
137
144
|
Changes require reviewing and refreshing `pytest_policy`; until then planning
|
|
138
145
|
fails safely and the CI action runs the complete configured roots.
|
|
139
146
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pytest-selective"
|
|
7
|
-
version = "1.1.
|
|
7
|
+
version = "1.1.2"
|
|
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"
|