Skip to content
BETAGuardrails are in beta. The library, the configuration format and the CLI command can still change.

Guardrails / Collectors

Test results

The test results the build already wrote, read from whichever report format it left behind and normalized into one shape: the totals across every report, and the cases that failed, errored or were skipped. The passing cases are left out, because a guardrail gates on the others and Buildnote already carries every case as a test event. Nothing identifies a case across reports, so a build writing both a report per class and a merged one counts every case in both.

Facts keytests
Versionv1
Scripttests.py
Timeout60 seconds

Inputs

InputDescriptionDefaultEnvironment
reportsComma separated globs matched against every file in the working directory and against bare file names, naming the test reports to read.TEST-*.xml,*-test-report.xml,junit*.xml,test-results*.xml,*.trx,*-ctrf.json,open-test-report.xmlGUARDRAIL_INPUT_REPORTS
maxFailuresMost cases to carry in failures and in skipped, counted separately. The totals stay whole when cases are dropped.50GUARDRAIL_INPUT_MAXFAILURES

A guardrail that declares an input of the same name passes it through, so the guardrail's configuration in buildnote.json is what decides these values.

A guardrail asks for these facts by name, and reads them back the same way:

json
{
  "collect": ["tests"]
}
python
tests = guardrail.facts("tests")

Facts

The document tests collects. A path carrying [] is an entry of the list before it, and one carrying [path] is a key of the object before it.

FactMeaning
sourcereport when a test report was read, none when there was none.
reasonWhy nothing was read, present only when source is none.
reportsEvery report that was read, in path order.
reports[].pathPath of the report, relative to the directory the CLI runs in.
reports[].modifiedWhen the report was last written, ISO 8601, so a stale report is recognisable.
reports[].formatFormat it was read as: junit, nunit, trx, otr or ctrf.
totals.testsCases across every report that was read, summed rather than deduplicated.
totals.passedCases that passed.
totals.failedCases that failed an assertion.
totals.errorsCases that ended in an error rather than a failed assertion.
totals.skippedCases that were skipped, ignored or left inconclusive.
totals.durationMsHow long the reports say the cases took, summed, or null when no report stated a duration.
suitesHow many report files contributed to the totals.
failuresThe failed and errored cases, up to maxFailures.
failures[].nameName of the case, as the report gave it.
failures[].classnameClass or suite the case belongs to, or null when the report names none.
failures[].statusfailed for a failed assertion, error for a case that ended in an error.
failures[].fileFile the case was declared in, or null when the report names none.
skippedThe skipped cases, up to maxFailures.
skipped[].nameName of the case, as the report gave it.
skipped[].classnameClass or suite the case belongs to, or null when the report names none.
skipped[].statusAlways skipped.
skipped[].fileFile the case was declared in, or null when the report names none.
droppedCases left out of failures and skipped because maxFailures was reached.

A collector that cannot finish prints what it had along with an incomplete key saying why, so the facts below the point it stopped are absent. A check that cares reads incomplete before the rest.

Example facts

The example facts collected by tests collector:

json
{
  "source": "report",
  "reports": [
    {
      "path": "TEST-com.acme.widget.QueueTest.xml",
      "modified": "2026-03-04T10:15:00Z",
      "format": "junit"
    }
  ],
  "totals": {
    "tests": 4,
    "passed": 2,
    "failed": 1,
    "errors": 0,
    "skipped": 1,
    "durationMs": 624
  },
  "suites": 1,
  "failures": [
    {
      "name": "rejects a negative limit",
      "classname": "com.acme.widget.QueueTest",
      "status": "failed",
      "file": "service/src/test/kotlin/QueueTest.kt"
    }
  ],
  "skipped": [
    {
      "name": "drops the oldest under load",
      "classname": "com.acme.widget.QueueTest",
      "status": "skipped",
      "file": "service/src/test/kotlin/QueueTest.kt"
    }
  ],
  "dropped": 0
}

Collected for

GuardrailCategoryInputs
tests/no-failurestestsnone
tests/no-skipped-growthtestsnone
tests/results-publishedtestsnone

All collectors

Buildnote Limited
Registered in England and Wales, Reg: 16140412