authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-07-18 00:43:11+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-07-22 16:58:21+02:00
log2c184f9a5fc78be4f38cc74106c203b7bc80deb4
treeec4df1baf19942ca759bd5b28bc1274b3f54ae19
parent2dfc78dc0369052033c8469e00bf599e12e73f52

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.

4 files changed, 100 insertions(+), 2 deletions(-)

lib/std/build/CheckObjectStep.zig+33-2
...@@ -50,7 +50,7 @@ pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Targe...@@ -50,7 +50,7 @@ pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Targe
50/// For example, if the two extracted values were saved as `vmaddr` and `entryoff` respectively50/// For example, if the two extracted values were saved as `vmaddr` and `entryoff` respectively
51/// they could then be added with this simple program `vmaddr entryoff +`.51/// they could then be added with this simple program `vmaddr entryoff +`.
52const Action = struct {52const Action = struct {
53 tag: enum { match, compute_cmp },53 tag: enum { match, not_present, compute_cmp },
54 phrase: []const u8,54 phrase: []const u8,
55 expected: ?ComputeCompareExpected = null,55 expected: ?ComputeCompareExpected = null,
5656
...@@ -63,7 +63,7 @@ const Action = struct {...@@ -63,7 +63,7 @@ const Action = struct {
63 /// name {*}libobjc{*}.dylib => will match `name` followed by a token which contains `libobjc` and `.dylib`63 /// name {*}libobjc{*}.dylib => will match `name` followed by a token which contains `libobjc` and `.dylib`
64 /// in that order with other letters in between64 /// in that order with other letters in between
65 fn match(act: Action, haystack: []const u8, global_vars: anytype) !bool {65 fn match(act: Action, haystack: []const u8, global_vars: anytype) !bool {
66 assert(act.tag == .match);66 assert(act.tag == .match or act.tag == .not_present);
6767
68 var candidate_var: ?struct { name: []const u8, value: u64 } = null;68 var candidate_var: ?struct { name: []const u8, value: u64 } = null;
69 var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " ");69 var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " ");
...@@ -202,6 +202,13 @@ const Check = struct {...@@ -202,6 +202,13 @@ const Check = struct {
202 }) catch unreachable;202 }) catch unreachable;
203 }203 }
204204
205 fn notPresent(self: *Check, phrase: []const u8) void {
206 self.actions.append(.{
207 .tag = .not_present,
208 .phrase = self.builder.dupe(phrase),
209 }) catch unreachable;
210 }
211
205 fn computeCmp(self: *Check, phrase: []const u8, expected: ComputeCompareExpected) void {212 fn computeCmp(self: *Check, phrase: []const u8, expected: ComputeCompareExpected) void {
206 self.actions.append(.{213 self.actions.append(.{
207 .tag = .compute_cmp,214 .tag = .compute_cmp,
...@@ -226,6 +233,15 @@ pub fn checkNext(self: *CheckObjectStep, phrase: []const u8) void {...@@ -226,6 +233,15 @@ pub fn checkNext(self: *CheckObjectStep, phrase: []const u8) void {
226 last.match(phrase);233 last.match(phrase);
227}234}
228235
236/// Adds another searched phrase to the latest created Check with `CheckObjectStep.checkStart(...)`
237/// however ensures there is no matching phrase in the output.
238/// Asserts at least one check already exists.
239pub fn checkNotPresent(self: *CheckObjectStep, phrase: []const u8) void {
240 assert(self.checks.items.len > 0);
241 const last = &self.checks.items[self.checks.items.len - 1];
242 last.notPresent(phrase);
243}
244
229/// Creates a new check checking specifically symbol table parsed and dumped from the object245/// Creates a new check checking specifically symbol table parsed and dumped from the object
230/// file.246/// file.
231/// Issuing this check will force parsing and dumping of the symbol table.247/// Issuing this check will force parsing and dumping of the symbol table.
...@@ -293,6 +309,21 @@ fn make(step: *Step) !void {...@@ -293,6 +309,21 @@ fn make(step: *Step) !void {
293 return error.TestFailed;309 return error.TestFailed;
294 }310 }
295 },311 },
312 .not_present => {
313 while (it.next()) |line| {
314 if (try act.match(line, &vars)) {
315 std.debug.print(
316 \\
317 \\========= Expected not to find: ===================
318 \\{s}
319 \\========= But parsed file does contain it: ========
320 \\{s}
321 \\
322 , .{ act.phrase, output });
323 return error.TestFailed;
324 }
325 }
326 },
296 .compute_cmp => {327 .compute_cmp => {
297 const res = act.computeCmp(gpa, vars) catch |err| switch (err) {328 const res = act.computeCmp(gpa, vars) catch |err| switch (err) {
298 error.UnknownVariable => {329 error.UnknownVariable => {
test/link.zig+4
...@@ -60,6 +60,10 @@ pub fn addCases(cases: *tests.StandaloneContext) void {...@@ -60,6 +60,10 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
60 .build_modes = true,60 .build_modes = true,
61 });61 });
6262
63 cases.addBuildFile("test/link/macho/dead_strip/build.zig", .{
64 .build_modes = false,
65 });
66
63 cases.addBuildFile("test/link/macho/dead_strip_dylibs/build.zig", .{67 cases.addBuildFile("test/link/macho/dead_strip_dylibs/build.zig", .{
64 .build_modes = true,68 .build_modes = true,
65 .requires_macos_sdk = true,69 .requires_macos_sdk = true,
test/link/macho/dead_strip/build.zig created+49
...@@ -0,0 +1,49 @@
1const std = @import("std");
2const Builder = std.build.Builder;
3const LibExeObjectStep = std.build.LibExeObjStep;
4
5pub fn build(b: *Builder) void {
6 const mode = b.standardReleaseOptions();
7
8 const test_step = b.step("test", "Test the program");
9 test_step.dependOn(b.getInstallStep());
10
11 {
12 // Without -dead_strip, we expect `iAmUnused` symbol present
13 const exe = createScenario(b, mode);
14
15 const check = exe.checkObject(.macho);
16 check.checkInSymtab();
17 check.checkNext("{*} (__TEXT,__text) external _iAmUnused");
18
19 test_step.dependOn(&check.step);
20
21 const run_cmd = exe.run();
22 run_cmd.expectStdOutEqual("Hello!\n");
23 test_step.dependOn(&run_cmd.step);
24 }
25
26 {
27 // With -dead_strip, no `iAmUnused` symbol should be present
28 const exe = createScenario(b, mode);
29 exe.link_gc_sections = true;
30
31 const check = exe.checkObject(.macho);
32 check.checkInSymtab();
33 check.checkNotPresent("{*} (__TEXT,__text) external _iAmUnused");
34
35 test_step.dependOn(&check.step);
36
37 const run_cmd = exe.run();
38 run_cmd.expectStdOutEqual("Hello!\n");
39 test_step.dependOn(&run_cmd.step);
40 }
41}
42
43fn createScenario(b: *Builder, mode: std.builtin.Mode) *LibExeObjectStep {
44 const exe = b.addExecutable("test", null);
45 exe.addCSourceFile("main.c", &[0][]const u8{});
46 exe.setBuildMode(mode);
47 exe.linkLibC();
48 return exe;
49}
test/link/macho/dead_strip/main.c created+14
...@@ -0,0 +1,14 @@
1#include <stdio.h>
2
3void printMe() {
4 printf("Hello!\n");
5}
6
7int main(int argc, char* argv[]) {
8 printMe();
9 return 0;
10}
11
12void iAmUnused() {
13 printf("YOU SHALL NOT PASS!\n");
14}