pytest-selective 0.4.2__tar.gz → 1.0.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-0.4.2 → pytest_selective-1.0.0}/.github/test-lanes.json +28 -2
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/ci.yml +45 -8
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/public-actions.yml +5 -1
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/release.yml +4 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.gitignore +3 -0
- pytest_selective-1.0.0/.release-please-manifest.json +3 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/AGENTS.md +5 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/CHANGELOG.md +15 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/CONTRIBUTING.md +34 -2
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/PKG-INFO +37 -2
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/README.md +36 -1
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/RELEASING.md +23 -1
- pytest_selective-1.0.0/contracts/selection/README.md +33 -0
- pytest_selective-1.0.0/contracts/selection/conftest.py +297 -0
- pytest_selective-1.0.0/contracts/selection/quint-connect.json +107 -0
- pytest_selective-1.0.0/contracts/selection/quint.ini +8 -0
- pytest_selective-1.0.0/contracts/selection/selection.qnt +336 -0
- pytest_selective-1.0.0/contracts/selection/selection_driver.py +112 -0
- pytest_selective-1.0.0/contracts/selection/test_selection_contract.py +4 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/ci-toolkit.md +5 -1
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/ci.md +12 -1
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/configuration.md +1 -1
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/tutorials/adoption-checklist.md +5 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/tutorials/skip-unrelated-ci.md +33 -0
- pytest_selective-1.0.0/docs/work-in-progress/tickets/graph-based-inputs-plan.md +58 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/pyproject.toml +3 -1
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/skills/pytest-selective/SKILL.md +8 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/skills/pytest-selective/references/adoption.md +3 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/ci.py +9 -4
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_graph.py +116 -3
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_models.py +3 -0
- pytest_selective-1.0.0/tests/test_release_workflow.py +36 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_repository_ci.py +90 -29
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_ci.py +53 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_impact_graph.py +69 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/uv.lock +18 -2
- pytest_selective-0.4.2/.release-please-manifest.json +0 -3
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.dry-lint/baseline.json +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.dry-lint/progress.json +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.dry-lint.toml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.gitattributes +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/dependabot.yml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/docs.yml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/dry-lint-audit.yml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/dry-lint-judge.yml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/dry-lint.yml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/observe-ci.yml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/pr-title.yml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/release-please.yml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.github/workflows/windows-performance.yml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.pre-commit-config.yaml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/.readthedocs.yaml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/CLAUDE.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/LICENSE +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/PROVENANCE.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/action.yml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/actions/README.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/actions/plan-lanes/action.yml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/actions/pytest-expansion/action.yml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/actions/test-impact/action.yml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/agentsync.toml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/README.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/actions.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/agents.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/assets/banner.png +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/assets/banner.svg +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/assets/brand.json +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/assets/icon.png +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/assets/logo.svg +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/assets/raster-sources.json +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/assets/social-preview.png +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/assets/social-preview.svg +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22-due-ci.json +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22-due-docs-ci.json +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22-due-docs-widget.json +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22-due-widget.json +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22-input-replays.json +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22-obligation-docs.json +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22-obligation.json +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22-routing.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22-selective-docs.json +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-22.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-23-windows-saleor/obligation-baseline.json +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after-3.11.xml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after-3.14.xml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after.json +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control-3.11.xml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control-3.14.xml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control.json +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-earlier.json +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/2026-10-23-windows-saleor.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/benchmarks/windows-runtime.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/comparison-pytest-impacted.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/evidence.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/local-development.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/pytest-hygiene.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/selection-health.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/tutorial.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/tutorials/sidecar-dependencies.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/work-in-progress/tickets/actions-directory-test-plan.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/work-in-progress/tickets/codex-selector-correctness-performance-test-plan.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/work-in-progress/tickets/codex-windows-git-performance-test-plan.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/work-in-progress/tickets/impacted-improvements-test-plan.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/work-in-progress/tickets/move-action-implementations-test-plan.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/work-in-progress/tickets/public-action-onboarding-test-plan.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/work-in-progress/tickets/ratchet-duplicates-zero-test-plan.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/docs/work-in-progress/tickets/rerun-selection-test-plan.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/documentation/assets/routing-comparison.svg +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/documentation/check_site.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/documentation/ecosystem.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/documentation/hooks.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/documentation/index.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/documentation/overrides/main.html +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/documentation/publishing.md +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/documentation/requirements.in +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/documentation/requirements.txt +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/mkdocs.yml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/release-please-config.json +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/benchmark_graph.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/benchmark_parsing.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/benchmark_rust.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/check_docs.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/check_metadata.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/ci_file_config.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/compare_impacted.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/measure_ci_runs.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/public_actions.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/render_brand.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/run_ci_tests.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/smoke_distribution.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/scripts/tutorial.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/skills/pytest-selective/agents/openai.yaml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/__init__.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/__main__.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/ast_imports.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/checkpoint.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/git_operations.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/hygiene.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_audit.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_audit_plugin.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_cli.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_declarations.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_explanation.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_first_party.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_name_scope.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/impact_selection.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/input_policy.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/local.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/parse_batch.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/plugin.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/py.typed +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/pytest_expansion.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/pytest_plugins.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/pytest_sharding.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/test_inventory.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/typecheck_scope.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/src/pytest_selective/working_tree.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/__init__.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_checkpoint_policy.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_ci_admission.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_ci_measurements.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_ci_test_execution.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_distribution_metadata.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_hygiene.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_public_actions.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_pytest_expansion.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_repository_hygiene.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_repository_hygiene.py.gv-dependency.yaml +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/test_tutorial.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/__init__.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/git_repo.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_ast_imports.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_git_repo.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_global_inputs.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_impact_audit.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_impact_explanation.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_impact_first_party.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_impact_name_scope.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_parse_batch.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_portability.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_pytest_plugin.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_pytest_plugins.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_pytest_sharding.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_typecheck_scope.py +0 -0
- {pytest_selective-0.4.2 → pytest_selective-1.0.0}/tests/testing/test_working_tree.py +0 -0
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
"test_roots": [
|
|
8
8
|
"tests"
|
|
9
9
|
],
|
|
10
|
+
"global_inputs": [
|
|
11
|
+
"pyproject.toml",
|
|
12
|
+
"uv.lock",
|
|
13
|
+
".python-version"
|
|
14
|
+
],
|
|
10
15
|
"pytest_policy": {
|
|
11
16
|
"pyproject.toml": "7a78d68735c0e3b091962244620d3a17557951123c645970638f15d3a737d987"
|
|
12
17
|
}
|
|
@@ -20,6 +25,27 @@
|
|
|
20
25
|
"src/**",
|
|
21
26
|
"tests/**"
|
|
22
27
|
]
|
|
28
|
+
},
|
|
29
|
+
"contracts": {
|
|
30
|
+
"roots": [
|
|
31
|
+
"contracts"
|
|
32
|
+
],
|
|
33
|
+
"inputs": [
|
|
34
|
+
"src/**",
|
|
35
|
+
"contracts/**",
|
|
36
|
+
"tests/testing/git_repo.py"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"package": {
|
|
40
|
+
"inputs": [
|
|
41
|
+
"src/**",
|
|
42
|
+
"pyproject.toml",
|
|
43
|
+
"uv.lock",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"scripts/check_metadata.py",
|
|
47
|
+
"scripts/smoke_distribution.py"
|
|
48
|
+
]
|
|
23
49
|
}
|
|
24
50
|
},
|
|
25
51
|
"global_inputs": [
|
|
@@ -33,9 +59,9 @@
|
|
|
33
59
|
".github/test-lanes.json",
|
|
34
60
|
".github/workflows/ci.yml",
|
|
35
61
|
".github/workflows/release.yml",
|
|
36
|
-
"actions
|
|
62
|
+
"actions/*/*",
|
|
63
|
+
"actions/*.py",
|
|
37
64
|
"action.yml",
|
|
38
|
-
"examples/**",
|
|
39
65
|
".github/workflows/public-actions.yml"
|
|
40
66
|
]
|
|
41
67
|
}
|
|
@@ -6,6 +6,8 @@ on:
|
|
|
6
6
|
types: [opened, synchronize, reopened]
|
|
7
7
|
workflow_dispatch:
|
|
8
8
|
workflow_call:
|
|
9
|
+
schedule:
|
|
10
|
+
- cron: '13 3 * * 1'
|
|
9
11
|
permissions:
|
|
10
12
|
contents: read
|
|
11
13
|
concurrency:
|
|
@@ -92,13 +94,12 @@ jobs:
|
|
|
92
94
|
with:
|
|
93
95
|
junit-pattern: test-timings.xml
|
|
94
96
|
artifact-name: test-timings-${{ matrix.python }}-${{ matrix.os }}
|
|
95
|
-
|
|
97
|
+
# tests/test_tutorial.py owns the end-to-end tutorial and follows the file plan.
|
|
96
98
|
- uses: actions/upload-artifact@v7
|
|
99
|
+
if: ${{ hashFiles('coverage.xml') != '' }}
|
|
97
100
|
with:
|
|
98
101
|
name: evidence-${{ matrix.os }}-${{ matrix.python }}
|
|
99
|
-
path:
|
|
100
|
-
coverage.xml
|
|
101
|
-
tutorial-report.json
|
|
102
|
+
path: coverage.xml
|
|
102
103
|
pytest-minimum:
|
|
103
104
|
needs: select-tests
|
|
104
105
|
if: >-
|
|
@@ -119,8 +120,10 @@ jobs:
|
|
|
119
120
|
CI_TEST_FILES: ${{ needs.select-tests.outputs.files }}
|
|
120
121
|
distributions:
|
|
121
122
|
needs: select-tests
|
|
122
|
-
#
|
|
123
|
-
if:
|
|
123
|
+
# The package lane includes the embedded README and LICENSE; planner failure must not skip it.
|
|
124
|
+
if: >-
|
|
125
|
+
${{ always() && !cancelled() && (needs.select-tests.result != 'success' ||
|
|
126
|
+
fromJSON(needs.select-tests.outputs.lanes).package) }}
|
|
124
127
|
runs-on: ubuntu-latest
|
|
125
128
|
steps:
|
|
126
129
|
- uses: actions/checkout@v7
|
|
@@ -141,9 +144,43 @@ jobs:
|
|
|
141
144
|
path: dist/*
|
|
142
145
|
if-no-files-found: error
|
|
143
146
|
|
|
147
|
+
selection-contract:
|
|
148
|
+
name: Selection contract (Quint)
|
|
149
|
+
needs: select-tests
|
|
150
|
+
permissions:
|
|
151
|
+
contents: read
|
|
152
|
+
# The contract report's pull-request comment.
|
|
153
|
+
pull-requests: write
|
|
154
|
+
if: >-
|
|
155
|
+
${{ always() && !cancelled() && (needs.select-tests.result != 'success' ||
|
|
156
|
+
fromJSON(needs.select-tests.outputs.lanes).contracts) }}
|
|
157
|
+
runs-on: ubuntu-latest
|
|
158
|
+
steps:
|
|
159
|
+
- uses: actions/checkout@v7
|
|
160
|
+
- uses: gigaverse-app/ci-esta/actions/setup-cached-uv@v1
|
|
161
|
+
with:
|
|
162
|
+
python-version: '3.14'
|
|
163
|
+
# pytest-quint-connect installs its locked Quint with npm on first use.
|
|
164
|
+
- uses: actions/setup-node@v7
|
|
165
|
+
with:
|
|
166
|
+
node-version: '24'
|
|
167
|
+
- name: Check the generated driver against the model
|
|
168
|
+
run: uv run --locked --group quint pytest-quint-connect codegen --check contracts/selection/quint-connect.json
|
|
169
|
+
- name: Generate histories from the model and replay them against the planner
|
|
170
|
+
run: uv run --locked --group quint pytest -c contracts/selection/quint.ini contracts/selection --quint-require-complete -q
|
|
171
|
+
# Match the README with the same locked renderer that produced these receipts.
|
|
172
|
+
# A presentation update to the floating report action must not break an immutable release.
|
|
173
|
+
- name: Check the README against the locked contract renderer
|
|
174
|
+
run: uv run --locked --group quint pytest-quint-connect report '**/.artifacts/quint-connect/results.json' --readme README.md --check
|
|
175
|
+
# The current action owns the sticky and artifacts, independently of README formatting.
|
|
176
|
+
- uses: gigaverse-app/pytest-quint-connect/report@v1
|
|
177
|
+
if: ${{ always() }}
|
|
178
|
+
with:
|
|
179
|
+
readme-mode: 'off'
|
|
180
|
+
|
|
144
181
|
validation-gate:
|
|
145
182
|
name: Validation gate
|
|
146
|
-
needs: [select-tests, quality, tests, pytest-minimum, distributions]
|
|
183
|
+
needs: [select-tests, quality, tests, pytest-minimum, distributions, selection-contract]
|
|
147
184
|
if: ${{ !cancelled() }}
|
|
148
185
|
runs-on: ubuntu-latest
|
|
149
186
|
steps:
|
|
@@ -151,4 +188,4 @@ jobs:
|
|
|
151
188
|
with:
|
|
152
189
|
needs: ${{ toJSON(needs) }}
|
|
153
190
|
lanes: ${{ needs.select-tests.outputs.lanes || '{}' }}
|
|
154
|
-
lane-jobs: '{"tests":["tests"],"pytest-minimum":["tests"]}'
|
|
191
|
+
lane-jobs: '{"tests":["tests"],"pytest-minimum":["tests"],"distributions":["package"],"selection-contract":["contracts"]}'
|
|
@@ -4,6 +4,8 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
branches: [main]
|
|
6
6
|
workflow_dispatch:
|
|
7
|
+
schedule:
|
|
8
|
+
- cron: '33 3 * * 1'
|
|
7
9
|
permissions:
|
|
8
10
|
contents: read
|
|
9
11
|
jobs:
|
|
@@ -67,7 +69,9 @@ jobs:
|
|
|
67
69
|
run: |
|
|
68
70
|
python - <<'PY'
|
|
69
71
|
import json, os
|
|
70
|
-
|
|
72
|
+
# A full checkpoint admits every configured lane.
|
|
73
|
+
configured = json.load(open('.github/test-lanes.json', encoding='utf-8'))['lanes']
|
|
74
|
+
assert json.loads(os.environ['LANES']) == dict.fromkeys(configured, True)
|
|
71
75
|
PY
|
|
72
76
|
- name: Require successful planning after fallback execution
|
|
73
77
|
if: ${{ !cancelled() && needs.select-tests.result != 'success' }}
|
|
@@ -21,6 +21,10 @@ jobs:
|
|
|
21
21
|
uv run --no-project python -c 'import os, pathlib, tomllib; version = tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"]; assert os.environ["RELEASE_REF"] == f"refs/tags/v{version}", "Run publishing from the version-matching v tag"'
|
|
22
22
|
checks:
|
|
23
23
|
needs: validate-tag
|
|
24
|
+
# Reusable CI's Quint report requests this grant, even on a tag with no PR.
|
|
25
|
+
permissions:
|
|
26
|
+
contents: read
|
|
27
|
+
pull-requests: write
|
|
24
28
|
uses: ./.github/workflows/ci.yml
|
|
25
29
|
publish:
|
|
26
30
|
needs: checks
|
|
@@ -11,6 +11,11 @@ on pytest-selective itself.
|
|
|
11
11
|
- Reproduce selection bugs using real temporary Git repositories and existing
|
|
12
12
|
`tests/testing/git_repo.py` helpers. Cover base and head trees, deleted inputs,
|
|
13
13
|
explicit declarations and the relevant fallback. Do not mock away selection.
|
|
14
|
+
- The planner's import, conftest, declaration and content-read closure, its parse-error
|
|
15
|
+
stop, its selection against older bases and its graph cache's transparency are also a
|
|
16
|
+
Quint contract in
|
|
17
|
+
`contracts/selection`. After changing selection, run it (CONTRIBUTING.md, *The selection
|
|
18
|
+
contract*); extend the model rather than weakening it when the promise changes.
|
|
14
19
|
- Planning supports committed revisions and explicit working-tree snapshots. Preserve full execution on
|
|
15
20
|
operational uncertainty and diagnostics for invalid declarations. An empty
|
|
16
21
|
selection is not an empty pytest argument list.
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.0](https://github.com/gigaverse-app/pytest-selective/compare/v0.4.3...v1.0.0) (2026-11-08)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug fixes
|
|
7
|
+
|
|
8
|
+
* **ci:** streamline dogfood checks and prepare the 1.0 release ([#97](https://github.com/gigaverse-app/pytest-selective/issues/97)) ([5c79edc](https://github.com/gigaverse-app/pytest-selective/commit/5c79edcc2e234ccd90902ef001d2852cf7ecfdbc))
|
|
9
|
+
* **selection:** follow star imports to the submodules a package lists in __all__ ([#94](https://github.com/gigaverse-app/pytest-selective/issues/94)) ([ea02fbf](https://github.com/gigaverse-app/pytest-selective/commit/ea02fbfe85c2d5b6e8685a4b159215d421f78653))
|
|
10
|
+
|
|
11
|
+
## [0.4.3](https://github.com/gigaverse-app/pytest-selective/compare/v0.4.2...v0.4.3) (2026-11-08)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug fixes
|
|
15
|
+
|
|
16
|
+
* **lanes:** select lanes through the graph and match ** like gitignore ([#90](https://github.com/gigaverse-app/pytest-selective/issues/90)) ([107b021](https://github.com/gigaverse-app/pytest-selective/commit/107b0212ed741ac72c36a39cc864906eca75d1ab))
|
|
17
|
+
|
|
3
18
|
## [0.4.2](https://github.com/gigaverse-app/pytest-selective/compare/v0.4.1...v0.4.2) (2026-11-07)
|
|
4
19
|
|
|
5
20
|
|
|
@@ -32,6 +32,31 @@ Changes to collection policy, runtime auditing, or the composite action need tes
|
|
|
32
32
|
at that boundary. Update the tutorial when the public command contract changes.
|
|
33
33
|
Keep performance claims tied to reproducible measurements and their limitations.
|
|
34
34
|
|
|
35
|
+
### The selection contract
|
|
36
|
+
|
|
37
|
+
[contracts/selection](contracts/selection/README.md) states the planner's core
|
|
38
|
+
promise as a [Quint](https://quint.sh) model and checks it with
|
|
39
|
+
[pytest-quint-connect](https://github.com/gigaverse-app/pytest-quint-connect):
|
|
40
|
+
generated histories of real commits, each selected by `affected()` and compared
|
|
41
|
+
with the model. It runs as its own CI lane, outside the xdist suite, and needs
|
|
42
|
+
Node.js 22+ for Quint:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uv run --group quint pytest -c contracts/selection/quint.ini contracts/selection --quint-require-complete
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
When selection semantics change on purpose, change `selection.qnt` with them and
|
|
49
|
+
regenerate the driver (`pytest-quint-connect codegen contracts/selection/quint-connect.json`).
|
|
50
|
+
Never drop a witness, mutant or observed field to make it pass.
|
|
51
|
+
|
|
52
|
+
CI reports each run in one pull-request comment and checks the README's contract block
|
|
53
|
+
against it. After a change to the contract's cases, refresh the block from a complete
|
|
54
|
+
local run and commit it:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
uv run --group quint pytest-quint-connect report '**/.artifacts/quint-connect/results.json' --readme README.md
|
|
58
|
+
```
|
|
59
|
+
|
|
35
60
|
### Shared implementation owners
|
|
36
61
|
|
|
37
62
|
| Contract | Owner |
|
|
@@ -72,8 +97,9 @@ limitations. The pull request template provides a short checklist. See
|
|
|
72
97
|
## CI lane selection
|
|
73
98
|
|
|
74
99
|
This repository uses its own `plan-lanes` action. Documentation-only PRs keep
|
|
75
|
-
quality checks (including Markdown links) and
|
|
76
|
-
|
|
100
|
+
quality checks (including Markdown links) and the documentation build, while
|
|
101
|
+
skipping every test lane and the distribution build. A README or LICENSE change
|
|
102
|
+
also runs the `package` lane, because both distributions embed them. Within an
|
|
77
103
|
admitted lane, the repository's public file-selection action selects affected
|
|
78
104
|
test files once; the platform matrix and minimum-pytest job execute that same
|
|
79
105
|
plan. CI-esta receives both lane and file explanations, learns JUnit timings,
|
|
@@ -171,6 +197,12 @@ history or failed planning must execute the full affected lanes and fail the
|
|
|
171
197
|
required planning gate. Ordinary PRs and pushes use selection; manual/release/scheduled/full overrides
|
|
172
198
|
remain complete checkpoints.
|
|
173
199
|
|
|
200
|
+
CI and public-action smoke run full checkpoints every Monday. Ordinary runs keep
|
|
201
|
+
the shared affected-file plan across all six OS/Python combinations and minimum
|
|
202
|
+
pytest. `tests/test_tutorial.py` owns the end-to-end tutorial execution and follows
|
|
203
|
+
that plan; there is no second tutorial invocation in each matrix job. JUnit retains
|
|
204
|
+
its test result and timing, and full plans also upload whole-suite coverage.
|
|
205
|
+
|
|
174
206
|
## Duplication
|
|
175
207
|
|
|
176
208
|
`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: 0.
|
|
3
|
+
Version: 1.0.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/
|
|
@@ -233,7 +233,7 @@ skip pytest, because invoking pytest without paths runs the whole suite.
|
|
|
233
233
|
| --- | --- |
|
|
234
234
|
| A transitive dependency | Import chains and package initialization |
|
|
235
235
|
| A deleted or renamed module | Dependencies in both the base and head trees |
|
|
236
|
-
| Fixtures or startup code | Ancestor conftests and configured global modules |
|
|
236
|
+
| Fixtures or startup code | Ancestor conftests, plugins named by `-p` in `addopts`, and configured global modules |
|
|
237
237
|
| Templates, schemas, or data files | [Reader-owned declarations and YAML sidecars](docs/tutorials/sidecar-dependencies.md) |
|
|
238
238
|
| A surprisingly large selection | Dependency paths, startup-floor and blast-radius explanations |
|
|
239
239
|
| A large CI suite | Cached parsing, graph reuse, and duration-based shard planning |
|
|
@@ -262,6 +262,41 @@ and evidence checks.
|
|
|
262
262
|
|
|
263
263
|
[Read the evidence, safety boundaries, and adoption checklist](https://github.com/gigaverse-app/pytest-selective/blob/main/docs/evidence.md).
|
|
264
264
|
|
|
265
|
+
### Selection checked against a model
|
|
266
|
+
|
|
267
|
+
The planner's core promise is also a [Quint](https://quint.sh) model. CI replays generated
|
|
268
|
+
histories of real commits through the planner and requires every deliberately broken
|
|
269
|
+
planner to be caught ([selection contract](https://github.com/gigaverse-app/pytest-selective/blob/main/contracts/selection/README.md)). The block below
|
|
270
|
+
is the latest complete run, checked by
|
|
271
|
+
[pytest-quint-connect](https://github.com/gigaverse-app/pytest-quint-connect) on every
|
|
272
|
+
change that runs the contract.
|
|
273
|
+
|
|
274
|
+
<!-- pytest-quint-connect:start -->
|
|
275
|
+
<!-- pytest-quint-connect:contracts selection -->
|
|
276
|
+
<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>
|
|
277
|
+
|
|
278
|
+
<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 · 44 histories · 10/10 planted bugs caught" src="https://img.shields.io/badge/Quint_contracts-1_contract_passed_%C2%B7_44_histories_%C2%B7_10%2F10_planted_bugs_caught-brightgreen"></a>
|
|
279
|
+
|
|
280
|
+
| | 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") |
|
|
281
|
+
| :-: | :-- | --: | --: |
|
|
282
|
+
| ✅ | `selection` `test_selection_contract.py::test_selection`<br><sub>44 histories (20 witnesses, 24 samples, seed 11) · every action covered · Quint 0.33.1</sub> | 55/55 | 10/10 |
|
|
283
|
+
|
|
284
|
+
**[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**
|
|
285
|
+
|
|
286
|
+
- `head-edges-only`: Plans with the head tree's edges only, so a deleted module's former importers are not selected. Caught by `witness-deletedModuleSelectsFormerImportersTest` at state 1, after `remove(file='beta', target='')`.
|
|
287
|
+
- `direct-importers-only`: Follows only files that import a changed file directly, not their importers. Caught by `witness-transitiveEditTest` at state 1, after `edit(file='beta', target='')`.
|
|
288
|
+
- `global-inputs-ignored`: Treats requirements.txt like any unreferenced file instead of selecting every test. Caught by `witness-globalInputSelectsEveryTestTest` at state 1, after `touchGlobal(file='', target='')`.
|
|
289
|
+
- `content-follows-imports`: Reads a kind: content declaration as an import, so it also follows the read file's imports. Caught by `witness-contentReadIsNotTransitiveTest` at state 2, after `edit(file='beta', target='')`.
|
|
290
|
+
- `conftest-scope-collapsed`: Scopes every conftest to the top test directory, so tests/sub/conftest.py reaches every test. Caught by `witness-nestedConftestSelectsItsSubtreeTest` at state 1, after `edit(file='subconftest', target='')`.
|
|
291
|
+
- `parse-errors-ignored`: Treats an unparseable file as importing nothing instead of stopping with an error. Caught by `witness-unparseableSourceIsAnErrorTest` at state 1, after `breakSyntax(file='beta', target='')`.
|
|
292
|
+
- `assumes-consecutive-base`: Plans every run against the head it planned last, which is right only between neighbouring commits. Caught by `witness-transitiveEditTest` at state 1, after `edit(file='beta', target='')`.
|
|
293
|
+
- `cache-key-omits-test-roots`: Leaves test_roots out of the cached graph's key, so a graph built for the old roots is reused after they change. Caught by `witness-narrowedTestRootsReplanFromScratchTest` at state 2, after `toggleConfig(file='narrow', target='')`.
|
|
294
|
+
- `parse-cache-keyed-by-path`: Caches parsed files by path instead of content, so an edited file keeps its old imports. Caught by `witness-importDeclarationFollowsImportsTest` at state 3, after `edit(file='beta', target='')`.
|
|
295
|
+
- `cache-checksum-ignored`: Trusts a stored graph without checking its checksum, so a damaged entry is used as if it were valid. Caught by `witness-damagedCacheIsRebuiltTest` at state 2, after `corrupt(file='', target='')`.
|
|
296
|
+
|
|
297
|
+
<sub>Generated by [pytest-quint-connect](https://github.com/gigaverse-app/pytest-quint-connect) from contract receipts; [what a passing run proves](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/limits.md).</sub>
|
|
298
|
+
<!-- pytest-quint-connect:end -->
|
|
299
|
+
|
|
265
300
|
## Choose the right tool
|
|
266
301
|
|
|
267
302
|
[pytest-impacted](https://github.com/promptromp/pytest-impacted) offers a pytest-native
|
|
@@ -202,7 +202,7 @@ skip pytest, because invoking pytest without paths runs the whole suite.
|
|
|
202
202
|
| --- | --- |
|
|
203
203
|
| A transitive dependency | Import chains and package initialization |
|
|
204
204
|
| A deleted or renamed module | Dependencies in both the base and head trees |
|
|
205
|
-
| Fixtures or startup code | Ancestor conftests and configured global modules |
|
|
205
|
+
| Fixtures or startup code | Ancestor conftests, plugins named by `-p` in `addopts`, and configured global modules |
|
|
206
206
|
| Templates, schemas, or data files | [Reader-owned declarations and YAML sidecars](docs/tutorials/sidecar-dependencies.md) |
|
|
207
207
|
| A surprisingly large selection | Dependency paths, startup-floor and blast-radius explanations |
|
|
208
208
|
| A large CI suite | Cached parsing, graph reuse, and duration-based shard planning |
|
|
@@ -231,6 +231,41 @@ and evidence checks.
|
|
|
231
231
|
|
|
232
232
|
[Read the evidence, safety boundaries, and adoption checklist](https://github.com/gigaverse-app/pytest-selective/blob/main/docs/evidence.md).
|
|
233
233
|
|
|
234
|
+
### Selection checked against a model
|
|
235
|
+
|
|
236
|
+
The planner's core promise is also a [Quint](https://quint.sh) model. CI replays generated
|
|
237
|
+
histories of real commits through the planner and requires every deliberately broken
|
|
238
|
+
planner to be caught ([selection contract](https://github.com/gigaverse-app/pytest-selective/blob/main/contracts/selection/README.md)). The block below
|
|
239
|
+
is the latest complete run, checked by
|
|
240
|
+
[pytest-quint-connect](https://github.com/gigaverse-app/pytest-quint-connect) on every
|
|
241
|
+
change that runs the contract.
|
|
242
|
+
|
|
243
|
+
<!-- pytest-quint-connect:start -->
|
|
244
|
+
<!-- pytest-quint-connect:contracts selection -->
|
|
245
|
+
<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>
|
|
246
|
+
|
|
247
|
+
<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 · 44 histories · 10/10 planted bugs caught" src="https://img.shields.io/badge/Quint_contracts-1_contract_passed_%C2%B7_44_histories_%C2%B7_10%2F10_planted_bugs_caught-brightgreen"></a>
|
|
248
|
+
|
|
249
|
+
| | 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") |
|
|
250
|
+
| :-: | :-- | --: | --: |
|
|
251
|
+
| ✅ | `selection` `test_selection_contract.py::test_selection`<br><sub>44 histories (20 witnesses, 24 samples, seed 11) · every action covered · Quint 0.33.1</sub> | 55/55 | 10/10 |
|
|
252
|
+
|
|
253
|
+
**[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**
|
|
254
|
+
|
|
255
|
+
- `head-edges-only`: Plans with the head tree's edges only, so a deleted module's former importers are not selected. Caught by `witness-deletedModuleSelectsFormerImportersTest` at state 1, after `remove(file='beta', target='')`.
|
|
256
|
+
- `direct-importers-only`: Follows only files that import a changed file directly, not their importers. Caught by `witness-transitiveEditTest` at state 1, after `edit(file='beta', target='')`.
|
|
257
|
+
- `global-inputs-ignored`: Treats requirements.txt like any unreferenced file instead of selecting every test. Caught by `witness-globalInputSelectsEveryTestTest` at state 1, after `touchGlobal(file='', target='')`.
|
|
258
|
+
- `content-follows-imports`: Reads a kind: content declaration as an import, so it also follows the read file's imports. Caught by `witness-contentReadIsNotTransitiveTest` at state 2, after `edit(file='beta', target='')`.
|
|
259
|
+
- `conftest-scope-collapsed`: Scopes every conftest to the top test directory, so tests/sub/conftest.py reaches every test. Caught by `witness-nestedConftestSelectsItsSubtreeTest` at state 1, after `edit(file='subconftest', target='')`.
|
|
260
|
+
- `parse-errors-ignored`: Treats an unparseable file as importing nothing instead of stopping with an error. Caught by `witness-unparseableSourceIsAnErrorTest` at state 1, after `breakSyntax(file='beta', target='')`.
|
|
261
|
+
- `assumes-consecutive-base`: Plans every run against the head it planned last, which is right only between neighbouring commits. Caught by `witness-transitiveEditTest` at state 1, after `edit(file='beta', target='')`.
|
|
262
|
+
- `cache-key-omits-test-roots`: Leaves test_roots out of the cached graph's key, so a graph built for the old roots is reused after they change. Caught by `witness-narrowedTestRootsReplanFromScratchTest` at state 2, after `toggleConfig(file='narrow', target='')`.
|
|
263
|
+
- `parse-cache-keyed-by-path`: Caches parsed files by path instead of content, so an edited file keeps its old imports. Caught by `witness-importDeclarationFollowsImportsTest` at state 3, after `edit(file='beta', target='')`.
|
|
264
|
+
- `cache-checksum-ignored`: Trusts a stored graph without checking its checksum, so a damaged entry is used as if it were valid. Caught by `witness-damagedCacheIsRebuiltTest` at state 2, after `corrupt(file='', target='')`.
|
|
265
|
+
|
|
266
|
+
<sub>Generated by [pytest-quint-connect](https://github.com/gigaverse-app/pytest-quint-connect) from contract receipts; [what a passing run proves](https://github.com/gigaverse-app/pytest-quint-connect/blob/main/docs/limits.md).</sub>
|
|
267
|
+
<!-- pytest-quint-connect:end -->
|
|
268
|
+
|
|
234
269
|
## Choose the right tool
|
|
235
270
|
|
|
236
271
|
[pytest-impacted](https://github.com/promptromp/pytest-impacted) offers a pytest-native
|
|
@@ -48,9 +48,31 @@ version: publishing Python `0.x.y` does not prevent updating Actions `v1`.
|
|
|
48
48
|
Consumers deliberately use `actions/NAME@v1` as a moving target. Change the
|
|
49
49
|
Actions major only when deliberately establishing a new Actions API line.
|
|
50
50
|
Package requirements use their own compatible ranges, such as
|
|
51
|
-
`pytest-selective>=0
|
|
51
|
+
`pytest-selective>=1.0,<2`; lockfiles retain resolved versions and artifact
|
|
52
52
|
hashes. Prereleases do not advance the Actions tag.
|
|
53
53
|
|
|
54
|
+
## Transition to Python 1.x
|
|
55
|
+
|
|
56
|
+
Request the first stable release with a one-time `Release-As: 1.0.0` footer in
|
|
57
|
+
the merged commit. Preserve that footer in the squash message. Release Please
|
|
58
|
+
then updates `pyproject.toml`, `uv.lock`, the manifest and changelog together in
|
|
59
|
+
its normal release PR. Do not keep a permanent `release-as` override, which
|
|
60
|
+
would interfere with subsequent automatic version bumps.
|
|
61
|
+
|
|
62
|
+
The package's stable-version transition keeps the current CLI, imports and
|
|
63
|
+
Actions inputs. `v1.0.0` is the immutable package tag; `v1` remains the moving
|
|
64
|
+
Actions compatibility tag and advances only after successful PyPI publication.
|
|
65
|
+
Existing `0.4.x` pins remain installable. Consumers that want the new package
|
|
66
|
+
must allow 1.x in their dependency range; CI-esta accepts both the existing
|
|
67
|
+
published 0.4.2 and 1.x while retaining its tested lockfile.
|
|
68
|
+
|
|
69
|
+
Before merging the 1.0 release PR, verify the Publish caller grants all
|
|
70
|
+
permissions requested by reusable CI. In particular, the Quint report requests
|
|
71
|
+
`pull-requests: write`; GitHub checks that grant even on release tags. Keep
|
|
72
|
+
OIDC permission confined to the separate `pypi` upload job. The 0.4.3 GitHub
|
|
73
|
+
release failed at workflow startup, before PyPI upload or movement of `v1`;
|
|
74
|
+
leave that immutable tag intact and publish the corrected pipeline as 1.0.0.
|
|
75
|
+
|
|
54
76
|
For an Actions-only publication, advance `v1` to the exact reviewed main commit
|
|
55
77
|
after its complete CI passes. This updates the Git tag without creating a GitHub
|
|
56
78
|
release or invoking PyPI publication. Do not move immutable package release tags.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Selection contract
|
|
2
|
+
|
|
3
|
+
pytest-selective's central promise, as a [Quint](https://quint.sh) model. After a commit,
|
|
4
|
+
the selected tests are exactly the tests that reach a changed file, in the base or the head
|
|
5
|
+
tree, through imports, an ancestor `conftest.py` or a `# gv:test-dependency:` comment
|
|
6
|
+
(import kind, so it follows the declared file's imports), plus the readers of a changed
|
|
7
|
+
file's contents (a `kind: content` sidecar, which does not follow imports). A change to a
|
|
8
|
+
global input selects every test. Unparseable Python in either tree is an error. The planner's
|
|
9
|
+
graph cache is not part of the promise: no configuration edit, cache restart or damaged cache
|
|
10
|
+
entry may change an answer.
|
|
11
|
+
|
|
12
|
+
[pytest-quint-connect](https://github.com/gigaverse-app/pytest-quint-connect) generates
|
|
13
|
+
histories from [selection.qnt](selection.qnt). [conftest.py](conftest.py) replays each step
|
|
14
|
+
as a real Git commit in a small fixture repository, then runs `affected()` twice through one
|
|
15
|
+
`GraphCache`: against the previous commit, and against the history's first commit, as a pull
|
|
16
|
+
request's base and head. Configuration edits, cache restarts and damaged cache entries are
|
|
17
|
+
followed by a re-plan of the last commit through the same cache. The plugin compares every
|
|
18
|
+
selection with the model's.
|
|
19
|
+
|
|
20
|
+
| Case | What it checks |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| 20 witnesses | transitive edits; a deleted module's former importers; declared reads, added and removed; root and nested conftests; a global input; an added import; a deleted test; a test-root helper module; content reads that do not follow imports; import declarations that do; unparseable source; a reverted import that is no change against the first commit; selection across several commits; narrowed `test_roots` after a cached plan; an extra global input; a damaged cache; a restarted cache |
|
|
23
|
+
| 24 samples | seeded random histories of up to 8 operations mixing every action |
|
|
24
|
+
| 10 mutants | the planner with base-tree edges dropped, only direct importers followed, global inputs ignored, content reads followed like imports, conftest scope collapsed to the test root, parse errors ignored, every base assumed to be the last planned head, `test_roots` left out of the graph cache key, parsed files cached by path, and cache checksums ignored; each must be caught |
|
|
25
|
+
| coverage | every action reached |
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
uv run --group quint pytest -c quint.ini . --quint-require-complete # from this directory
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The [manifest](quint-connect.json) lists what the model leaves out: first-party pins,
|
|
32
|
+
name-scoped modules, deferred strings, Django URLconf policy and plugin-registering test
|
|
33
|
+
modules have their own tests in `tests/`.
|