Code Coverage

Code coverage measures used and unused code lines, statements, branches, etc. Depending on the programming language this is measured by instrumenting the code/binary and running the program, its test cases or simulating the code. In general, code coverage is a measure of test coverage. Unused code is not (yet) covered by tests.

The code coverage metric in percent is a ratio of used code versus all possibly usable code. A coverage of <100% indicates unused code. This can be dead code (unreachable) or untested code (⇒ needs more test cases).

Code Coverage Kinds

  • Coverage

    • Code coverage

      • statement coverage / line coverage

      • branch coverage

      • expression coverage

      • toggle coverage

      • state coverage (of state machines)

      • transition coverage (of state machines)

    • Functional coverage

    • Documentation coverage

    • Test coverage

Data Model

pyEDAA.Reports reads a code coverage report in two layers: a model of the report format, which keeps what the report states, and the common model of pyEDAA.Reports.CodeCoverage, which every format converts to - as the JUnit dialects convert to the unified unit test model.

The common model is a superset: it has two hierarchies over the same lines.

  • CoverageSummarythe report
    • physical hierarchybuilt from the file paths the report names
    • logical hierarchythe language units the report names - each: file, first and last line
      • Packagee.g. myPackage
        • Modulee.g. myPackage.Shapes
          • Classe.g. myPackage.Shapes.Circle
            • Methode.g. myPackage.Shapes.Circle.Area
          • Functione.g. myPackage.Shapes.Distance
        • SourceFilee.g. main.c - for languages, where the file is the unit
  • Every format has files and lines, so lines, branches and their counts live in the physical hierarchy.

  • A unit - Package, Module, SourceFile, Class, Function, Method - spans its file from its first to its last line, both Line objects of the file: a language construct wraps the constructs nested in it. So both hierarchies count the same lines. A unit without lines, e.g. a package of several files, counts the lines of the units it contains. A unit can state how often it was called.

  • A SourceFile is the unit of a language without modules or classes, where the file is the unit: e.g. a C translation unit, a Bash or TCL script.

  • Every element knows the element containing it - Parent - and the report’s root - Root. Assigning a parent, in the constructor or later, adds the element to it and passes the root on to the elements it contains.

A line, a branch and a unit carry a LineCoverageStatus and - if the report says - a count: how often the line ran, the branch was taken, the unit was called. A count of 0 is uncovered, a positive count covered.

Status

Meaning

Covered

The line ran, the branch was taken, the unit was called.

PartiallyCovered

The line ran, but not all of its branches were taken.

Uncovered

The line never ran, the branch was never taken, the unit was never called.

Excluded

Excluded from the measurement, e.g. by a pragma comment.

Unknown

The report doesn’t say.

A line a report doesn’t list - a comment, a declaration - isn’t executable and has no line object. A file keeps its lines in a list indexed by line number, with None for such a line; IterateLines() walks the lines in order and GetLine() looks one up.

Aggregate() computes the counters of a file from its lines, of a directory from its directories and files, and of a unit from its lines and those of its units, each line counted once: executable, covered, missing, excluded and partially covered lines, branches, covered and missing branches. The ratios LineCoverage, BranchCoverage and Coverage (lines and branches combined, as coverage.py computes it) are 1.0 where there is nothing to cover.

Report Formats

Code coverage tools write their results in XML, JSON or text formats, and many keep the raw data in a database or binary format of their own, which only the tool itself or its API reads.

Tool / Framework

XML

JSON

Text

Proprietary

Cobertura (Java)

Cobertura XML (coverage-04.dtd)

—

—

cobertura.ser

JaCoCo (Java)

JaCoCo XML (report.dtd)

—

CSV

*.exec

coverage.py (Python)

Cobertura dialect (coverage xml)

coverage json

lcov (coverage lcov), coverage report

.coverage (SQLite)

GCC (gcov)

—

gcov JSON (gcov --json-format)

*.gcov

*.gcno, *.gcda

gcovr

Cobertura (--cobertura), JaCoCo (--jacoco), SonarQube (--sonarqube), Clover (--clover)

gcovr JSON (--json), Coveralls (--coveralls)

lcov (--lcov), --txt

— (reads GCC’s data)

lcov

—

—

lcov tracefile (*.info)

— (reads GCC’s data)

LLVM (llvm-cov)

—

llvm-cov export -format=text

lcov (llvm-cov export -format=lcov), *.gcov (llvm-cov gcov), llvm-cov report

*.profraw, *.profdata

cargo-llvm-cov (Rust)

Cobertura (--cobertura)

LLVM’s export format (--json)

lcov (--lcov), --text

— (reads LLVM’s data)

grcov (Rust)

Cobertura (-t cobertura)

covdir (-t covdir), Coveralls (-t coveralls)

lcov (-t lcov)

— (reads LLVM’s or GCC’s data)

Go (go test)

—

—

cover profile (go test -coverprofile)

covmeta.*, covcounters.* (GOCOVERDIR)

gocover-cobertura (Go)

Cobertura

—

—

— (reads a cover profile)

gcov2lcov (Go)

—

—

lcov

— (reads a cover profile)

coverlet (.NET)

Cobertura, OpenCover

coverlet JSON

lcov

—

Microsoft Code Coverage (.NET)

Cobertura

—

—

*.coverage

ReportGenerator (.NET)

Cobertura, among others

—

lcov, among others

— (reads other reports)

GHDL (VHDL)

—

coverage-*.json (ghdl -r --coverage), gcovr JSON (ghdl coverage --format=gcovr)

lcov, *.gcov (ghdl coverage --format=lcov|gcov)

— (GCC backend: GCC’s data)

NVC (VHDL)

Cobertura (nvc --cover-export --format=cobertura); an undocumented internal dump (--format=xml)

—

—

*.ncdb

Aldec Active-HDL, Riviera-PRO (VHDL, Verilog, SystemVerilog)

UCDB XML (acdb2xml)

—

—

ACDB

Siemens QuestaSim (VHDL, Verilog, SystemVerilog)

coverage report XML (vcover report -xml -details)

—

text, HTML (vcover report)

UCDB (read via the UCDB or UCIS C API)

pyucis, PyVSC (Python)

UCIS XML without namespace (pyucis convert)

—

—

— (reads UCIS XML, UCDB via a tool’s UCIS library)

FC4SC (SystemC)

UCIS XML

—

—

—

pyEDAA.Reports reads these formats:

The Go cover profile, OpenCover XML and coverlet’s JSON are described below, but not read yet.

Cobertura XML

Cobertura’s XML format is written by many tools - e.g. coverage.py (coverage xml) or gcovr (--cobertura) - for any language they measure. pyEDAA.Reports.CodeCoverage.Cobertura.Document validates a report against Any-Cobertura.xsd and reads it into the format’s model, after Cobertura’s DTD coverage-04.dtd: packages, classes, methods, lines and conditions, and the figures and rates the report states. Cobertura-04.xsd is the DTD’s strict translation.

ToCoverageSummary() converts it to the common model:

  • A <class> names its source file in filename, relative to one of the <source> directories. Several classes of one file - e.g. Java’s nested classes - become one file, their lines merged.

  • A <line>’s hits is its count; a branching line’s condition-coverage - e.g. 50% (1/2) - states its taken and all branches.

  • Packages, classes and methods become units; a package’s name is split at . into nested packages.

  • The format has no excluded lines.

Rust’s coverage tools write Cobertura XML too, as the pipeline job Rust-Cargo shows: cargo-llvm-cov (cargo llvm-cov report --cobertura) and grcov (--output-types cobertura). Both state complexity on a <method>, which coverage-04.dtd doesn’t declare, so only Any-Cobertura.xsd accepts them. A library’s function is compiled into the unit tests’ binary and into each integration test’s binary, so it is a <method> per binary, each with the same name and an empty signature.

from pathlib import Path
from pyEDAA.Reports.CodeCoverage.Cobertura import Document

report = Document(Path("coverage.xml"), analyzeAndConvert=True)
print(f"{report.LinesCovered} of {report.LinesValid} lines, as the report states")

summary = report.ToCoverageSummary()
for file in summary.IterateFiles():
  print(f"{file.Path}: {file.LineCoverage:.1%}")

FromCoverageSummary() converts the common model - read from any format - to the format’s model, and Write() writes it as a report following Cobertura’s DTD, valid according to Cobertura-04.xsd:

  • A source file, a module or a class of the logical hierarchy becomes a <class> of its file, named by its qualified name below its packages, e.g. Shapes.Circle. Its packages name its <package>, e.g. myPackage; without packages, the file’s directories do, e.g. src.Utilities - or . for the report’s root.

  • Every line is listed once: by the innermost <class> spanning it, or - outside of every one - by a <class> named after the file, e.g. main.c.

  • A function or a method becomes a <method> of the <class> of the unit containing it, listing its lines.

  • A line’s count is its hits; a line without count has 1 hit, if it ran, else 0. A branching line states its taken and all branches in condition-coverage, e.g. 50% (1/2).

  • The rates and figures are computed from the lines; the source directories are the summary’s.

  • The format has no excluded lines, no counts or targets of branches, and no calls of units: they are left out.

from pathlib import Path
from pyEDAA.Reports.CodeCoverage.Cobertura import Document

report = Document.FromCoverageSummary(Path("Cobertura.xml"), summary)
report.Write(regenerate=True)

A report read can be written again, too: Generate() builds the XML document from the format’s model, leaving out what the DTD doesn’t define - e.g. coverage.py’s missing-branches -, and computes the rates and figures the report doesn’t state.

Cobertura Dialects

Each tool writes Cobertura XML in a shape of its own - a dialect -, as the frameworks writing JUnit XML do. A dialect has a module of its own in pyEDAA.Reports.CodeCoverage.Cobertura: a Document derived from the generic one, which validates a report against the strict schema of the dialect and reads what the dialect adds. The class a report is read with chooses the dialect.

Feature

Any Cobertura

coverage.py Cobertura

NVC Cobertura

Schema

Any-Cobertura.xsd, lenient

CoveragePy-Cobertura.xsd, strict

NVC-Cobertura.xsd, strict

condition-coverage

percentage, taken and all, e.g. 50% (1/2)

percentage, taken and all, e.g. 50% (1/2)

percentage only: 0 %, 50 % or 100 %

A line’s hits

count

0 or 1: no count

count of the statements starting in the line, all instances added; 0 without a statement

Branches

taken and all, from condition-coverage

also the targets of those never taken, from missing-branches

true and false of each <condition>, taken from condition-coverage

Units

packages, classes and methods

packages and modules, from the file paths

library, entity and architecture

coverage.py Cobertura

coverage.py writes Cobertura XML with coverage xml. pyEDAA.Reports.CodeCoverage.Cobertura.CoveragePyCobertura.Document validates a report against CoveragePy-Cobertura.xsd, reverse-engineered from coverage.py 7.x, and reads it into the generic format’s model; a line also keeps the targets of its branches never taken, which coverage.py states in missing-branches.

ToCoverageSummary() converts it to the common model:

  • A line’s hits - 0 or 1 - says whether it ran: a line, which ran, is covered - partially covered, if one of its branches wasn’t taken -, otherwise uncovered. The format has no counts.

  • A branch never taken names its target line - none for an exit of a function, which coverage.py states as exit -, a taken one doesn’t.

  • A file’s directories become packages, the file a module spanning the whole file.

  • The format has no excluded lines.

from pathlib import Path
from pyEDAA.Reports.CodeCoverage.Cobertura.CoveragePyCobertura import Document

report = Document(Path("coverage.xml"), analyzeAndConvert=True)
summary = report.ToCoverageSummary()
for unit in summary.IterateUnits():
  print(f"{unit.QualifiedName}: {unit.LineCoverage:.1%}")

NVC Cobertura

NVC writes Cobertura XML with nvc --cover-export --format=cobertura, from the coverage database (*.ncdb) of a simulation elaborated with --cover, or of several merged with nvc --cover-merge. pyEDAA.Reports.CodeCoverage.Cobertura.NVCCobertura.Document validates a report against NVC-Cobertura.xsd, reverse-engineered from NVC 1.23, and reads it into the generic format’s model; a class also keeps its entity and architecture.

The beginning of a report, of the simulation in tests/data/CodeCoverage/NVC:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE coverage SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-04.dtd'>
<coverage version="nvc 1.23.0" line-rate="0.687500" branch-rate="0.363636" complexity="0.0" lines-valid="32" lines-covered="22" branches-valid="11" branches-covered="4" timestamp="1791499953">
<sources>
<source>.</source>
</sources>
<packages>
<package name="WORK" line-rate="0.687500" branch-rate="0.363636" complexity="0.0">
<classes>
<class name="COUNTER(RTL)" filename="src/Counter.vhdl" line-rate="0.526316" branch-rate="0.250000" complexity="0.0" >
<methods/>
<lines>
<line number="29" hits="86" branch="true" condition-coverage="100 %">
<conditions>
<condition number="0" type="jump" coverage="100 %"/>
</conditions>
</line>
<line number="30" hits="42" branch="true" condition-coverage="50 %">
<conditions>
<condition number="0" type="jump" coverage="50 %"/>
</conditions>
</line>
<line number="31" hits="0" branch="false"/>
<line number="32" hits="0" branch="true" condition-coverage="50 %">
<conditions>
<condition number="0" type="jump" coverage="50 %"/>
</conditions>
</line>

What NVC writes

  • A document type declaration naming coverage-04.dtd, and every attribute the DTD requires: version is NVC’s name and version, e.g. nvc 1.23.0, timestamp in seconds, a rate has six decimals, every complexity is 0.0. The only <source> is .; a file is named relative to the directory given by --relative, or as it was given to the analysis.

  • One package, named after the library of the top-level design unit, e.g. WORK - also for design units of other libraries.

  • A class per design unit, named after its entity and architecture in upper case, e.g. COUNTER(RTL), with the file of its first coverage item. All instances of a design unit are one class, their counts added. The classes follow in the reverse order of elaboration, the lines in the order of their coverage items, not sorted; there are no methods. Subprograms of a package aren’t measured.

  • A line’s hits adds how often each statement starting in the line ran: a line of two statements, e.g. State <= Idle;  Count <= (others => '0');, counts 2 each time it runs. A line without a statement - e.g. an elsif, a when of a case statement, the continuation of a conditional signal assignment - has hits 0, even when its condition was evaluated.

  • A branching line has branch="true", a condition-coverage and one <condition number="0" type="jump"> of the same coverage: 0 %, 50 % or 100 %, whether its condition was never decided, decided one way, or both ways. A choice of a case statement is always 0 %, taken or not: NVC counts only the true and false directions of a branch.

  • lines-valid counts the lines listed, lines-covered those with hits; branches-valid counts the branching lines, branches-covered those decided both ways.

  • Only statement and branch coverage reach the export. Expression, toggle, FSM state and functional coverage (--cover=expression, toggle, fsm-state, functional) add a line of hits 0 for each line with an item - e.g. a signal’s declaration for toggle coverage, a PSL cover directive for functional coverage -, which can’t be told from a statement that never ran.

Differences from coverage-04.dtd and the generic reader

  • condition-coverage is a percentage with a space before %, without the taken and all branches the DTD’s form 50% (1/2) states. Any-Cobertura.xsd and Cobertura-04.xsd reject it, so Document doesn’t read an NVC report with branch coverage.

  • A class’ name isn’t a class, but a design unit: entity and architecture. branches-valid counts branching lines, not branches.

  • A file is named per design unit, the file of its first coverage item. An entity’s statements are listed in each of its architectures’ classes; if the entity is in another file than the architecture, the architecture’s lines are listed under the entity’s file, with their line numbers in the architecture’s file.

Conversion to the common model

ToCoverageSummary() converts it to the common model:

  • A line’s hits is its count. Classes of one file are one file; a line listed by several of them - e.g. an entity’s statement - has their hits added.

  • Each <condition> becomes two branches without count - its true and its false direction -, of which condition-coverage states how many were taken: 50 % is one of two.

  • A line, which ran or took a branch, is covered - partially covered, if one of its branches wasn’t taken -, otherwise uncovered. A line without a statement, which took a branch, has no count.

  • The library becomes a package, an entity a module in it, and an architecture a module in its entity, spanning its file from the first to the last line its class lists: WORK.COUNTER.RTL.

  • The format has no excluded lines.

So the common model counts other figures than NVC: two branches per branching line, and a line without a statement, which took a branch, as covered. What the format doesn’t state is lost: which direction of a 50 % condition was taken, the counts per instance - NVC’s HTML report (nvc --cover-report) has them -, and the expression, toggle, FSM state and functional coverage.

from pathlib import Path
from pyEDAA.Reports.CodeCoverage.Cobertura.NVCCobertura import Document

report = Document(Path("coverage.xml"), analyzeAndConvert=True)
summary = report.ToCoverageSummary()
for unit in summary.IterateUnits():
  print(f"{unit.QualifiedName}: {unit.LineCoverage:.1%}")

coverage.py JSON

coverage.py writes its JSON report with coverage json. pyEDAA.Reports.CodeCoverage.CoveragePy.Document reads the format version a report states - FormatVersion - and validates the report against the JSON Schema of that version: CoveragePy-2.schema.json for format 2 (coverage.py 7.4.1 to 7.5), CoveragePy-3.schema.json for format 3 (coverage.py 7.6 and later). It reads the report into the format’s model: the measured files, their executed, missing and excluded lines, the branches as pairs of source and destination line, the summaries coverage.py computed, and - in format 3 - the functions and classes of each file.

ToCoverageSummary() converts it to the common model:

  • A file’s path is relative to the directory coverage.py ran in.

  • Executed lines are covered - partially covered, if one of their branches wasn’t taken -, missing lines uncovered, excluded lines excluded. The format has no counts.

  • A branch becomes a branch of its source line, naming its target line; an exit of a function - a negative number - has none.

  • A file’s directories become packages, the file a module spanning the whole file; its classes and functions - qualified names like Circle.Area - become classes, methods and functions, each spanning its class or def line to its last line. coverage.py’s own summary of a function counts the def line for the enclosing scope.

from pathlib import Path
from pyEDAA.Reports.CodeCoverage.CoveragePy import Document

report = Document(Path("coverage.json"), analyzeAndConvert=True)
summary = report.ToCoverageSummary()
for unit in summary.IterateUnits():
  print(f"{unit.QualifiedName}: {unit.LineCoverage:.1%}")

The pipeline job Python-pytest measures the example in examples/Python/pytest and writes both reports, JSON and Cobertura XML; see pytest.

Hint

coverage.py writes Cobertura XML too, from the same measurement, naming the files relative to the measured source directory - e.g. Shapes.py -, while its JSON report names them relative to the directory coverage.py ran in - e.g. myPackage/Shapes.py.

GHDL coverage JSON

GHDL writes a coverage file when simulating with ghdl -r --coverage, by default to coverage-<timestamp>.json, or to the file named by --coverage-output=<file>. pyEDAA.Reports.CodeCoverage.GHDL.Document reads the format version the file states as a FormatVersion - GHDL writes 1.0.0 - and validates the file against that version’s JSON Schema, GHDL-Coverage-1.0.0.schema.json. It reads the file into the format’s model: the source files, each with the directory it was analyzed in, its SHA-1 checksum, the kind of coverage as a CoverageMode - GHDL writes only stmt, statement coverage -, and per line with a coverage point, whether a statement of the line ran. GHDL instruments the design’s sources, not the libraries ieee and std.

ToCoverageSummary() converts it to the common model, as ghdl coverage --format=lcov converts it to an lcov tracefile:

  • A file’s path is the name it was analyzed by, relative to the directory GHDL ran in. A file analyzed in another directory is prefixed by that directory.

  • A line that ran is covered, a line that didn’t uncovered. The format states a flag per line, not a count: lcov’s count 1 means the line ran, at least once.

  • The format has no branches and no excluded lines, and names no units: no design units, processes or subprograms.

MergedReport merges the coverage files of several simulation runs: a line ran, if it ran in one of the runs. The files must agree on each source file’s checksum.

from pathlib import Path
from pyEDAA.Reports.CodeCoverage.GHDL import Document, MergedReport

documents = []
for path in sorted(Path(".").glob("coverage-*.json")):
  documents.append(Document(path, analyzeAndConvert=True))

summary = MergedReport("Counter", documents).ToCoverageSummary()
for file in summary.IterateFiles():
  print(f"{file.Path}: {file.LineCoverage:.1%}")

Hint

ghdl coverage reads several coverage files too, but a line’s result there is the result of the last file naming the line, so the merged coverage depends on the order of the files.

The pipeline job VHDL-GHDL simulates the example in examples/VHDL/Counter twice and writes both coverage files and an lcov tracefile of both runs; see VHDL: GHDL and NVC.

gcov JSON

GCC’s gcov writes its JSON report with gcov --json-format, for any language GCC compiles - e.g. C, C++, Fortran, Ada, or VHDL with GHDL’s GCC backend: gzip-compressed to a *.gcov.json.gz file per data file, or - with --stdout - as plain JSON, one line per data file. pyEDAA.Reports.CodeCoverage.Gcov.Document reads either, validates each JSON object against the JSON Schema of the format version it states - Gcov-1.schema.json for format 1 (GCC 9 to 13), Gcov-2.schema.json for format 2 (GCC 14 and later) - and reads it into the format’s model: the data files, their source files, functions and lines, and each line’s branches, calls and conditions (MC/DC, gcc -fcondition-coverage), in format 2 with the IDs of the basic blocks they belong to. An object stating another format version is rejected.

ToCoverageSummary() converts it to the common model:

  • A file’s path is relative to the directory the compiler ran in, which becomes a source directory.

  • A line’s count is its count; a line, which ran without taking all of its branches, is partially covered. A line several functions share - e.g. the instantiations of a template - or several data files state - e.g. a header - becomes one line, its counts added. So gcov’s own summary counts the lines of a template once per instantiation.

  • A branch becomes a branch of its line, with its count; exceptional branches (throw) are branches too, as gcov counts them. A branch leads to a basic block, not to a line, so it has no target.

  • A file becomes a SourceFile, its functions - by demangled name, e.g. Containers::Stack::Pop() - become functions, each spanning its first to its last line, with its execution count. A demangled name doesn’t tell a class from a namespace, so there are no classes and methods.

  • Calls, conditions and basic blocks have no counterpart in the common model; the format’s model keeps them. The format has no excluded lines.

from pathlib import Path
from pyEDAA.Reports.CodeCoverage.Gcov import Document

report = Document(Path("main.gcov.json.gz"), analyzeAndConvert=True)
summary = report.ToCoverageSummary()
for unit in summary.IterateUnits():
  print(f"{unit.QualifiedName}: {unit.LineCoverage:.1%}")

The pipeline jobs Cpp-GoogleTest and Cpp-Catch2 write gcov JSON for the examples in examples/Cpp/GoogleTest and examples/Cpp/Catch2, see GoogleTest (gtest). A source file outside the directory the compiler ran in keeps its absolute path.

lcov Tracefile

lcov writes its tracefile with lcov --capture; many tools write the format too - e.g. llvm-cov (llvm-cov export -format=lcov), coverage.py (coverage lcov) or GHDL (ghdl coverage --format=lcov). The format is specified by lcov’s manual page geninfo(1), section TRACEFILE FORMAT: a text file of records, one per line, e.g. DA:<line number>,<execution count>. pyEDAA.Reports.CodeCoverage.LCOV.Document reads it with a strict line parser - an unknown, malformed or misplaced record raises an exception noting its line - into the format’s model: a section per source file and test, with its functions and their aliases, its lines with counts and checksums, its branches, its MC/DC conditions, and the summaries it states.

ToCoverageSummary() converts it to the common model:

  • A section names its source file in SF. The sections of one file - one per test, named by TN - become one file, their counts added.

  • A line’s DA count is its count. A branch - BRDA - is covered, if it was taken; a branch never evaluated - - - is uncovered without count. A line is partially covered, if one of its branches wasn’t taken.

  • A file becomes a SourceFile unit, its functions - FN, or FNL with its aliases FNA - Function units, each spanning its start to its end line. A function without end line names its start line only.

  • The format has no excluded lines - lcov leaves them out -, no branch targets and no units but functions. The MC/DC conditions - MCDC - stay in the format’s model.

from pathlib import Path
from pyEDAA.Reports.CodeCoverage.LCOV import Document

tracefile = Document(Path("coverage.info"), analyzeAndConvert=True)
summary = tracefile.ToCoverageSummary()
for unit in summary.IterateUnits():
  print(f"{unit.QualifiedName}: {unit.Status.name}, {unit.LineCoverage:.1%}")

The tools write different parts of the format:

Tool

Tracefile

lcov 2.3 (GCC)

TN from --test-name. Functions as FNL leader with end line and FNA aliases. Branches never evaluated as -. MCDC conditions with --mcdc-coverage, summarized by MCF and MCH - the manual page names them MRF and MRH, while lcov writes and reads MCF and MCH. Checksums with --checksum.

llvm-cov 19

No TN. Functions as FN without end line and FNDA; a static function of a header named after its translation unit, e.g. Classify.c:Clamp. No branch as -.

coverage.py 7.16

No TN. Functions as FN with end line, named by qualified names like Circle.Area. Branches named after their target, e.g. jump to line 8. A section without records for an empty file.

GHDL 7.0

TN only before the first section. Each file as one function file at line 1. No summaries. Lines of a subprogram, which was never called, aren’t listed.

UCIS XML

The Unified Coverage Interoperability Standard (UCIS) 1.0 of Accellera (June 2012) specifies the data model of a coverage database, a C API to it, and an XML interchange format. An XML report names the namespace UCIS and its root <UCIS>, which states the UCIS version - ucisVersion -, the tool, which wrote it, and when. pyEDAA.Reports.CodeCoverage.UCIS.Document checks the root element, validates the report against the XML schema of its UCIS version - UCIS-1.0.xsd, the schema the standard specifies - and reads it into the format’s model:

  • the source files, by the ID statement identifiers name them by,

  • the history nodes - the tests and merges -, by their ID, with the tool, the date and how the test ran,

  • the instances, where they are instantiated, their design unit, the instance they are instantiated in, their design parameters,

  • each instance’s coverage of each kind, once per metric mode: statement and block coverage - statements, blocks, nested blocks, processes -, branch coverage - branching statements, their branches, the branching statements nested in a branch -, toggle coverage - signals, their dimensions, bits and transitions -, condition and expression coverage - expressions, nested by their sub-expressions, a bin per combination of values -, FSM coverage - state machines, their states and transitions -, assertion coverage - a bin per outcome, e.g. pass, fail or vacuous pass - and covergroup coverage - covergroup instances, their options, coverpoints with bins of value ranges or sequences, crosses with bins of combined coverpoint bins -,

  • each coverage item’s bin: how often it was covered, the tests, which covered it, whether it is excluded, its goal,

  • and every element’s user-defined attributes.

ToCoverageSummary() converts the statement and branch coverage to the common model:

  • The statements are the statements of the block coverages, and the statements of their blocks, which ran as often as their block - a block without statements counts for its own line. A statement is identified by its file, its line and its index in the line. With mergeInstances=True, the statements of all instances are merged: a statement ran as often as in all instances together. Otherwise each instance’s statement counts on its own.

  • A file’s path is the one its source file states; a source file without coverage becomes no file. The directories the tests ran in are the summary’s source directories. The summary is named after the report file.

  • A line ran as often as its least often run statement: it is covered, if all its statements ran, otherwise uncovered. A line, whose statements are all excluded, is excluded; an excluded statement doesn’t count for its line.

  • A branching statement’s branches become branches of its line - with mergeInstances=True summed per branch over the instances. A line, which ran without taking all its branches, is partially covered. A branch goes to the line of its first statement, if the report lists that line. A line with a branching statement, but no statement, ran as often as its branches were taken.

  • A design unit - an instance’s moduleName - becomes a Module, spanning the lines of its instances’ statements and branching statements from the first to the last one, if they are in one file.

What the conversion leaves out: the instance hierarchy, the history nodes, the bins’ goals and weights, the metric modes and the user-defined attributes. An excluded branch or branching statement isn’t converted. The toggle, condition, expression, FSM, assertion and covergroup coverage stays in the format’s model: the common model has no counterpart.

from pathlib import Path
from pyEDAA.Reports.CodeCoverage.UCIS import Document

report = Document(Path("coverage.xml"), analyzeAndConvert=True)
summary = report.ToCoverageSummary(mergeInstances=True)
for file in summary.IterateFiles():
  print(f"{file.Path}: {file.LineCoverage:.1%}")

for instance in report.Instances:
  print(f"{instance.Name}: {instance.ModuleName}")

The command line reads the report as UCIS-XML, without merging the instances.

Hint

Which tools write the UCIS XML interchange format:

  • Siemens QuestaSim documents no command writing it - neither in the command reference of Questa SIM 2024.2 nor of 2025.2. Questa supports the UCIS by a C library implementing its API; its XML report (vcover report -xml) is a format of its own: QuestaSim coverage report XML.

  • Aldec’s acdb2xml writes a dump of the UCIS data model in a format of its own (namespace www.aldec.com, root <ux:ucdb>), not the interchange format.

  • pyucis - and PyVSC, which writes its coverage with pyucis - write the interchange format with the elements in no namespace: pyucis UCIS XML.

  • FC4SC writes covergroups in the interchange format, in namespace UCIS since 2020; before, in the XML Schema instance namespace.

The standard’s chapter on the XML format contradicts its own schema in several examples - e.g. branchStatement for statement, exprBin for bin, a coverpointBin with name and key -, which look like leftovers of a draft. The reader follows the schema.

See also

Why pyEDAA.Reports reads UCIS XML itself

→ pyucis and pyEDAA.Reports compared, where the schema comes from, and how both work together.

pyucis UCIS XML

pyucis writes UCIS XML - e.g. with pyucis convert, or for PyVSC’s vsc.write_coverage_db("coverage.xml") -, whose root binds the prefix ucis to the XML Schema instance namespace, but whose elements are in no namespace. pyEDAA.Reports.CodeCoverage.UCIS.PyUCIS.Document validates a report against PyUCIS-1.0.xsd - the standard’s schema without a target namespace; as pyucis’ own schema has it, a coverpoint’s and a cross’ bin state a name and a key, and a covergroup instance may have no coverpoint - and reads and converts it as the standard’s format. pyucis names a source file __null__file__ with ID 1 for the statement identifiers, which name no file; it has no coverage, so it becomes no file.

from pathlib import Path
from pyEDAA.Reports.CodeCoverage.UCIS.PyUCIS import Document

report = Document(Path("coverage.xml"), analyzeAndConvert=True)
summary = report.ToCoverageSummary()

The command line reads the report as PyUCIS-XML, without merging the instances.

JaCoCo XML

JaCoCo - the Java code coverage library - writes its XML report with jacococli.jar report --xml, Maven’s goals report and report-aggregate, Ant’s task report or Gradle’s task jacocoTestReport. pyEDAA.Reports.CodeCoverage.JaCoCo.Document reads the format version from the public identifier of the DTD a report names - -//JACOCO//DTD Report 1.1//EN -, validates the report against the XML schema of that version - JaCoCo-1.1.xsd, a translation of JaCoCo’s DTD report.dtd - and reads it into the format’s model: the sessions, which contributed execution data, the groups - nested -, the packages, their classes with their methods and their source files with their lines, and every element’s counters - per kind of items, e.g. instructions, branches or lines, the missed and the covered ones. A report naming no or another public identifier is rejected. JaCoCo’s CSV report isn’t read.

ToCoverageSummary() converts it to the common model:

  • A source file’s path is its package’s name in VM notation - e.g. my/pack - and its name, relative to a source directory, which the report doesn’t name.

  • A line ran, if it has a covered instruction - partially covered, if it has a missed branch. Its missed and covered branches become as many branches.

  • Packages, classes and methods become units; a package’s name is split at / into nested packages. A class is named by its binary name without its package - e.g. MyClass$Inner -, a method by its name and descriptor - e.g. absolute(I)I. The state of a class or method is its CLASS or METHOD counter’s.

The format states no counts, no branch targets, no excluded lines and no last line of a method. What the conversion loses or approximates:

  • A line’s numbers of instructions and every counter: only the format’s model keeps them. A line with missed and covered instructions, but no missed branch, is covered.

  • A method spans its first line and the next lines of its file, as many as its LINE counter counts - so it ends too early, if the lines of a lambda or of a nested class lie in between. A class spans its methods.

  • The groups have no counterpart: their packages are converted, as if the report had none. Two packages of the same name in two groups, which state a source file of the same name, are rejected.

  • A class compiled without debug information has no source file, its methods no lines: their units have no lines.

  • gcovr’s JaCoCo report (--jacoco) names the DTD by its system identifier only, so it is rejected.

from pathlib import Path
from pyEDAA.Reports.CodeCoverage.JaCoCo import Document

report = Document(Path("jacocoTestReport.xml"), analyzeAndConvert=True)
summary = report.ToCoverageSummary()
for unit in summary.IterateUnits():
  print(f"{unit.QualifiedName}: {unit.LineCoverage:.1%}")

Aldec UCDB XML

Aldec’s simulators Riviera-PRO and Active-HDL keep code coverage in a coverage database (ACDB), which the tool acdb2xml exports as XML:

acdb2xml -i aggregate.acdb -o ucdb.xml

The XML names its namespace www.aldec.com and its root <ux:ucdb>: it is a dump of the database’s UCIS data model - history nodes, scopes and their coverage items (bins) -, not the XML interchange format of the Accellera UCIS standard, whose root is <UCIS>. pyEDAA.Reports.CodeCoverage.AldecUCDB.Document checks the root element, validates the report against Aldec-UCDB.xsd - reverse-engineered, the format states no version, only the tool’s, e.g. Riviera-PRO 2022.04 - and reads it into the format’s model:

  • the report’s attributes, e.g. the path separator,

  • the history nodes: the tests, which wrote a coverage database, and the merges of databases, each with its attributes, e.g. the tool, the date and the command line,

  • the scopes - design units, their instances, packages, classes, processes, branching statements, … - with their kind, language, flags and source location, nested as in the design,

  • each scope’s bins: the coverage items, e.g. statements, branches or blocks, with their count, flags - e.g. whether excluded - and source location,

  • the commands, e.g. of an exclusion, kept as stated.

ToCoverageSummary() converts the statement coverage to the common model, as pyEDAA.UCIS converted it to Cobertura XML:

  • The statements are the statement bins (STMTBIN) of the instances: the bins of the scopes below each top-level scope, which isn’t a design unit; a design unit’s scope holds the coverage of all its instances, which the instances state already. A statement is identified by its file, line and index in the line (attribute #SINDEX#).

  • With mergeInstances=True, the statements of all instances of a design unit are merged: a statement ran as often as in all instances together. Otherwise each instance’s statement counts on its own.

  • A file’s path is the one the statement bins state, relative to the directory the tool ran in; these directories are the summary’s source directories. The summary is named after the report file.

  • A line ran as often as its least often run statement: it is covered, if all its statements ran, otherwise uncovered. A line, whose statements are all excluded, is excluded; an excluded statement doesn’t count for its line.

What the conversion leaves out: the branches, blocks and other kinds of coverage, the design units and instances - the common model has no units for them -, and the history nodes. IterateStatements() yields the statements themselves, e.g. to count them.

from pathlib import Path
from pyEDAA.Reports.CodeCoverage.AldecUCDB import Document

report = Document(Path("ucdb.xml"), analyzeAndConvert=True)
summary = report.ToCoverageSummary(mergeInstances=True)
for file in summary.IterateFiles():
  print(f"{file.Path}: {file.LineCoverage:.1%}")

statements = [statement for statement in report.IterateStatements() if not statement.IsExcluded]
covered = sum(1 for statement in statements if statement.Count > 0)
print(f"Statements: {covered} of {len(statements)}")

The command line reads the report as Aldec-UCDB-XML, without merging the instances.

Hint

The reader was transferred from pyEDAA.UCIS, contributed by Aldec Inc. Its command pyedaa-ucis export --ucdb ucdb.xml --cobertura cobertura.xml corresponds to pyedaa-reports coverage --input=Aldec-UCDB-XML:ucdb.xml --output=Cobertura:cobertura.xml; its option --merge-instances to the parameter mergeInstances of ToCoverageSummary().

QuestaSim coverage report XML

Questa SIM - and ModelSim before it - keeps code coverage in a coverage database (UCDB), whose format is proprietary. The command vcover report writes a report of a database; with -xml -details, the report is XML with the coverage items of each instance, which pyEDAA.Reports reads:

vcover report -xml -details -output CoverageReport.xml Testsuite.ucdb

-code chooses the kinds of coverage items - by default, all kinds collected at compile time -, e.g. -code bcesf for branches (b), conditions (c), expressions (e), statements (s) and finite state machines (f). In a simulation, coverage report -xml -details -output CoverageReport.xml writes the same format. The XML’s root is <coverage_report>, without namespace; it isn’t the XML interchange format of the UCIS standard. pyEDAA.Reports.CodeCoverage.QuestaSim.Document validates a report against QuestaSim-Coverage.xsd and reads it into the format’s model:

  • The Questa version and the command, which wrote the report.

  • The report’s mode - by instance (the default), by design unit (-du=<name>) or by source file - and its scopes: <instanceData>, <DuData> or <fileData>. An instance names its path, its design unit and - unless it is a package - its architecture, and its source files, numbered in a source table.

  • Per scope, the coverage statistics - per kind of coverage items, how many the scope has and how many were hit - and the coverage items:

    • statements (<stmt>): file, line, index in the line, and how often the statement ran;

    • if statements (<if>) with a branch per if, elsif and else (<ielem>): how often the branch was taken (true), and how often its condition was false, so the evaluation went on to the next branch (false). An if without else has an implicit AllFalse branch as its last branch, in the line of the if;

    • case statements (<case>) with a branch per case item (<celem>): how often it was taken;

    • the states (<state>) and transitions (<trans>) of finite state machines: how often a state was entered and a transition taken.

ToCoverageSummary() converts the statements and branches to the common model:

  • The coverage items of all scopes are merged by file, line and index: a statement or branch of a design unit with several instances counts as often as in all instances together.

  • An if or case statement adds its branches to the line of its first branch - the line of the if, or of the first case item, as Questa states no line of the case itself -; a branch counts as often as Questa counted it taken. The line ran as often as the statement was evaluated: the sum of its branches’ counts.

  • A line ran as often as its least often run statement or branching statement. A line, which ran, is covered, if all its branches were taken, otherwise partially covered.

  • A file’s path is the one the report states - relative to the directory Questa ran in, or absolute.

What the conversion leaves out:

  • The coverage statistics, and the states and transitions of finite state machines - only the format’s model has them.

  • Conditions, expressions and toggles: no report with their details is known yet, so they aren’t read.

  • The design units and instances: they don’t become units of the common model.

  • Functional coverage reports (-cvg, -directive, -assert) are rejected.

from pathlib import Path
from pyEDAA.Reports.CodeCoverage.QuestaSim import Document

report = Document(Path("CoverageReport.xml"), analyzeAndConvert=True)
summary = report.ToCoverageSummary()
for file in summary.IterateFiles():
  print(f"{file.Path}: {file.LineCoverage:.1%} lines, {file.BranchCoverage:.1%} branches")

The command line reads the report as QuestaSim-XML and writes it as Cobertura XML:

vcover report -xml -details -output CoverageReport.xml Testsuite.ucdb
pyedaa-reports coverage --input=QuestaSim-XML:CoverageReport.xml --output=Cobertura:coverage.xml

LLVM coverage JSON

llvm-cov export -format=text writes the source-based code coverage of Clang - and of other compilers based on LLVM, e.g. rustc or Swift - as JSON. pyEDAA.Reports.CodeCoverage.LLVM.Document validates a report against the JSON Schema LLVM-Coverage-JSON.schema.json - format versions 2.0.0 to 3.1.0, LLVM 4 to 23 - and reads it into the format’s model: per file its segments, branch regions, MC/DC records, macro expansions and summary, per function its call count, regions, branch regions and MC/DC records, and the totals. A region names its source range by line and column.

Each class of the format’s model takes typed values in its constructor, so a model can be built by hand, too. A classmethod Parse reads the class’ JSON element and calls the constructor - e.g. File.Parse() reads a file’s JSON object.

Every element knows the element containing it (Parent) and the report (Root). A container connects the elements given to its constructor; an element created with a parent is added to the parent’s collection. When a file or function joins the report, the report becomes the root of every element below it.

A region names its file by an index into the file paths of its function or expansion. Properties look it up: FilePath returns the path, File the report’s File of that path; an expansion region’s ExpandedFile and a function’s MainFile likewise. A file ID is checked when the region joins a function or expansion, so the lookup needs no separate resolution run.

ToCoverageSummary() converts it to the common model:

  • A file’s path is relative to the directory common to all files, which becomes the report’s source directory.

  • A line’s count is derived from the file’s segments, as llvm-cov show derives it: a line is executable, if a region with a count starts on it, or one continues from a previous line - unless a skipped region starts the line, e.g. #if 0, an empty line or a comment. Its count is the largest of the regions starting on it - but gap regions - and of the region continuing from a previous line.

  • A branch region becomes two branches - its true and its false outcome - of the line it starts at; a branch region in a macro expansion becomes branches of the line the macro is expanded at. A line with an outcome never taken is partially covered.

  • A file becomes a SourceFile, named by its path, and each function a Function of its file, with its call count. A function local to its translation unit is named without the file name prefixing it, e.g. Statistics.c:Square becomes Square.

  • The format has no excluded lines.

from pathlib import Path
from pyEDAA.Reports.CodeCoverage.LLVM import Document

report = Document(Path("coverage.json"), analyzeAndConvert=True)
print(f"{report.Totals.Lines.Covered} of {report.Totals.Lines.Count} lines, as llvm-cov counts them")

summary = report.ToCoverageSummary()
for file in summary.IterateFiles():
  print(f"{file.Path}: {file.LineCoverage:.1%} of lines, {file.BranchCoverage:.1%} of branches")

Hint

llvm-cov’s own summaries count lines and branches per function: a template’s instantiations count as their best one, and a macro’s definition line doesn’t count. The common model counts per line of the file: the branch outcomes of a template’s instantiations are summed, and a macro defined in the same file counts at its definition line, as llvm-cov show shows it.

Attention

The common model has no columns: the regions, the MC/DC records and the expansions stay in the format’s model. The export doesn’t demangle names - a C++ function is named e.g. _ZNK8Geometry9Rectangle4AreaEv -, and states no classes or namespaces.

Go Cover Profile

go test -coverprofile=coverage.out -covermode=count writes Go’s cover profile: a text format, whose first line names the mode and every further line a block of a source file. pyEDAA.Reports doesn’t read it yet.

mode: count
github.com/edaa-org/pyEDAA.Reports/examples/Go/testing/counter/counter.go:34.44,35.18 1 2
github.com/edaa-org/pyEDAA.Reports/examples/Go/testing/counter/counter.go:35.18,37.3 1 1
  • The mode is set - whether a block ran: 0 or 1 -, count - how often - or atomic - how often, counted thread-safe.

  • A block is <file>:<start line>.<start column>,<end line>.<end column> <statements> <count>: the file named by its package’s import path, the block’s start and end - columns in bytes, starting at 1, the end exclusive -, the number of its statements and its count.

  • A block is a sequence of statements, not a line: it starts and ends inside a line, two blocks share the line, where a branch starts. The format has no branches, functions or excluded lines.

  • Each package’s test binary adds the blocks of its package. A package without tests is listed with count 0; a package, whose test binary panicked, is missing.

Converters write it in the formats other tools read:

gocover-cobertura

gocover-cobertura < coverage.out > cobertura.xml writes Cobertura XML after coverage-04.dtd. A package is named by its import path, a class by its receiver type - - for the package’s functions -, a method by its function; so several classes name the same file, relative to the module’s directory in <source>. Every line from a block’s start to its end line gets the block’s count - also a blank line or a comment line -, a line shared by two blocks the sum of their counts. version is empty, timestamp in milliseconds and the branch figures are 0. -ignore-non-code-lines leaves out lines without code, -by-files writes a class per file.

gcov2lcov

gcov2lcov -infile coverage.out -outfile coverage.info writes an lcov tracefile: per file a record of TN: (empty), SF:, DA:, LF: and LH:, but no function or branch records. A line’s count is figured as by gocover-cobertura. SF: is relative to the repository, if a .git directory is found above the file, otherwise absolute. The order of the records changes from run to run.

The pipeline job Go-Test writes all three for the example in examples/Go/testing, see Go (go test).

OpenCover XML

OpenCover’s XML format - written e.g. by coverlet (opencover) for .NET - has a root <CoverageSession> and a <Summary> on each level: the numbers of sequence and branch points and of the visited ones, the coverage ratios and the cyclomatic complexity. A <Module> (assembly) lists its source files (<File uid="..." fullPath="..."/>) and classes, a class its methods. A method states its <SequencePoints> - visit count vc, the source range from sl/sc to el/ec, branch exits bec and visited ones bev - and its <BranchPoints> - visit count, IL offsets of the branch and its target, and the line. coverlet writes no real columns.

There is no reader yet.

coverlet JSON

coverlet’s own JSON report nests objects by assembly (e.g. MyLibrary.dll), absolute source file path, class and method (IL signature, e.g. System.Void MyLibrary.Counter::Decrement()). A method has Lines - the count of each line - and Branches - a list of Line, IL Offset and EndOffset, Path, Ordinal and Hits. The report states no figures and no ratios.

There is no reader yet.

Command Line

The command coverage of pyedaa-reports reads a report of any format above, which a reader exists for, shows its line and branch figures and, with --output, writes it as Cobertura XML - so any format becomes the format most tools read (see Command Line Interfaces):

$ pyedaa-reports coverage --input=Gcov-JSON:main.gcov.json.gz --output=Cobertura:coverage.xml
Reading code coverage report 'main.gcov.json.gz' (Gcov-JSON) ...
Lines:    26 of 27 covered (96.3%)
Writing Cobertura XML report 'coverage.xml' ...

--input names the format and the file: Aldec-UCDB-XML, Any-Cobertura, CoveragePy-Cobertura, CoveragePy-JSON, Gcov-JSON, GHDL-JSON, JaCoCo-XML, LCOV, LLVM-JSON, NVC-Cobertura, PyUCIS-XML, QuestaSim-XML or UCIS-XML, spelled as here (InputFormat). A file without format is read as Any-Cobertura. --output names the format Cobertura (OutputFormat, also the default) and the file. -v adds the figures of each file.

Tools

VHDL: GHDL and NVC

The example in examples/VHDL/Counter - a counter and its testbench, without a verification framework - is simulated by a Bash script per simulator, twice: counting, then counting with resets (generics CYCLES and RESETS).

  • GHDL.sh (job VHDL-GHDL, GHDL’s mcode backend): ghdl -r --coverage --coverage-output=<file> writes a GHDL coverage file per run (statement coverage); ghdl coverage --format=lcov writes an lcov tracefile of both runs.

  • NVC.sh (job VHDL-NVC): nvc -e --cover=statement,branch writes a coverage database per run, nvc --cover-merge merges them, nvc --cover-export --format=cobertura --relative=. exports Cobertura XML - NVC’s only export format. NVC writes condition-coverage as e.g. 50 %, without the numbers of covered and valid conditions coverage-04.dtd writers add (50% (1/2)), so the generic Cobertura reader rejects the file. Document (CLI --input=NVC-Cobertura:...) reads it, see NVC Cobertura.

.NET: coverlet, Microsoft’s code coverage collector, ReportGenerator

The example in examples/CSharp/xUnit is built and run by the pipeline job CSharp-xUnit (see dotnet test (VSTest)). Its tests run twice:

  • with coverlet (coverlet.collector, dotnet test --collect "XPlat Code Coverage"), writing Cobertura XML, an lcov tracefile, OpenCover XML and coverlet’s JSON. coverlet.msbuild writes the same reports.

  • with Microsoft’s code coverage collector (part of Microsoft.NET.Test.Sdk, dotnet test --collect "Code Coverage;Format=cobertura"), writing Cobertura XML.

ReportGenerator converts coverlet’s Cobertura report to Cobertura XML and an lcov tracefile of its own.

The three Cobertura reports differ:

Feature

coverlet

Microsoft

ReportGenerator

A line’s branch

True, False

True, False

true, false

Read by Document

no: True isn’t an XML Schema boolean

no: True isn’t an XML Schema boolean

yes

version of <coverage>

1.9

1.9

0, with a DOCTYPE naming coverage-04.dtd

filename of <class>

relative to the only <source>

absolute, no <sources>

absolute, and a <source>

A line’s hits

count

0 or 1

count

<conditions>

one per branching line, number is the IL offset

one per branching line, number is 0

none

Compiler-generated classes, e.g. of an async method

own class, nested by /: Counter/<IncrementAsync>d__6

own class, nested by .: Counter.<IncrementAsync>d__6; also a lambda’s class <>c

merged into the declaring class and method

Test assembly

excluded

included

excluded

signature of <method>

(System.Int32,System.Int32)

(int, int)

(System.Int32,System.Int32)

All three name a class with its namespace, e.g. MyLibrary.Calculator in package MyLibrary, and state complexity on <method>, which coverage-04.dtd doesn’t declare; coverlet’s <coverage> lacks the complexity the DTD requires.

The two lcov tracefiles differ too:

Feature

coverlet

ReportGenerator

TN:

none

an empty one at the start

FN: line number

the line before the function’s first line

the function’s first line

FN: function name

IL signature with commas, e.g. System.Int32 MyLibrary.Calculator::Add(System.Int32,System.Int32)

method name and parameter types, e.g. Add(System.Int32,System.Int32)

BRDA: block

IL offset of the branch, e.g. BRDA:11,7,0,2

line number, e.g. BRDA:11,11,0,1

BRDA: branch

0 and 1 per block

numbered through the file

BRDA: taken

count

1, or - for a branch not taken

DA: order

by function; a state machine’s lines follow the lines of its class

ascending

Both use the function records of lcov before version 2.2 (FN, FNDA); coverlet’s tracefile doesn’t end with a newline.