test/link/macho: migrate all tests to the new test matrix

This commit is contained in:
Jakub Konka 2024-01-15 12:30:55 +01:00
parent 2b3ac3e82f
commit 03b33b0f01
21 changed files with 136 additions and 429 deletions

View File

@ -89,30 +89,4 @@ pub const cases = [_]Case{
.build_root = "test/link/wasm/type",
.import = @import("link/wasm/type/build.zig"),
},
// Mach-O Cases
.{
.build_root = "test/link/macho/bugs/13056",
.import = @import("link/macho/bugs/13056/build.zig"),
},
.{
.build_root = "test/link/macho/bugs/13457",
.import = @import("link/macho/bugs/13457/build.zig"),
},
.{
.build_root = "test/link/macho/bugs/16308",
.import = @import("link/macho/bugs/16308/build.zig"),
},
.{
.build_root = "test/link/macho/bugs/16628",
.import = @import("link/macho/bugs/16628/build.zig"),
},
.{
.build_root = "test/link/macho/linksection",
.import = @import("link/macho/linksection/build.zig"),
},
.{
.build_root = "test/link/macho/objcpp",
.import = @import("link/macho/objcpp/build.zig"),
},
};

View File

@ -48,6 +48,8 @@ const OverlayOptions = struct {
cpp_source_flags: []const []const u8 = &.{},
objc_source_bytes: ?[]const u8 = null,
objc_source_flags: []const []const u8 = &.{},
objcpp_source_bytes: ?[]const u8 = null,
objcpp_source_flags: []const []const u8 = &.{},
zig_source_bytes: ?[]const u8 = null,
pic: ?bool = null,
strip: ?bool = null,
@ -100,6 +102,12 @@ fn addCompileStep(
.static_lib => .static,
},
});
if (overlay.objcpp_source_bytes) |bytes| {
compile_step.addCSourceFile(.{
.file = b.addWriteFiles().add("a.mm", bytes),
.flags = overlay.objcpp_source_flags,
});
}
if (overlay.objc_source_bytes) |bytes| {
compile_step.addCSourceFile(.{
.file = b.addWriteFiles().add("a.m", bytes),

View File

@ -1,5 +1,4 @@
//! Here we test our MachO linker for correctness and functionality.
//! TODO migrate standalone tests from test/link/macho/* to here.
pub fn testAll(b: *Build, build_opts: BuildOptions) *Step {
const macho_step = b.step("test-macho", "Run MachO tests");
@ -18,12 +17,14 @@ pub fn testAll(b: *Build, build_opts: BuildOptions) *Step {
macho_step.dependOn(testDeadStrip(b, .{ .target = default_target }));
macho_step.dependOn(testEmptyObject(b, .{ .target = default_target }));
macho_step.dependOn(testEmptyZig(b, .{ .target = default_target }));
macho_step.dependOn(testEntryPoint(b, .{ .target = default_target }));
macho_step.dependOn(testHeaderWeakFlags(b, .{ .target = default_target }));
macho_step.dependOn(testHelloC(b, .{ .target = default_target }));
macho_step.dependOn(testHelloZig(b, .{ .target = default_target }));
macho_step.dependOn(testLargeBss(b, .{ .target = default_target }));
macho_step.dependOn(testLayout(b, .{ .target = default_target }));
macho_step.dependOn(testLinksection(b, .{ .target = default_target }));
macho_step.dependOn(testMhExecuteHeader(b, .{ .target = default_target }));
macho_step.dependOn(testNoDeadStrip(b, .{ .target = default_target }));
macho_step.dependOn(testNoExportsDylib(b, .{ .target = default_target }));
@ -59,8 +60,10 @@ pub fn testAll(b: *Build, build_opts: BuildOptions) *Step {
if (build_opts.has_macos_sdk) {
macho_step.dependOn(testDeadStripDylibs(b, .{ .target = b.host }));
macho_step.dependOn(testHeaderpad(b, .{ .target = b.host }));
macho_step.dependOn(testLinkDirectlyCppTbd(b, .{ .target = b.host }));
macho_step.dependOn(testNeededFramework(b, .{ .target = b.host }));
macho_step.dependOn(testObjc(b, .{ .target = b.host }));
macho_step.dependOn(testObjcpp(b, .{ .target = b.host }));
macho_step.dependOn(testWeakFramework(b, .{ .target = b.host }));
}
}
@ -255,6 +258,18 @@ fn testEmptyObject(b: *Build, opts: Options) *Step {
return test_step;
}
fn testEmptyZig(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "macho-empty-zig", opts);
const exe = addExecutable(b, opts, .{ .name = "empty", .zig_source_bytes = "pub fn main() void {}" });
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testEntryPoint(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "macho-entry-point", opts);
@ -745,6 +760,65 @@ fn testLayout(b: *Build, opts: Options) *Step {
return test_step;
}
fn testLinkDirectlyCppTbd(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "macho-link-directly-cpp-tbd", opts);
const sdk = std.zig.system.darwin.getSdk(b.allocator, opts.target.result) orelse
@panic("macOS SDK is required to run the test");
const exe = addExecutable(b, opts, .{
.name = "main",
.cpp_source_bytes =
\\#include <new>
\\#include <cstdio>
\\int main() {
\\ int *x = new int;
\\ *x = 5;
\\ fprintf(stderr, "x: %d\n", *x);
\\ delete x;
\\}
,
.cpp_source_flags = &.{ "-nostdlib++", "-nostdinc++" },
});
exe.root_module.addSystemIncludePath(.{ .path = b.pathJoin(&.{ sdk, "/usr/include" }) });
exe.root_module.addIncludePath(.{ .path = b.pathJoin(&.{ sdk, "/usr/include/c++/v1" }) });
exe.root_module.addObjectFile(.{ .path = b.pathJoin(&.{ sdk, "/usr/lib/libc++.tbd" }) });
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("[referenced dynamically] external __mh_execute_header");
test_step.dependOn(&check.step);
return test_step;
}
fn testLinksection(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "macho-linksection", opts);
const obj = addObject(b, opts, .{ .name = "main", .zig_source_bytes =
\\export var test_global: u32 linksection("__DATA,__TestGlobal") = undefined;
\\export fn testFn() linksection("__TEXT,__TestFn") callconv(.C) void {
\\ testGenericFn("A");
\\}
\\fn testGenericFn(comptime suffix: []const u8) linksection("__TEXT,__TestGenFn" ++ suffix) void {}
});
const check = obj.checkObject();
check.checkInSymtab();
check.checkContains("(__DATA,__TestGlobal) external _test_global");
check.checkInSymtab();
check.checkContains("(__TEXT,__TestFn) external _testFn");
if (opts.optimize == .Debug) {
check.checkInSymtab();
check.checkContains("(__TEXT,__TestGenFnA) _a.testGenericFn__anon_");
}
test_step.dependOn(&check.step);
return test_step;
}
fn testMhExecuteHeader(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "macho-mh-execute-header", opts);
@ -870,6 +944,59 @@ fn testObjc(b: *Build, opts: Options) *Step {
return test_step;
}
fn testObjcpp(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "macho-objcpp", opts);
const foo_h = foo_h: {
const wf = WriteFile.create(b);
break :foo_h wf.add("Foo.h",
\\#import <Foundation/Foundation.h>
\\@interface Foo : NSObject
\\- (NSString *)name;
\\@end
);
};
const foo_o = addObject(b, opts, .{ .name = "foo", .objcpp_source_bytes =
\\#import "Foo.h"
\\@implementation Foo
\\- (NSString *)name
\\{
\\ NSString *str = [[NSString alloc] initWithFormat:@"Zig"];
\\ return str;
\\}
\\@end
});
foo_o.root_module.addIncludePath(foo_h.dirname());
foo_o.linkLibCpp();
const exe = addExecutable(b, opts, .{ .name = "main", .objcpp_source_bytes =
\\#import "Foo.h"
\\#import <assert.h>
\\#include <iostream>
\\int main(int argc, char *argv[])
\\{
\\ @autoreleasepool {
\\ Foo *foo = [[Foo alloc] init];
\\ NSString *result = [foo name];
\\ std::cout << "Hello from C++ and " << [result UTF8String];
\\ assert([result isEqualToString:@"Zig"]);
\\ return 0;
\\ }
\\}
});
exe.root_module.addIncludePath(foo_h.dirname());
exe.addObject(foo_o);
exe.linkLibCpp();
exe.root_module.linkFramework("Foundation", .{});
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello from C++ and Zig");
test_step.dependOn(&run.step);
return test_step;
}
fn testPagezeroSize(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "macho-pagezero-size", opts);

View File

@ -1,38 +0,0 @@
const std = @import("std");
pub const requires_macos_sdk = true;
pub const requires_symlinks = true;
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const target = b.resolveTargetQuery(.{ .os_tag = .macos });
const sdk = std.zig.system.darwin.getSdk(b.allocator, target.result) orelse
@panic("macOS SDK is required to run the test");
const exe = b.addExecutable(.{
.name = "test",
.optimize = optimize,
.target = b.host,
});
exe.addSystemIncludePath(.{ .path = b.pathJoin(&.{ sdk, "/usr/include" }) });
exe.addIncludePath(.{ .path = b.pathJoin(&.{ sdk, "/usr/include/c++/v1" }) });
exe.addCSourceFile(.{ .file = .{ .path = "test.cpp" }, .flags = &.{
"-nostdlib++",
"-nostdinc++",
} });
exe.addObjectFile(.{ .path = b.pathJoin(&.{ sdk, "/usr/lib/libc++.tbd" }) });
const run_cmd = b.addRunArtifact(exe);
run_cmd.expectStdErrEqual("x: 5\n");
test_step.dependOn(&run_cmd.step);
}

View File

@ -1,10 +0,0 @@
// test.cpp
#include <new>
#include <cstdio>
int main() {
int *x = new int;
*x = 5;
fprintf(stderr, "x: %d\n", *x);
delete x;
}

View File

@ -1,30 +0,0 @@
const std = @import("std");
pub const requires_symlinks = true;
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const target = b.resolveTargetQuery(.{ .os_tag = .macos });
const exe = b.addExecutable(.{
.name = "test",
.root_source_file = .{ .path = "main.zig" },
.optimize = optimize,
.target = target,
});
const run = b.addRunArtifact(exe);
run.skip_foreign_checks = true;
run.expectStdOutEqual("");
test_step.dependOn(&run.step);
}

View File

@ -1 +0,0 @@
pub fn main() void {}

View File

@ -1,23 +0,0 @@
const std = @import("std");
pub const requires_symlinks = true;
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
const target = b.resolveTargetQuery(.{ .os_tag = .macos });
const lib = b.addSharedLibrary(.{
.name = "a",
.root_source_file = .{ .path = "main.zig" },
.optimize = .Debug,
.target = target,
});
const check = lib.checkObject();
check.checkInSymtab();
check.checkNotPresent("external _abc");
test_step.dependOn(&check.step);
}

View File

@ -1 +0,0 @@
fn abc() void {}

View File

@ -1,37 +0,0 @@
.globl _foo
.align 4
_foo:
.cfi_startproc
stp x29, x30, [sp, #-32]!
.cfi_def_cfa_offset 32
.cfi_offset w30, -24
.cfi_offset w29, -32
mov x29, sp
.cfi_def_cfa w29, 32
bl _bar
ldp x29, x30, [sp], #32
.cfi_restore w29
.cfi_restore w30
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.globl _bar
.align 4
_bar:
.cfi_startproc
sub sp, sp, #32
.cfi_def_cfa_offset -32
stp x29, x30, [sp, #16]
.cfi_offset w30, -24
.cfi_offset w29, -32
mov x29, sp
.cfi_def_cfa w29, 32
mov w0, #4
ldp x29, x30, [sp, #16]
.cfi_restore w29
.cfi_restore w30
add sp, sp, #32
.cfi_def_cfa_offset 0
ret
.cfi_endproc

View File

@ -1,29 +0,0 @@
.globl _foo
_foo:
.cfi_startproc
push %rbp
.cfi_def_cfa_offset 8
.cfi_offset %rbp, -8
mov %rsp, %rbp
.cfi_def_cfa_register %rbp
call _bar
pop %rbp
.cfi_restore %rbp
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.globl _bar
_bar:
.cfi_startproc
push %rbp
.cfi_def_cfa_offset 8
.cfi_offset %rbp, -8
mov %rsp, %rbp
.cfi_def_cfa_register %rbp
mov $4, %rax
pop %rbp
.cfi_restore %rbp
.cfi_def_cfa_offset 0
ret
.cfi_endproc

View File

@ -1,42 +0,0 @@
const std = @import("std");
const builtin = @import("builtin");
pub const requires_symlinks = true;
pub const requires_macos_sdk = false;
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const target = b.resolveTargetQuery(.{ .os_tag = .macos });
const exe = b.addExecutable(.{
.name = "test",
.optimize = optimize,
.target = target,
});
exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
switch (builtin.cpu.arch) {
.aarch64 => {
exe.addCSourceFile(.{ .file = .{ .path = "a_arm64.s" }, .flags = &[0][]const u8{} });
},
.x86_64 => {
exe.addCSourceFile(.{ .file = .{ .path = "a_x64.s" }, .flags = &[0][]const u8{} });
},
else => unreachable,
}
exe.linkLibC();
const run = b.addRunArtifact(exe);
run.skip_foreign_checks = true;
run.expectStdOutEqual("4\n");
test_step.dependOn(&run.step);
}

View File

@ -1,8 +0,0 @@
#include <stdio.h>
int foo();
int main() {
printf("%d\n", foo());
return 0;
}

View File

@ -1,61 +0,0 @@
const std = @import("std");
pub const requires_macos_sdk = true;
pub const requires_symlinks = true;
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
{
// Without -dead_strip_dylibs we expect `-la` to include liba.dylib in the final executable
const exe = createScenario(b, optimize, "no-dead-strip");
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd LOAD_DYLIB");
check.checkContains("Cocoa");
check.checkInHeaders();
check.checkExact("cmd LOAD_DYLIB");
check.checkContains("libobjc");
test_step.dependOn(&check.step);
const run_cmd = b.addRunArtifact(exe);
test_step.dependOn(&run_cmd.step);
}
{
// With -dead_strip_dylibs, we should include liba.dylib as it's unreachable
const exe = createScenario(b, optimize, "yes-dead-strip");
exe.dead_strip_dylibs = true;
const run_cmd = b.addRunArtifact(exe);
run_cmd.expectExitCode(@as(u8, @bitCast(@as(i8, -2)))); // should fail
test_step.dependOn(&run_cmd.step);
}
}
fn createScenario(
b: *std.Build,
optimize: std.builtin.OptimizeMode,
name: []const u8,
) *std.Build.Step.Compile {
const exe = b.addExecutable(.{
.name = name,
.optimize = optimize,
.target = b.host,
});
exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
exe.linkLibC();
exe.linkFramework("Cocoa");
return exe;
}

View File

@ -1,11 +0,0 @@
#include <objc/runtime.h>
int main(int argc, char* argv[]) {
if (objc_getClass("NSObject") == 0) {
return -1;
}
if (objc_getClass("NSApplication") == 0) {
return -2;
}
return 0;
}

View File

@ -1,39 +0,0 @@
const std = @import("std");
pub const requires_symlinks = true;
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const target = b.resolveTargetQuery(.{ .os_tag = .macos });
const obj = b.addObject(.{
.name = "test",
.root_source_file = .{ .path = "main.zig" },
.optimize = optimize,
.target = target,
});
const check = obj.checkObject();
check.checkInSymtab();
check.checkContains("(__DATA,__TestGlobal) external _test_global");
check.checkInSymtab();
check.checkContains("(__TEXT,__TestFn) external _testFn");
if (optimize == .Debug) {
check.checkInSymtab();
check.checkContains("(__TEXT,__TestGenFnA) _main.testGenericFn__anon_");
}
test_step.dependOn(&check.step);
}

View File

@ -1,5 +0,0 @@
export var test_global: u32 linksection("__DATA,__TestGlobal") = undefined;
export fn testFn() linksection("__TEXT,__TestFn") callconv(.C) void {
testGenericFn("A");
}
fn testGenericFn(comptime suffix: []const u8) linksection("__TEXT,__TestGenFn" ++ suffix) void {}

View File

@ -1,7 +0,0 @@
#import <Foundation/Foundation.h>
@interface Foo : NSObject
- (NSString *)name;
@end

View File

@ -1,11 +0,0 @@
#import "Foo.h"
@implementation Foo
- (NSString *)name
{
NSString *str = [[NSString alloc] initWithFormat:@"Zig"];
return str;
}
@end

View File

@ -1,35 +0,0 @@
const std = @import("std");
pub const requires_symlinks = true;
pub const requires_macos_sdk = true;
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const exe = b.addExecutable(.{
.name = "test",
.optimize = optimize,
.target = b.host,
});
b.default_step.dependOn(&exe.step);
exe.addIncludePath(.{ .path = "." });
exe.addCSourceFile(.{ .file = .{ .path = "Foo.mm" }, .flags = &[0][]const u8{} });
exe.addCSourceFile(.{ .file = .{ .path = "test.mm" }, .flags = &[0][]const u8{} });
exe.linkLibCpp();
// TODO when we figure out how to ship framework stubs for cross-compilation,
// populate paths to the sysroot here.
exe.linkFramework("Foundation");
const run_cmd = b.addRunArtifact(exe);
run_cmd.expectStdOutEqual("Hello from C++ and Zig");
test_step.dependOn(&run_cmd.step);
}

View File

@ -1,14 +0,0 @@
#import "Foo.h"
#import <assert.h>
#include <iostream>
int main(int argc, char *argv[])
{
@autoreleasepool {
Foo *foo = [[Foo alloc] init];
NSString *result = [foo name];
std::cout << "Hello from C++ and " << [result UTF8String];
assert([result isEqualToString:@"Zig"]);
return 0;
}
}