05 · Certification Evidence & Test Reporting¶
Passing tests are necessary but not sufficient for certification under DO-178C, ISO 26262, or similar regimes (module 02). An auditor doesn't re-run your suite and trust the green checkmark — they review a documented, traceable evidence package showing what was tested, how, against what requirements, with what coverage, and under what configuration. This module is about assembling that package from the artifacts earlier modules already produce.
Environment note
Process and documentation module; the report-generation script in section 4 was written in Python and run on this host against synthetic data, and its output is real. No certification tool or audit process was actually exercised — this describes the shape of the artifacts such a process consumes.
1. What "evidence" means, concretely¶
An auditor's question is never "did the tests pass" alone — it's "can I independently verify, from the paper trail alone, that these tests adequately verify these requirements, in this exact software configuration." The evidence package answers that without re-running anything.
| Evidence artifact | Answers |
|---|---|
| Requirements traceability matrix (module 03) | Does every requirement have a test, and vice versa? |
| Test procedures (not just test code — described in reviewable prose) | What does each test actually do, in human-readable form? |
| Test results log, tied to a specific build/config | Which exact software version was tested, and did it pass? |
| Structural coverage report (module 02, section 3) | Was the required MC/DC / decision / statement coverage achieved? |
| Tool qualification records (module 02, section 5) | Can this evidence be trusted given the tools that generated it? |
| Problem reports and their resolution/retest evidence | Was every found defect fixed and re-verified? |
2. Test procedures: readable independent of the code¶
A certification review does not assume the reviewer reads C++. Test procedures translate a test's intent into prose a domain reviewer (who may be a systems engineer, not a programmer) can independently assess against the requirement.
TEST PROCEDURE: TP-PARSE-003
Verifies: REQ-PARSE-003
Objective: Confirm the parser rejects a message whose declared payload
length would exceed the available buffer, without reading past
the buffer's end.
Preconditions: None.
Steps:
1. Construct a 3-byte input buffer: type=0x01, length field=0xFFFF.
2. Invoke parse_message with this buffer and its exact length (3 bytes).
3. Observe the return value.
Expected Result: The function returns a non-zero (error) status. The
output message_t structure's contents are unspecified
(the requirement does not constrain them on failure).
Pass/Fail Criterion: Return value is non-zero.
The corresponding executable test (from Level 3's capstone,
test_rejects_overrunning_length) is the implementation of this
procedure — both exist, and the traceability matrix (module 03) links
them, but the procedure is what a non-programmer reviewer actually reads.
3. Test results must be tied to an exact configuration¶
"The tests passed" is meaningless as certification evidence without stating which exact build — compiler version, flags, source revision — was tested. This is the software equivalent of chain-of-custody.
TEST RESULTS RECORD
Software Under Test: msgparser
Configuration:
Git commit: c9777b0a1f2e...
Compiler: gcc 13.2.0
Compiler flags: -O2 -std=c11 -Wall -Wextra
Target: x86_64-linux-gnu
Test framework: Custom C harness, invoked via CTest 3.28.1
Test Run Date: 2026-08-25
Test Run By: CI pipeline, workflow run #4821
Results:
TP-PARSE-001: PASS
TP-PARSE-002: PASS
TP-PARSE-003: PASS
TP-PARSE-004: FAIL -- see Problem Report PR-0042
Structural Coverage Achieved:
Statement coverage: 100% (47/47 statements)
Decision coverage: 100% (12/12 decisions)
MC/DC: 92% (11/12 -- see PR-0043 for the gap)
A results record with a FAIL in it is not itself disqualifying — a
certification package routinely includes failed runs, as long as each is
traced to a Problem Report with a documented resolution and a subsequent
passing re-test tied to the fixed configuration.
4. Generating results records mechanically, not by hand transcription¶
Hand-transcribing CTest/CI output into a results record is exactly the kind of step that introduces transcription errors and gets skipped under deadline pressure. Generate it from the same machine-readable output the CI pipeline (Level 3, module 06) already produces.
#!/usr/bin/env python3
# generate_results_record.py
import json
import subprocess
from datetime import datetime, timezone
def generate_report(ctest_json_path: str, git_commit: str) -> str:
with open(ctest_json_path) as f:
data = json.load(f)
lines = [
"TEST RESULTS RECORD",
"",
f"Git commit: {git_commit}",
f"Run date: {datetime.now(timezone.utc).isoformat()}",
"",
"Results:",
]
for test in data["tests"]:
status = "PASS" if test["status"] == "passed" else "FAIL"
lines.append(f" {test['name']}: {status}")
return "\n".join(lines)
if __name__ == "__main__":
# Synthetic CTest-shaped JSON, standing in for real `ctest --output-junit`
# or CTest's own JSON output, to demonstrate the generation step runs.
synthetic = {
"tests": [
{"name": "TP-PARSE-001", "status": "passed"},
{"name": "TP-PARSE-002", "status": "passed"},
{"name": "TP-PARSE-003", "status": "passed"},
{"name": "TP-PARSE-004", "status": "failed"},
]
}
with open("/tmp/synthetic_results.json", "w") as f:
json.dump(synthetic, f)
print(generate_report("/tmp/synthetic_results.json", "c9777b0a1f2e"))
TEST RESULTS RECORD
Git commit: c9777b0a1f2e
Run date: 2026-08-25T10:14:32.481207+00:00
Results:
TP-PARSE-001: PASS
TP-PARSE-002: PASS
TP-PARSE-003: PASS
TP-PARSE-004: FAIL
This script ran on this host and produced this real output (only the
timestamp will differ on re-run) — generated directly from structured test
output, the same pattern a real pipeline would apply to ctest's actual
JUnit/JSON output instead of the synthetic data used here for
demonstration.
5. Problem reports close the loop¶
Every FAIL in a results record needs a Problem Report (PR) with a defined
lifecycle: opened, root-caused, fixed, and — critically — re-verified
against a new, passing results record tied to the fixed configuration,
not just marked resolved on the strength of the fix existing.
PROBLEM REPORT PR-0042
Opened: 2026-08-20
Related test: TP-PARSE-004
Description: parse_message does not reject a null `out` pointer; a
call with out=NULL causes a null pointer dereference
rather than returning an error code.
Root cause: Missing null check, introduced when the function was
refactored to accept the current signature.
Fix: Commit a1b2c3d adds an explicit `if (out == NULL) return -1;` check.
Status: Closed
Verification: TP-PARSE-004 re-run against commit a1b2c3d -- PASS.
See Test Results Record dated 2026-08-22.
6. Configuration management ties it all together¶
Certification evidence is only trustworthy if every artifact — requirements, code, tests, results, coverage reports — can be shown to correspond to the same software baseline. This is why the git commit hash appears in every artifact above: it's the join key across the entire evidence package, without which "these tests passed" and "this is the code that shipped" cannot be connected with certainty.
Cheat sheet¶
| Evidence need | Artifact | Generated from |
|---|---|---|
| Non-programmer-readable test intent | Test procedures | Written once per test, tied to a requirement ID |
| Proof a specific build was tested | Test results record | Generated from CI's structured test output, never hand-transcribed |
| Proof required coverage was hit | Structural coverage report | lcov/DO-178C-qualified coverage tool output |
| Proof every failure was resolved | Problem reports, each with re-verification evidence | Opened per FAIL, closed only on a new PASS record |
| Everything ties to one software version | Configuration management (commit hash in every artifact) | Version control, referenced everywhere |
How It Actually Works: the commit hash as a cryptographic join key¶
"The git commit hash appears in every artifact" is doing more mechanical work than a simple label — it's what makes the evidence chain tamper-evident, not just organized.
- A git commit hash is a SHA-1 (or SHA-256, in newer repos) digest of the
commit's content and its parent's hash, recursively. Because the hash of
a commit depends on the hash of every ancestor, changing a single byte
anywhere in the tree's history — even in a commit from months earlier —
changes that commit's hash, which changes every descendant commit's hash
in turn. This is precisely why "the commit hash is in every artifact" is a
meaningful integrity claim rather than a bookkeeping convenience: an
artifact stamped with hash
Hcan be checked against a repository's actual history, and ifHdoesn't correspond to a real commit reachable in that history, the evidence chain is provably broken, not just poorly filed. - Structural coverage and test-result artifacts are only trustworthy if
they were generated from a build checked out at that exact commit, with
no uncommitted local modifications — a coverage report generated against
a working tree with an uncommitted one-line fix reports coverage for code
that, strictly, does not correspond to any hash at all, which is why real
certification pipelines fail the build if
git statusshows anything dirty before generating evidence, rather than trusting a human to remember to commit first. - This is the same idea as JUnit XML being the untouched CI-generated handoff format in Level 3 Module 6 — "generated from CI's structured output, never hand-transcribed" matters mechanically because a hand-transcribed number has no cryptographic or automated link back to the run that produced it; a generated report retains an unbroken, checkable chain from source commit → CI job → raw tool output → final artifact, with every link machine-verifiable rather than trusted on faith.
Exercise¶
- Write a test procedure (section 2's format) for one test in your own project, written so a non-programmer could assess pass/fail against the requirement without reading the test code.
- Adapt the
generate_results_record.pyscript to read real output fromctest --output-junit results.xml(or your test runner's actual machine-readable output format) instead of the synthetic JSON, and run it against one of your own test suites. - Write a Problem Report (section 5's format) for a real or hypothetical test failure in your project, including the re-verification step.
- Identify, for your own project, which artifact from the cheat sheet you currently do NOT produce, and describe in two sentences what would be needed to start producing it.
- Explain in three sentences why "the CI badge is green" is not, by itself, certification evidence — what's missing, specifically, that the artifacts in this module supply?