Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
143 changes: 143 additions & 0 deletions test/libc.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
pub fn addCases(cases: *tests.LibcContext) void {
cases.addLibcTestCase("api/main.c", true, .{});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This doesn’t actually run any of the api tests. In the original Makefile build, main is compiled by including all of the object files under src/api.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The api tests make sure that the libc headers are correct. I did not include the tests, since I don't think it's planned to modify the headers (@alexrp correct me if that's wrong).

I included the main.c test to have one test, which does not depend on the used functions but signals bigger errors like bugs in the C startup code on failure.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The api tests make sure that the libc headers are correct. I did not include the tests, since I don't think it's planned to modify the headers (@alexrp correct me if that's wrong).

That's right. The goal for libzigc is to be API/ABI compatible with established libcs (at least when targeting them).

I included the main.c test to have one test, which does not depend on the used functions but signals bigger errors like bugs in the C startup code on failure.

I imagine our regular module tests would already catch this. But I also don't have any particular objection to including this test either.


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
Copy link
Contributor Author

@rpkak rpkak Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only non-pthread test included in the tests of wasi-libc, which fails with zig. It seems to be caused by a difference in memory allocation:

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main(void) {
    printf("%p, %s\n", malloc(SIZE_MAX), strerror(errno));
}
$ clang --target=wasm32-wasi --sysroot=/path/to/wasi-libc/sysroot test.c -c -fno-builtin
$ clang --target=wasm32-wasi --sysroot=/path/to/wasi-libc/sysroot -resource-dir /path/to/wasi-libc/build/wasm32-wasi/resource-dir test.o -fno-builtin -o compiled_with_clang
$ zig build-exe -target wasm32-wasi-musl test.c -lc -cflags -fno-builtin -- -femit-bin=compiled_with_zig
$ wasmtime compiled_with_clang
0, Out of memory
$ wasmtime compiled_with_zig
0, Success

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's pretty odd... I don't think we do anything differently from upstream wasi-libc with regards to malloc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, we do actually! wasi-libc defaults to dlmalloc, but we default to emmalloc. That might explain it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @jedisct1 do you know why we do this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

emmalloc has better performance.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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");
118 changes: 118 additions & 0 deletions test/src/Libc.zig
Original file line number Diff line number Diff line change
@@ -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");
Loading