Trace a C# repo¶
TL;DR
Read the repo's .cs files with csharp-file and its xUnit tests with xunit. A file links
to its design element with an Implements: line in its first type's /// comment. A test
names the test case it runs, or what it verifies, at the start of its DisplayName, as a
string literal. Then run qmstrace check, and read a test run's verdicts from the TRX file
dotnet test --logger trx writes with qmstrace results.
Steps¶
-
Declare the two item types in the repo's
qmstrace.toml— components from the source files, automated tests from the test files. This is the fixture project's C# agent:# This product repo's TRACE CONFIGURATION — a C# agent (ADR-0004). It holds code and automated # tests only: the requirements, the design and the test cases are in the project's ledger. version = 1 ledger = "../ledger" [schema] # by path, as repos side by side read it path = "../ledger/qmstrace.schema.toml" [items.component] # ID = the file's path extractor = "csharp-file" files = ["src/**/*.cs"] [items.automated_test] # ID = path::Class::Method; the probes are not traced extractor = "xunit" files = ["tests/Agent.Tests/**/*Tests.cs"]The type the tests are read into must carry exactly one link in the project's schema —
verifies— because aDisplayNamenames an ID, not a link. A schema that gives it two is refused, and says so.qmstrace initon a repo with no configuration still writes the Python starter (#38), and apytest.inithat registers theverifiesmarker: edit[items]as above, and delete thepytest.ini. -
Mark each component in its first type's leading doc comment. A field line, with XML tags around it if you like; the same line in any other comment is not read, and is reported:
namespace Agent; /// <summary>Refuses a call that carries no valid key, before any handler sees it.</summary> /// <remarks> /// Implements: DSN-SVC-01 /// </remarks> public static class KeyCheck { /// <summary>Whether a call carrying <paramref name="key"/> may go on.</summary> public static bool Admits(string? key, string expected) => !string.IsNullOrEmpty(key) && key == expected; } -
Start each test's
DisplayNamewith its ID. In a project with a Test Specification, that is the test case the test runs; several tests may run one. A[Theory]is one automated test, its rows its runs:using FluentAssertions; namespace Agent.Tests.Unit; /// <summary>The agent's side of the key check.</summary> public class KeyCheckTests { [Fact(DisplayName = "TC-SVC-01.1 a call without a key is refused")] public void Admits_NoKey_IsFalse() { KeyCheck.Admits(null, "the-key").Should().BeFalse(); } [Theory(DisplayName = "TC-SVC-01.2 the key check decides before any handler")] [InlineData("the-key", true)] [InlineData("a-wrong-key", false)] public void Admits_AnyKey_DecidesAlone(string key, bool admitted) { KeyCheck.Admits(key, "the-key").Should().Be(admitted); } }The ID ends where an ID in prose ends:
TC-SVC-01.1/O03 …andTC-SVC-01.1: …both linkTC-SVC-01.1. A letter, digit, hyphen or dotted number straight after it makes it no ID —TC-SVC-01.1a …is reported asV14. The level comes from the folder the file sits in —Unit/is the levelunit. -
Check.
-
Read a test run's verdicts. Write a TRX file, then read it as a separate command, so a failing run does not skip it:
dotnet test tests/Agent.Tests --logger "trx;LogFileName=report.trx" --results-directory . qmstrace results report.trxA run is matched by its class and method, never by its
DisplayName, so rewording a test keeps its results and renaming its method does not. Each row of a[Theory]is a run of one automated test. Nothing of the file's build paths, machine or user names, or output is shown. In this run one[Fact]was skipped and one[Theory]row failed:report: report.trx 3 XML entries · 3 reported runs (distinct full identities) matched: 3 runs of 2 automated tests · scanned: 2 automated tests · unmatched: 0 run identities matched against the current working tree — report commit NOT VERIFIED a partial parameter set does not prove every possible variant ran baselines: judged from reachable local tags — remote-tag completeness NOT VERIFIED; qmstrace never fetches baselines: no matching reachable local tag — nothing frozen locally change requests: ../ledger/change-requests (1 readable; 0 malformed) — current working tree, not pinned requirements — verdicts along verifies links, from automated tests directly or through test cases: REQ-SVC-01 skip via TC-SVC-01.1 · 1 automated tests · 1 runs — A call carries a key · requirement, in sibling ledger — not pinned · ../ledger/docs/req/service-srs.md:5 via TC-SVC-01.1 skip · 1 automated tests · verifies REQ-SVC-01 — A call without a valid key is refused skip tests/Agent.Tests/Unit/KeyCheckTests.cs::KeyCheckTests::Admits_NoKey_IsFalse · 1 runs skip: TC-SVC-01.1 a call without a key is refused (NotExecuted) NotExecuted: the key store is offline test cases that reach no requirement — no requirement verdict inferred: TC-SVC-01.2 fail · 1 automated tests · verifies DSN-SVC-01 — The key check runs before any handler fail tests/Agent.Tests/Unit/KeyCheckTests.cs::KeyCheckTests::Admits_AnyKey_DecidesAlone · 2 runs fail: TC-SVC-01.2 the key check decides before any handler(key: "a-wrong-key", admitted: True) (Failed) Failed: Expected KeyCheck.Admits(key, "the-key") to be True, but found False. result: 2 matched automated tests · 3 matched runs · 0 unmatched run identities · exit 0 (informational) — report commit NOT VERIFIED; this repo's tests onlyWith several test projects, write one TRX per project, each with its own name, and give
resultsall of them. They are read as one; a run in two files keeps its worst verdict:dotnet test tests/Agent.Tests --logger "trx;LogFileName=unit.trx" --results-directory TestResults dotnet test tests/Agent.IntegrationTests --logger "trx;LogFileName=integration.trx" --results-directory TestResults qmstrace results TestResults/*.trxHere, two runs of the agent's tests read together:
reports: 2 TestResults/first.trx · 3 XML entries TestResults/second.trx · 3 XML entries 6 XML entries · 4 reported runs (distinct full identities) matched: 4 runs of 2 automated tests · scanned: 2 automated tests · unmatched: 0 run identities matched against the current working tree — report commit NOT VERIFIED a partial parameter set does not prove every possible variant ran baselines: judged from reachable local tags — remote-tag completeness NOT VERIFIED; qmstrace never fetches baselines: no matching reachable local tag — nothing frozen locally change requests: ../ledger/change-requests (1 readable; 0 malformed) — current working tree, not pinned 2 duplicate XML entries combined by full identity; retry attempts are not inferred requirements — verdicts along verifies links, from automated tests directly or through test cases: REQ-SVC-01 skip via TC-SVC-01.1 · 1 automated tests · 1 runs — A call carries a key · requirement, in sibling ledger — not pinned · ../ledger/docs/req/service-srs.md:5 via TC-SVC-01.1 skip · 1 automated tests · verifies REQ-SVC-01 — A call without a valid key is refused skip tests/Agent.Tests/Unit/KeyCheckTests.cs::KeyCheckTests::Admits_NoKey_IsFalse · 1 runs skip: TC-SVC-01.1 a call without a key is refused (NotExecuted) · reported in TestResults/first.trx, TestResults/second.trx NotExecuted: the key store is offline test cases that reach no requirement — no requirement verdict inferred: TC-SVC-01.2 fail · 1 automated tests · verifies DSN-SVC-01 — The key check runs before any handler fail tests/Agent.Tests/Unit/KeyCheckTests.cs::KeyCheckTests::Admits_AnyKey_DecidesAlone · 3 runs fail: TC-SVC-01.2 the key check decides before any handler(key: "a-wrong-key", admitted: True) (Failed) · reported in TestResults/second.trx Failed: Expected KeyCheck.Admits(key, "the-key") to be True, but found False. result: 2 matched automated tests · 4 matched runs · 0 unmatched run identities · exit 0 (informational) — report commit NOT VERIFIED; this repo's tests onlyOne
LogFileNamefor a whole solution overwritesGiven a solution and a fixed
LogFileName, every project writes the same file and only the last one's results remain, without warning. qmstrace cannot see the overwrite; the header's entry count per report is where the shortfall shows.
What is not read¶
Each of these is reported as V15, never skipped:
| Written | Why it is not read |
|---|---|
DisplayName = Id + "…", $"{Id} …", a constant |
not a string literal: the tool reads source, it does not evaluate it |
[Trait("verifies", "REQ-…")] |
a trait never reaches a test report, so it cannot carry the trace |
[SkippableFact], any attribute derived from FactAttribute |
not xUnit's own: its base class is in another file |
a test inside #if … |
it may never be built |
A test attribute in a comment is a stray: it links nothing, and the gap it leaves names it.