Skip to content

pkg/coverage

LLVM source-based code coverage collection, HTML report generation, and per-file summary statistics.

import "github.com/professor-moody/crucible/pkg/coverage"

Types

FileCoverage

type FileCoverage struct {
    Filename  string
    Lines     int
    LineHits  int
    LinePct   float64
    Funcs     int
    FuncHits  int
    FuncPct   float64
    Branches  int
    BranchHit int
    BranchPct float64
}

Coverage statistics for a single source file, parsed from llvm-cov export --summary-only JSON output.


ReportSummary

type ReportSummary struct {
    Files      []FileCoverage
    TotalLines int
    HitLines   int
    LinePct    float64
}

Aggregate coverage data across all source files. Files is sorted by LinePct ascending (worst-covered files first). The summary deliberately does not retain the temporary profile directory: the CLI removes that directory after reporting, so exposing it would publish a dead path.


Functions

CollectAndMerge

func CollectAndMerge(
    harness, corpusDir, tmpDir string,
    timeout time.Duration,
    extraEnv []string,
) (string, error)

Recursively replays seed files in corpusDir through the harness binary, collecting raw .profraw files via LLVM_PROFILE_FILE. Hidden directories and non-seed extensions such as dictionaries, logs, scripts, and existing profile data are skipped. Each seed receives a unique profile name derived from its relative path.

timeout is the per-seed deadline (0 means no deadline). extraEnv entries are appended to the child environment, so callers can supply target-specific assets and runtime settings. The function merges all collected profiles into one .profdata with llvm-profdata merge -sparse.

Returns the path to the merged profdata file within tmpDir. The caller is responsible for cleaning up tmpDir.

The harness must be built with -fprofile-instr-generate -fcoverage-mapping and must execute one input then return; a libFuzzer main loop is not a coverage-replay harness.

Returns an error if no profile data is collected from any corpus entry.

GenerateHTML

func GenerateHTML(harness, profdata, sourceDir, outputDir string) error

Runs llvm-cov show to produce an HTML coverage report directory. The report includes annotated source files with line-by-line execution counts and region highlighting.

When sourceDir is non-empty, it is passed as -path-equivalence=/,<sourceDir> to remap source paths.

GenerateSummary

func GenerateSummary(harness, profdata string) (*ReportSummary, error)

Runs llvm-cov export --summary-only and parses the JSON result into per-file coverage statistics. Returns a ReportSummary with files sorted by line coverage percentage (worst first).

FormatSummary

func FormatSummary(s *ReportSummary) string

Returns a human-readable table of per-file coverage stats. Includes columns for file name, lines (total/hit/percent), and functions (total/hit/percent). Long file paths are shortened for display.


Usage

tmpDir, _ := os.MkdirTemp("", "crucible-cov-*")
defer os.RemoveAll(tmpDir)

// Collect coverage from corpus replay
profdata, err := coverage.CollectAndMerge(
    "./crucible-cov", "./corpus", tmpDir,
    30*time.Second, nil,
)
if err != nil {
    log.Fatal(err)
}

// Generate HTML report
coverage.GenerateHTML("./crucible-cov", profdata, "/path/to/llama.cpp", "./coverage-report")

// Print per-file summary
summary, _ := coverage.GenerateSummary("./crucible-cov", profdata)
fmt.Println(coverage.FormatSummary(summary))

Prerequisites

The harness binary must be built with LLVM coverage instrumentation:

clang -g -O1 -fprofile-instr-generate -fcoverage-mapping \
  -DGGML_USE_CPU \
  ggml/src/ggml.c ggml/src/gguf.c \
  harness/libfuzzer/harness.c \
  -o crucible-cov \
  -lstdc++ -lm

llvm-profdata and llvm-cov must be available in PATH.