Commit 2c184f9a5f

Jakub Konka <kubkon@jakubkonka.com>
2022-07-18 00:43:11
link-tests: add checkNotPresent and add -dead_strip smoke test
`checkNotPresent` is the inverse of `checkNext` - if the phrase is found in the output, then it fails the test.
1 parent 2dfc78d
Changed files (4)
lib
test
link
macho
dead_strip
lib/std/build/CheckObjectStep.zig
@@ -50,7 +50,7 @@ pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Targe
 /// For example, if the two extracted values were saved as `vmaddr` and `entryoff` respectively
 /// they could then be added with this simple program `vmaddr entryoff +`.
 const Action = struct {
-    tag: enum { match, compute_cmp },
+    tag: enum { match, not_present, compute_cmp },
     phrase: []const u8,
     expected: ?ComputeCompareExpected = null,
 
@@ -63,7 +63,7 @@ const Action = struct {
     /// name {*}libobjc{*}.dylib => will match `name` followed by a token which contains `libobjc` and `.dylib`
     ///                             in that order with other letters in between
     fn match(act: Action, haystack: []const u8, global_vars: anytype) !bool {
-        assert(act.tag == .match);
+        assert(act.tag == .match or act.tag == .not_present);
 
         var candidate_var: ?struct { name: []const u8, value: u64 } = null;
         var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " ");
@@ -202,6 +202,13 @@ const Check = struct {
         }) catch unreachable;
     }
 
+    fn notPresent(self: *Check, phrase: []const u8) void {
+        self.actions.append(.{
+            .tag = .not_present,
+            .phrase = self.builder.dupe(phrase),
+        }) catch unreachable;
+    }
+
     fn computeCmp(self: *Check, phrase: []const u8, expected: ComputeCompareExpected) void {
         self.actions.append(.{
             .tag = .compute_cmp,
@@ -226,6 +233,15 @@ pub fn checkNext(self: *CheckObjectStep, phrase: []const u8) void {
     last.match(phrase);
 }
 
+/// Adds another searched phrase to the latest created Check with `CheckObjectStep.checkStart(...)`
+/// however ensures there is no matching phrase in the output.
+/// Asserts at least one check already exists.
+pub fn checkNotPresent(self: *CheckObjectStep, phrase: []const u8) void {
+    assert(self.checks.items.len > 0);
+    const last = &self.checks.items[self.checks.items.len - 1];
+    last.notPresent(phrase);
+}
+
 /// Creates a new check checking specifically symbol table parsed and dumped from the object
 /// file.
 /// Issuing this check will force parsing and dumping of the symbol table.
@@ -293,6 +309,21 @@ fn make(step: *Step) !void {
                         return error.TestFailed;
                     }
                 },
+                .not_present => {
+                    while (it.next()) |line| {
+                        if (try act.match(line, &vars)) {
+                            std.debug.print(
+                                \\
+                                \\========= Expected not to find: ===================
+                                \\{s}
+                                \\========= But parsed file does contain it: ========
+                                \\{s}
+                                \\
+                            , .{ act.phrase, output });
+                            return error.TestFailed;
+                        }
+                    }
+                },
                 .compute_cmp => {
                     const res = act.computeCmp(gpa, vars) catch |err| switch (err) {
                         error.UnknownVariable => {
test/link/macho/dead_strip/build.zig
@@ -0,0 +1,49 @@
+const std = @import("std");
+const Builder = std.build.Builder;
+const LibExeObjectStep = std.build.LibExeObjStep;
+
+pub fn build(b: *Builder) void {
+    const mode = b.standardReleaseOptions();
+
+    const test_step = b.step("test", "Test the program");
+    test_step.dependOn(b.getInstallStep());
+
+    {
+        // Without -dead_strip, we expect `iAmUnused` symbol present
+        const exe = createScenario(b, mode);
+
+        const check = exe.checkObject(.macho);
+        check.checkInSymtab();
+        check.checkNext("{*} (__TEXT,__text) external _iAmUnused");
+
+        test_step.dependOn(&check.step);
+
+        const run_cmd = exe.run();
+        run_cmd.expectStdOutEqual("Hello!\n");
+        test_step.dependOn(&run_cmd.step);
+    }
+
+    {
+        // With -dead_strip, no `iAmUnused` symbol should be present
+        const exe = createScenario(b, mode);
+        exe.link_gc_sections = true;
+
+        const check = exe.checkObject(.macho);
+        check.checkInSymtab();
+        check.checkNotPresent("{*} (__TEXT,__text) external _iAmUnused");
+
+        test_step.dependOn(&check.step);
+
+        const run_cmd = exe.run();
+        run_cmd.expectStdOutEqual("Hello!\n");
+        test_step.dependOn(&run_cmd.step);
+    }
+}
+
+fn createScenario(b: *Builder, mode: std.builtin.Mode) *LibExeObjectStep {
+    const exe = b.addExecutable("test", null);
+    exe.addCSourceFile("main.c", &[0][]const u8{});
+    exe.setBuildMode(mode);
+    exe.linkLibC();
+    return exe;
+}
test/link/macho/dead_strip/main.c
@@ -0,0 +1,14 @@
+#include <stdio.h>
+
+void printMe() {
+  printf("Hello!\n");
+}
+
+int main(int argc, char* argv[]) {
+  printMe();
+  return 0;
+}
+
+void iAmUnused() {
+  printf("YOU SHALL NOT PASS!\n");
+}
test/link.zig
@@ -60,6 +60,10 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
             .build_modes = true,
         });
 
+        cases.addBuildFile("test/link/macho/dead_strip/build.zig", .{
+            .build_modes = false,
+        });
+
         cases.addBuildFile("test/link/macho/dead_strip_dylibs/build.zig", .{
             .build_modes = true,
             .requires_macos_sdk = true,