From d3271963a8b937d00c9ee419fc7e7d0f76dc0ffa Mon Sep 17 00:00:00 2001 From: Pascal Zittlau Date: Fri, 12 Dec 2025 09:21:33 +0100 Subject: [PATCH] some tests --- build.zig | 48 +++++++++++++++++++++ src/main.zig | 93 +++++++++++++++++++++++++++++++++++++++++ src/test/exit.zig | 3 ++ src/test/helloWorld.zig | 9 ++++ src/test/printArgs.zig | 17 ++++++++ src/test/readlink.zig | 13 ++++++ 6 files changed, 183 insertions(+) create mode 100644 src/test/exit.zig create mode 100644 src/test/helloWorld.zig create mode 100644 src/test/printArgs.zig create mode 100644 src/test/readlink.zig diff --git a/build.zig b/build.zig index fcfb6e4..152ecdf 100644 --- a/build.zig +++ b/build.zig @@ -47,8 +47,56 @@ pub fn build(b: *std.Build) !void { run_cmd.addArgs(args); } + try compileTestApplications(b, target, optimize, false, false); + try compileTestApplications(b, target, optimize, false, true); + try compileTestApplications(b, target, optimize, true, true); + const exe_tests = b.addTest(.{ .root_module = mod }); const run_exe_tests = b.addRunArtifact(exe_tests); const test_step = b.step("test", "Run tests"); + test_step.dependOn(b.getInstallStep()); test_step.dependOn(&run_exe_tests.step); } + +pub fn compileTestApplications( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + comptime link_libc: bool, + comptime pie: bool, +) !void { + // Compile test applications + const test_path = "src/test/"; + const test_prefix = prefix: { + const p1 = "test_" ++ if (link_libc) "libc_" else "nolibc_"; + const p2 = p1 ++ if (pie) "pie_" else "nopie_"; + break :prefix p2; + }; + var test_dir = try std.fs.cwd().openDir(test_path, .{ .iterate = true }); + defer test_dir.close(); + var iterator = test_dir.iterate(); + while (try iterator.next()) |entry| { + if (entry.kind != .file) continue; + if (!std.mem.endsWith(u8, entry.name, ".zig")) continue; + + const name = try std.mem.concat(b.allocator, u8, &.{ + test_prefix, entry.name[0 .. entry.name.len - 4], // strip .zig suffix + }); + const test_executable = b.addExecutable(.{ + .name = name, + .root_module = b.createModule(.{ + .root_source_file = b.path(b.pathJoin(&.{ test_path, entry.name })), + .optimize = optimize, + .target = target, + .link_libc = link_libc, + .link_libcpp = false, + .pic = pie, + }), + .linkage = if (link_libc) .dynamic else .static, + .use_llvm = true, + .use_lld = true, + }); + test_executable.pie = pie; + b.installArtifact(test_executable); + } +} diff --git a/src/main.zig b/src/main.zig index b48c703..8b1daa2 100644 --- a/src/main.zig +++ b/src/main.zig @@ -274,3 +274,96 @@ test { _ = @import("Range.zig"); _ = @import("PatchLocationIterator.zig"); } + +// TODO: make this be passed in from the build system +const bin_path = "zig-out/bin/"; +fn getTestExePath(comptime name: []const u8) []const u8 { + return bin_path ++ "test_" ++ name; +} +const flicker_path = bin_path ++ "flicker"; + +test "nolibc_nopie_exit" { + try testHelper(&.{ flicker_path, getTestExePath("nolibc_nopie_exit") }, ""); +} +test "nolibc_pie_exit" { + try testHelper(&.{ flicker_path, getTestExePath("nolibc_pie_exit") }, ""); +} +// BUG: This one is flaky +// test "libc_pie_exit" { +// try testHelper(&.{ flicker_path, getTestExePath("libc_pie_exit") }, ""); +// } + +test "nolibc_nopie_helloWorld" { + try testHelper(&.{ flicker_path, getTestExePath("nolibc_nopie_helloWorld") }, "Hello World!\n"); +} +test "nolibc_pie_helloWorld" { + try testHelper(&.{ flicker_path, getTestExePath("nolibc_pie_helloWorld") }, "Hello World!\n"); +} +// BUG: This one is flaky +// test "libc_pie_helloWorld" { +// try testHelper(&.{ flicker_path, getTestExePath("libc_pie_helloWorld") }, "Hello World!\n"); +// } + +test "nolibc_nopie_printArgs" { + try testPrintArgs("nolibc_nopie_printArgs"); +} +test "nolibc_pie_printArgs" { + try testPrintArgs("nolibc_pie_printArgs"); +} +// BUG: This one is flaky +// test "libc_pie_printArgs" { +// try testPrintArgs("libc_pie_printArgs"); +// } + +test "nolibc_nopie_readlink" { + try testReadlink("nolibc_nopie_readlink"); +} +test "nolibc_pie_readlink" { + try testReadlink("nolibc_pie_readlink"); +} +// BUG: This one just outputs the path to the flicker executable and is likely also flaky +// test "libc_pie_readlink" { +// try testReadlink("libc_pie_readlink"); +// } + +test "echo" { + try testHelper(&.{ "echo", "Hello", "There" }, "Hello There\n"); +} + +fn testPrintArgs(comptime name: []const u8) !void { + const exe_path = getTestExePath(name); + const loader_argv: []const []const u8 = &.{ flicker_path, exe_path, "foo", "bar", "baz hi" }; + const target_argv = loader_argv[1..]; + const expected_stout = try mem.join(testing.allocator, " ", target_argv); + defer testing.allocator.free(expected_stout); + try testHelper(loader_argv, expected_stout); +} + +fn testReadlink(comptime name: []const u8) !void { + const exe_path = getTestExePath(name); + const loader_argv: []const []const u8 = &.{ flicker_path, exe_path }; + const cwd_path = try std.fs.cwd().realpathAlloc(testing.allocator, "."); + defer testing.allocator.free(cwd_path); + const expected_path = try std.fs.path.join(testing.allocator, &.{ cwd_path, exe_path }); + defer testing.allocator.free(expected_path); + try testHelper(loader_argv, expected_path); +} + +fn testHelper( + argv: []const []const u8, + expected_stdout: []const u8, +) !void { + const result = try std.process.Child.run(.{ + .allocator = testing.allocator, + .argv = argv, + }); + defer testing.allocator.free(result.stdout); + defer testing.allocator.free(result.stderr); + errdefer std.log.err("term: {}", .{result.term}); + errdefer std.log.err("stdout: {s}", .{result.stdout}); + errdefer std.log.err("stderr: {s}", .{result.stderr}); + + try testing.expectEqualStrings(expected_stdout, result.stdout); + try testing.expect(result.term == .Exited); + try testing.expectEqual(0, result.term.Exited); +} diff --git a/src/test/exit.zig b/src/test/exit.zig new file mode 100644 index 0000000..187a4eb --- /dev/null +++ b/src/test/exit.zig @@ -0,0 +1,3 @@ +pub fn main() void { + return; +} diff --git a/src/test/helloWorld.zig b/src/test/helloWorld.zig new file mode 100644 index 0000000..811a56e --- /dev/null +++ b/src/test/helloWorld.zig @@ -0,0 +1,9 @@ +const std = @import("std"); + +pub fn main() !void { + var stdout_buffer: [64]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + const stdout = &stdout_writer.interface; + try stdout.print("Hello World!\n", .{}); + try stdout.flush(); +} diff --git a/src/test/printArgs.zig b/src/test/printArgs.zig new file mode 100644 index 0000000..df2c74a --- /dev/null +++ b/src/test/printArgs.zig @@ -0,0 +1,17 @@ +const std = @import("std"); + +pub fn main() !void { + var stdout_buffer: [64]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + const stdout = &stdout_writer.interface; + + // It is done this way to remove the trailing space with a naive implementation. + var args = std.process.args(); + if (args.next()) |arg| { + try stdout.print("{s}", .{arg}); + } + while (args.next()) |arg| { + try stdout.print(" {s}", .{arg}); + } + try stdout.flush(); +} diff --git a/src/test/readlink.zig b/src/test/readlink.zig new file mode 100644 index 0000000..3bc36ce --- /dev/null +++ b/src/test/readlink.zig @@ -0,0 +1,13 @@ +const std = @import("std"); + +pub fn main() !void { + var buf: [std.fs.max_path_bytes]u8 = undefined; + // We use /proc/self/exe to test if the loader interception works. + // const path = try std.posix.readlink("/proc/self/exe", &buf); + const size = std.posix.system.readlink("/proc/self/exe", &buf, buf.len); + var stdout_buffer: [64]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + const stdout = &stdout_writer.interface; + try stdout.print("{s}", .{buf[0..@intCast(size)]}); + try stdout.flush(); +}