testpulse 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.
- testpulse-0.1.0/.gitignore +49 -0
- testpulse-0.1.0/LICENSE +21 -0
- testpulse-0.1.0/PKG-INFO +432 -0
- testpulse-0.1.0/README.md +407 -0
- testpulse-0.1.0/deployment.env +1 -0
- testpulse-0.1.0/hatch_build.py +49 -0
- testpulse-0.1.0/pyproject.toml +95 -0
- testpulse-0.1.0/scripts/bootstrap_deployment_env.sh +65 -0
- testpulse-0.1.0/scripts/deployment_env.py +18 -0
- testpulse-0.1.0/scripts/deployment_environment_verification.py +16 -0
- testpulse-0.1.0/scripts/sync_project_name.py +51 -0
- testpulse-0.1.0/scripts/verify_deployment_environment.py +21 -0
- testpulse-0.1.0/scripts/write_deployment_env.py +32 -0
- testpulse-0.1.0/src/testpulse/__init__.py +110 -0
- testpulse-0.1.0/src/testpulse/_distribution.py +3 -0
- testpulse-0.1.0/src/testpulse/_entry.py +13 -0
- testpulse-0.1.0/src/testpulse/api.py +121 -0
- testpulse-0.1.0/src/testpulse/auth_store.py +148 -0
- testpulse-0.1.0/src/testpulse/bundled_deployment.env +1 -0
- testpulse-0.1.0/src/testpulse/ci_stats.py +166 -0
- testpulse-0.1.0/src/testpulse/citations.py +115 -0
- testpulse-0.1.0/src/testpulse/cli.py +350 -0
- testpulse-0.1.0/src/testpulse/config.py +93 -0
- testpulse-0.1.0/src/testpulse/deployment_verify.py +133 -0
- testpulse-0.1.0/src/testpulse/explanations.py +164 -0
- testpulse-0.1.0/src/testpulse/flakiness.py +129 -0
- testpulse-0.1.0/src/testpulse/git_diff.py +51 -0
- testpulse-0.1.0/src/testpulse/health.py +35 -0
- testpulse-0.1.0/src/testpulse/heuristics.py +64 -0
- testpulse-0.1.0/src/testpulse/llm_client.py +89 -0
- testpulse-0.1.0/src/testpulse/paths.py +14 -0
- testpulse-0.1.0/src/testpulse/py.typed +0 -0
- testpulse-0.1.0/src/testpulse/pytest_plugin.py +17 -0
- testpulse-0.1.0/src/testpulse/recommend.py +105 -0
- testpulse-0.1.0/src/testpulse/recording.py +143 -0
- testpulse-0.1.0/src/testpulse/regret_hits.py +83 -0
- testpulse-0.1.0/src/testpulse/report.py +282 -0
- testpulse-0.1.0/src/testpulse/risk.py +110 -0
- testpulse-0.1.0/src/testpulse/store.py +400 -0
- testpulse-0.1.0/src/testpulse/terminal_ui.py +44 -0
- testpulse-0.1.0/src/testpulse/web_app.py +173 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Environment
|
|
2
|
+
.env
|
|
3
|
+
.env.*
|
|
4
|
+
!.env.example
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.py[cod]
|
|
9
|
+
*$py.class
|
|
10
|
+
*.so
|
|
11
|
+
.Python
|
|
12
|
+
build/
|
|
13
|
+
develop-eggs/
|
|
14
|
+
dist/
|
|
15
|
+
downloads/
|
|
16
|
+
eggs/
|
|
17
|
+
.eggs/
|
|
18
|
+
lib/
|
|
19
|
+
lib64/
|
|
20
|
+
parts/
|
|
21
|
+
sdist/
|
|
22
|
+
var/
|
|
23
|
+
wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
|
|
28
|
+
# Virtual environments
|
|
29
|
+
.venv/
|
|
30
|
+
|
|
31
|
+
# Test / tooling
|
|
32
|
+
.pytest_cache/
|
|
33
|
+
.mypy_cache/
|
|
34
|
+
.ruff_cache/
|
|
35
|
+
.coverage
|
|
36
|
+
htmlcov/
|
|
37
|
+
.hypothesis/
|
|
38
|
+
|
|
39
|
+
# TestPulse local data
|
|
40
|
+
.testpulse/
|
|
41
|
+
|
|
42
|
+
# IDE
|
|
43
|
+
.idea/
|
|
44
|
+
.vscode/
|
|
45
|
+
*.swp
|
|
46
|
+
*~
|
|
47
|
+
|
|
48
|
+
# OS
|
|
49
|
+
.DS_Store
|
testpulse-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TestPulse contributors
|
|
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.
|
testpulse-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: testpulse
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Time your tests, optimize your outcomes.
|
|
5
|
+
Project-URL: Homepage, https://github.com/MaruthiCharan2403/testpulse
|
|
6
|
+
Project-URL: Documentation, https://github.com/MaruthiCharan2403/testpulse#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/MaruthiCharan2403/testpulse
|
|
8
|
+
Project-URL: Issues, https://github.com/MaruthiCharan2403/testpulse/issues
|
|
9
|
+
Author: TestPulse contributors
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: cli,developer-productivity,pytest,testing
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Testing
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# TestPulse
|
|
27
|
+
|
|
28
|
+
**Time your tests, optimize your outcomes.**
|
|
29
|
+
|
|
30
|
+
Turn your current git diff into a single, evidence-based pytest command by learning from locally recorded test timings, failures, and which tests historically correlate with your changes; quantify flakiness and produce explicit risk guidance for skipping tests.
|
|
31
|
+
|
|
32
|
+
TestPulse is a **Python library and CLI** (`testtime`). It stores history in a **local SQLite database** (stdlib only—no runtime dependencies). There is **no web application** and **no HTTP API server**; you use pytest, the terminal, and optional library imports.
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
### Local test recording (pytest plugin)
|
|
37
|
+
|
|
38
|
+
**What it does:** Records each test’s nodeid, duration, pass/fail/skip/xfail outcome, and hashes of captured stdout/stderr into SQLite. Associates the run with the current git diff paths and updates a **change↔test co-occurrence** graph for later recommendations.
|
|
39
|
+
|
|
40
|
+
**How to use:**
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
export PYPI_PACKAGE_NAME=testpulse
|
|
44
|
+
pytest --testpulse-record
|
|
45
|
+
pytest --testpulse-record --testpulse-ci # quiet CI mode
|
|
46
|
+
pytest --testpulse-record --testpulse-store /path/to/store.sqlite3
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Default store: `.testpulse/store.sqlite3` (override with `TESTPULSE_STORE`).
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
### Recommended pytest command (`testtime run`)
|
|
54
|
+
|
|
55
|
+
**What it does:** Reads your git diff (or a commit range), maps changed files to pytest targets via **static heuristics** and **learned co-occurrence**, weights candidates by **expected reliability** from flakiness history, and prints one exact `pytest ...` line.
|
|
56
|
+
|
|
57
|
+
**How to use:**
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
testtime run
|
|
61
|
+
testtime run --commit-range main..HEAD
|
|
62
|
+
testtime run --min-reliability 0.8 --store .testpulse/store.sqlite3
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Copy the printed command and run it in your shell.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
### Flakiness estimates (Bayesian smoothing)
|
|
70
|
+
|
|
71
|
+
**What it does:** Per-test **flaky probability** (inconsistent pass/fail history), **confidence band** (low / medium / high by sample size), and **expected reliability** for ranking and skip-risk logic.
|
|
72
|
+
|
|
73
|
+
**How to use (library):**
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from testpulse import Store, flakiness_estimates, flakiness_for_nodeid
|
|
77
|
+
from testpulse.paths import default_store_path
|
|
78
|
+
|
|
79
|
+
store = Store.open(default_store_path())
|
|
80
|
+
try:
|
|
81
|
+
for est in flakiness_estimates(store):
|
|
82
|
+
print(est.nodeid, est.flaky_probability, est.expected_reliability)
|
|
83
|
+
finally:
|
|
84
|
+
store.close()
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Also surfaced in `testtime report` and in recommendation / risk scoring.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
### Skip and regret risk
|
|
92
|
+
|
|
93
|
+
**What it does:** For tests **not** included in the recommendation, computes a **regret risk** score (likelihood of missing a real failure) and an aggregate **risk level**: `low`, `medium`, `high`, or `critical`.
|
|
94
|
+
|
|
95
|
+
**How to use (library):**
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from testpulse import compute_skip_risk_report
|
|
99
|
+
|
|
100
|
+
report = compute_skip_risk_report(["src/mod.py"], store)
|
|
101
|
+
print(report.aggregate_level, report.max_regret_risk)
|
|
102
|
+
for item in report.excluded:
|
|
103
|
+
print(item.target, item.regret_risk)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Shown in `testtime report` (menu **6**) and `--no-interactive` skip-risk section.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### Regret hits
|
|
111
|
+
|
|
112
|
+
**What it does:** Across **consecutive recorded runs**, finds tests that **passed** in an earlier run but **failed** in a later run when diffs link through shared changed paths or learned co-occurrence—tests that “should have caught” a later breakage.
|
|
113
|
+
|
|
114
|
+
**How to use (library):**
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from testpulse import regret_hits
|
|
118
|
+
|
|
119
|
+
for hit in regret_hits(store):
|
|
120
|
+
print(hit.nodeid, hit.prior_run_id, hit.later_run_id, hit.linking_path)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Listed in `testtime report` (menu **5**).
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
### Interactive report (`testtime report`)
|
|
128
|
+
|
|
129
|
+
**What it does:** Terminal menu to explore:
|
|
130
|
+
|
|
131
|
+
1. Slowest tests (average duration)
|
|
132
|
+
2. Most flaky tests
|
|
133
|
+
3. Most change-relevant tests (for current diff)
|
|
134
|
+
4. Top drivers of today’s recommendation
|
|
135
|
+
5. Regret hits
|
|
136
|
+
6. Skip / regret risk summary
|
|
137
|
+
|
|
138
|
+
**Filter:** press **`f`** to filter by pytest **marker** or **class** name (parsed from stored markers and nodeids).
|
|
139
|
+
|
|
140
|
+
**How to use:**
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
testtime report
|
|
144
|
+
testtime report --no-interactive # drivers + skip risk only, then exit
|
|
145
|
+
testtime report --commit-range HEAD~5..HEAD
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
### Explanations with local citations (`testtime explain`)
|
|
151
|
+
|
|
152
|
+
**What it does:** For each included/excluded candidate, explains **why** and which **changed files** relate, with **citations** pointing at rows in your SQLite history (heuristic maps, co-occurrence links, outcome counts)—no external datasets.
|
|
153
|
+
|
|
154
|
+
**Optional LLM:** With `--llm` and your own API key, adds a short narrative constrained to those citations only.
|
|
155
|
+
|
|
156
|
+
**How to use:**
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
testtime explain
|
|
160
|
+
testtime explain --llm
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Exit code **3** if `--llm` is requested but no API key is configured.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### CI stats export (de-identified)
|
|
168
|
+
|
|
169
|
+
**What it does:** Exports **aggregated** timing and outcome stats (run counts, outcome totals, duration percentiles, flaky buckets). **No** test logs, **no** raw nodeids in the JSON (only counts and hashed identifiers internally). Optional HTTPS upload when URL and token env vars are set.
|
|
170
|
+
|
|
171
|
+
**How to use:**
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
testtime ci-export --output testpulse-ci-stats.json
|
|
175
|
+
testtime ci-export --upload
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
GitHub Actions workflow `.github/workflows/testpulse-stats.yml` records (optional), exports JSON, and uploads an artifact.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
### Brand assets
|
|
183
|
+
|
|
184
|
+
Static logo and favicon for documentation and packaging: `frontend/public/logo.svg`, `frontend/public/favicon.svg` (primary `#003366`, accent `#FFCC00`). Not a runnable UI.
|
|
185
|
+
|
|
186
|
+
## Pages
|
|
187
|
+
|
|
188
|
+
TestPulse does **not** ship a browser frontend or routes. User-facing surfaces are:
|
|
189
|
+
|
|
190
|
+
| Surface | What you see |
|
|
191
|
+
|---------|----------------|
|
|
192
|
+
| **Terminal — `testtime report`** | Menu of report sections; optional marker/class filter prompts |
|
|
193
|
+
| **Terminal — other commands** | Single-shot stdout (pytest command, explanations, CI JSON path) |
|
|
194
|
+
| **Pytest session** | Optional line `testpulse: recorded N test(s)` when recording (hidden in `--testpulse-ci`) |
|
|
195
|
+
|
|
196
|
+
There are no `/login`, settings, or dashboard URLs—only the CLI and library APIs below.
|
|
197
|
+
|
|
198
|
+
## API
|
|
199
|
+
|
|
200
|
+
There is **no REST backend**. The stable **Python public API** is exported from `testpulse` (defined in `testpulse.api`). Open a `Store` unless noted.
|
|
201
|
+
|
|
202
|
+
### Package metadata
|
|
203
|
+
|
|
204
|
+
| Symbol | Purpose |
|
|
205
|
+
|--------|---------|
|
|
206
|
+
| `VERSION`, `tagline` | Version string and product tagline |
|
|
207
|
+
| `distribution_name()` | Read `PYPI_PACKAGE_NAME` |
|
|
208
|
+
| `import_package_name()` | Distribution name with `-` → `_` for imports |
|
|
209
|
+
|
|
210
|
+
### Recording and storage
|
|
211
|
+
|
|
212
|
+
| Symbol | Purpose |
|
|
213
|
+
|--------|---------|
|
|
214
|
+
| `Store.open(path)` / `Store.connect_memory()` | SQLite store (`testpulse.store`) |
|
|
215
|
+
| Pytest plugin | Entry point `testpulse` — flags `--testpulse-record`, `--testpulse-ci`, `--testpulse-store` |
|
|
216
|
+
|
|
217
|
+
### Recommendations
|
|
218
|
+
|
|
219
|
+
| Symbol | Purpose |
|
|
220
|
+
|--------|---------|
|
|
221
|
+
| `recommend_pytest_command(changed_paths, store, min_reliability=0.0)` | One `pytest ...` shell line |
|
|
222
|
+
|
|
223
|
+
### Flakiness
|
|
224
|
+
|
|
225
|
+
| Symbol | Purpose |
|
|
226
|
+
|--------|---------|
|
|
227
|
+
| `flakiness_estimates(store)` | All tests with `FlakinessEstimate` |
|
|
228
|
+
| `flakiness_for_nodeid(store, nodeid)` | Single test or `None` |
|
|
229
|
+
| `flaky_probability(passes, fails)` | Smoothed flaky score |
|
|
230
|
+
| `beta_smoothed_pass_rate(passes, fails)` | Posterior mean pass rate |
|
|
231
|
+
| `confidence_band(passes, fails)` | `low` / `medium` / `high` |
|
|
232
|
+
| `estimate_from_counts(OutcomeCounts)` | Build `FlakinessEstimate` |
|
|
233
|
+
|
|
234
|
+
### Skip / regret risk
|
|
235
|
+
|
|
236
|
+
| Symbol | Purpose |
|
|
237
|
+
|--------|---------|
|
|
238
|
+
| `compute_skip_risk_report(changed_paths, store, …)` | `SkipRiskReport` with excluded tests |
|
|
239
|
+
| `regret_risk(relevance, fail_probability, max_relevance=…)` | Single regret score |
|
|
240
|
+
| `aggregate_risk_level(excluded)` | Overall `RiskLevel` |
|
|
241
|
+
|
|
242
|
+
### Regret hits
|
|
243
|
+
|
|
244
|
+
| Symbol | Purpose |
|
|
245
|
+
|--------|---------|
|
|
246
|
+
| `regret_hits(store)` | List of `RegretHit` across consecutive runs |
|
|
247
|
+
|
|
248
|
+
### Reports
|
|
249
|
+
|
|
250
|
+
| Symbol | Purpose |
|
|
251
|
+
|--------|---------|
|
|
252
|
+
| `build_report_snapshot(changed_paths, store, …)` | `ReportSnapshot` for all sections |
|
|
253
|
+
| `run_interactive_report(changed_paths, store, …)` | Interactive menu |
|
|
254
|
+
| `render_report_view(snapshot, view, filter, …)` | Print one section (`slowest`, `flaky`, `relevant`, `drivers`, `regret`, `skip_risk`) |
|
|
255
|
+
| `ReportFilter`, `matches_filter`, `nodeid_class` | Marker/class filtering helpers |
|
|
256
|
+
|
|
257
|
+
### Explanations and citations
|
|
258
|
+
|
|
259
|
+
| Symbol | Purpose |
|
|
260
|
+
|--------|---------|
|
|
261
|
+
| `explain_recommendation(changed_paths, store, …)` | Tuple of `TestDecisionExplanation` |
|
|
262
|
+
| `explain_with_optional_llm(…, client)` | Explanations plus optional LLM text |
|
|
263
|
+
| `build_test_explanation(store, paths, target, decision)` | One explanation |
|
|
264
|
+
| `gather_citations(store, paths, target)` | `HistoryCitation` rows from SQLite |
|
|
265
|
+
| `format_explanations_text(explanations)` | Plain-text rendering |
|
|
266
|
+
| `enhance_explanations_with_llm(…, client)` | LLM narrative from citations only |
|
|
267
|
+
|
|
268
|
+
### LLM (optional, caller credentials)
|
|
269
|
+
|
|
270
|
+
| Symbol | Purpose |
|
|
271
|
+
|--------|---------|
|
|
272
|
+
| `llm_config_from_env()` | `LLMConfig` or `None` |
|
|
273
|
+
| `client_from_env()` | `OpenAICompatibleClient` or `None` |
|
|
274
|
+
| `OpenAICompatibleClient`, `LLMConfig` | OpenAI-compatible HTTP client (stdlib) |
|
|
275
|
+
| `LLMCompletionError` | Request / response errors |
|
|
276
|
+
|
|
277
|
+
### CI stats
|
|
278
|
+
|
|
279
|
+
| Symbol | Purpose |
|
|
280
|
+
|--------|---------|
|
|
281
|
+
| `build_aggregated_ci_stats(store)` | `AggregatedCiStats` |
|
|
282
|
+
| `export_ci_stats_json(store)` | JSON string |
|
|
283
|
+
| `write_ci_stats_file(path, store)` | Write JSON file |
|
|
284
|
+
| `upload_ci_stats(store, endpoint_url=…, token=…)` | HTTPS POST |
|
|
285
|
+
| `upload_ci_stats_from_env(store)` | Upload if env configured; else `False` |
|
|
286
|
+
| `hash_nodeid(nodeid)` | De-identified hash (not for display of raw ids in export) |
|
|
287
|
+
| `CiUploadError` | Upload failures |
|
|
288
|
+
|
|
289
|
+
### CLI entry point
|
|
290
|
+
|
|
291
|
+
| Command | Purpose |
|
|
292
|
+
|---------|---------|
|
|
293
|
+
| `testtime run` | Print recommended pytest command |
|
|
294
|
+
| `testtime report` | Interactive or `--no-interactive` report |
|
|
295
|
+
| `testtime explain` | Citations; optional `--llm` |
|
|
296
|
+
| `testtime ci-export` | De-identified JSON; optional `--upload` |
|
|
297
|
+
| `testtime health` | Deployment health JSON (`status`, `version`, `distribution`) |
|
|
298
|
+
|
|
299
|
+
Exit codes: **0** success, **1** failure, **2** usage, **3** missing optional config.
|
|
300
|
+
|
|
301
|
+
## Configuration
|
|
302
|
+
|
|
303
|
+
Set variables in the environment (names only in `.env.example`; do not commit values).
|
|
304
|
+
|
|
305
|
+
| Variable | Required | Purpose |
|
|
306
|
+
|----------|----------|---------|
|
|
307
|
+
| `PYPI_PACKAGE_NAME` | No (recommended in CI) | Overrides distribution name; defaults to installed package metadata |
|
|
308
|
+
| `TESTPULSE_STORE` | No | SQLite path (default `.testpulse/store.sqlite3`) |
|
|
309
|
+
|
|
310
|
+
### Third-party keys (optional; you provide your own)
|
|
311
|
+
|
|
312
|
+
| Variable | Feature | Where to get it |
|
|
313
|
+
|----------|---------|-----------------|
|
|
314
|
+
| `TESTPULSE_LLM_API_KEY` or `OPENAI_API_KEY` | LLM explanations (`testtime explain --llm`) | Your OpenAI or compatible provider account |
|
|
315
|
+
| `TESTPULSE_LLM_BASE_URL` | LLM API base URL | Provider docs (default OpenAI v1 URL) |
|
|
316
|
+
| `TESTPULSE_LLM_MODEL` | Chat model id | Provider model list (e.g. `gpt-4o-mini`) |
|
|
317
|
+
| `TESTPULSE_STATS_UPLOAD_URL` | CI stats HTTPS upload | Your webhook or ingest endpoint |
|
|
318
|
+
| `TESTPULSE_STATS_UPLOAD_TOKEN` | Bearer token for upload | Same service that issued the URL |
|
|
319
|
+
| `TESTPULSE_STATS_SALT` | Salt for hashed ids in CI export | Any secret string you choose (default internal salt if unset) |
|
|
320
|
+
|
|
321
|
+
GitHub Actions can store upload URL/token as repository secrets for `.github/workflows/testpulse-stats.yml`.
|
|
322
|
+
|
|
323
|
+
## Deployment contract
|
|
324
|
+
|
|
325
|
+
After build, run `bash scripts/bootstrap_deployment_env.sh` (or use the manifest `install` command) so
|
|
326
|
+
`PYPI_PACKAGE_NAME` is set when infrastructure did not inject it. Smoke checks:
|
|
327
|
+
`python scripts/verify_deployment_environment.py` and `testtime health`.
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
TestPulse ships as a **PyPI package** with a CLI-first workflow and an optional stdlib HTTP API for deployment checks:
|
|
331
|
+
|
|
332
|
+
- `testtime health` — CLI health JSON
|
|
333
|
+
- `testtime serve` — `GET /api/health`, `POST /api/auth/sign-up`, `POST /api/auth/sign-in`, protected `GET /api/me` (set `JWT_SECRET`)
|
|
334
|
+
- `python scripts/deployment_environment_verification.py` — Factory env verification (loads committed `deployment.env`)
|
|
335
|
+
|
|
336
|
+
| Contract item | TestPulse behavior |
|
|
337
|
+
|---------------|-------------------|
|
|
338
|
+
| Install / build | `uv sync`, `uv build`; CI runs the same checks in `.github/workflows/ci.yml` |
|
|
339
|
+
| Runtime config | `PYPI_PACKAGE_NAME` optional at runtime; falls back to installed wheel / `pyproject.toml` name |
|
|
340
|
+
| Health / smoke | `testtime health` (JSON), `testtime --help`, and `uv run pytest` in CI |
|
|
341
|
+
| Optional upload | De-identified CI stats only when you set upload URL + token |
|
|
342
|
+
|
|
343
|
+
## Run it locally
|
|
344
|
+
|
|
345
|
+
Requires **Python 3.11+**, **git**, and **uv**.
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
export PYPI_PACKAGE_NAME=testpulse
|
|
349
|
+
python3 scripts/sync_project_name.py
|
|
350
|
+
uv sync
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Record history and get a recommendation:
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
uv run pytest --testpulse-record
|
|
357
|
+
uv run testtime run
|
|
358
|
+
uv run testtime report --no-interactive
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Development checks:
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
uv run ruff check .
|
|
365
|
+
uv run mypy src
|
|
366
|
+
uv run pytest
|
|
367
|
+
uv build
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Install the console script: `uv sync` exposes **`testtime`** in the project virtualenv.
|
|
371
|
+
|
|
372
|
+
## Architecture
|
|
373
|
+
|
|
374
|
+
| Layer | Technology |
|
|
375
|
+
|-------|------------|
|
|
376
|
+
| Frontend | None (static SVG brand assets only) |
|
|
377
|
+
| Backend / HTTP API | None |
|
|
378
|
+
| Database | Local SQLite via stdlib (`testpulse.store`) |
|
|
379
|
+
| Vector database | None |
|
|
380
|
+
| Package manager | uv |
|
|
381
|
+
| CLI | `testtime` → `testpulse.cli` |
|
|
382
|
+
| Pytest integration | Entry point `testpulse` → `testpulse.recording` |
|
|
383
|
+
|
|
384
|
+
### Repository layout
|
|
385
|
+
|
|
386
|
+
```text
|
|
387
|
+
src/testpulse/
|
|
388
|
+
api.py # Public re-exports
|
|
389
|
+
cli.py # testtime subcommands
|
|
390
|
+
store.py # SQLite schema and queries
|
|
391
|
+
recording.py # Pytest hooks
|
|
392
|
+
git_diff.py # git diff → changed paths
|
|
393
|
+
heuristics.py # Static path → test mapping
|
|
394
|
+
recommend.py # Merge heuristics + co-occurrence → pytest command
|
|
395
|
+
flakiness.py # Bayesian flaky scores
|
|
396
|
+
risk.py # Skip / regret risk
|
|
397
|
+
regret_hits.py # Temporal pass-then-fail analysis
|
|
398
|
+
report.py # Report snapshot and TUI
|
|
399
|
+
citations.py # SQLite citation records
|
|
400
|
+
explanations.py # Template + optional LLM explanations
|
|
401
|
+
llm_client.py # Optional OpenAI-compatible client
|
|
402
|
+
ci_stats.py # De-identified CI export/upload
|
|
403
|
+
paths.py, config.py # Store path and PYPI_PACKAGE_NAME
|
|
404
|
+
tests/ # Unit and CLI tests
|
|
405
|
+
scripts/ # sync_project_name.py for PYPI_PACKAGE_NAME → pyproject name
|
|
406
|
+
.github/workflows/ # CI and optional TestPulse stats artifact
|
|
407
|
+
frontend/public/ # logo.svg, favicon.svg
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### How components connect
|
|
411
|
+
|
|
412
|
+
```text
|
|
413
|
+
pytest --testpulse-record
|
|
414
|
+
│
|
|
415
|
+
▼
|
|
416
|
+
recording.py ──► Store (SQLite) ◄── git_diff.py (changed paths per run)
|
|
417
|
+
│ │
|
|
418
|
+
│ ├── learn change_test_links (co-occurrence)
|
|
419
|
+
│ └── test_results (timing, outcome, marker names)
|
|
420
|
+
│
|
|
421
|
+
testtime run / report / explain / ci-export
|
|
422
|
+
│
|
|
423
|
+
├── recommend.py ← heuristics + co-occurrence + flakiness
|
|
424
|
+
├── risk.py, regret_hits.py, report.py, explanations.py
|
|
425
|
+
└── ci_stats.py → JSON artifact (optional HTTPS upload)
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
All learning and recommendations use **your machine’s SQLite file** only; nothing is sent unless you opt into LLM or CI upload with **your** credentials.
|
|
429
|
+
|
|
430
|
+
## License
|
|
431
|
+
|
|
432
|
+
MIT
|