Skip to content
zit Artifact [81673bf1e3]
Login

Artifact [81673bf1e3]

Artifact 81673bf1e311adf522b4be4f110d0412f86ca6e2a29447368ff071de987462e4:


//! Import map container tests, on the export-state model: a validated hit
//! round-trips exactly; every damaged file is a DISTINCT load error, never a
//! partial load; a publish that fails before the rename leaves the previous
//! map authoritative.

const std = @import("std");
const repeat = @import("../repeat.zig").repeat;
const map_mod = @import("git_import_map.zig");
const Crc32 = @import("../crc32.zig").Crc32;

const testing = std.testing;

const oid_root_a = repeat("1", 40);
const oid_root_b = repeat("0", 40);
const oid_c = "0123456789abcdef0123456789abcdef01234567";
const checkin_a = repeat("a", 64);
const checkin_b = repeat("b", 40);

fn sampleCommits() [3]map_mod.CommitRecord {
    return .{
        .{ .oid = oid_root_a, .checkin = checkin_a, .branch = "trunk", .depth = 0, .height = 0 },
        .{ .oid = oid_root_b, .checkin = checkin_b, .branch = "feature", .depth = 0, .height = 1 },
        .{ .oid = oid_c, .checkin = repeat("c", 64), .branch = "trunk", .depth = 1, .height = 2 },
    };
}

fn sampleRefs() [2]map_mod.RefRecord {
    return .{
        .{ .name = "refs/heads/main", .oid = oid_c },
        .{ .name = "refs/tags/v1", .oid = oid_root_a },
    };
}

/// Roots deliberately out of order: the writer sorts them.
const sample_roots = [_][]const u8{ oid_root_a, oid_root_b };

fn sampleContents(commits: []const map_mod.CommitRecord, refs: []const map_mod.RefRecord) map_mod.Contents {
    return .{
        .object_format = .sha1,
        .roots = &sample_roots,
        .settings = .{
            .submanifest = .{ .tree = 7 },
            .attribution = .author,
            .refuse_signed = true,
            .default_branch = "main",
        },
        .mirror_classifier_fired = true,
        .artifact_count = 12345,
        .commits = commits,
        .refs = refs,
    };
}

fn storedBytes(gpa: std.mem.Allocator, io: std.Io, dir: std.Io.Dir) ![]u8 {
    return dir.readFileAlloc(io, map_mod.map_filename, gpa, .limited(1 << 20));
}

fn rewrite(io: std.Io, dir: std.Io.Dir, bytes: []const u8) !void {
    var f = try dir.createFile(io, map_mod.map_filename, .{ .truncate = true });
    defer f.close(io);
    try f.writePositionalAll(io, bytes, 0);
}

/// Rewrite `bytes` with a recomputed checksum, so the load gets past the
/// self-check and the field validation under test is what answers.
fn rewriteHonest(io: std.Io, dir: std.Io.Dir, bytes: []u8) !void {
    var crc = Crc32.init();
    crc.update(bytes[0 .. bytes.len - 4]);
    std.mem.writeInt(u32, bytes[bytes.len - 4 ..][0..4], crc.final(), .little);
    try rewrite(io, dir, bytes);
}

// Fixed offsets in the sample file.
const object_format_at = 8 + 2;
const submanifest_at = object_format_at + 1;
const k_at = submanifest_at + 1;
const attribution_at = k_at + 1;
const refuse_signed_at = attribution_at + 1;
const classifier_at = refuse_signed_at + 1;
const artifact_count_at = classifier_at + 1;
const default_branch_at = artifact_count_at + 8;
const root_count_at = default_branch_at + 2 + "main".len;
const first_root_at = root_count_at + 8 + 1;
const second_root_at = first_root_at + 40 + 1;
const commit_count_at = second_root_at + 40;
const first_commit_oid_at = commit_count_at + 8 + 1;

test "import map: a populated map round-trips identity, settings, records, and refs" {
    const io = testing.io;
    const gpa = testing.allocator;
    var tmp = testing.tmpDir(.{});
    defer tmp.cleanup();

    const commits = sampleCommits();
    const refs = sampleRefs();
    try map_mod.write(gpa, io, tmp.dir, sampleContents(&commits, &refs));

    var loaded = try map_mod.load(gpa, io, tmp.dir);
    defer loaded.deinit();
    const c = loaded.contents;
    try testing.expectEqual(map_mod.ObjectFormat.sha1, c.object_format);
    try testing.expectEqual(@as(usize, 2), c.roots.len);
    try testing.expectEqualStrings(oid_root_b, c.roots[0]);
    try testing.expectEqualStrings(oid_root_a, c.roots[1]);
    try testing.expectEqual(@as(u4, 7), c.settings.submanifest.tree);
    try testing.expectEqual(map_mod.Attribution.author, c.settings.attribution);
    try testing.expect(c.settings.refuse_signed);
    try testing.expectEqualStrings("main", c.settings.default_branch);
    try testing.expect(c.mirror_classifier_fired);
    try testing.expectEqual(@as(u64, 12345), c.artifact_count);
    try testing.expectEqual(@as(usize, 3), c.commits.len);
    for (commits, c.commits) |want, got| {
        try testing.expectEqualStrings(want.oid, got.oid);
        try testing.expectEqualStrings(want.checkin, got.checkin);
        try testing.expectEqualStrings(want.branch, got.branch);
        try testing.expectEqual(want.depth, got.depth);
        try testing.expectEqual(want.height, got.height);
    }
    try testing.expectEqual(@as(usize, 2), c.refs.len);
    try testing.expectEqualStrings("refs/tags/v1", c.refs[1].name);
    try testing.expectEqualStrings(oid_root_a, c.refs[1].oid);

    // A loaded map writes back byte-identically.
    const first = try storedBytes(gpa, io, tmp.dir);
    defer gpa.free(first);
    try map_mod.write(gpa, io, tmp.dir, loaded.contents);
    const second = try storedBytes(gpa, io, tmp.dir);
    defer gpa.free(second);
    try testing.expectEqualSlices(u8, first, second);
}

test "import map: an empty sha256 flat map round-trips" {
    const io = testing.io;
    const gpa = testing.allocator;
    var tmp = testing.tmpDir(.{});
    defer tmp.cleanup();

    try map_mod.write(gpa, io, tmp.dir, .{
        .object_format = .sha256,
        .roots = &.{},
        .settings = .{ .submanifest = .flat, .attribution = .committer, .refuse_signed = false, .default_branch = "" },
        .mirror_classifier_fired = false,
        .artifact_count = 0,
        .commits = &.{},
        .refs = &.{},
    });
    var loaded = try map_mod.load(gpa, io, tmp.dir);
    defer loaded.deinit();
    try testing.expectEqual(map_mod.ObjectFormat.sha256, loaded.contents.object_format);
    try testing.expect(loaded.contents.settings.submanifest == .flat);
    try testing.expectEqual(map_mod.Attribution.committer, loaded.contents.settings.attribution);
    try testing.expect(!loaded.contents.settings.refuse_signed);
    try testing.expect(!loaded.contents.mirror_classifier_fired);
    try testing.expectEqual(@as(usize, 0), loaded.contents.roots.len);
    try testing.expectEqual(@as(usize, 0), loaded.contents.commits.len);
    try testing.expectEqual(@as(usize, 0), loaded.contents.refs.len);
    try testing.expect(loaded.lookupOid(oid_c) == null);
    try testing.expect(loaded.lookupRef("refs/heads/main") == null);
}

test "import map: absence is Absent, distinct from damage" {
    const io = testing.io;
    var tmp = testing.tmpDir(.{});
    defer tmp.cleanup();
    try testing.expectError(error.Absent, map_mod.load(testing.allocator, io, tmp.dir));
}

test "import map: lookups hit recorded commits and refs and miss everything else" {
    const io = testing.io;
    const gpa = testing.allocator;
    var tmp = testing.tmpDir(.{});
    defer tmp.cleanup();

    const commits = sampleCommits();
    const refs = sampleRefs();
    try map_mod.write(gpa, io, tmp.dir, sampleContents(&commits, &refs));
    var loaded = try map_mod.load(gpa, io, tmp.dir);
    defer loaded.deinit();

    const hit = loaded.lookupOid(oid_c) orelse return error.TestUnexpectedResult;
    try testing.expectEqualStrings(repeat("c", 64), hit.checkin);
    try testing.expectEqual(@as(u64, 1), hit.depth);
    try testing.expect(loaded.lookupOid(repeat("e", 40)) == null);
    // A check-in name is not a Git OID.
    try testing.expect(loaded.lookupOid(checkin_b) == null);

    const ref = loaded.lookupRef("refs/heads/main") orelse return error.TestUnexpectedResult;
    try testing.expectEqualStrings(oid_c, ref.oid);
    try testing.expect(loaded.lookupRef("refs/heads/mai") == null);
    try testing.expect(loaded.lookupRef("main") == null);
}

test "import map: every framing violation is a distinct error, never a partial load" {
    const io = testing.io;
    const gpa = testing.allocator;
    var tmp = testing.tmpDir(.{});
    defer tmp.cleanup();

    const commits = sampleCommits();
    const refs = sampleRefs();
    try map_mod.write(gpa, io, tmp.dir, sampleContents(&commits, &refs));
    const good = try storedBytes(gpa, io, tmp.dir);
    defer gpa.free(good);

    // Wrong magic.
    {
        const bad = try gpa.dupe(u8, good);
        defer gpa.free(bad);
        bad[0] = 'X';
        try rewrite(io, tmp.dir, bad);
        try testing.expectError(error.WrongMagic, map_mod.load(gpa, io, tmp.dir));
    }
    // Wrong version.
    {
        const bad = try gpa.dupe(u8, good);
        defer gpa.free(bad);
        bad[8] = 0xEE;
        try rewrite(io, tmp.dir, bad);
        try testing.expectError(error.WrongVersion, map_mod.load(gpa, io, tmp.dir));
    }
    // A flipped byte anywhere past the version is a checksum mismatch.
    {
        var offset: usize = 10;
        while (offset < good.len) : (offset += 5) {
            const bad = try gpa.dupe(u8, good);
            defer gpa.free(bad);
            bad[offset] ^= 0xFF;
            try rewrite(io, tmp.dir, bad);
            try testing.expectError(error.ChecksumMismatch, map_mod.load(gpa, io, tmp.dir));
        }
    }
    // Truncation anywhere: a named error, never success.
    {
        var cut: usize = 0;
        while (cut < good.len) : (cut += 3) {
            try rewrite(io, tmp.dir, good[0..cut]);
            if (map_mod.load(gpa, io, tmp.dir)) |_| return error.TestUnexpectedResult else |err| switch (err) {
                error.Truncated, error.ChecksumMismatch, error.WrongMagic, error.WrongVersion => {},
                else => return err,
            }
        }
    }
    // A commit count lying about the body, checksum honest.
    {
        const bad = try gpa.dupe(u8, good);
        defer gpa.free(bad);
        std.mem.writeInt(u64, bad[commit_count_at..][0..8], 1 << 40, .little);
        try rewriteHonest(io, tmp.dir, bad);
        try testing.expectError(error.LyingLength, map_mod.load(gpa, io, tmp.dir));
    }
    // Trailing bytes the counts do not account for.
    {
        const bad = try gpa.alloc(u8, good.len + 1);
        defer gpa.free(bad);
        @memcpy(bad[0 .. good.len - 4], good[0 .. good.len - 4]);
        bad[good.len - 4] = 0;
        try rewriteHonest(io, tmp.dir, bad);
        try testing.expectError(error.LyingLength, map_mod.load(gpa, io, tmp.dir));
    }

    // Control: the untouched bytes still load.
    try rewrite(io, tmp.dir, good);
    var loaded = try map_mod.load(gpa, io, tmp.dir);
    loaded.deinit();
}

test "import map: each invalid field value has its own error" {
    const io = testing.io;
    const gpa = testing.allocator;
    var tmp = testing.tmpDir(.{});
    defer tmp.cleanup();

    const commits = sampleCommits();
    const refs = sampleRefs();
    try map_mod.write(gpa, io, tmp.dir, sampleContents(&commits, &refs));
    const good = try storedBytes(gpa, io, tmp.dir);
    defer gpa.free(good);

    const Case = struct { at: usize, byte: u8, want: anyerror };
    const cases = [_]Case{
        .{ .at = object_format_at, .byte = 2, .want = error.UnknownEnum },
        .{ .at = submanifest_at, .byte = 9, .want = error.UnknownEnum },
        .{ .at = attribution_at, .byte = 2, .want = error.UnknownEnum },
        .{ .at = refuse_signed_at, .byte = 2, .want = error.UnknownEnum },
        .{ .at = classifier_at, .byte = 0xFF, .want = error.UnknownEnum },
        .{ .at = k_at, .byte = 0, .want = error.KOutOfRange },
        .{ .at = k_at, .byte = 13, .want = error.KOutOfRange },
        // Flat with a nonzero k is not a state the writer can produce.
        .{ .at = submanifest_at, .byte = 0, .want = error.KOutOfRange },
        .{ .at = first_root_at, .byte = 'A', .want = error.BadHex },
        .{ .at = first_commit_oid_at + 39, .byte = 'g', .want = error.BadHex },
        // Roots swapped out of ascending order.
        .{ .at = first_root_at, .byte = '2', .want = error.NotCanonical },
    };
    for (cases) |case| {
        const bad = try gpa.dupe(u8, good);
        defer gpa.free(bad);
        bad[case.at] = case.byte;
        try rewriteHonest(io, tmp.dir, bad);
        try testing.expectError(case.want, map_mod.load(gpa, io, tmp.dir));
    }

    // A sha1 file whose OIDs are sha256-length is BadHex, not a format
    // switch: flipping the object format byte alone mismatches every OID.
    {
        const bad = try gpa.dupe(u8, good);
        defer gpa.free(bad);
        bad[object_format_at] = 1;
        try rewriteHonest(io, tmp.dir, bad);
        try testing.expectError(error.BadHex, map_mod.load(gpa, io, tmp.dir));
    }
}

test "import map: duplicate commits and refs are refused at write and at load" {
    const io = testing.io;
    const gpa = testing.allocator;
    var tmp = testing.tmpDir(.{});
    defer tmp.cleanup();

    const commits = sampleCommits();
    const refs = sampleRefs();
    const dup_commits = [_]map_mod.CommitRecord{ commits[0], commits[0] };
    try testing.expectError(error.InvalidContents, map_mod.write(gpa, io, tmp.dir, sampleContents(&dup_commits, &refs)));
    const dup_refs = [_]map_mod.RefRecord{ refs[0], refs[0] };
    try testing.expectError(error.InvalidContents, map_mod.write(gpa, io, tmp.dir, sampleContents(&commits, &dup_refs)));
    // Neither refusal published anything.
    try testing.expectError(error.Absent, map_mod.load(gpa, io, tmp.dir));

    // The load side, reached by rewriting a valid file's second commit OID
    // to equal its first.
    try map_mod.write(gpa, io, tmp.dir, sampleContents(&commits, &refs));
    const good = try storedBytes(gpa, io, tmp.dir);
    defer gpa.free(good);
    const bad = try gpa.dupe(u8, good);
    defer gpa.free(bad);
    const first_record_len = 1 + 40 + 1 + 64 + 2 + "trunk".len + 8 + 1;
    const second_oid_at = first_commit_oid_at + first_record_len;
    @memcpy(bad[second_oid_at..][0..40], oid_root_a);
    try rewriteHonest(io, tmp.dir, bad);
    try testing.expectError(error.NotCanonical, map_mod.load(gpa, io, tmp.dir));
}

test "import map: a file over the size ceiling is TooLarge before any read" {
    const io = testing.io;
    var tmp = testing.tmpDir(.{});
    defer tmp.cleanup();

    // Sparse: the length is claimed, no data blocks are written.
    var f = try tmp.dir.createFile(io, map_mod.map_filename, .{});
    defer f.close(io);
    try f.setLength(io, map_mod.max_map_bytes + 1);
    try testing.expectError(error.TooLarge, map_mod.load(testing.allocator, io, tmp.dir));
}

test "import map: a failed or interrupted write never unseats the published map" {
    const io = testing.io;
    const gpa = testing.allocator;
    var tmp = testing.tmpDir(.{});
    defer tmp.cleanup();

    const commits = sampleCommits();
    const refs = sampleRefs();
    try map_mod.write(gpa, io, tmp.dir, sampleContents(&commits, &refs));

    // A write that fails before the rename (contents the loader would
    // refuse: a pinned k of zero) leaves the old map in place.
    {
        var bad = sampleContents(commits[0..1], &refs);
        bad.settings.submanifest = .{ .tree = 0 };
        try testing.expectError(error.InvalidContents, map_mod.write(gpa, io, tmp.dir, bad));
        var loaded = try map_mod.load(gpa, io, tmp.dir);
        defer loaded.deinit();
        try testing.expectEqual(@as(usize, 3), loaded.contents.commits.len);
    }
    // A temp sibling left by a crash between temp write and rename does not
    // affect the load.
    {
        var f = try tmp.dir.createFile(io, map_mod.map_filename ++ ".tmp-crash", .{});
        try f.writePositionalAll(io, "torn temp garbage", 0);
        f.close(io);
        var loaded = try map_mod.load(gpa, io, tmp.dir);
        defer loaded.deinit();
        try testing.expectEqual(@as(usize, 3), loaded.contents.commits.len);
    }
    // A successor write replaces the map whole.
    {
        try map_mod.write(gpa, io, tmp.dir, sampleContents(commits[2..], &refs));
        var loaded = try map_mod.load(gpa, io, tmp.dir);
        defer loaded.deinit();
        try testing.expectEqual(@as(usize, 1), loaded.contents.commits.len);
        try testing.expectEqualStrings(oid_c, loaded.contents.commits[0].oid);
    }
}