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.
Source
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.
tiny.profiling.host.coverage.plan[function] atsrc/profiling/host/coverage.zig:31tiny.profiling.record.workloadPaths[function] atsrc/profiling/record.zig:241src.profiling.report.coverage.deinitCoverageMap[function] — private; no exact target atsrc/profiling/report/coverage.zig:408in nearest public ownertiny.profiling.report.coveragesrc.profiling.report.coverage.gitTrackedZigFiles[function] — private; no exact target atsrc/profiling/report/coverage.zig:114in nearest public ownertiny.profiling.report.coveragesrc.profiling.report.coverage.mergeWorkloadLines[function] — private; no exact target atsrc/profiling/report/coverage.zig:134in nearest public ownertiny.profiling.report.coveragesrc.profiling.report.coverage.writeLineMaps[function] — private; no exact target atsrc/profiling/report/coverage.zig:216in nearest public ownertiny.profiling.report.coveragesrc.profiling.report.coverage.writeSummaryFile[function] — private; no exact target atsrc/profiling/report/coverage.zig:348in nearest public ownertiny.profiling.report.coveragetiny.smg.command.check.selected[function] attools/smg/src/command/check/selection.zig:6
Audit
| Definitions | 10 |
|---|---|
| Public names | 10 |
| Members | 16 |
| Version | 26.7.0 |
| Revision | daab053ee433 |