Skip to documentation
SLOP

tiny.profiling.report.coverage

Reference tiny.profiling report coverage

Defined in report.

API (9)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

No direct callersNo direct callsreportcoverage
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsprivate; no linksrc.profiling.driver.runfinishRunhost.coverageplanrecordworkloadPathsprivate; no linksrc.profiling.report.coveragedeinitCoverageMapprivate; no linksrc.profiling.report.coveragegitTrackedZigFilesprivate; no linksrc.profiling.report.coveragemergeWorkloadLines+3 morereport.coveragewriteRunArtifacts
Static calls · unresolved targets: 2 · external targets: 3.

Source: src/profiling/report/coverage.zig

zig
const std = @import("std");const capture = @import("capture");const pretty = @import("pretty");const sys = @import("sys");const catalog = @import("../root.zig").catalog;const host = @import("../root.zig").host;const json = @import("../root.zig").json;const plan = @import("../root.zig").plan;const record = @import("../root.zig").record;const pretty_json = pretty.json;const fs_io = sys.fs.debugIo();pub const summary_schema = "tiny.repository-source-coverage-summary/v1";pub const line_schema = "tiny.repository-source-coverage-line/v1";pub const summary_name = "coverage.summary.json";pub const lines_name = "coverage.lines.jsonl";pub const uncovered_name = "coverage.uncovered.jsonl";pub const line_universe = "git_tracked_zig_physical_lines";const max_git_ls_bytes = 32 * 1024 * 1024;const max_source_file_bytes = 64 * 1024 * 1024;const max_workload_lines_bytes = 512 * 1024 * 1024;pub const Summary = struct {    source_files: usize,    physical_lines: usize,    code_lines: usize,    blank_lines: usize,    selected_workloads: usize,    captured_workloads: usize,    instrumented_lines: usize,    covered_lines: usize,    covered_code_lines: usize,    uncovered_code_lines: usize,    uninstrumented_code_lines: usize,    instrumented_uncovered_code_lines: usize,    summary_path: []const u8,    lines_path: []const u8,    uncovered_path: []const u8,    workloads: []const []const u8,    pub fn coveredPercent(self: Summary) ?f64 {        if (self.code_lines == 0) return null;        return (@as(f64, @floatFromInt(self.covered_code_lines)) * 100.0) / @as(f64, @floatFromInt(self.code_lines));    }};const CoveredLine = struct {    hits: u64 = 0,    instrumented_workload_count: usize = 0,    covered_workloads: std.ArrayListUnmanaged([]const u8) = .empty,    fn add(self: *CoveredLine, allocator: std.mem.Allocator, workload: []const u8, hits: u64) !void {        self.instrumented_workload_count += 1;        self.hits += hits;        if (hits == 0) return;        for (self.covered_workloads.items) |existing| {            if (std.mem.eql(u8, existing, workload)) return;        }        try self.covered_workloads.append(allocator, workload);    }    fn deinit(self: *CoveredLine, allocator: std.mem.Allocator) void {        self.covered_workloads.deinit(allocator);    }};const CoverageMap = std.StringHashMapUnmanaged(CoveredLine);pub fn writeRunArtifacts(    allocator: std.mem.Allocator,    process_io: std.Io,    paths: record.Paths,    selection: plan.Selection,) !Summary {    const files = try gitTrackedZigFiles(allocator, process_io);    const repository_root = try sys.fs.cwdAlloc(allocator);    var coverage_map: CoverageMap = .empty;    defer deinitCoverageMap(allocator, &coverage_map);    var workloads: std.ArrayList([]const u8) = .empty;    for (catalog.workloads) |workload| {        if (!selection.selected(workload)) continue;        const workload_paths = try record.workloadPaths(allocator, paths, workload.name);        const lane = try host.coverage.plan(allocator, .{}, workload_paths.root);        if (!sys.fs.exists(lane.summary_path) or !sys.fs.exists(lane.lines_path)) continue;        try mergeWorkloadLines(            allocator,            &coverage_map,            repository_root,            lane.summary_path,            lane.lines_path,            workload.name,        );        try workloads.append(allocator, workload.name);    }    const summary_path = try std.fs.path.join(allocator, &.{ paths.root, summary_name });    const lines_path = try std.fs.path.join(allocator, &.{ paths.root, lines_name });    const uncovered_path = try std.fs.path.join(allocator, &.{ paths.root, uncovered_name });    var summary = try writeLineMaps(allocator, files, &coverage_map, lines_path, uncovered_path);    summary.selected_workloads = selection.count();    summary.captured_workloads = workloads.items.len;    summary.summary_path = summary_path;    summary.lines_path = lines_path;    summary.uncovered_path = uncovered_path;    summary.workloads = try workloads.toOwnedSlice(allocator);    try writeSummaryFile(summary_path, summary);    return summary;}fn gitTrackedZigFiles(allocator: std.mem.Allocator, process_io: std.Io) ![]const []const u8 {    const result = try sys.process.run(allocator, process_io, .{        .argv = &.{ "git", "ls-files", "*.zig" },        .stdout_limit = .limited(max_git_ls_bytes),        .stderr_limit = .limited(64 * 1024),    });    defer allocator.free(result.stdout);    defer allocator.free(result.stderr);    if (sys.process.exitCode(result.term) != 0) return error.GitLsFilesFailed;    var files: std.ArrayList([]const u8) = .empty;    var lines = std.mem.splitScalar(u8, result.stdout, '\n');    while (lines.next()) |line| {        const trimmed = std.mem.trim(u8, line, " \t\r\n");        if (trimmed.len == 0) continue;        try files.append(allocator, try allocator.dupe(u8, trimmed));    }    return try files.toOwnedSlice(allocator);}fn mergeWorkloadLines(    allocator: std.mem.Allocator,    coverage_map: *CoverageMap,    repository_root: []const u8,    summary_path: []const u8,    lines_path: []const u8,    workload: []const u8,) !void {    const source_root = try workloadSourceRoot(allocator, summary_path);    const text = try sys.fs.readFileAlloc(allocator, lines_path, max_workload_lines_bytes);    var lines = std.mem.splitScalar(u8, text, '\n');    while (lines.next()) |raw| {        const trimmed = std.mem.trim(u8, raw, " \t\r\n");        if (trimmed.len == 0) continue;        var parsed = std.json.parseFromSlice(std.json.Value, allocator, trimmed, .{}) catch continue;        defer parsed.deinit();        const object = json.object(parsed.value) catch continue;        const file = json.string(object.get("file")) orelse continue;        const raw_line = json.asU64(object.get("line")) orelse continue;        if (raw_line == 0 or raw_line > std.math.maxInt(u32)) continue;        const hits = json.asU64(object.get("hits")) orelse continue;        const repository_file = try repositoryFile(allocator, repository_root, source_root, file) orelse continue;        try addCoveredLine(allocator, coverage_map, repository_file, @intCast(raw_line), hits, workload);    }}fn workloadSourceRoot(allocator: std.mem.Allocator, path: []const u8) !?[]const u8 {    const text = try sys.fs.readFileAlloc(allocator, path, max_source_file_bytes);    var parsed = try std.json.parseFromSlice(std.json.Value, allocator, text, .{});    defer parsed.deinit();    const object = try json.object(parsed.value);    const schema = json.string(object.get("schema")) orelse return error.InvalidCoverageSummary;    if (!std.mem.eql(u8, schema, capture.coverage.summary_schema)) return error.InvalidCoverageSummary;    const value = object.get("source_root") orelse return error.InvalidCoverageSummary;    return switch (value) {        .null => null,        .string => |source_root| try allocator.dupe(u8, source_root),        else => error.InvalidCoverageSummary,    };}fn repositoryFile(    allocator: std.mem.Allocator,    repository_root: []const u8,    source_root: ?[]const u8,    file: []const u8,) !?[]const u8 {    std.debug.assert(std.fs.path.isAbsolute(repository_root));    const absolute = if (std.fs.path.isAbsolute(file))        try std.fs.path.resolve(allocator, &.{file})    else if (source_root) |root|        if (std.fs.path.isAbsolute(root))            try std.fs.path.resolve(allocator, &.{ root, file })        else            try std.fs.path.resolve(allocator, &.{ repository_root, root, file })    else        try std.fs.path.resolve(allocator, &.{ repository_root, file });    const relative = try std.fs.path.relative(allocator, repository_root, null, repository_root, absolute);    if (relative.len == 0 or std.fs.path.isAbsolute(relative)) return null;    if (std.mem.eql(u8, relative, "..")) return null;    if (relative.len >= 3 and relative[0] == '.' and relative[1] == '.' and std.fs.path.isSep(relative[2])) return null;    return relative;}fn addCoveredLine(    allocator: std.mem.Allocator,    coverage_map: *CoverageMap,    file: []const u8,    line: u32,    hits: u64,    workload: []const u8,) !void {    const key = try lineKey(allocator, file, line);    const entry = try coverage_map.getOrPut(allocator, key);    if (!entry.found_existing) {        entry.value_ptr.* = .{};    } else {        allocator.free(key);    }    try entry.value_ptr.add(allocator, workload, hits);}fn writeLineMaps(    allocator: std.mem.Allocator,    files: []const []const u8,    coverage_map: *CoverageMap,    lines_path: []const u8,    uncovered_path: []const u8,) !Summary {    var lines_file = try sys.fs.cwd().createFile(fs_io, lines_path, .{ .truncate = true });    defer lines_file.close(fs_io);    var lines_buffer: [64 * 1024]u8 = undefined;    var lines_writer = lines_file.writer(fs_io, &lines_buffer);    defer lines_writer.interface.flush() catch {};    var uncovered_file = try sys.fs.cwd().createFile(fs_io, uncovered_path, .{ .truncate = true });    defer uncovered_file.close(fs_io);    var uncovered_buffer: [64 * 1024]u8 = undefined;    var uncovered_writer = uncovered_file.writer(fs_io, &uncovered_buffer);    defer uncovered_writer.interface.flush() catch {};    var summary = Summary{        .source_files = files.len,        .physical_lines = 0,        .code_lines = 0,        .blank_lines = 0,        .selected_workloads = 0,        .captured_workloads = 0,        .instrumented_lines = 0,        .covered_lines = 0,        .covered_code_lines = 0,        .uncovered_code_lines = 0,        .uninstrumented_code_lines = 0,        .instrumented_uncovered_code_lines = 0,        .summary_path = "",        .lines_path = lines_path,        .uncovered_path = uncovered_path,        .workloads = &.{},    };    for (files) |file| {        const text = sys.fs.readFileAlloc(allocator, file, max_source_file_bytes) catch continue;        try writeFileRows(allocator, &summary, coverage_map, file, text, &lines_writer.interface, &uncovered_writer.interface);    }    try lines_writer.interface.flush();    try uncovered_writer.interface.flush();    return summary;}fn writeFileRows(    allocator: std.mem.Allocator,    summary: *Summary,    coverage_map: *CoverageMap,    file: []const u8,    text: []const u8,    lines_writer: *std.Io.Writer,    uncovered_writer: *std.Io.Writer,) !void {    var cursor: usize = 0;    var line_number: u32 = 1;    while (cursor < text.len) : (line_number += 1) {        const line_end = std.mem.indexOfScalarPos(u8, text, cursor, '\n') orelse text.len;        const line = text[cursor..line_end];        const code_line = std.mem.trim(u8, line, " \t\r\n").len != 0;        const key = try lineKey(allocator, file, line_number);        defer allocator.free(key);        const covered_line = coverage_map.get(key);        const instrumented = covered_line != null;        const hits = if (covered_line) |covered| covered.hits else 0;        const covered = hits != 0;        summary.physical_lines += 1;        if (code_line) {            summary.code_lines += 1;            if (covered) {                summary.covered_code_lines += 1;            } else {                summary.uncovered_code_lines += 1;                if (instrumented) {                    summary.instrumented_uncovered_code_lines += 1;                } else {                    summary.uninstrumented_code_lines += 1;                }            }        } else {            summary.blank_lines += 1;        }        if (instrumented) summary.instrumented_lines += 1;        if (covered) summary.covered_lines += 1;        try writeLineRow(lines_writer, file, line_number, code_line, covered_line);        if (code_line and !covered) try writeLineRow(uncovered_writer, file, line_number, code_line, covered_line);        cursor = if (line_end == text.len) text.len else line_end + 1;    }}fn writeLineRow(    writer: *std.Io.Writer,    file: []const u8,    line: u32,    code_line: bool,    covered_line: ?CoveredLine,) !void {    var stringify = pretty_json.Writer.init(writer, .minified);    try stringify.beginObject();    try stringify.objectField("schema");    try stringify.write(line_schema);    try stringify.objectField("file");    try stringify.write(file);    try stringify.objectField("line");    try stringify.write(line);    try stringify.objectField("source_kind");    try stringify.write(if (code_line) "code" else "blank");    try stringify.objectField("instrumented");    try stringify.write(covered_line != null);    try stringify.objectField("covered");    try stringify.write(if (covered_line) |covered| covered.hits != 0 else false);    try stringify.objectField("hits");    try stringify.write(if (covered_line) |covered| covered.hits else @as(u64, 0));    try stringify.objectField("instrumented_workload_count");    try stringify.write(if (covered_line) |covered| covered.instrumented_workload_count else @as(usize, 0));    try stringify.objectField("covered_workload_count");    try stringify.write(if (covered_line) |covered| covered.covered_workloads.items.len else @as(usize, 0));    try stringify.objectField("covered_workloads");    try stringify.beginArray();    if (covered_line) |covered| {        for (covered.covered_workloads.items) |workload| try stringify.write(workload);    }    try stringify.endArray();    try stringify.endObject();    try writer.writeByte('\n');}fn writeSummaryFile(path: []const u8, summary: Summary) !void {    var file = try sys.fs.cwd().createFile(fs_io, path, .{ .truncate = true });    defer file.close(fs_io);    var buffer: [8192]u8 = undefined;    var file_writer = file.writer(fs_io, &buffer);    const writer = &file_writer.interface;    var stringify = pretty_json.Writer.init(writer, .indent_2);    try writeSummaryValue(&stringify, summary);    try writer.writeByte('\n');    try writer.flush();}fn writeSummaryValue(stringify: *pretty_json.Writer, summary: Summary) !void {    try stringify.beginObject();    try stringify.objectField("schema");    try stringify.write(summary_schema);    try stringify.objectField("line_universe");    try stringify.write(line_universe);    try stringify.objectField("source_files");    try stringify.write(summary.source_files);    try stringify.objectField("physical_lines");    try stringify.write(summary.physical_lines);    try stringify.objectField("code_lines");    try stringify.write(summary.code_lines);    try stringify.objectField("blank_lines");    try stringify.write(summary.blank_lines);    try stringify.objectField("selected_workloads");    try stringify.write(summary.selected_workloads);    try stringify.objectField("captured_workloads");    try stringify.write(summary.captured_workloads);    try stringify.objectField("instrumented_lines");    try stringify.write(summary.instrumented_lines);    try stringify.objectField("covered_lines");    try stringify.write(summary.covered_lines);    try stringify.objectField("covered_code_lines");    try stringify.write(summary.covered_code_lines);    try stringify.objectField("uncovered_code_lines");    try stringify.write(summary.uncovered_code_lines);    try stringify.objectField("uninstrumented_code_lines");    try stringify.write(summary.uninstrumented_code_lines);    try stringify.objectField("instrumented_uncovered_code_lines");    try stringify.write(summary.instrumented_uncovered_code_lines);    try stringify.objectField("covered_code_percent");    try stringify.write(summary.coveredPercent());    try stringify.objectField("artifacts");    try stringify.beginObject();    try stringify.objectField("summary");    try stringify.write(summary.summary_path);    try stringify.objectField("lines");    try stringify.write(summary.lines_path);    try stringify.objectField("uncovered");    try stringify.write(summary.uncovered_path);    try stringify.endObject();    try stringify.objectField("workloads");    try stringify.beginArray();    for (summary.workloads) |workload| try stringify.write(workload);    try stringify.endArray();    try stringify.endObject();}fn deinitCoverageMap(allocator: std.mem.Allocator, coverage_map: *CoverageMap) void {    var iterator = coverage_map.iterator();    while (iterator.next()) |entry| {        allocator.free(entry.key_ptr.*);        entry.value_ptr.deinit(allocator);    }    coverage_map.deinit(allocator);}fn lineKey(allocator: std.mem.Allocator, file: []const u8, line: u32) ![]const u8 {    return try std.fmt.allocPrint(allocator, "{s}:{d}", .{ file, line });}test "profiling coverage writes complete line maps" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena_state.deinit();    const allocator = arena_state.allocator();    const root = try std.fs.path.join(allocator, &.{ ".zig-cache", "tmp", tmp.sub_path[0..] });    const source_path = try std.fs.path.join(allocator, &.{ root, "a.zig" });    try sys.fs.writeFile(source_path,        \\const a = 1;        \\        \\const b = 2;        \\    );    var coverage_map: CoverageMap = .empty;    defer deinitCoverageMap(allocator, &coverage_map);    try addCoveredLine(allocator, &coverage_map, source_path, 1, 3, "w");    try addCoveredLine(allocator, &coverage_map, source_path, 3, 0, "w");    const lines_path = try std.fs.path.join(allocator, &.{ root, "coverage.lines.jsonl" });    const uncovered_path = try std.fs.path.join(allocator, &.{ root, "coverage.uncovered.jsonl" });    const summary = try writeLineMaps(allocator, &.{source_path}, &coverage_map, lines_path, uncovered_path);    try std.testing.expectEqual(@as(usize, 3), summary.physical_lines);    try std.testing.expectEqual(@as(usize, 2), summary.code_lines);    try std.testing.expectEqual(@as(usize, 1), summary.blank_lines);    try std.testing.expectEqual(@as(usize, 1), summary.covered_code_lines);    try std.testing.expectEqual(@as(usize, 1), summary.instrumented_uncovered_code_lines);    try std.testing.expect(sys.fs.exists(lines_path));    try std.testing.expect(sys.fs.exists(uncovered_path));}test "profiling coverage rebases workload lines onto repository paths" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena_state.deinit();    const allocator = arena_state.allocator();    const root = try std.fs.path.join(allocator, &.{ ".zig-cache", "tmp", tmp.sub_path[0..] });    const summary_path = try std.fs.path.join(allocator, &.{ root, "coverage.summary.json" });    const lines_path = try std.fs.path.join(allocator, &.{ root, "coverage.lines.jsonl" });    const repository_root = try sys.fs.cwdAlloc(allocator);    const source_root = try std.fs.path.join(allocator, &.{ repository_root, "lib" });    const workload_summary = capture.coverage.Summary{        .source_root = source_root,        .files = &.{.{            .file = "sys/src/process.zig",            .instrumented_lines = 1,            .covered_lines = 1,        }},        .lines = &.{.{            .file = "sys/src/process.zig",            .line = 1,            .hits = 3,        }},        .instrumented_lines = 1,        .covered_lines = 1,    };    try capture.coverage.writeSummaryFile(summary_path, workload_summary, "coverage.lines.jsonl");    try capture.coverage.writeLinesJsonlFile(lines_path, workload_summary, "w");    var coverage_map: CoverageMap = .empty;    defer deinitCoverageMap(allocator, &coverage_map);    try mergeWorkloadLines(allocator, &coverage_map, repository_root, summary_path, lines_path, "w");    const covered = coverage_map.get("lib/sys/src/process.zig:1") orelse return error.MissingCoverageLine;    try std.testing.expectEqual(@as(u64, 3), covered.hits);    try std.testing.expectEqual(@as(usize, 1), covered.instrumented_workload_count);    try std.testing.expectEqualStrings("w", covered.covered_workloads.items[0]);}

Source: src/profiling/report/root.zig:15

zig
pub const coverage = @import("coverage.zig");

Complete call list for report.coverage.writeRunArtifacts

8 direct calls.

Audit

Definitions10
Public names10
Members16
Version26.7.0
Revisiondaab053ee433