test: Starting work on the new linker tests

This commit is contained in:
kcbanner committed 2026-06-23 00:26:57 -04:00
1 parent c6d0c68141
commit ab46b82354
4 files changed
+293

No files matched your search

+9
View File
@@ -629,6 +629,15 @@ pub fn build(b: *std.Build) !void {
.skip_llvm = skip_llvm,
.max_rss = 3_300_000_000,
}));
test_step.dependOn(tests.addLinkTests(b, .{
.test_target_filters = test_target_filters,
.test_filters = test_filters,
.optimize_modes = optimize_modes,
.skip_non_native = skip_non_native,
.skip_windows = skip_windows,
.skip_llvm = skip_llvm,
.max_rss = 100_000_000,
}));
test_step.dependOn(tests.addStackTraceTests(b, test_filters, skip_non_native));
test_step.dependOn(tests.addErrorTraceTests(b, test_filters, optimize_modes, skip_non_native));
test_step.dependOn(tests.addCliTests(b));
+17
View File
@@ -0,0 +1,17 @@
pub fn addCases(cases: @import("tests.zig").LinkContext) void {
if (cases.addTestStep("static-lib-exports")) |name| {
const lib = cases.addStaticLibrary(.{
.name = "lib",
.zig_source_bytes =
\\export fn foo() void {}
\\var bar: u32 = 1234;
\\comptime { @export(&bar, .{ .name = "bar", .linkage = .strong }); }
\\const baz: u64 = 5678;
\\comptime { @export(&baz, .{ .name = "baz", .linkage = .strong }); }
,
});
cases.verifyObjdump(name, lib, &.{"--symbols"}, .{ .os = true });
}
}
const std = @import("std");
+159
View File
@@ -0,0 +1,159 @@
b: *Build,
step: *Step,
optimize: std.builtin.OptimizeMode,
target: std.Build.ResolvedTarget,
use_llvm: bool,
use_lld: bool,
link_libc: bool,
suffix: []const u8,
test_filters: []const []const u8,
max_rss: usize,
pub fn addTestStep(self: *const Link, prefix: []const u8) ?[]const u8 {
if (for (self.test_filters) |filter| {
if (std.mem.containsAtLeast(u8, prefix, 1, filter)) break false;
} else self.test_filters.len > 0) return null;
return std.fmt.allocPrint(self.b.allocator, "test-{s}", .{prefix}) catch @panic("OOM");
}
pub fn addStaticLibrary(self: *const Link, overlay: OverlayOptions) *Step.Compile {
return self.b.addLibrary(.{
.linkage = .static,
.name = overlay.name,
.root_module = self.createModule(overlay),
.use_llvm = self.use_llvm,
.use_lld = self.use_lld,
});
}
// TODO: Use std.meta.FieldEnum on TargetQuery?
const SnapshotScope = packed struct {
arch: bool = false,
os: bool = false,
abi: bool = false,
optimize: bool = false,
use_llvm: bool = false,
use_lld: bool = false,
link_libc: bool = false,
};
pub fn verifyObjdump(
self: *const Link,
name: []const u8,
compile: *Step.Compile,
args: []const []const u8,
scope: SnapshotScope,
) void {
const snapshot_name = self.snapshotName(name, compile.name, scope) catch @panic("OOM");
const run_step = Step.Run.create(self.b, self.b.fmt("objdump {s}", .{snapshot_name}));
run_step.addArgs(&.{ self.b.graph.zig_exe, "objdump" });
run_step.addArtifactArg(compile);
run_step.addArgs(args);
run_step.addCheck(.{ .expect_term = .{ .exited = 0 } });
const actual_path = run_step.captureStdOut(.{ .trim_whitespace = .none });
const expected_path = self.b.path(self.b.pathJoin(&.{ "test/link/snapshots/", snapshot_name }));
const check_step = self.b.addCheckFile(actual_path, .{
.expected_file = .{
.file = expected_path,
.if_missing = .fail,
// TODO: Option to do UpdateSourceFiles if not matching / missing?
// TODO: Option to output to <name>-<self.suffix>.actual.dmp file?
},
});
self.step.dependOn(&check_step.step);
}
fn snapshotName(
self: *const Link,
test_name: []const u8,
compile_name: []const u8,
scope: SnapshotScope,
) ![]const u8 {
var snapshot_name: std.Io.Writer.Allocating = .init(self.b.allocator);
const w = &snapshot_name.writer;
try w.print("{s}.{s}", .{ test_name, compile_name });
if (scope.arch) try w.print("-{t}", .{self.target.result.cpu.arch});
if (scope.os) try w.print("-{t}", .{self.target.result.os.tag});
if (scope.abi) try w.print("-{t}", .{self.target.result.abi});
if (scope.optimize) try w.print("-{t}", .{self.optimize});
if (scope.use_llvm and self.use_llvm) try w.writeAll("-llvm");
if (scope.use_lld and self.use_lld) try w.writeAll("-lld");
if (scope.link_libc and self.link_libc) try w.writeAll("-libc");
try w.writeAll(".dmp");
return try snapshot_name.toOwnedSlice();
}
fn createModule(self: *const Link, overlay: OverlayOptions) *Build.Module {
const write_files = self.b.addWriteFiles();
const mod = self.b.createModule(.{
.target = self.target,
.optimize = self.optimize,
.root_source_file = rsf: {
const bytes = overlay.zig_source_bytes orelse break :rsf null;
const name = self.b.fmt("{s}.zig", .{overlay.name});
break :rsf write_files.add(name, bytes);
},
.link_libc = self.link_libc, // TODO: Should this be in overlay instead?
.pic = overlay.pic,
.strip = overlay.strip,
});
if (overlay.objcpp_source_bytes) |bytes| {
mod.addCSourceFile(.{
.file = write_files.add("a.mm", bytes),
.flags = overlay.objcpp_source_flags,
});
}
if (overlay.objc_source_bytes) |bytes| {
mod.addCSourceFile(.{
.file = write_files.add("a.m", bytes),
.flags = overlay.objc_source_flags,
});
}
if (overlay.cpp_source_bytes) |bytes| {
mod.addCSourceFile(.{
.file = write_files.add("a.cpp", bytes),
.flags = overlay.cpp_source_flags,
});
}
if (overlay.c_source_bytes) |bytes| {
mod.addCSourceFile(.{
.file = write_files.add("a.c", bytes),
.flags = overlay.c_source_flags,
});
}
if (overlay.asm_source_bytes) |bytes| {
mod.addAssemblyFile(write_files.add("a.s", bytes));
}
return mod;
}
const OverlayOptions = struct {
name: []const u8,
asm_source_bytes: ?[]const u8 = null,
c_source_bytes: ?[]const u8 = null,
c_source_flags: []const []const u8 = &.{},
cpp_source_bytes: ?[]const u8 = null,
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,
};
const std = @import("std");
const Build = std.Build;
const Step = Build.Step;
const Link = @This();
+108
View File
@@ -10,6 +10,7 @@ const error_traces = @import("error_traces.zig");
const stack_traces = @import("stack_traces.zig");
const llvm_ir = @import("llvm_ir.zig");
const libc = @import("libc.zig");
const link = @import("link.zig");
// Implementations
pub const ErrorTracesContext = @import("src/ErrorTrace.zig");
@@ -17,6 +18,7 @@ pub const StackTracesContext = @import("src/StackTrace.zig");
pub const DebuggerContext = @import("src/Debugger.zig");
pub const LlvmIrContext = @import("src/LlvmIr.zig");
pub const LibcContext = @import("src/Libc.zig");
pub const LinkContext = @import("src/Link.zig");
const ModuleTestTarget = struct {
linkage: ?std.builtin.LinkMode = null,
@@ -2059,6 +2061,57 @@ const c_abi_targets = blk: {
};
};
const LinkTarget = struct {
target: std.Target.Query = .{},
link_libc: bool = false,
use_llvm: bool = false,
use_lld: bool = false,
};
const link_targets = blk: {
@setEvalBranchQuota(30000);
break :blk [_]LinkTarget{
// Native Targets
// .{
// .use_llvm = true,
// },
// Windows Targets
.{
.target = .{
.cpu_arch = .x86_64,
.os_tag = .windows,
.abi = .gnu,
},
},
.{
.target = .{
.cpu_arch = .x86_64,
.os_tag = .windows,
.abi = .gnu,
},
.link_libc = true,
},
.{
.target = .{
.cpu_arch = .x86_64,
.os_tag = .windows,
.abi = .msvc,
},
},
.{
.target = .{
.cpu_arch = .x86_64,
.os_tag = .windows,
.abi = .msvc,
},
.link_libc = true,
},
};
};
/// Unlike `test_targets` and `c_abi_targets`, these targets are just simple strings which we pass
/// directly to `incr-check`. They include the target triple and the compiler backend.
///
@@ -3083,6 +3136,61 @@ pub fn addCAbiTests(b: *std.Build, options: CAbiTestOptions) *Step {
return step;
}
const LinkTestOptions = struct {
test_target_filters: []const []const u8,
test_filters: []const []const u8,
optimize_modes: []const OptimizeMode,
skip_non_native: bool,
skip_windows: bool,
skip_llvm: bool,
max_rss: usize,
};
pub fn addLinkTests(b: *std.Build, options: LinkTestOptions) *Step {
const step = b.step("test-link", "Run the linker tests");
for (link_targets) |link_target| {
if (options.skip_non_native and !link_target.target.isNative()) continue;
if (options.skip_windows and link_target.target.os_tag == .windows) continue;
const resolved_target = b.resolveTargetQuery(link_target.target);
const triple_txt = resolved_target.query.zigTriple(b.allocator) catch @panic("OOM");
const target = &resolved_target.result;
if (options.test_target_filters.len > 0) {
for (options.test_target_filters) |filter| {
if (std.mem.indexOf(u8, triple_txt, filter) != null) break;
} else continue;
}
for (options.optimize_modes) |optimize_mode| {
const would_use_llvm = wouldUseLlvm(link_target.use_llvm, link_target.target, optimize_mode);
if (options.skip_llvm and would_use_llvm) continue;
if (link_target.link_libc and target.abi == .msvc and b.graph.host.result.os.tag != .windows) continue;
link.addCases(.{
.b = b,
.step = step,
.optimize = optimize_mode,
.target = resolved_target,
.suffix = std.fmt.allocPrint(b.allocator, "{s}-{t}{s}{s}{s}", .{
target.zigTriple(b.allocator) catch @panic("OOM"),
optimize_mode,
if (link_target.use_llvm) "-llvm" else "",
if (link_target.use_lld) "-lld" else "",
if (link_target.link_libc) "-libc" else "",
}) catch @panic("OOM"),
.use_llvm = link_target.use_llvm,
.use_lld = link_target.use_lld,
.link_libc = link_target.link_libc,
.test_filters = options.test_filters,
.max_rss = options.max_rss,
});
}
}
return step;
}
pub fn addCases(
b: *std.Build,
parent_step: *Step,