Commit 937464f398

Jakub Konka <kubkon@jakubkonka.com>
2022-06-21 22:19:55
link-tests: dump metadata to string and grep results
This approach is more inline with what LLVM/LLD does for testing of their output, and seems to be more generic and easier to extend than implementing a lot of repetitive and nontrivial comparison logic when working directly on structures.
1 parent 5fbdfb3
Changed files (3)
lib
test
link
dylib
pagezero
lib/std/build/CheckMachOStep.zig
@@ -1,36 +1,23 @@
 const std = @import("../std.zig");
+const assert = std.debug.assert;
 const build = std.build;
-const Step = build.Step;
-const Builder = build.Builder;
 const fs = std.fs;
 const macho = std.macho;
 const mem = std.mem;
 
 const CheckMachOStep = @This();
 
+const Allocator = mem.Allocator;
+const Builder = build.Builder;
+const Step = build.Step;
+
 pub const base_id = .check_macho;
 
 step: Step,
 builder: *Builder,
 source: build.FileSource,
 max_bytes: usize = 20 * 1024 * 1024,
-lc_checks: std.ArrayList(LCCheck),
-
-const LCCheck = struct {
-    // common to most LCs
-    cmd: macho.LC,
-    name: ?[]const u8 = null,
-    // LC.SEGMENT_64 specific
-    index: ?usize = null,
-    vaddr: ?u64 = null,
-    memsz: ?u64 = null,
-    offset: ?u64 = null,
-    filesz: ?u64 = null,
-    // LC.LOAD_DYLIB specific
-    timestamp: ?u64 = null,
-    current_version: ?u32 = null,
-    compat_version: ?u32 = null,
-};
+checks: std.ArrayList(Check),
 
 pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep {
     const gpa = builder.allocator;
@@ -39,25 +26,38 @@ pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep {
         .builder = builder,
         .step = Step.init(.check_file, "CheckMachO", gpa, make),
         .source = source.dupe(builder),
-        .lc_checks = std.ArrayList(LCCheck).init(gpa),
+        .checks = std.ArrayList(Check).init(gpa),
     };
     self.source.addStepDependencies(&self.step);
     return self;
 }
 
-pub fn checkLoadCommand(self: *CheckMachOStep, check: LCCheck) void {
-    self.lc_checks.append(.{
-        .cmd = check.cmd,
-        .index = check.index,
-        .name = if (check.name) |name| self.builder.dupe(name) else null,
-        .vaddr = check.vaddr,
-        .memsz = check.memsz,
-        .offset = check.offset,
-        .filesz = check.filesz,
-        .timestamp = check.timestamp,
-        .current_version = check.current_version,
-        .compat_version = check.compat_version,
-    }) catch unreachable;
+const Check = struct {
+    builder: *Builder,
+    phrases: std.ArrayList([]const u8),
+
+    fn create(b: *Builder) Check {
+        return .{
+            .builder = b,
+            .phrases = std.ArrayList([]const u8).init(b.allocator),
+        };
+    }
+
+    fn addPhrase(self: *Check, phrase: []const u8) void {
+        self.phrases.append(self.builder.dupe(phrase)) catch unreachable;
+    }
+};
+
+pub fn check(self: *CheckMachOStep, phrase: []const u8) void {
+    var new_check = Check.create(self.builder);
+    new_check.addPhrase(phrase);
+    self.checks.append(new_check) catch unreachable;
+}
+
+pub fn checkNext(self: *CheckMachOStep, phrase: []const u8) void {
+    assert(self.checks.items.len > 0);
+    const last = &self.checks.items[self.checks.items.len - 1];
+    last.addPhrase(phrase);
 }
 
 fn make(step: *Step) !void {
@@ -76,135 +76,95 @@ fn make(step: *Step) !void {
         return error.InvalidMagicNumber;
     }
 
-    var load_commands = std.ArrayList(macho.LoadCommand).init(gpa);
-    try load_commands.ensureTotalCapacity(hdr.ncmds);
+    var metadata = std.ArrayList(u8).init(gpa);
+    const writer = metadata.writer();
 
     var i: u16 = 0;
     while (i < hdr.ncmds) : (i += 1) {
         var cmd = try macho.LoadCommand.read(gpa, reader);
-        load_commands.appendAssumeCapacity(cmd);
+        try dumpLoadCommand(cmd, i, writer);
+        try writer.writeByte('\n');
     }
 
-    outer: for (self.lc_checks.items) |ch| {
-        if (ch.index) |index| {
-            const lc = load_commands.items[index];
-            try cmpLoadCommand(ch, lc);
-        } else {
-            for (load_commands.items) |lc| {
-                if (lc.cmd() == ch.cmd) {
-                    try cmpLoadCommand(ch, lc);
-                    continue :outer;
+    for (self.checks.items) |chk| {
+        const first_phrase = chk.phrases.items[0];
+
+        if (mem.indexOf(u8, metadata.items, first_phrase)) |index| {
+            // TODO backtrack to track current scope
+            var it = std.mem.tokenize(u8, metadata.items[index..], "\r\n");
+
+            outer: for (chk.phrases.items[1..]) |next_phrase| {
+                while (it.next()) |line| {
+                    if (mem.eql(u8, line, next_phrase)) {
+                        std.debug.print("{s} == {s}\n", .{ line, next_phrase });
+                        continue :outer;
+                    }
+                    std.debug.print("{s} != {s}\n", .{ line, next_phrase });
+                } else {
+                    return error.TestFailed;
                 }
-            } else {
-                return err("LC not found", ch.cmd, "");
             }
+        } else {
+            return error.TestFailed;
         }
     }
 }
 
-fn cmpLoadCommand(exp: LCCheck, given: macho.LoadCommand) error{TestFailed}!void {
-    if (exp.cmd != given.cmd()) {
-        return err("LC mismatch", exp.cmd, given.cmd());
-    }
-    switch (exp.cmd) {
+fn dumpLoadCommand(lc: macho.LoadCommand, index: u16, writer: anytype) !void {
+    // print header first
+    try writer.print(
+        \\LC {d}
+        \\cmd {s}
+        \\cmdsize {d}
+    , .{ index, @tagName(lc.cmd()), lc.cmdsize() });
+
+    switch (lc.cmd()) {
         .SEGMENT_64 => {
-            const lc = given.segment.inner;
-            if (exp.name) |name| {
-                if (!mem.eql(u8, name, lc.segName())) {
-                    return err("segment name mismatch", name, lc.segName());
-                }
-            }
-            if (exp.vaddr) |vaddr| {
-                if (vaddr != lc.vmaddr) {
-                    return err("segment VM address mismatch", vaddr, lc.vmaddr);
-                }
-            }
-            if (exp.memsz) |memsz| {
-                if (memsz != lc.vmsize) {
-                    return err("segment VM size mismatch", memsz, lc.vmsize);
-                }
-            }
-            if (exp.offset) |offset| {
-                if (offset != lc.fileoff) {
-                    return err("segment file offset mismatch", offset, lc.fileoff);
-                }
-            }
-            if (exp.filesz) |filesz| {
-                if (filesz != lc.filesize) {
-                    return err("segment file size mismatch", filesz, lc.filesize);
-                }
-            }
+            // TODO dump section headers
+            const seg = lc.segment.inner;
+            try writer.writeByte('\n');
+            try writer.print(
+                \\segname {s}
+                \\vmaddr {x}
+                \\vmsize {x}
+                \\fileoff {x}
+                \\filesz {x}
+            , .{
+                seg.segName(),
+                seg.vmaddr,
+                seg.vmsize,
+                seg.fileoff,
+                seg.filesize,
+            });
         },
-        .ID_DYLIB, .LOAD_DYLIB => {
-            const lc = given.dylib;
-            if (exp.name) |name| {
-                if (!mem.eql(u8, name, mem.sliceTo(lc.data, 0))) {
-                    return err("dylib path mismatch", name, mem.sliceTo(lc.data, 0));
-                }
-            }
-            if (exp.timestamp) |ts| {
-                if (ts != lc.inner.dylib.timestamp) {
-                    return err("timestamp mismatch", ts, lc.inner.dylib.timestamp);
-                }
-            }
-            if (exp.current_version) |cv| {
-                if (cv != lc.inner.dylib.current_version) {
-                    return err("current version mismatch", cv, lc.inner.dylib.current_version);
-                }
-            }
-            if (exp.compat_version) |cv| {
-                if (cv != lc.inner.dylib.compatibility_version) {
-                    return err("compatibility version mismatch", cv, lc.inner.dylib.compatibility_version);
-                }
-            }
+
+        .ID_DYLIB,
+        .LOAD_DYLIB,
+        => {
+            const dylib = lc.dylib.inner.dylib;
+            try writer.writeByte('\n');
+            try writer.print(
+                \\path {s}
+                \\timestamp {d}
+                \\current version {x}
+                \\compatibility version {x}
+            , .{
+                mem.sliceTo(lc.dylib.data, 0),
+                dylib.timestamp,
+                dylib.current_version,
+                dylib.compatibility_version,
+            });
         },
+
         .RPATH => {
-            const lc = given.rpath;
-            if (exp.name) |name| {
-                if (!mem.eql(u8, name, mem.sliceTo(lc.data, 0))) {
-                    return err("rpath path mismatch", name, mem.sliceTo(lc.data, 0));
-                }
-            }
+            try writer.writeByte('\n');
+            try writer.print(
+                \\path {s}
+            , .{
+                mem.sliceTo(lc.rpath.data, 0),
+            });
         },
-        else => @panic("TODO compare more load commands"),
-    }
-}
 
-fn err(msg: []const u8, exp: anytype, giv: anytype) error{TestFailed} {
-    const fmt_specifier = if (comptime isString(@TypeOf(exp))) "{s}" else switch (@typeInfo(@TypeOf(exp))) {
-        .Int => "{x}",
-        .Float => "{d}",
-        else => "{any}",
-    };
-    std.debug.print(
-        \\=====================================
-        \\{s}
-        \\
-        \\======== Expected to find: ==========
-        \\
-    ++ fmt_specifier ++
-        \\
-        \\======== But instead found: =========
-        \\
-    ++ fmt_specifier ++
-        \\
-        \\
-    , .{ msg, exp, giv });
-    return error.TestFailed;
-}
-
-fn isString(comptime T: type) bool {
-    switch (@typeInfo(T)) {
-        .Array => return std.meta.Elem(T) == u8,
-        .Pointer => |pinfo| {
-            switch (pinfo.size) {
-                .Slice, .Many => return std.meta.Elem(T) == u8,
-                else => switch (@typeInfo(pinfo.child)) {
-                    .Array => return isString(pinfo.child),
-                    else => return false,
-                },
-            }
-        },
-        else => return false,
+        else => {},
     }
 }
test/link/dylib/build.zig
@@ -13,17 +13,14 @@ pub fn build(b: *Builder) void {
     dylib.linkLibC();
     dylib.install();
 
-    {
-        const check_macho = dylib.checkMachO();
-        check_macho.checkLoadCommand(.{
-            .cmd = std.macho.LC.ID_DYLIB,
-            .name = "@rpath/liba.dylib",
-            .timestamp = 2,
-            .current_version = 0x10000,
-            .compat_version = 0x10000,
-        });
-        test_step.dependOn(&check_macho.step);
-    }
+    const check_dylib = dylib.checkMachO();
+    check_dylib.check("cmd ID_DYLIB");
+    check_dylib.checkNext("path @rpath/liba.dylib");
+    check_dylib.checkNext("timestamp 2");
+    check_dylib.checkNext("current version 10000");
+    check_dylib.checkNext("compatibility version 10000");
+
+    test_step.dependOn(&check_dylib.step);
 
     const exe = b.addExecutable("main", null);
     exe.setBuildMode(mode);
@@ -33,25 +30,17 @@ pub fn build(b: *Builder) void {
     exe.addLibraryPath(b.pathFromRoot("zig-out/lib/"));
     exe.addRPath(b.pathFromRoot("zig-out/lib"));
 
-    {
-        const check_macho = exe.checkMachO();
-        check_macho.checkLoadCommand(.{
-            .cmd = std.macho.LC.LOAD_DYLIB,
-            .name = "@rpath/liba.dylib",
-            .timestamp = 2,
-            .current_version = 0x10000,
-            .compat_version = 0x10000,
-        });
-        test_step.dependOn(&check_macho.step);
-    }
-    {
-        const check_macho = exe.checkMachO();
-        check_macho.checkLoadCommand(.{
-            .cmd = std.macho.LC.RPATH,
-            .name = b.pathFromRoot("zig-out/lib"),
-        });
-        test_step.dependOn(&check_macho.step);
-    }
+    const check_exe = exe.checkMachO();
+    check_exe.check("cmd LOAD_DYLIB");
+    check_exe.checkNext("path @rpath/liba.dylib");
+    check_exe.checkNext("timestamp 2");
+    check_exe.checkNext("current version 10000");
+    check_exe.checkNext("compatibility version 10000");
+
+    check_exe.check("cmd RPATH");
+    check_exe.checkNext(std.fmt.allocPrint(b.allocator, "path {s}", .{b.pathFromRoot("zig-out/lib")}) catch unreachable);
+
+    test_step.dependOn(&check_exe.step);
 
     const run = exe.run();
     run.cwd = b.pathFromRoot(".");
test/link/pagezero/build.zig
@@ -14,22 +14,16 @@ pub fn build(b: *Builder) void {
         exe.linkLibC();
         exe.pagezero_size = 0x4000;
 
-        const check_macho = exe.checkMachO();
-        check_macho.checkLoadCommand(.{
-            .cmd = std.macho.LC.SEGMENT_64,
-            .index = 0,
-            .name = "__PAGEZERO",
-            .vaddr = 0,
-            .memsz = 0x4000,
-        });
-        check_macho.checkLoadCommand(.{
-            .cmd = std.macho.LC.SEGMENT_64,
-            .index = 1,
-            .name = "__TEXT",
-            .vaddr = 0x4000,
-        });
-
-        test_step.dependOn(&check_macho.step);
+        const check = exe.checkMachO();
+        check.check("LC 0");
+        check.checkNext("segname __PAGEZERO");
+        check.checkNext("vmaddr 0");
+        check.checkNext("vmsize 4000");
+
+        check.check("segname __TEXT");
+        check.checkNext("vmaddr 4000");
+
+        test_step.dependOn(&check.step);
     }
 
     {
@@ -39,14 +33,11 @@ pub fn build(b: *Builder) void {
         exe.linkLibC();
         exe.pagezero_size = 0;
 
-        const check_macho = exe.checkMachO();
-        check_macho.checkLoadCommand(.{
-            .cmd = std.macho.LC.SEGMENT_64,
-            .index = 0,
-            .name = "__TEXT",
-            .vaddr = 0,
-        });
+        const check = exe.checkMachO();
+        check.check("LC 0");
+        check.checkNext("segname __TEXT");
+        check.checkNext("vmaddr 0");
 
-        test_step.dependOn(&check_macho.step);
+        test_step.dependOn(&check.step);
     }
 }