smoke-optimiser 0.1.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.
Files changed (28) hide show
  1. smoke_optimiser-0.1.0/.github/workflows/ci.yml +32 -0
  2. smoke_optimiser-0.1.0/.github/workflows/release.yml +72 -0
  3. smoke_optimiser-0.1.0/.gitignore +15 -0
  4. smoke_optimiser-0.1.0/CHANGELOG.md +69 -0
  5. smoke_optimiser-0.1.0/Justfile +82 -0
  6. smoke_optimiser-0.1.0/LICENSE +21 -0
  7. smoke_optimiser-0.1.0/PKG-INFO +143 -0
  8. smoke_optimiser-0.1.0/README.md +90 -0
  9. smoke_optimiser-0.1.0/pyproject.toml +140 -0
  10. smoke_optimiser-0.1.0/smoke_optimiser/__init__.py +1 -0
  11. smoke_optimiser-0.1.0/smoke_optimiser/__main__.py +9 -0
  12. smoke_optimiser-0.1.0/smoke_optimiser/cli.py +249 -0
  13. smoke_optimiser-0.1.0/smoke_optimiser/config.py +152 -0
  14. smoke_optimiser-0.1.0/smoke_optimiser/environment.py +86 -0
  15. smoke_optimiser-0.1.0/smoke_optimiser/optimiser/__init__.py +0 -0
  16. smoke_optimiser-0.1.0/smoke_optimiser/optimiser/filters.py +111 -0
  17. smoke_optimiser-0.1.0/smoke_optimiser/optimiser/greedy.py +189 -0
  18. smoke_optimiser-0.1.0/smoke_optimiser/optimiser/models.py +38 -0
  19. smoke_optimiser-0.1.0/smoke_optimiser/plugin.py +126 -0
  20. smoke_optimiser-0.1.0/smoke_optimiser/profiler/__init__.py +0 -0
  21. smoke_optimiser-0.1.0/smoke_optimiser/profiler/models.py +112 -0
  22. smoke_optimiser-0.1.0/smoke_optimiser/profiler/parser.py +138 -0
  23. smoke_optimiser-0.1.0/smoke_optimiser/profiler/runner.py +218 -0
  24. smoke_optimiser-0.1.0/smoke_optimiser/py.typed +0 -0
  25. smoke_optimiser-0.1.0/smoke_optimiser/reports/__init__.py +0 -0
  26. smoke_optimiser-0.1.0/smoke_optimiser/reports/repro.py +47 -0
  27. smoke_optimiser-0.1.0/smoke_optimiser/reports/smoke_suite.py +144 -0
  28. smoke_optimiser-0.1.0/smoke_optimiser/reports/summary.py +84 -0
@@ -0,0 +1,32 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [main]
6
+
7
+ jobs:
8
+ validate:
9
+ runs-on: ubuntu-latest
10
+ env:
11
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
12
+ steps:
13
+ - uses: actions/checkout@v6
14
+
15
+ - uses: actions/setup-python@v6
16
+ with:
17
+ python-version: "3.13"
18
+
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v8.0.0
21
+
22
+ - name: Install just
23
+ uses: taiki-e/install-action@v2
24
+ with:
25
+ tool: just
26
+
27
+ - name: Install dependencies
28
+ run: uv sync
29
+
30
+ # Pre-commit logic is defined in the Justfile `precommit` target — do not add steps here.
31
+ - name: Validate project
32
+ run: just precommit
@@ -0,0 +1,72 @@
1
+ name: release
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ permissions:
9
+ contents: write
10
+ pull-requests: write
11
+
12
+ jobs:
13
+ release-please:
14
+ runs-on: ubuntu-latest
15
+ outputs:
16
+ release_created: ${{ steps.release.outputs.release_created }}
17
+ pr: ${{ steps.release.outputs.pr }}
18
+ steps:
19
+ - uses: googleapis/release-please-action@v4
20
+ id: release
21
+ env:
22
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
23
+ with:
24
+ release-type: python
25
+ token: ${{ secrets.GITHUB_TOKEN }}
26
+
27
+ update-lockfile:
28
+ runs-on: ubuntu-latest
29
+ needs: release-please
30
+ if: ${{ needs.release-please.outputs.pr }}
31
+ permissions:
32
+ contents: write
33
+ steps:
34
+ - uses: actions/checkout@v6
35
+ with:
36
+ ref: release-please--branches--main
37
+ token: ${{ secrets.GITHUB_TOKEN }}
38
+
39
+ - name: Install uv
40
+ uses: astral-sh/setup-uv@v8.0.0
41
+
42
+ - name: Update lock file
43
+ run: uv lock
44
+
45
+ - name: Commit updated lock file
46
+ run: |
47
+ git config user.name "github-actions[bot]"
48
+ git config user.email "github-actions[bot]@users.noreply.github.com"
49
+ git add uv.lock
50
+ git diff --staged --quiet || git commit -m "chore: update uv.lock for version bump"
51
+ git push
52
+
53
+ publish:
54
+ runs-on: ubuntu-latest
55
+ needs: release-please
56
+ if: ${{ needs.release-please.outputs.release_created }}
57
+ environment: pypi
58
+ permissions:
59
+ id-token: write # Mandatory for Trusted Publishing
60
+ steps:
61
+ - uses: actions/checkout@v6
62
+ env:
63
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
64
+
65
+ - name: Install uv
66
+ uses: astral-sh/setup-uv@v8.0.0
67
+
68
+ - name: Build package
69
+ run: uv build
70
+
71
+ - name: Publish to PyPI
72
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,15 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.egg-info/
4
+ dist/
5
+ .coverage
6
+ *.pyc
7
+ .pytest_cache/
8
+ .smoke_suite.json
9
+ .smoke_profiling_data.json
10
+ .smoke_outcomes.json
11
+ .smoke_optimiser_coverage.json
12
+ _smoke_hook.py
13
+ .smoke_coveragerc
14
+ .gstack/
15
+ .jules/
@@ -0,0 +1,69 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-04-28)
4
+
5
+
6
+ ### Features
7
+
8
+ * add --iterations argument to average test durations across multiple runs ([e85b02c](https://github.com/lawther/smoke-optimiser/commit/e85b02ce94a195bf8510a6d64e889da544fe6826))
9
+ * add --src top-level argument for coverage target with conflict validation ([e6a1373](https://github.com/lawther/smoke-optimiser/commit/e6a13737aed3e1f96c6149036aed4ae3044e19dd))
10
+ * add CLI argument parsing and config merging ([6b77dde](https://github.com/lawther/smoke-optimiser/commit/6b77dde50b42ffd71c5ed8a544f1eb6af439defa))
11
+ * add config model with pyproject.toml loading ([e7f85a7](https://github.com/lawther/smoke-optimiser/commit/e7f85a7fa3eb1e629dba839770cbf1a76044abec))
12
+ * add coverage JSON parser with streaming support ([1cad88f](https://github.com/lawther/smoke-optimiser/commit/1cad88f83c2d107ce2da481f2643548f254b45a3))
13
+ * add full suite coverage metric to summary reports ([db6f218](https://github.com/lawther/smoke-optimiser/commit/db6f218420b748206ad377324f9a0af5d053552d))
14
+ * add greedy set-cover optimiser ([0015142](https://github.com/lawther/smoke-optimiser/commit/00151429c2ef326384286c2dc3777a11465016c0))
15
+ * add heuristic coverage target discovery with warning ([9ada322](https://github.com/lawther/smoke-optimiser/commit/9ada322039e51a8218e6020a507a1bde017cad51))
16
+ * Add Justfile for development task automation ([6aa80bc](https://github.com/lawther/smoke-optimiser/commit/6aa80bc3d41f85f9aa0f74c2e8f7ab333a668666))
17
+ * add machine environment capture ([b8ef0f9](https://github.com/lawther/smoke-optimiser/commit/b8ef0f92e58eecd0d52949a50a8e5343136919b7))
18
+ * add optimiser data models ([4f6bbf4](https://github.com/lawther/smoke-optimiser/commit/4f6bbf4ae1a4a21dfe2fffac1496296eb445cc3b))
19
+ * add optimiser filters (include/exclude/failing) ([5b8c049](https://github.com/lawther/smoke-optimiser/commit/5b8c0492686a08703caa7d73684cc2a7c7369f7d))
20
+ * add plugin summary header showing smoke suite metadata ([7eabb0c](https://github.com/lawther/smoke-optimiser/commit/7eabb0cd806b58db89e40b6bec2619bfce8ea4d4))
21
+ * add profiler data models ([0ece94c](https://github.com/lawther/smoke-optimiser/commit/0ece94cf4d93f7d39217d35d94522f030b902000))
22
+ * add profiler runner (pytest + coverage orchestration) ([dd9b6ae](https://github.com/lawther/smoke-optimiser/commit/dd9b6ae3396afc471809dd52cfa7f097b1edb2d6))
23
+ * add pytest plugin option registration and smoke suite loading ([44eee93](https://github.com/lawther/smoke-optimiser/commit/44eee93464936724cf79e2e2f19b941f82b6b0f6))
24
+ * add report generation (JSON and human-readable) ([4f761b6](https://github.com/lawther/smoke-optimiser/commit/4f761b6ab4a690f20f616ef3d61b4a691a2de2a5))
25
+ * add test collection filtering for --smoke ([f1ab66e](https://github.com/lawther/smoke-optimiser/commit/f1ab66edfdec290db8b83d6d4007bd610ce6de55))
26
+ * allow matching include/exclude filters by test function name ([b85bcd0](https://github.com/lawther/smoke-optimiser/commit/b85bcd066c0d6936c67a7ef28f1d1c36cc9d6ce5))
27
+ * enable direct installation of `smoke-optimiser`, update README instructions, and add MIT license. ([5c9c616](https://github.com/lawther/smoke-optimiser/commit/5c9c616a7133db88c7c43e8d4984ff7a1412f9cf))
28
+ * support comma-separated patterns in include and exclude filters ([985f878](https://github.com/lawther/smoke-optimiser/commit/985f87886beb16fbea560176efcdc2c5c454d16e))
29
+ * support file-level matching in include/exclude filters ([bb8f8db](https://github.com/lawther/smoke-optimiser/commit/bb8f8db337241952d085ccad221262357de24441))
30
+ * warn user about unmatched include/exclude patterns during optimization ([41cabaa](https://github.com/lawther/smoke-optimiser/commit/41cabaa457526812a15f31275512637b895c2700))
31
+ * wire CLI to profiler and optimiser phases ([d2a3534](https://github.com/lawther/smoke-optimiser/commit/d2a35345f87998db077e9a2aa40ab2a409b45433))
32
+
33
+
34
+ ### Bug Fixes
35
+
36
+ * arbitrary file overwrite (Symlink Attack) ([7e97075](https://github.com/lawther/smoke-optimiser/commit/7e97075139f4ddf64493c73a3e2face57c013c9a))
37
+ * capture heuristically discovered coverage target in repro command ([401f5d3](https://github.com/lawther/smoke-optimiser/commit/401f5d33a276e97d2d05289330b3dd434d749452))
38
+ * Enhance CLI message consistency and add pluralisation ([20a7c56](https://github.com/lawther/smoke-optimiser/commit/20a7c56ed7a711e7cdc4ed1fc7519d4fe010d6be))
39
+ * Fix B110 exception swallowing in config parser ([aa182f9](https://github.com/lawther/smoke-optimiser/commit/aa182f9e3d70e4e396914114423a0b7b6074a512))
40
+ * insecure relative path generation in hook output ([ec54988](https://github.com/lawther/smoke-optimiser/commit/ec54988462fea499c9ccb79f041bdbc24c7269ae))
41
+ * Predictable Temporary File Names in Project Root ([9ffdf7f](https://github.com/lawther/smoke-optimiser/commit/9ffdf7fc9866a8eb02b52bf4cdcd9e94a23be738))
42
+ * prevent information disclosure on invalid profile data ([6d3dfac](https://github.com/lawther/smoke-optimiser/commit/6d3dface5ed0267593c668cf7af8297293a66717))
43
+ * profiler hook loading and streaming parser robustness ([f8e7637](https://github.com/lawther/smoke-optimiser/commit/f8e7637cad0ec6540360b43305bd06361c46fb4c))
44
+ * refactor Justfile for reliable error reporting and 4-space indentation ([4a588ba](https://github.com/lawther/smoke-optimiser/commit/4a588bafd273f784219d16a39ed23566cd9cc951))
45
+ * refactor Justfile recipes for reliable error reporting and add linting ([8b96301](https://github.com/lawther/smoke-optimiser/commit/8b96301ef22b0b0b8420cb257ee500c87c7dda8f))
46
+ * refactor precommit recipe for reliable error reporting ([67f2918](https://github.com/lawther/smoke-optimiser/commit/67f29189b9045ea0dc93a20b7e30e43d654a5833))
47
+ * resolve conflicting coverage dynamic contexts and promote warnings to errors ([17a16dd](https://github.com/lawther/smoke-optimiser/commit/17a16ddd7c7b5d152ce92cdaf19f3e6124f545c7))
48
+ * resolve partial executable paths using `shutil.which` ([d3619b5](https://github.com/lawther/smoke-optimiser/commit/d3619b58f5b8236ad23dbdb993fa0b0183f7ac62))
49
+ * resolve ty check errors and enforce type safety in models ([c3579e9](https://github.com/lawther/smoke-optimiser/commit/c3579e9805726befd28b047cc9b41b778b243ed8))
50
+ * suppress confusing coverage.py JSON report message in runner ([0d567e8](https://github.com/lawther/smoke-optimiser/commit/0d567e88054335afad192f7c8fe3c2d1dcbdfac5))
51
+
52
+
53
+ ### Performance Improvements
54
+
55
+ * Lazy greedy evaluation for set cover ([b9a325c](https://github.com/lawther/smoke-optimiser/commit/b9a325c96c94e0063e9d7f18f6620e1e0b81fe42))
56
+ * optimize equivalent group calculation by utilizing frozenset dictionary keys ([57da5a8](https://github.com/lawther/smoke-optimiser/commit/57da5a8bd02ac49fb4f87d40d33964a536697230))
57
+ * optimize greedy set cover selection algorithm in `optimise()` ([#4](https://github.com/lawther/smoke-optimiser/issues/4)) ([4318821](https://github.com/lawther/smoke-optimiser/commit/4318821fdab35abfa7c4bbf28421e1125792f6e3))
58
+ * Optimize slow test ID parsing in coverage JSON ([4cfe127](https://github.com/lawther/smoke-optimiser/commit/4cfe1272940f518ad142fa15b184a988562d9dba))
59
+ * pre-format branch strings in parser ([9cc27c8](https://github.com/lawther/smoke-optimiser/commit/9cc27c89e55836771a9643b91f2ed4e582ff2e3a))
60
+
61
+
62
+ ### Documentation
63
+
64
+ * add comprehensive README with usage and argument documentation ([3666db3](https://github.com/lawther/smoke-optimiser/commit/3666db30c11014692917b63d9b69c26bd3bac8b3))
65
+ * add readme, delete impl plans ([a412bf1](https://github.com/lawther/smoke-optimiser/commit/a412bf1f15fd936157fc4331b1b482d4688d64b8))
66
+ * be super explicit about linting rules ([759fa77](https://github.com/lawther/smoke-optimiser/commit/759fa77a94a2c40873eb0e66220e90d4f8fe6253))
67
+ * plan documents ([172ef92](https://github.com/lawther/smoke-optimiser/commit/172ef92b84a19df8edbc41d9b1755476f616c456))
68
+
69
+ ## Changelog
@@ -0,0 +1,82 @@
1
+ # List available recipes
2
+ default:
3
+ @just --list
4
+
5
+ # Run all checks (lint, typecheck, test)
6
+ check: lint typecheck test
7
+
8
+ # Run linting and formatting
9
+ lint:
10
+ uv run ruff format
11
+ uv run ruff check --fix
12
+
13
+ # Format the code
14
+ format:
15
+ uv run ruff format
16
+
17
+ # Run type checks
18
+ typecheck:
19
+ uv run ty check
20
+
21
+ # Run tests
22
+ test:
23
+ uv run pytest
24
+
25
+ # Run tests with coverage
26
+ test-cov:
27
+ uv run pytest --cov=smoke_optimiser
28
+
29
+ # Run formatting, linting and tests (quiet on success, shows errors on failure)
30
+ # This Justfile is the Single Source Of Truth (SSOT) for all pre-commit checks.
31
+ precommit:
32
+ #!/usr/bin/env bash
33
+ echo "Running precommit checks..."
34
+ uv lock --check || { echo "❌ uv.lock is out of sync with pyproject.toml"; exit 1; }
35
+ tmpfile=$(mktemp)
36
+ trap 'rm -f "$tmpfile"' EXIT
37
+ (
38
+ set -e
39
+ just _lint-justfile
40
+ uv run ruff format
41
+ uv run ruff check --fix
42
+ git add $(git diff --cached --name-only)
43
+ uv run ty check
44
+ uv run pytest
45
+ ) > "$tmpfile" 2>&1
46
+ status=$?
47
+ if [ $status -ne 0 ]; then
48
+ cat "$tmpfile"
49
+ exit $status
50
+ fi
51
+ echo "✅ Precommit checks passed!"
52
+
53
+ # [private] Ensure Justfile recipes don't use && chains (which suppress set -e)
54
+ _lint-justfile:
55
+ #!/usr/bin/env bash
56
+ set -euo pipefail
57
+ violations=$(awk '
58
+ /^[[:space:]]+#!/ { in_shebang = 1 }
59
+ /^[^[:space:]]/ && NF > 0 { in_shebang = 0 }
60
+ !in_shebang && /&&/ && !/^[[:space:]]*#/ { print NR": "$0 }
61
+ ' Justfile)
62
+ if [[ -n "$violations" ]]; then
63
+ echo "❌ Justfile recipes must not use && chains. Use separate lines for reliable error reporting."
64
+ echo "$violations"
65
+ exit 1
66
+ fi
67
+
68
+ # Setup the development environment from a fresh clone
69
+ setup-dev:
70
+ @uv sync
71
+ @just setup-git-hooks
72
+ @echo "✅ Development environment setup complete!"
73
+
74
+ # Setup local git hooks
75
+ setup-git-hooks:
76
+ @echo "Setting up local git hooks..."
77
+ @echo "#!/bin/sh" > .git/hooks/pre-commit
78
+ @echo "# This hook invokes the Justfile, which is the Single Source Of Truth for precommit logic." >> .git/hooks/pre-commit
79
+ @echo "# DO NOT add precommit logic here; add it to the 'precommit' recipe in the Justfile." >> .git/hooks/pre-commit
80
+ @echo "just precommit" >> .git/hooks/pre-commit
81
+ @chmod +x .git/hooks/pre-commit
82
+ @echo "✅ Git hooks set up!"
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Mike Lawther
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,143 @@
1
+ Metadata-Version: 2.4
2
+ Name: smoke-optimiser
3
+ Version: 0.1.0
4
+ Summary: A Python CLI tool and pytest plugin to identify and run a minimal smoke test suite.
5
+ Project-URL: Homepage, https://github.com/lawther/smoke-optimiser
6
+ Project-URL: Repository, https://github.com/lawther/smoke-optimiser
7
+ Project-URL: Bug Tracker, https://github.com/lawther/smoke-optimiser/issues
8
+ Author-email: Mike Lawther <mike.lawther@gmail.com>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2025 Mike Lawther
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: coverage,optimisation,pytest,smoke-test,testing
32
+ Classifier: Development Status :: 3 - Alpha
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Operating System :: OS Independent
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.12
38
+ Classifier: Programming Language :: Python :: 3.13
39
+ Classifier: Topic :: Software Development :: Testing
40
+ Requires-Python: >=3.12
41
+ Requires-Dist: coverage~=7.0
42
+ Requires-Dist: ijson~=3.5
43
+ Requires-Dist: pydantic~=2.0
44
+ Requires-Dist: pytest-cov~=7.0
45
+ Requires-Dist: pytest~=9.0
46
+ Requires-Dist: typer~=0.9
47
+ Provides-Extra: dev
48
+ Requires-Dist: pytest; extra == 'dev'
49
+ Requires-Dist: ruff~=0.15; extra == 'dev'
50
+ Provides-Extra: machine
51
+ Requires-Dist: psutil~=7.0; extra == 'machine'
52
+ Description-Content-Type: text/markdown
53
+
54
+ # smoke-optimiser
55
+
56
+ `smoke-optimiser` is a tool and pytest plugin that analyses your test suite to produce a minimal **smoke suite** — a subset of tests that delivers maximum code coverage in minimum wall-clock time.
57
+
58
+ It helps you find the "bang for buck" sweet spot: for example, achieving 80% of your total branch coverage in only 5% of the total runtime.
59
+
60
+ ## Installation
61
+
62
+ Add `smoke-optimiser` as a development dependency in your project:
63
+
64
+ ```bash
65
+ uv add --dev smoke-optimiser
66
+ ```
67
+
68
+ Or with pip:
69
+
70
+ ```bash
71
+ pip install smoke-optimiser
72
+ ```
73
+
74
+ This will make the `smoke-optimiser` command available in your environment and register the pytest plugin automatically.
75
+
76
+ ## Quickstart
77
+
78
+ 1. **Generate the smoke suite**:
79
+ Run the optimiser in your project root. It will automatically detect your source code and profile your tests.
80
+ ```bash
81
+ uv run smoke-optimiser
82
+ ```
83
+
84
+ 2. **Run the smoke suite**:
85
+ Use the `--smoke` flag with pytest to run only the selected high-value tests.
86
+ ```bash
87
+ uv run pytest --smoke
88
+ ```
89
+
90
+ ## Common Usages
91
+
92
+ ### Custom Efficiency Targets
93
+ By default, the tool tries to get maximum coverage within a 15-second time cap. You can tighten these bounds:
94
+ ```bash
95
+ # Aim for 80% coverage, but stop if it takes longer than 5 seconds
96
+ uv run smoke-optimiser --target-cov=80 --time-cap=5
97
+ ```
98
+
99
+ ### Stabilising Timing Data
100
+ Test execution times can vary. Use `--iterations` to run the suite multiple times and average the results for a more stable smoke suite:
101
+ ```bash
102
+ uv run smoke-optimiser --iterations=3
103
+ ```
104
+
105
+ ### Mandatory Inclusion/Exclusion
106
+ Force certain tests (or markers) to be included or excluded from the smoke suite:
107
+ ```bash
108
+ # Always include authentication tests, but exclude anything marked as 'slow'
109
+ uv run smoke-optimiser --include="tests/test_auth.py" --exclude="@pytest.mark.slow"
110
+ ```
111
+ *Multiple items can be separated by commas.*
112
+
113
+ ## Command-line Arguments
114
+
115
+ ### `smoke-optimiser` (Generator)
116
+
117
+ | Argument | Description | Default |
118
+ | :--- | :--- | :--- |
119
+ | `--src` | The source directory or package to measure coverage for. | *Discovered* |
120
+ | `--iterations` | Number of times to run profiling to average timing data. | `1` |
121
+ | `--time-cap` | Maximum wall-clock runtime (seconds) of the smoke suite. | `15.0` |
122
+ | `--target-cov` | Target % of the full suite's branch coverage to achieve. | `100.0` |
123
+ | `--include` | Comma-separated list of tests, files, or markers to force include. | `[]` |
124
+ | `--exclude` | Comma-separated list of tests, files, or markers to force exclude. | `[]` |
125
+ | `--pytest-args` | Extra arguments forwarded to pytest during profiling. | `""` |
126
+ | `--output-json` | Path for the generated smoke suite definition file. | `.smoke_suite.json` |
127
+ | `--profile-only` | Run only the profiling phase and save intermediate data. | `False` |
128
+ | `--optimise-only` | Run only the optimisation phase using existing profile data. | `False` |
129
+ | `--allow-ordered` | Suppress warning when `pytest-randomly` is not installed. | `False` |
130
+
131
+ ### `pytest` (Plugin)
132
+
133
+ | Argument | Description | Default |
134
+ | :--- | :--- | :--- |
135
+ | `--smoke` | Activates the plugin; filters collection to the smoke suite. | `False` |
136
+ | `--smoke-file-path` | Path to the smoke suite JSON file to use. | `.smoke_suite.json` |
137
+
138
+ ## How it works
139
+
140
+ 1. **Profiling**: It runs your suite with `pytest-cov` and a custom hook to map every single branch execution to specific tests.
141
+ 2. **Analysis**: It calculates the "efficiency" of every test (New Branches Covered / Duration).
142
+ 3. **Greedy Selection**: It iteratively picks the most efficient test until your coverage target or time cap is reached.
143
+ 4. **Redundancy Reporting**: It identifies "Coverage-equivalent groups" — sets of tests that cover the exact same logic.
@@ -0,0 +1,90 @@
1
+ # smoke-optimiser
2
+
3
+ `smoke-optimiser` is a tool and pytest plugin that analyses your test suite to produce a minimal **smoke suite** — a subset of tests that delivers maximum code coverage in minimum wall-clock time.
4
+
5
+ It helps you find the "bang for buck" sweet spot: for example, achieving 80% of your total branch coverage in only 5% of the total runtime.
6
+
7
+ ## Installation
8
+
9
+ Add `smoke-optimiser` as a development dependency in your project:
10
+
11
+ ```bash
12
+ uv add --dev smoke-optimiser
13
+ ```
14
+
15
+ Or with pip:
16
+
17
+ ```bash
18
+ pip install smoke-optimiser
19
+ ```
20
+
21
+ This will make the `smoke-optimiser` command available in your environment and register the pytest plugin automatically.
22
+
23
+ ## Quickstart
24
+
25
+ 1. **Generate the smoke suite**:
26
+ Run the optimiser in your project root. It will automatically detect your source code and profile your tests.
27
+ ```bash
28
+ uv run smoke-optimiser
29
+ ```
30
+
31
+ 2. **Run the smoke suite**:
32
+ Use the `--smoke` flag with pytest to run only the selected high-value tests.
33
+ ```bash
34
+ uv run pytest --smoke
35
+ ```
36
+
37
+ ## Common Usages
38
+
39
+ ### Custom Efficiency Targets
40
+ By default, the tool tries to get maximum coverage within a 15-second time cap. You can tighten these bounds:
41
+ ```bash
42
+ # Aim for 80% coverage, but stop if it takes longer than 5 seconds
43
+ uv run smoke-optimiser --target-cov=80 --time-cap=5
44
+ ```
45
+
46
+ ### Stabilising Timing Data
47
+ Test execution times can vary. Use `--iterations` to run the suite multiple times and average the results for a more stable smoke suite:
48
+ ```bash
49
+ uv run smoke-optimiser --iterations=3
50
+ ```
51
+
52
+ ### Mandatory Inclusion/Exclusion
53
+ Force certain tests (or markers) to be included or excluded from the smoke suite:
54
+ ```bash
55
+ # Always include authentication tests, but exclude anything marked as 'slow'
56
+ uv run smoke-optimiser --include="tests/test_auth.py" --exclude="@pytest.mark.slow"
57
+ ```
58
+ *Multiple items can be separated by commas.*
59
+
60
+ ## Command-line Arguments
61
+
62
+ ### `smoke-optimiser` (Generator)
63
+
64
+ | Argument | Description | Default |
65
+ | :--- | :--- | :--- |
66
+ | `--src` | The source directory or package to measure coverage for. | *Discovered* |
67
+ | `--iterations` | Number of times to run profiling to average timing data. | `1` |
68
+ | `--time-cap` | Maximum wall-clock runtime (seconds) of the smoke suite. | `15.0` |
69
+ | `--target-cov` | Target % of the full suite's branch coverage to achieve. | `100.0` |
70
+ | `--include` | Comma-separated list of tests, files, or markers to force include. | `[]` |
71
+ | `--exclude` | Comma-separated list of tests, files, or markers to force exclude. | `[]` |
72
+ | `--pytest-args` | Extra arguments forwarded to pytest during profiling. | `""` |
73
+ | `--output-json` | Path for the generated smoke suite definition file. | `.smoke_suite.json` |
74
+ | `--profile-only` | Run only the profiling phase and save intermediate data. | `False` |
75
+ | `--optimise-only` | Run only the optimisation phase using existing profile data. | `False` |
76
+ | `--allow-ordered` | Suppress warning when `pytest-randomly` is not installed. | `False` |
77
+
78
+ ### `pytest` (Plugin)
79
+
80
+ | Argument | Description | Default |
81
+ | :--- | :--- | :--- |
82
+ | `--smoke` | Activates the plugin; filters collection to the smoke suite. | `False` |
83
+ | `--smoke-file-path` | Path to the smoke suite JSON file to use. | `.smoke_suite.json` |
84
+
85
+ ## How it works
86
+
87
+ 1. **Profiling**: It runs your suite with `pytest-cov` and a custom hook to map every single branch execution to specific tests.
88
+ 2. **Analysis**: It calculates the "efficiency" of every test (New Branches Covered / Duration).
89
+ 3. **Greedy Selection**: It iteratively picks the most efficient test until your coverage target or time cap is reached.
90
+ 4. **Redundancy Reporting**: It identifies "Coverage-equivalent groups" — sets of tests that cover the exact same logic.
@@ -0,0 +1,140 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "smoke-optimiser"
7
+ version = "0.1.0"
8
+ requires-python = ">=3.12"
9
+ description = "A Python CLI tool and pytest plugin to identify and run a minimal smoke test suite."
10
+ readme = "README.md"
11
+ license = { file = "LICENSE" }
12
+ authors = [
13
+ { name = "Mike Lawther", email = "mike.lawther@gmail.com" }
14
+ ]
15
+ keywords = ["pytest", "testing", "smoke-test", "coverage", "optimisation"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Software Development :: Testing",
25
+ ]
26
+ dependencies = [
27
+ "typer~=0.9",
28
+ "pydantic~=2.0",
29
+ "coverage~=7.0",
30
+ "pytest~=9.0",
31
+ "pytest-cov~=7.0",
32
+ "ijson~=3.5",
33
+ ]
34
+
35
+ [project.urls]
36
+ "Homepage" = "https://github.com/lawther/smoke-optimiser"
37
+ "Repository" = "https://github.com/lawther/smoke-optimiser"
38
+ "Bug Tracker" = "https://github.com/lawther/smoke-optimiser/issues"
39
+
40
+ [project.optional-dependencies]
41
+ machine = ["psutil~=7.0"]
42
+ dev = ["ruff~=0.15", "pytest"]
43
+
44
+ [project.scripts]
45
+ smoke-optimiser = "smoke_optimiser.__main__:main"
46
+
47
+ [project.entry-points."pytest11"]
48
+ smoke_optimiser = "smoke_optimiser.plugin"
49
+
50
+ [tool.ruff]
51
+ line-length = 120
52
+ # these are explanation of the rules that are selected:
53
+ # B: Basic checks
54
+ # C4: Code complexity checks
55
+ # D: Docstring checks
56
+ # E: Error checks
57
+ # F: Formatting checks
58
+ # I: Import checks
59
+ # S: Bandit checks
60
+ # ICN: Import convention checks
61
+ # PGH: Python grammar checks
62
+ # PL: Pylint checks
63
+ # RUF: Ruff checks
64
+ # UP: Unused code checks
65
+ # ANN: Type annotation checks
66
+ # ASYNC: Async code checks
67
+ # SIM: Simple checks
68
+ # LOG: Logging checks
69
+ lint.select = [
70
+ "B",
71
+ "C4",
72
+ "D",
73
+ "E",
74
+ "F",
75
+ "I",
76
+ "S",
77
+ "ICN",
78
+ "PGH",
79
+ "PL",
80
+ "RUF",
81
+ "UP",
82
+ "ANN",
83
+ "ASYNC",
84
+ "SIM",
85
+ "LOG",
86
+ ]
87
+ lint.ignore = [
88
+ # pydocstyle
89
+ # D1: Missing docstring in public module
90
+ "D100",
91
+ # D2: Missing docstring in public package
92
+ "D104",
93
+ # D3: Missing docstring in public function
94
+ "D103",
95
+ # D4: Missing docstring in public package
96
+ "D107",
97
+ # D5: Missing docstring in public method
98
+ "D102",
99
+ # D6: Missing docstring in public class
100
+ "D101",
101
+ # D7: Missing docstring in __init__
102
+ "D107",
103
+ # pylint
104
+ # PLR09: Too many <...>
105
+ "PLR0912",
106
+ "PLR0913",
107
+ "PLR0915",
108
+ ]
109
+ lint.per-file-ignores = { "tests/*" = ["S101"] }
110
+ lint.pydocstyle.convention = "google"
111
+
112
+ [tool.pytest.ini_options]
113
+ testpaths = ["tests"]
114
+ pythonpath = ["."]
115
+ timeout = 10
116
+ filterwarnings = [
117
+ "error::RuntimeWarning",
118
+ "error::pytest.PytestUnraisableExceptionWarning",
119
+ "error::pytest_cov.engine.CentralCovContextWarning",
120
+ "error::coverage.exceptions.CoverageWarning",
121
+ ]
122
+
123
+ [tool.hatch.build.targets.sdist]
124
+ exclude = [
125
+ "/.claude",
126
+ "/CLAUDE.md",
127
+ "/GEMINI.md",
128
+ "/PRD-smoke-optimiser.md",
129
+ "/uv.lock",
130
+ "/tests",
131
+ ]
132
+
133
+ [dependency-groups]
134
+ dev = [
135
+ "psutil>=7.2.2",
136
+ "pytest-randomly~=4.0",
137
+ "pytest-timeout~=2.4",
138
+ "ruff>=0.15.4",
139
+ "ty>=0.0.25",
140
+ ]
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"