Skip to main content

Analyze test strategy across SaaS products with coverage metrics

Project description

mentistest-coverage

PyPI Downloads

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
--config / -c Project manifest (mentistest.yaml); auto-detected when omitted
--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)
--coverage Real coverage report in any format (Cobertura, JaCoCo, Istanbul, SimpleCov, LCOV, Go) — auto-detected
--coverage-format Format of --coverage (auto default, or an explicit format)
--per-test-coverage Per-test coverage (Jest/Vitest Istanbul JSON map or a dir of Go labelled coverprofiles) — populates "tests touching this file" for JS/TS and Go
--per-test-format Format of --per-test-coverage (auto default, jest, vitest, go)
--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

Multi-repo projects: the manifest

When different types of test live in different places — unit tests in-repo, an e2e suite in a sibling repo, a Pact contract suite elsewhere — declare them once in a checked-in mentistest.yaml and run mentistest analyze with no other flags:

project: my-service
source: [src]
tests: [tests/unit, tests/integration]
external:
  - path: ../e2e-suite
    type: e2e
    attribution: endpoint        # match URL literals against the OpenAPI spec
    junit: [../e2e-suite/reports/junit.xml]
  - path: ../contract-tests
    type: contract
    attribution: none

CLI flags take precedence over manifest scalars; test locations are merged. Paths resolve relative to the manifest. See the manifest guide for the full schema.

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.3.0.tar.gz (329.9 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.3.0-py3-none-any.whl (382.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: mentistest_coverage-0.3.0.tar.gz
  • Upload date:
  • Size: 329.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.8

File hashes

Hashes for mentistest_coverage-0.3.0.tar.gz
Algorithm Hash digest
SHA256 615a82ca26031f2e405ff007e523b29abd9a8b465c11357d6f88f58682ac9c5c
MD5 e5babdc4fb5023046f95a61aba8e2528
BLAKE2b-256 0261c4b15bbfb583372ae27c14869b334f30de3371c4ecb46bd3e52ec71e7ac2

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mentistest_coverage-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5f86f2b023d507d9336778f0262f6e97b020e9812029087b1e094007d2506685
MD5 4d426a3a4f393a1f784a401b985404e3
BLAKE2b-256 42c9db80b3942b8250f0659fc0bda5572f4b9d760e5efd2a8522154e97d0078d

See more details on using hashes here.

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