Skip to content
AITesting|novetest
Main For Agents Products · novetest Products · Test Case Generator Products · novetest Console soon Docs · novetest Docs · novetest Console soon Blog Contact ★ Star on GitHub Get started →
Docs/Supported Languages
Audience

Supported Languages

The canonical happy path in Quick Start is language-agnostic. This page documents only what differs per language: detection markers, the (ecosystem, engine) identifier pair, the toolchain you need on PATH, the external command the adapter invokes (shown simplified — coverage runs add a few extra flags), and whether coverage is available.

novetest init detects which engine your project uses from workspace markers and pins it into .novetest/store.json; every later verb runs the pinned engine — nothing is re-detected at run time. In the common single-engine project you never pass an --engine flag; it exists for ambiguous roots (init --engine <name>) and one-off overrides (test|run --engine <name>, pin untouched).


The six supported engines

novetest ships six fully-wired native engine adapters. The engine string is exactly what appears as engine_name on your Run Record — note go-test, cargo-test, and xunit (the .NET engine is named xunit, not dotnet):

ecosystemengineMarker(s)Coverage
pythonpytestpyproject.toml, setup.py, setup.cfg, pytest.iniyes
javascript-typescriptjestpackage.jsonyes
javajunit (JUnit 5 Jupiter only)pom.xml, build.gradle, build.gradle.ktsyes
gogo-testgo.modno
rustcargo-test (nextest required)Cargo.tomlyes
dotnetxunit (xUnit v2 only)*.csproj (root + 1-deep glob), *.slnyes

Java is JUnit 5 Jupiter only — JUnit 4 and TestNG are explicitly rejected. .NET is xUnit v2 only — MSTest and NUnit are explicitly rejected. Rust requires cargo-nextest — there is no plain cargo test fallback. go test runs, but its coverage is not consumed (see the gotest section).


Engine selection — the pin, one engine at a time

novetest test (and every other verb) runs one engine at a time: the one pinned by init. Exactly one viable engine at the init directory → pinned silently (the common case). Two or more viable engines at the same root → init refuses, creates nothing, and requires an explicit choice — there is no silent priority-win; novetest never guesses which suite you meant. "Viable" means the marker is present AND that engine's toolchain is actually installed, so a tooling-only package.json next to pyproject.toml does not count as ambiguity.

For human

If your workspace root has both pyproject.toml AND Cargo.toml (both toolchains installed), novetest init exits with engine-ambiguous and asks you to pick:

bash
novetest init --engine pytest      # or cargo-test, jest, …

Need the other engine occasionally? novetest test --engine cargo-test runs it once without changing the pin. Verbs also work from any subdirectory — they walk up to the nearest .novetest/ (like git), and a bare novetest test is always workspace-scoped no matter where you stand.

For agent

Route on the init outcome: exit 0 + data.pinned_engine ({"ecosystem": …, "engine_name": …}) → proceed; exit 4 + errors[0].code = "no-engine-detected"data.candidates[] lists {ecosystem, engine_name, path} sub-projects (bounded scan, depth ≤ 2; data.scan_refused: true at / and $HOME) — cd into each and init there; exit 2 + errors[0].code = "engine-ambiguous" → re-run init --engine <name>. Both refusals create nothing. Viability is host-dependent (marker + toolchain-READY) — never cache init outcomes across machines. Legacy pin-less stores: the first execution verb (run / test) backfills pinned_engine silently when unambiguous, or exits 2 with engine-ambiguous (instructing re-init) when not; read-only verbs (status, memory …, inspect, coverage show, regression compare/latest, localization <run_id>/latest, compare) neither backfill nor refuse — they proceed engine-less (exit 0) over a legacy store and write nothing. Each run still carries …run_record.engine_name + …ecosystem.

Working with a polyglot repository

The supported pattern is one .novetest/ per ecosystem subdirectory:

polyglot-repo/
├── backend/
│   ├── pyproject.toml
│   ├── my_module/
│   ├── tests/
│   └── .novetest/        ← `cd backend && novetest init`
└── frontend/
    ├── package.json
    ├── src/
    ├── __tests__/
    └── .novetest/        ← `cd frontend && novetest init`
bash
( cd backend  && novetest test )   # runs pytest only
( cd frontend && novetest test )   # runs jest only

Each subdirectory's .novetest/ carries its own run history, coverage facts, regression baselines, and SBFL findings — completely isolated. The walk-up rule from Quick Start -> Where do I run novetest test from? guarantees that novetest test from backend/tests/ finds backend/.novetest/, not the frontend one.


Quick toolchain matrix

EngineWhat must be on PATHCoverage tool
pytestpytest + pytest-json-report importable from the resolved interpreter (your project's .venv first)pytest-cov
jestNode.js ≥ 18 (node and npx) + workspace-local jestjest built-in (Istanbul)
go-testGo ≥ 1.21 (go)— (not consumed)
cargo-testRust toolchain + cargo-nextest ≥ 0.9.50 (mandatory) + cargo-llvm-cov (coverage)cargo-llvm-cov (LCOV)
junitJDK ≥ 17 + Maven ≥ 3.9 OR Gradle ≥ 7.6/wrapper; JUnit 5 Jupiter in depsJaCoCo (XML)
xunit.NET SDK ≥ 8.0; xUnit v2 in test projectcoverlet.collector ≥ 6.0.2

novetest init never fails on a bad engine — it records the readiness state and moves on.

For human

If a toolchain piece is missing, novetest init reports it on the engine readiness: line. The next novetest test/novetest run exits 4 until you fix the host. For example, a Python workspace with no pytest config:

✗ run
  engine-missing: engine readiness state: engine-missing (engine=(none detected))

(exit 4)

For agent

Missing toolchain → init records data.engine_readiness.state ∈ {"engine-missing", "engine-misconfigured"} (hints in engine_readiness.issues[]) but init still exits 0. A subsequent run verb exits 4 with errors[0].code = "engine-missing" (or "engine-misconfigured" — the code is the readiness state verbatim). Real novetest run against a Python workspace with no pytest config:

json
{
  "command": "run",
  "data": {
    "engine_readiness": {
      "ecosystem": null,
      "engine": null,
      "engine_version": null,
      "evidence": [
        "pyproject.toml"
      ],
      "issues": [
        "Python workspace detected but no pytest configuration (pytest.ini, [tool.pytest.ini_options], conftest.py, or tests/ dir) found"
      ],
      "state": "engine-missing"
    }
  },
  "errors": [
    {
      "code": "engine-missing",
      "details": {},
      "message": "engine readiness state: engine-missing (engine=(none detected))"
    }
  ],
  "ok": false,
  "schema": "novetest/v1",
  "warnings": []
}

(exit 4). The error code is the readiness state verbatimengine-missing (the code IS the state; no extra engine- prefix, and no engine-not-ready state). A markerless anchor with a pin-less legacy store surfaces no-engine-detected (exit 4) instead, carrying the same data.engine_readiness (state engine-missing) rather than data.candidates.


pytest (Python)

This is the baseline used in Quick Start.

Markers: pyproject.toml, setup.py, setup.cfg, pytest.ini. Readiness additionally requires a pytest config — one of pytest.ini, conftest.py, a tests/ dir, [tool.pytest.ini_options] in pyproject.toml, or [tool:pytest] in setup.cfg. A Python workspace with none reports engine-missing.

The external command resolves its interpreter venv-first — your project's .venv python when that venv ships pytest, else novetest's own interpreter; never a bare pytest off PATH. (Consequence: a project .venv that ships pytest but not pytest-json-report refuses with adapter-missing-plugin, exit 4, hint pip install pytest-json-report — install the plugin into your project's venv.) Plugin autoload is disabled:

<python> -m pytest -p pytest_jsonreport --json-report
  --json-report-file=<artifacts>/native/pytest-report.json -q [<target>]

Coverage is JSON via pytest-cov, collected with --cov-context=test (enabling per-test SBFL). Node-id form: tests/test_arithmetic.py::test_subtract.

For human

Project skeleton (the canonical calc example):

calc-demo/
├── pyproject.toml          # [tool.pytest.ini_options] testpaths=["tests"]
├── calc/
│   ├── __init__.py
│   └── arithmetic.py
└── tests/
    └── test_arithmetic.py
bash
cd calc-demo
novetest init
✓ Initialized .novetest/ at /abs/path/to/calc-demo/.novetest
  engine readiness: ready — python/pytest 9.0.3

(The pytest version is your project's pytest, not a novetest-controlled constant — novetest's own version is whatever novetest --version reports.)

For agent
bash
NOVETEST_OUTPUT=json novetest init

data.engine_readiness for a ready calc project:

json
{
  "ecosystem": "python",
  "engine": "pytest",
  "engine_version": "9.0.3",
  "evidence": [
    "pyproject.toml"
  ],
  "issues": [],
  "state": "ready"
}

Misconfig messages name the resolved interpreter and point at a project .venv remediation (novetest prefers the workspace's own .venv over its own interpreter). pytest not importable → engine-misconfigured, issues[] text:

pytest is not importable from the resolved interpreter (<interpreter>);
install pytest, pytest-json-report and pytest-cov into <workspace>/.venv
(novetest prefers the workspace's own .venv over its interpreter;
pytest-cov is what the coverage-collecting verbs such as `novetest test`
additionally need) — from <workspace> run: python3 -m venv .venv &&
.venv/bin/python -m pip install pytest pytest-json-report pytest-cov

Plugin missing → the same text with "pytest-json-report plugin is not importable from the resolved interpreter (<interpreter>); …". Readiness gates only on pytest + pytest-json-report; a missing pytest-cov shows up later as adapter-missing-plugin (exit 4) on a coverage-collecting run, so the hint installs all three at once.


jest (JavaScript / TypeScript)

Marker: package.json. Readiness requires node and npx on PATH, plus jest declared in dependencies/devDependencies or installed at node_modules/.bin/jest. Node.js ≥ 18.

External command (Windows launcher is cmd /c npx …):

npx jest --ci --json --testLocationInResults
  --outputFile=<artifacts>/native/jest-results.json
  --reporters=default --watchman=false [<target>]

Unlike pytest, jest has no plugin isolation — your workspace jest.config.js is honored as written. Coverage is Istanbul JSON (--coverage --coverageReporters=json), collected at aggregate granularity: the fact set reports mapping_granularity: "aggregate" with per-file totals only — the jest adapter has no equivalent of pytest's --cov-context=test, so no per-test attribution is recorded. Node-id form: <workspace-relative POSIX file>::<ancestors>::<title>.

Consequence for fault localization. With aggregate-only coverage, localization runs in sbfl_aggregate mode — and on jest that mode currently returns an empty suspect list, structurally: jest assertion stack frames name the failing test file (and jest internals), never the product source under test, so no product location can accumulate suspicion, and entries comes back [] on every failing run. This is a known, registered limitation of the current jest adapter — per-test coverage attribution is the tracked fix — not a project misconfiguration: nothing in jest.config.js changes it. Test execution, coverage measurement, and regression comparison are unaffected.

For human
my-js-project/
├── package.json            # jest in devDependencies
├── src/
│   └── math.js
└── __tests__/
    └── math.test.js

If node/npx are missing readiness is engine-missing; if jest is absent it is engine-misconfigured with a npm install --save-dev jest hint.

For agent

Readiness messages: no node/npx → engine-missing "Node.js (\node\/\npx\) not found on PATH; install Node.js >=18 …"; jest absent → engine-misconfigured "jest not found in package.json … install with: npm install --save-dev jest"; declared-but-not-installed → "jest is declared in package.json but not installed; run: npm install".

On a failing run, expect data.localization_outcome with kind: "fact-set", mode: "sbfl_aggregate", and entries: [], and data.recommendations[] topped by unavailable_analysis (priority 6) rather than a ranked suspect. Route on the recommendation category and the entries list, not on confidence — the finding can read confidence: "medium" even when entries is empty. The guarded Step-3 snippet in Quick Start handles this envelope: with nothing to rank, surface the envelope and diagnose from the failure output itself.


gotest (Go / go test)

Marker: go.mod. Readiness needs go on PATH and a working go version. Default target ./... (every package in the module).

External command (-count=1 disables Go's result cache):

go test -json -count=1 -timeout=<seconds>s [coverage flags] <target>

Coverage is NOT consumed. go test runs fine, but novetest run --coverage on a Go project produces no coverage facts: the adapter writes a Go cover.out profile under a different artifact key than the coverage engine reads. Node-id form: <package>::<test>.

Consequence for fault localization. SBFL ranks suspects from coverage facts, so with no coverage facts there is no SBFL ranking on Go: fault localization degrades to failure_proximity mode at confidence: "low", which reports the failing test's own location rather than a ranked set of suspect source locations. Test execution, regression comparison and replay are unaffected.

For human

Coverage runs complete, but the coverage line reports unavailable ("this run was executed without coverage collection"). Test execution works; coverage facts do not.

For agent

novetest run --coverage returns data.coverage_outcome with kind="unavailable" and detail "RunRecord.artifact_paths has no 'coverage_json' entry; this run was executed without coverage collection". Route coverage-dependent logic around engine_name == "go-test" — and localization-dependent logic too: data.localization_outcome.mode comes back failure_proximity with confidence: "low", so slots.rank / slots.score_normalized carry no SBFL signal on this engine.


cargo (Rust / cargo nextest)

Marker: Cargo.toml. The default target is the workspace.

The adapter requires cargo nextest — a successful cargo nextest --version is a load-bearing readiness gate; missing it → engine-misconfigured. There is no fallback to plain cargo test. Coverage additionally needs cargo-llvm-cov.

bash
rustup install stable
cargo install cargo-nextest --locked    # MANDATORY — no fallback
cargo install cargo-llvm-cov            # required for coverage

Non-coverage and coverage runs use different, mutually-exclusive invocations:

# non-coverage:
cargo nextest run --message-format=libtest-json --no-fail-fast --workspace [<filter>]
# coverage:
cargo llvm-cov nextest --lcov --output-path <artifacts>/native/coverage.lcov
  --ignore-run-fail --workspace --message-format=libtest-json

--ignore-run-fail emits the LCOV even when tests fail. A directory target (novetest test .) does NOT append a filter token (nextest treats positionals as filter expressions, not paths). Coverage is LCOV. Node-id is the nextest test name directly.


junit (Java / JUnit 5 Jupiter)

JUnit 5 Jupiter is the only supported framework — JUnit 4 and TestNG are detected and rejected as engine-misconfigured. Windows hosts are unsupported for JUnit (rejected as engine-misconfigured).

Markers: pom.xml (→ Maven) or build.gradle{,.kts} (→ Gradle). When both are present, Maven wins and an ambiguous-build-tool warning is emitted in envelope.warnings[].

Readiness gates, in order: Windows → reject; build tool resolvable; java on PATH (else "\java\ not found on PATH; install JDK 17+"); mvn/gradle/./gradlew; JUnit Jupiter declared; JUnit 4 → reject; TestNG → reject.

External command:

# Maven:
mvn -B test [jacoco:report] -Dsurefire.reportFormat=plain
  -Dsurefire.useFile=false [-Dtest=<filter>]
# Gradle:
./gradlew test --no-daemon [--tests <filter>] [jacocoTestReport]

Coverage is JaCoCo XML; requesting --coverage without JaCoCo wired in emits a missing-jacoco warning and coverage degrades.

For human

Project skeleton (Maven): pom.xml declaring org.junit.jupiter:junit-jupiter (test scope) + src/{main,test}/java/…. Gradle is the same with build.gradle declaring the Jupiter dependency and useJUnitPlatform(). Toolchain: JDK ≥ 17 plus Maven ≥ 3.9 or Gradle ≥ 7.6 (the ./gradlew wrapper is honored).

For agent

On the Maven path engine_version is extracted (e.g. "5.10.2"); on the Gradle path it is commonly null (the Jupiter-version regex frequently can't extract it). Don't rely on a non-null Java engine_version.


xunit (.NET / xUnit v2)

xUnit v2 is the only supported framework — MSTest and NUnit are detected and rejected as engine-misconfigured ("…supports xUnit v2 only"). xUnit v3 runs, but coverage is deferred with a warning (xunit-v3-coverage-deferred).

Detection is by glob: *.csproj at the root or one directory deep, or *.sln at the root. novetest picks the first csproj whose name contains "test"; it must declare <PackageReference Include="xunit">.

The glob is shallow (root and one directory deep), but a root .sln closes the gap: discovery also reads the projects the solution file lists, at any depth, so the canonical src/ + tests/ layout that dotnet new sln conventions produce initializes from the solution root. Among the projects found, test-project selection prefers a project declaring Microsoft.NET.Test.Sdk or <IsTestProject> over the name-token match, so a sample or benchmark project whose name happens to carry "test" does not shadow the real test project. Execution is still a single dotnet test <csproj> against the selected project — whole-solution runs are not supported.

Toolchain: .NET SDK ≥ 8.0 (dotnet on PATH). For coverage only: coverlet.collector ≥ 6.0.2 — Coverlet is not a readiness gate, so bare novetest run works without it.

External command (coverage runs dotnet restore <csproj> first):

dotnet test <csproj> --logger "trx;LogFileName=results.trx"
  --results-directory <results>
  [--collect:"XPlat Code Coverage" --settings <runsettings>]
  [--filter "FullyQualifiedName~<target>"]

Coverage is Cobertura XML via Coverlet, using a per-run hermetic runsettings (your own runsettings are never modified). Coverlet below the 6.0.2 floor degrades coverage with a coverlet-below-floor warning; Coverlet absent means coverage is not collected (coverlet-absent).

Consequence for fault localization. On the current release, fault localization on xunit projects runs in sbfl_aggregate mode and has returned an empty suspect list in every measured failing run: xUnit TRX failure frames name the failing test file (and framework internals), never the product source under test, so no product location can accumulate suspicion and entries comes back []. Raising coverlet.collector does not change this — it was measured unchanged at and above the 6.0.2 floor. This is a known limitation of the current xunit adapter path, not a project misconfiguration. Test execution, coverage measurement, and regression comparison are unaffected.

For human

Project skeleton:

my-dotnet-project/
├── MathLib/
│   ├── MathLib.csproj
│   └── Math.cs
└── MathLib.Tests/
    ├── MathLib.Tests.csproj    # PackageReference Include="xunit"
    └── MathTests.cs
For agent

Warning codes: xunit-v3-coverage-deferred, ambiguous-project-layout, coverlet-below-floor (Coverlet below the 6.0.2 floor; coverage degraded to aggregate mode), coverlet-absent (--coverage requested but coverlet.collector not in the package graph; coverage not collected). All warning codes are dedicated tokens, disjoint from the readiness-state names — engine-misconfigured only ever names the readiness state and its same-named error code, never a warning.

On a failing run, expect data.localization_outcome with kind: "fact-set", mode: "sbfl_aggregate", and entries: [], and data.recommendations[] topped by unavailable_analysis (priority 6) rather than a ranked suspect. Route on the recommendation category and the entries list, not on confidence — the finding can read confidence: "medium" even when entries is empty. The guarded Step-3 snippet in Quick Start handles this envelope: with nothing to rank, surface the envelope and diagnose from the failure output itself.


After language setup, return to the canonical flow

Once your engine's toolchain is installed and your project skeleton is in place, the rest of the workflow is identical to Quick Start:

bash
cd my-project
novetest init
novetest test

The output shape is the same across all six engines. Only the per-test identity strings and the coverage format differ; the orchestration layer normalizes those so results stay language-agnostic. Three capability limits are worth planning around: go-test collects no coverage facts (and, with them, has no SBFL ranking — localization degrades to failure_proximity), and jest and xunit fault localization currently return an empty suspect list in sbfl_aggregate mode. Each limit is detailed in its engine's section above; coverage itself is available for pytest, jest, junit, xunit, and cargo-test.

For human

The text-mode output shape (<glyph> [<category>] <sentence>\n ↳ <citation>) is identical across engines.

For agent

Your agent can rely on:

  • data.run_reference.run_id is a ULID, format-stable across engines.
  • data.recommendations[].category comes from a closed taxonomy shared by all engines.
  • data.stage_eligibility.localization is the SBFL mode string when available; data.stage_eligibility.replay is always not_run.

Treat run_record.test_results[].node_id as an opaque string keyed off engine_name — its format differs per engine (e.g. tests/test_arithmetic.py::test_subtract for pytest vs <package>::<test> for go-test). Do not parse it cross-engine.