Commit 9b3b7aa911

rpkak <rpkak@users.noreply.github.com>
2025-08-26 21:47:59
Integrate libc-test cases into the build system
zig build test-libc -Dlibc-test-path=/path/to/libc-test
1 parent 4fb0898
Changed files (4)
test/src/Libc.zig
@@ -0,0 +1,118 @@
+b: *std.Build,
+options: Options,
+root_step: *std.Build.Step,
+
+libc_test_src_path: std.Build.LazyPath,
+
+test_cases: std.ArrayList(TestCase) = .empty,
+
+pub const Options = struct {
+    optimize_modes: []const std.builtin.OptimizeMode,
+    test_filters: []const []const u8,
+    test_target_filters: []const []const u8,
+};
+
+const TestCase = struct {
+    name: []const u8,
+    src_file: std.Build.LazyPath,
+    additional_src_file: ?std.Build.LazyPath,
+    supports_wasi_libc: bool,
+};
+
+pub const LibcTestCaseOption = struct {
+    additional_src_file: ?[]const u8 = null,
+};
+
+pub fn addLibcTestCase(
+    libc: *Libc,
+    path: []const u8,
+    supports_wasi_libc: bool,
+    options: LibcTestCaseOption,
+) void {
+    const name = libc.b.dupe(path[0 .. path.len - std.fs.path.extension(path).len]);
+    std.mem.replaceScalar(u8, name, '/', '.');
+    libc.test_cases.append(libc.b.allocator, .{
+        .name = name,
+        .src_file = libc.libc_test_src_path.path(libc.b, path),
+        .additional_src_file = if (options.additional_src_file) |additional_src_file| libc.libc_test_src_path.path(libc.b, additional_src_file) else null,
+        .supports_wasi_libc = supports_wasi_libc,
+    }) catch @panic("OOM");
+}
+
+pub fn addTarget(libc: *const Libc, target: std.Build.ResolvedTarget) void {
+    if (libc.options.test_target_filters.len > 0) {
+        const triple_txt = target.query.zigTriple(libc.b.allocator) catch @panic("OOM");
+        for (libc.options.test_target_filters) |filter| {
+            if (std.mem.indexOf(u8, triple_txt, filter)) |_| break;
+        } else return;
+    }
+
+    const common = libc.libc_test_src_path.path(libc.b, "common");
+
+    for (libc.options.optimize_modes) |optimize| {
+        const libtest_mod = libc.b.createModule(.{
+            .target = target,
+            .optimize = optimize,
+            .link_libc = true,
+        });
+
+        var libtest_c_source_files: []const []const u8 = &.{ "print.c", "rand.c", "setrlim.c", "memfill.c", "vmfill.c", "fdfill.c", "utf8.c" };
+        libtest_mod.addCSourceFiles(.{
+            .root = common,
+            .files = libtest_c_source_files[0..if (target.result.isMuslLibC()) 7 else 2],
+            .flags = &.{"-fno-builtin"},
+        });
+
+        const libtest = libc.b.addLibrary(.{
+            .name = "test",
+            .root_module = libtest_mod,
+        });
+
+        for (libc.test_cases.items) |*test_case| {
+            if (target.result.isWasiLibC() and !test_case.supports_wasi_libc)
+                continue;
+
+            const annotated_case_name = libc.b.fmt("run libc-test {s} ({t})", .{ test_case.name, optimize });
+            for (libc.options.test_filters) |test_filter| {
+                if (std.mem.indexOf(u8, annotated_case_name, test_filter)) |_| break;
+            } else if (libc.options.test_filters.len > 0) continue;
+
+            const mod = libc.b.createModule(.{
+                .target = target,
+                .optimize = optimize,
+                .link_libc = true,
+            });
+            mod.addIncludePath(common);
+            if (target.result.isWasiLibC())
+                mod.addCMacro("_WASI_EMULATED_SIGNAL", "");
+            mod.addCSourceFile(.{
+                .file = test_case.src_file,
+                .flags = &.{"-fno-builtin"},
+            });
+            if (test_case.additional_src_file) |additional_src_file| {
+                mod.addCSourceFile(.{
+                    .file = additional_src_file,
+                    .flags = &.{"-fno-builtin"},
+                });
+            }
+            mod.linkLibrary(libtest);
+
+            const exe = libc.b.addExecutable(.{
+                .name = test_case.name,
+                .root_module = mod,
+            });
+
+            const run = libc.b.addRunArtifact(exe);
+            run.setName(annotated_case_name);
+            run.skip_foreign_checks = true;
+            run.expectStdErrEqual("");
+            run.expectStdOutEqual("");
+            run.expectExitCode(0);
+
+            libc.root_step.dependOn(&run.step);
+        }
+    }
+}
+
+const Libc = @This();
+const std = @import("std");
test/libc.zig
@@ -0,0 +1,143 @@
+pub fn addCases(cases: *tests.LibcContext) void {
+    cases.addLibcTestCase("api/main.c", true, .{});
+
+    cases.addLibcTestCase("functional/argv.c", true, .{});
+    cases.addLibcTestCase("functional/basename.c", true, .{});
+    cases.addLibcTestCase("functional/clocale_mbfuncs.c", true, .{});
+    cases.addLibcTestCase("functional/clock_gettime.c", true, .{});
+    cases.addLibcTestCase("functional/crypt.c", true, .{});
+    cases.addLibcTestCase("functional/dirname.c", true, .{});
+    cases.addLibcTestCase("functional/env.c", true, .{});
+    cases.addLibcTestCase("functional/fcntl.c", false, .{});
+    cases.addLibcTestCase("functional/fdopen.c", false, .{});
+    cases.addLibcTestCase("functional/fnmatch.c", true, .{});
+    cases.addLibcTestCase("functional/fscanf.c", false, .{});
+    cases.addLibcTestCase("functional/fwscanf.c", false, .{});
+    cases.addLibcTestCase("functional/iconv_open.c", true, .{});
+    cases.addLibcTestCase("functional/inet_pton.c", false, .{});
+    // "functional/ipc_msg.c": Probably a bug in qemu
+    // "functional/ipc_sem.c": Probably a bug in qemu
+    // "functional/ipc_shm.c": Probably a bug in qemu
+    cases.addLibcTestCase("functional/mbc.c", true, .{});
+    cases.addLibcTestCase("functional/memstream.c", true, .{});
+    // "functional/mntent.c": https://www.openwall.com/lists/musl/2024/10/22/1
+    cases.addLibcTestCase("functional/popen.c", false, .{});
+    cases.addLibcTestCase("functional/pthread_cancel-points.c", false, .{});
+    cases.addLibcTestCase("functional/pthread_cancel.c", false, .{});
+    cases.addLibcTestCase("functional/pthread_cond.c", false, .{});
+    cases.addLibcTestCase("functional/pthread_mutex.c", false, .{});
+    // "functional/pthread_mutex_pi.c": Probably a bug in qemu (big/little endian FUTEX_LOCK_PI)
+    // "functional/pthread_robust.c": https://gitlab.com/qemu-project/qemu/-/issues/2424
+    cases.addLibcTestCase("functional/pthread_tsd.c", false, .{});
+    cases.addLibcTestCase("functional/qsort.c", true, .{});
+    cases.addLibcTestCase("functional/random.c", true, .{});
+    cases.addLibcTestCase("functional/search_hsearch.c", false, .{}); // The test suite of wasi-libc runs this test case
+    cases.addLibcTestCase("functional/search_insque.c", true, .{});
+    cases.addLibcTestCase("functional/search_lsearch.c", true, .{});
+    cases.addLibcTestCase("functional/search_tsearch.c", true, .{});
+    cases.addLibcTestCase("functional/sem_init.c", false, .{});
+    cases.addLibcTestCase("functional/sem_open.c", false, .{});
+    cases.addLibcTestCase("functional/setjmp.c", false, .{});
+    cases.addLibcTestCase("functional/snprintf.c", true, .{});
+    cases.addLibcTestCase("functional/socket.c", false, .{});
+    cases.addLibcTestCase("functional/spawn.c", false, .{});
+    cases.addLibcTestCase("functional/sscanf.c", true, .{});
+    cases.addLibcTestCase("functional/sscanf_long.c", false, .{});
+    cases.addLibcTestCase("functional/stat.c", false, .{});
+    cases.addLibcTestCase("functional/strftime.c", true, .{});
+    cases.addLibcTestCase("functional/string.c", true, .{});
+    cases.addLibcTestCase("functional/string_memcpy.c", true, .{});
+    cases.addLibcTestCase("functional/string_memmem.c", true, .{});
+    cases.addLibcTestCase("functional/string_memset.c", true, .{});
+    cases.addLibcTestCase("functional/string_strchr.c", true, .{});
+    cases.addLibcTestCase("functional/string_strcspn.c", true, .{});
+    cases.addLibcTestCase("functional/string_strstr.c", true, .{});
+    cases.addLibcTestCase("functional/strtod.c", true, .{});
+    cases.addLibcTestCase("functional/strtod_long.c", true, .{});
+    cases.addLibcTestCase("functional/strtod_simple.c", true, .{});
+    cases.addLibcTestCase("functional/strtof.c", true, .{});
+    cases.addLibcTestCase("functional/strtol.c", true, .{});
+    cases.addLibcTestCase("functional/strtold.c", true, .{});
+    cases.addLibcTestCase("functional/swprintf.c", true, .{});
+    cases.addLibcTestCase("functional/tgmath.c", true, .{});
+    cases.addLibcTestCase("functional/time.c", false, .{});
+    cases.addLibcTestCase("functional/tls_align.c", true, .{ .additional_src_file = "functional/tls_align_dso.c" });
+    cases.addLibcTestCase("functional/tls_init.c", false, .{});
+    cases.addLibcTestCase("functional/tls_local_exec.c", false, .{});
+    cases.addLibcTestCase("functional/udiv.c", true, .{});
+    cases.addLibcTestCase("functional/ungetc.c", false, .{});
+    cases.addLibcTestCase("functional/utime.c", false, .{});
+    cases.addLibcTestCase("functional/vfork.c", false, .{});
+    cases.addLibcTestCase("functional/wcsstr.c", true, .{});
+    cases.addLibcTestCase("functional/wcstol.c", true, .{});
+
+    cases.addLibcTestCase("regression/daemon-failure.c", false, .{});
+    cases.addLibcTestCase("regression/dn_expand-empty.c", false, .{});
+    cases.addLibcTestCase("regression/dn_expand-ptr-0.c", false, .{});
+    cases.addLibcTestCase("regression/execle-env.c", false, .{});
+    cases.addLibcTestCase("regression/fflush-exit.c", false, .{});
+    cases.addLibcTestCase("regression/fgets-eof.c", true, .{});
+    cases.addLibcTestCase("regression/fgetwc-buffering.c", false, .{});
+    cases.addLibcTestCase("regression/flockfile-list.c", false, .{});
+    cases.addLibcTestCase("regression/fpclassify-invalid-ld80.c", true, .{});
+    cases.addLibcTestCase("regression/ftello-unflushed-append.c", false, .{});
+    cases.addLibcTestCase("regression/getpwnam_r-crash.c", false, .{});
+    cases.addLibcTestCase("regression/getpwnam_r-errno.c", false, .{});
+    cases.addLibcTestCase("regression/iconv-roundtrips.c", true, .{});
+    cases.addLibcTestCase("regression/inet_ntop-v4mapped.c", true, .{});
+    cases.addLibcTestCase("regression/inet_pton-empty-last-field.c", true, .{});
+    cases.addLibcTestCase("regression/iswspace-null.c", true, .{});
+    cases.addLibcTestCase("regression/lrand48-signextend.c", true, .{});
+    cases.addLibcTestCase("regression/lseek-large.c", false, .{});
+    cases.addLibcTestCase("regression/malloc-0.c", true, .{});
+    // "regression/malloc-brk-fail.c": QEMU OOM
+    cases.addLibcTestCase("regression/malloc-oom.c", false, .{}); // wasi-libc: requires t_memfill
+    cases.addLibcTestCase("regression/mbsrtowcs-overflow.c", true, .{});
+    cases.addLibcTestCase("regression/memmem-oob-read.c", true, .{});
+    cases.addLibcTestCase("regression/memmem-oob.c", true, .{});
+    cases.addLibcTestCase("regression/mkdtemp-failure.c", false, .{});
+    cases.addLibcTestCase("regression/mkstemp-failure.c", false, .{});
+    cases.addLibcTestCase("regression/printf-1e9-oob.c", true, .{});
+    cases.addLibcTestCase("regression/printf-fmt-g-round.c", true, .{});
+    cases.addLibcTestCase("regression/printf-fmt-g-zeros.c", true, .{});
+    cases.addLibcTestCase("regression/printf-fmt-n.c", true, .{});
+    // "regression/pthread-robust-detach.c": https://gitlab.com/qemu-project/qemu/-/issues/2424
+    cases.addLibcTestCase("regression/pthread_atfork-errno-clobber.c", false, .{});
+    cases.addLibcTestCase("regression/pthread_cancel-sem_wait.c", false, .{});
+    cases.addLibcTestCase("regression/pthread_cond-smasher.c", false, .{});
+    cases.addLibcTestCase("regression/pthread_cond_wait-cancel_ignored.c", false, .{});
+    cases.addLibcTestCase("regression/pthread_condattr_setclock.c", false, .{});
+    // "regression/pthread_create-oom.c": QEMU OOM
+    cases.addLibcTestCase("regression/pthread_exit-cancel.c", false, .{});
+    cases.addLibcTestCase("regression/pthread_exit-dtor.c", false, .{});
+    cases.addLibcTestCase("regression/pthread_once-deadlock.c", false, .{});
+    cases.addLibcTestCase("regression/pthread_rwlock-ebusy.c", false, .{});
+    cases.addLibcTestCase("regression/putenv-doublefree.c", true, .{});
+    cases.addLibcTestCase("regression/raise-race.c", false, .{});
+    cases.addLibcTestCase("regression/regex-backref-0.c", true, .{});
+    cases.addLibcTestCase("regression/regex-bracket-icase.c", true, .{});
+    cases.addLibcTestCase("regression/regex-ere-backref.c", true, .{});
+    cases.addLibcTestCase("regression/regex-escaped-high-byte.c", true, .{});
+    cases.addLibcTestCase("regression/regex-negated-range.c", true, .{});
+    cases.addLibcTestCase("regression/regexec-nosub.c", true, .{});
+    cases.addLibcTestCase("regression/rewind-clear-error.c", false, .{});
+    cases.addLibcTestCase("regression/rlimit-open-files.c", false, .{});
+    cases.addLibcTestCase("regression/scanf-bytes-consumed.c", true, .{});
+    cases.addLibcTestCase("regression/scanf-match-literal-eof.c", true, .{});
+    cases.addLibcTestCase("regression/scanf-nullbyte-char.c", true, .{});
+    cases.addLibcTestCase("regression/sem_close-unmap.c", false, .{});
+    // "regression/setenv-oom.c": QEMU OOM
+    cases.addLibcTestCase("regression/setvbuf-unget.c", true, .{});
+    cases.addLibcTestCase("regression/sigaltstack.c", false, .{});
+    cases.addLibcTestCase("regression/sigprocmask-internal.c", false, .{});
+    cases.addLibcTestCase("regression/sigreturn.c", true, .{});
+    cases.addLibcTestCase("regression/sscanf-eof.c", true, .{});
+    cases.addLibcTestCase("regression/strverscmp.c", true, .{});
+    cases.addLibcTestCase("regression/syscall-sign-extend.c", false, .{});
+    cases.addLibcTestCase("regression/uselocale-0.c", true, .{});
+    cases.addLibcTestCase("regression/wcsncpy-read-overflow.c", true, .{});
+    cases.addLibcTestCase("regression/wcsstr-false-negative.c", true, .{});
+}
+
+const std = @import("std");
+const tests = @import("tests.zig");
test/tests.zig
@@ -10,6 +10,7 @@ const stack_traces = @import("stack_traces.zig");
 const translate_c = @import("translate_c.zig");
 const run_translated_c = @import("run_translated_c.zig");
 const llvm_ir = @import("llvm_ir.zig");
+const libc = @import("libc.zig");
 
 // Implementations
 pub const TranslateCContext = @import("src/TranslateC.zig");
@@ -17,6 +18,7 @@ pub const RunTranslatedCContext = @import("src/RunTranslatedC.zig");
 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");
 
 const TestTarget = struct {
     linkage: ?std.builtin.LinkMode = null,
@@ -2669,3 +2671,191 @@ pub fn addLlvmIrTests(b: *std.Build, options: LlvmIrContext.Options) ?*Step {
 
     return step;
 }
+
+const libc_targets: []const std.Target.Query = &.{
+    .{
+        .cpu_arch = .arm,
+        .os_tag = .linux,
+        .abi = .musleabi,
+    },
+    .{
+        .cpu_arch = .arm,
+        .os_tag = .linux,
+        .abi = .musleabihf,
+    },
+    .{
+        .cpu_arch = .armeb,
+        .os_tag = .linux,
+        .abi = .musleabi,
+    },
+    .{
+        .cpu_arch = .armeb,
+        .os_tag = .linux,
+        .abi = .musleabihf,
+    },
+    .{
+        .cpu_arch = .thumb,
+        .os_tag = .linux,
+        .abi = .musleabi,
+    },
+    .{
+        .cpu_arch = .thumb,
+        .os_tag = .linux,
+        .abi = .musleabihf,
+    },
+    .{
+        .cpu_arch = .thumbeb,
+        .os_tag = .linux,
+        .abi = .musleabi,
+    },
+    .{
+        .cpu_arch = .thumbeb,
+        .os_tag = .linux,
+        .abi = .musleabihf,
+    },
+    .{
+        .cpu_arch = .aarch64,
+        .os_tag = .linux,
+        .abi = .musl,
+    },
+    .{
+        .cpu_arch = .aarch64_be,
+        .os_tag = .linux,
+        .abi = .musl,
+    },
+    // .{
+    //     .cpu_arch = .hexagon,
+    //     .os_tag = .linux,
+    //     .abi = .musl,
+    // },
+    .{
+        .cpu_arch = .loongarch64,
+        .os_tag = .linux,
+        .abi = .musl,
+    },
+    .{
+        .cpu_arch = .loongarch64,
+        .os_tag = .linux,
+        .abi = .muslsf,
+    },
+    // .{
+    //     .cpu_arch = .mips,
+    //     .os_tag = .linux,
+    //     .abi = .musleabi,
+    // },
+    // .{
+    //     .cpu_arch = .mips,
+    //     .os_tag = .linux,
+    //     .abi = .musleabihf,
+    // },
+    // .{
+    //     .cpu_arch = .mipsel,
+    //     .os_tag = .linux,
+    //     .abi = .musleabi,
+    // },
+    // .{
+    //     .cpu_arch = .mipsel,
+    //     .os_tag = .linux,
+    //     .abi = .musleabihf,
+    // },
+    // .{
+    //     .cpu_arch = .mips64,
+    //     .os_tag = .linux,
+    //     .abi = .muslabi64,
+    // },
+    // .{
+    //     .cpu_arch = .mips64,
+    //     .os_tag = .linux,
+    //     .abi = .muslabin32,
+    // },
+    // .{
+    //     .cpu_arch = .mips64el,
+    //     .os_tag = .linux,
+    //     .abi = .muslabi64,
+    // },
+    // .{
+    //     .cpu_arch = .mips64el,
+    //     .os_tag = .linux,
+    //     .abi = .muslabin32,
+    // },
+    .{
+        .cpu_arch = .powerpc,
+        .os_tag = .linux,
+        .abi = .musleabi,
+    },
+    .{
+        .cpu_arch = .powerpc,
+        .os_tag = .linux,
+        .abi = .musleabihf,
+    },
+    .{
+        .cpu_arch = .powerpc64,
+        .os_tag = .linux,
+        .abi = .musl,
+    },
+    .{
+        .cpu_arch = .powerpc64le,
+        .os_tag = .linux,
+        .abi = .musl,
+    },
+    .{
+        .cpu_arch = .riscv32,
+        .os_tag = .linux,
+        .abi = .musl,
+    },
+    .{
+        .cpu_arch = .riscv64,
+        .os_tag = .linux,
+        .abi = .musl,
+    },
+    .{
+        .cpu_arch = .s390x,
+        .os_tag = .linux,
+        .abi = .musl,
+    },
+    .{
+        .cpu_arch = .wasm32,
+        .os_tag = .wasi,
+        .abi = .musl,
+    },
+    .{
+        .cpu_arch = .x86,
+        .os_tag = .linux,
+        .abi = .musl,
+    },
+    .{
+        .cpu_arch = .x86_64,
+        .os_tag = .linux,
+        .abi = .musl,
+    },
+    .{
+        .cpu_arch = .x86_64,
+        .os_tag = .linux,
+        .abi = .muslx32,
+    },
+};
+
+pub fn addLibcTests(b: *std.Build, options: LibcContext.Options) ?*Step {
+    const step = b.step("test-libc", "Run libc-test test cases");
+    const opt_libc_test_path = b.option(std.Build.LazyPath, "libc-test-path", "path to libc-test source directory");
+    if (opt_libc_test_path) |libc_test_path| {
+        var context: LibcContext = .{
+            .b = b,
+            .options = options,
+            .root_step = step,
+            .libc_test_src_path = libc_test_path.path(b, "src"),
+        };
+
+        libc.addCases(&context);
+
+        for (libc_targets) |target_query| {
+            const target = b.resolveTargetQuery(target_query);
+            context.addTarget(target);
+        }
+
+        return step;
+    } else {
+        step.dependOn(&b.addFail("The -Dlibc-test-path=... option is required for this step").step);
+        return null;
+    }
+}
build.zig
@@ -632,6 +632,12 @@ pub fn build(b: *std.Build) !void {
     const test_incremental_step = b.step("test-incremental", "Run the incremental compilation test cases");
     try tests.addIncrementalTests(b, test_incremental_step);
     test_step.dependOn(test_incremental_step);
+
+    if (tests.addLibcTests(b, .{
+        .optimize_modes = optimization_modes,
+        .test_filters = test_filters,
+        .test_target_filters = test_target_filters,
+    })) |test_libc_step| test_step.dependOn(test_libc_step);
 }
 
 fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {