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.
This commit is contained in:
Jakub Konka 2022-07-18 00:43:11 +02:00
parent 2dfc78dc03
commit 2c184f9a5f
4 changed files with 100 additions and 2 deletions

View File

@ -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 => {

View File

@ -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,

View File

@ -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;
}

View File

@ -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");
}