Skip to main content

Analyze test strategy across SaaS products with coverage metrics

Project description

mentistest-coverage

Strategic test-suite analyser. Looks at a Python project's source tree, test tree, JUnit XML and (optionally) an OpenAPI spec, and produces a test-strategy report — not just a coverage percentage.

What it does that coverage.py doesn't:

  • Classifies every test as unit / integration / contract / e2e using markers, imports, fixtures and directory layout.
  • Weights coverage gaps by cyclomatic complexity and call-graph depth so the 20 functions that matter rank above the 200 that don't.
  • Correlates tests against the OpenAPI spec to flag uncovered endpoints.
  • Tracks results over time in a local SQLite history store with full git/CI provenance, and renders a trend dashboard per project.
  • Detects regressions between snapshots and can fail CI when a previously- covered symbol loses coverage.
  • Ships an opt-in AI second opinion (Claude Haiku → Sonnet escalation, gated to skip healthy projects — typical cost <£0.01/run).
  • Exports HTML (interactive), JSON (machine-readable) and PDF (board-ready) reports.
  • Ships a first-class GitHub Action that posts a coverage-delta PR comment and uploads artifacts.

Local-first. The analyser runs against filesystem paths; source code is never uploaded anywhere. The optional AI call sends a structured summary (file paths, coverage numbers, test names) — never the source itself.

Install

pip install mentistest-coverage

…or from a clone:

pip install -e .

Verify:

mentistest --help

Every PyPI release is built via OIDC trusted publishing and signed with Sigstore — see RELEASING.md for the full chain and verification recipe.

Quick start

A complete analysis with all the features wired in:

mentistest analyze my-service \
    --source ./src --tests ./tests \
    --junit ./reports/junit.xml \
    --api-spec ./openapi.yaml \
    --output-json ./report.json \
    --output-html ./report.html \
    --output-pdf  ./report.pdf \
    --track --compare-baseline --fail-on-regression \
    --detect-git \
    --ai

What that does:

Flag Effect
--source / -s Source directory (repeatable)
--tests / -t Test directory (repeatable)
--junit JUnit XML report path (repeatable)
--api-spec OpenAPI / Swagger spec — drives endpoint-coverage analysis
--coverage-xml Real coverage.xml from coverage.py (preferred over the fallback)
--output-json / -j JSON report path
--output-html / -H Interactive HTML dashboard path
--output-pdf / -P Board-ready PDF report path
--track / --no-track Save the run as a snapshot in the history DB
--db-path Override ~/.test-coverage/history.db
--compare-baseline Diff this run against the most recent snapshot
--regression-threshold N Allowable coverage drop in pp (default 0)
--fail-on-regression exit 1 if a regression is detected
--detect-git / --no-detect-git Capture commit, branch, CI run ID, provider
--ai Run the optional AI commentary (requires ANTHROPIC_API_KEY)
--test-type Default type label when no signal fires (unit/integration/contract/e2e)
--verbose / -v Debug logging

Server mode

mentistest serve --host 0.0.0.0 --port 8000

Because the /analyze endpoint reads arbitrary filesystem paths, serve refuses to bind a non-loopback host (anything other than 127.0.0.1/ ::1/localhost) unless TEST_COVERAGE_API_KEY or TEST_COVERAGE_ALLOWED_PATHS is configured. Pass --insecure to override on a trusted private network. The published Docker image binds 0.0.0.0 and sets TEST_COVERAGE_REQUIRE_SECURITY=1, so the container likewise won't start unprotected.

Then:

  • http://localhost:8000/docs — interactive OpenAPI docs for the REST API
  • http://localhost:8000/benchmarks — multi-project benchmark dashboard
  • http://localhost:8000/trends/<project> — single-project trend dashboard
  • http://localhost:8000/report/html?project_name=<name> — last-rendered report
  • http://localhost:8000/report/pdf?project_name=<name> — last-rendered PDF
  • http://localhost:8000/history/<project> — JSON list of snapshots
  • http://localhost:8000/regression/<project> — JSON regression diff

GitHub Action

- uses: HarryDouglas/test_coverage_tool@v1
  with:
    source_dirs: src
    test_dirs: tests
    junit_xml_paths: reports/junit.xml
    track_history: 'true'
    fail_on_regression: 'true'

Posts a PR comment with the coverage delta, uploads JSON + HTML reports as workflow artifacts, and fails the check if a symbol loses coverage. See action.yml for all inputs.

Action vs PyPI wheel: the action is its own distribution channel — referenced by git tag, not installed from PyPI. Under the hood it installs the wheel from PyPI and runs the CLI, so the two move together but version independently. The action's @v1 tag pins a major; minor / patch wheel updates roll through without a workflow change.

Security

If you're deploying the HTTP API beyond localhost, read SECURITY.md. Two env vars (TEST_COVERAGE_API_KEY and TEST_COVERAGE_ALLOWED_PATHS) enable bearer-token auth and filesystem path-traversal protection. Both are off by default so local, loopback-only use is unaffected — but the tool fails closed the moment it would be network-reachable: serve rejects a non-loopback --host (unless --insecure), and create_app refuses to start when TEST_COVERAGE_REQUIRE_SECURITY=1 (set in the Docker image) is configured without a protection layer.

To report a vulnerability, email harrydtrott@gmail.com — don't open a public issue.

Public benchmark suite

The tool ships with a 16+ repo OSS benchmark suite (flask, fastapi, pydantic, httpx, celery, mlflow, attrs, rich, schemathesis, dbt-core, alembic, …) used both as a smoke test and as a demo gallery for the consulting site.

make benchmark        # clone, test, analyse, render all projects
make benchmark-serve  # serve the rendered dashboard at localhost:8003/benchmarks

Outputs land in reports/benchmarks/<project>/ (HTML + JSON) and are git-ignored. Re-run after any classifier change to verify the public pyramid still looks sensible.

Library use

from pathlib import Path
from test_coverage_tool.core.analyzer import AnalysisEngine
from test_coverage_tool.core.git import detect_git_metadata
from test_coverage_tool.reports.html_generator import HTMLReportGenerator
from test_coverage_tool.reports.json_generator import JSONReportGenerator
from test_coverage_tool.history.store import HistoryStore

engine   = AnalysisEngine()
analysis = engine.analyze(
    project_name     = "my-service",
    source_dirs      = [Path("src")],
    test_dirs        = [Path("tests")],
    junit_xml_paths  = [Path("reports/junit.xml")],
    api_spec_path    = Path("openapi.yaml"),
    git_metadata     = detect_git_metadata(),
)
JSONReportGenerator().write(analysis, Path("report.json"))
HTMLReportGenerator().write(analysis, Path("report.html"))

# Persist a snapshot
HistoryStore(Path("history.db")).save("my-service", analysis_to_dict(analysis))

Configuration

Environment variables:

Variable Purpose
ANTHROPIC_API_KEY Enables --ai (otherwise the flag is a no-op)
TEST_COVERAGE_DB_PATH Override the default ~/.test-coverage/history.db location
MENTIS_BENCH_AI Set to 1 to opt-in to AI commentary on make benchmark

Development

make install     # install dev deps via uv
make test        # pytest --cov on unittests/
make lint        # ruff + mypy --strict + pylint
make benchmark   # rebuild the public benchmark dashboard

The repo uses ruff, mypy (--strict), pylint and pytest. The quality bar: zero ruff/mypy/pylint issues, pytest -x -q must pass before merge.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mentistest_coverage-0.2.2.tar.gz (257.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mentistest_coverage-0.2.2-py3-none-any.whl (300.9 kB view details)

Uploaded Python 3

File details

Details for the file mentistest_coverage-0.2.2.tar.gz.

File metadata

  • Download URL: mentistest_coverage-0.2.2.tar.gz
  • Upload date:
  • Size: 257.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mentistest_coverage-0.2.2.tar.gz
Algorithm Hash digest
SHA256 32ddfe49c8997405b5ce5b2db6ae620e7897277bb1cd1d28a6c4a635b76cf0ec
MD5 e0672c1ee22ba7044685ecb0b496bd89
BLAKE2b-256 b67c36d350affe50ee92c9f28fc8832b62f4ac63d662a5228002dddf61e4b96b

See more details on using hashes here.

Provenance

The following attestation bundles were made for mentistest_coverage-0.2.2.tar.gz:

Publisher: release.yml on HarryDouglas/test_coverage_tool

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mentistest_coverage-0.2.2-py3-none-any.whl.

File metadata

File hashes

Hashes for mentistest_coverage-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 1d916657d529e253f704a6a11d7cf1c40465caea9c68969fc92ac931bf62fc55
MD5 5be52ca1b5c44c65c91701441ba2b73c
BLAKE2b-256 ea8bcc7609b0fe1ba5678c34893badd25c24d3939d24f18b927bb7bae568664a

See more details on using hashes here.

Provenance

The following attestation bundles were made for mentistest_coverage-0.2.2-py3-none-any.whl:

Publisher: release.yml on HarryDouglas/test_coverage_tool

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page