Getting started

TestDetta compares your branch with its base, works out which test projects and which test classes can observe the change, and runs just those. When it cannot be sure, it runs more, never less.

Requirements

Quick start

GitHub Actions

The GitHub Action builds TestDetta for you; there is nothing to install. Add two steps to your workflow: one records a coverage map on pushes to your default branch, the other runs only the affected tests on pull requests.

- if: github.event_name == 'push'
  uses: <owner>/testdetta@<sha>
  with:
    mode: record

- if: github.event_name == 'pull_request'
  uses: <owner>/testdetta@<sha>
  with:
    mode: test

The full workflow, the permissions it needs and every input are in GitHub Actions.

Other CI systems

GitLab CI, Jenkins, Azure DevOps, Bitbucket and on-premises setups run the same two commands, with a folder of per-commit coverage maps (--maps-dir) kept on a shared disk or in object storage. Install TestDetta as a .NET tool:

dotnet tool install --global <package>

The package name is set at launch. Until then, build the tool from source with dotnet build src/TestDetta.Cli -c Release -o testdetta-bin and call dotnet testdetta-bin/TestDetta.Cli.dll wherever td appears.

Pipelines for each system are in Other CI systems.

First commands

Run these from anywhere inside your repository. --base defaults to origin/main.

# What does my branch affect, and why?
td affected --base origin/main

# Run only the affected tests (add --dry-run to print the dotnet test commands)
td test --base origin/main

# Run every test with coverage and write one map per test project to .testdetta/coverage
td record

Without a coverage map, td test still works: it decides by name only, which is safe but selects more. Once td record has run at the base of your branch, selection becomes precise.

Add .testdetta/ to your .gitignore. It holds coverage maps and caches, never anything to commit.

Commands

CommandWhat it does
td affected [--format text|json|paths|commands]Print what a change affects and why. --tests-only lists only affected test projects.
td test [--dry-run] [--summary-file <path>] [-- <dotnet test args>]Run only the affected tests. --summary-file appends a Markdown summary, for example to $GITHUB_STEP_SUMMARY. Arguments after -- go to every dotnet test, for example -- -c Release.
td record [--project <csproj>]... [-c <configuration>] [-f <tfm>] [--parallel <n>] [--no-build]Run all tests with coverage and write one map per test project to .testdetta/coverage. --parallel spreads each project's test classes over n test processes; --no-build records the output of an earlier build with the same configuration.
td license statusCheck the license and show what it covers.
td license install <license|file>Store a license in ~/.testdetta/license, for machines where no CI secret can hold it.

Shared options

OptionMeaning
--base <ref>Git ref to compare against. Default: origin/main.
--repo <path>Any path inside the repository. Default: the current directory.
--max-map-age <n>Use coverage maps recorded up to n commits before the merge base, bridging the changes in between. Default: 50; 0 uses only exact maps. See How it decides.
--maps-dir <dir>Coverage maps kept outside the repository, one folder per commit: record saves there, affected and test restore the newest one within --max-map-age. For CI systems other than GitHub Actions.
--drift-fallback <all|name>When the changes since an older map include a build file, configuration or a global using: run the affected test projects completely (all, the default) or decide them by name (name).
-v, --verboseWrite debug logs to stderr: every decision a user might question, with its reason.
--log-level <level>trace, debug, information, warning (default), error or none.

Logs go to stderr; stdout carries the command's output and stays machine readable.

Exit codes

CodeMeaning
0Success.
1The analysis failed.
2Invalid command line.
3Tests failed.