From 1e3c10e2c6fda35e6efe78b440104690f05f2a09 Mon Sep 17 00:00:00 2001 From: rpkak Date: Tue, 26 Aug 2025 21:47:59 +0200 Subject: [PATCH] Integrate libc-test cases into the build system zig build test-libc -Dlibc-test-path=/path/to/libc-test --- build.zig | 6 ++ test/libc.zig | 143 ++++++++++++++++++++++++++++++++++ test/src/Libc.zig | 118 ++++++++++++++++++++++++++++ test/tests.zig | 190 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 457 insertions(+) create mode 100644 test/libc.zig create mode 100644 test/src/Libc.zig diff --git a/build.zig b/build.zig index bbc0c57b805d..4901233258f4 100644 --- a/build.zig +++ b/build.zig @@ -630,6 +630,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 { diff --git a/test/libc.zig b/test/libc.zig new file mode 100644 index 000000000000..8572f05ce7a8 --- /dev/null +++ b/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"); diff --git a/test/src/Libc.zig b/test/src/Libc.zig new file mode 100644 index 000000000000..7a5801ea28b8 --- /dev/null +++ b/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"); diff --git a/test/tests.zig b/test/tests.zig index 5b80ec5b0b7d..e3ad0f508c02 100644 --- a/test/tests.zig +++ b/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, @@ -2663,3 +2665,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; + } +}