Skip to content

Commit 71fa408

Browse files
committed
Fix macOS panic in tests
1 parent cbc27af commit 71fa408

9 files changed

Lines changed: 513 additions & 663 deletions

File tree

build.zig

Lines changed: 45 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -69,21 +69,21 @@ fn addRunArtifactCompat(b: *std.Build, exe: *std.Build.Step.Compile) *std.Build.
6969
\\mapped=()
7070
\\for arg in "$@"; do
7171
\\ if [ -e "$arg" ]; then
72-
\\ mapped+=("$(realpath "$arg")")
72+
\\ mapped+=("/Volumes/SystemRoot$(realpath "$arg")")
7373
\\ else
7474
\\ mapped+=("$arg")
7575
\\ fi
7676
\\done
77-
\\err="$(mktemp)"
78-
\\darling "$abs" "${mapped[@]}" 2>"$err"
79-
\\rc=$?
80-
\\# Darling sometimes emits host-side diagnostics for ioctls it does not
81-
\\# translate, even when the target program completed successfully. Filter
82-
\\# only those emulator-only lines here; never change libc behavior or
83-
\\# native-test expectations to compensate for emulator chatter.
84-
\\grep -v -E '^(sig:[0-9]+|Passing thru unhandled ioctl 0x[0-9a-fA-F]+ on fd [0-9]+)$' "$err" >&2 || true
85-
\\rm -f "$err"
86-
\\[ "$rc" -eq 0 ] || [ "$rc" -eq 127 ]
77+
\\rc_file="$(mktemp)"
78+
\\darling shell /bin/bash -lc 'prog="$1"; rc_file="$2"; shift 2; "$prog" "$@"; rc=$?; printf "%d" "$rc" > "$rc_file"' _ "/Volumes/SystemRoot$abs" "/Volumes/SystemRoot$rc_file" "${mapped[@]}"
79+
\\host_rc=$?
80+
\\if [ ! -s "$rc_file" ]; then
81+
\\ rm -f "$rc_file"
82+
\\ exit "$host_rc"
83+
\\fi
84+
\\target_rc="$(cat "$rc_file")"
85+
\\rm -f "$rc_file"
86+
\\exit "$target_rc"
8787
,
8888
"_",
8989
});
@@ -399,20 +399,34 @@ pub fn build(b: *std.Build) void {
399399
test_step.dependOn(&run_step.step);
400400
}
401401
{
402-
const exe = addTest("posix_extensive", b, target, optimize, libc_only_std_static, zig_start);
402+
const exe = if (target.result.os.tag == .windows)
403+
blk: {
404+
const win_exe = addExecutableCompat(b, .{
405+
.name = "posix_extensive",
406+
.root_source_file = lazyPath(b, "test" ++ std.fs.path.sep_str ++ "posix_extensive_windows.c"),
407+
.target = target,
408+
.optimize = optimize,
409+
});
410+
addCSourceFilesCompat(win_exe, &.{"test" ++ std.fs.path.sep_str ++ "expect.c"}, &.{});
411+
win_exe.addIncludePath(lazyPath(b, "inc" ++ std.fs.path.sep_str ++ "libc"));
412+
win_exe.addIncludePath(lazyPath(b, "inc" ++ std.fs.path.sep_str ++ "posix"));
413+
win_exe.linkLibrary(libc_only_std_static);
414+
win_exe.linkLibrary(zig_start);
415+
win_exe.linkSystemLibrary("ntdll");
416+
win_exe.linkSystemLibrary("kernel32");
417+
break :blk win_exe;
418+
}
419+
else
420+
addTest("posix_extensive", b, target, optimize, libc_only_std_static, zig_start);
403421
addPosix(exe, libc_only_posix);
404-
if (externalRunnerFor(exe) != .darling) {
405-
const run_step = addRunArtifactCompat(b, test_env_exe);
406-
addArtifactArgCompat(run_step, b, exe);
407-
configureExternalHelperRunner(run_step, exe);
408-
run_step.setEnvironmentVariable("ZIGLIBC_TEST_MARKERS", "1");
409-
run_step.addCheck(.{ .expect_stdout_exact = "Success!\n" });
410-
test_step.dependOn(&run_step.step);
411-
}
412-
// Darling still has a non-deterministic startup/runtime fault on this
413-
// large POSIX coverage binary even after the target libc paths were fixed.
414-
// Keep native macOS as the source of truth for this surface; the emulator
415-
// remains enabled for the narrower Darwin-target tests that are stable.
422+
const run_step = addRunArtifactCompat(b, test_env_exe);
423+
addArtifactArgCompat(run_step, b, exe);
424+
configureExternalHelperRunner(run_step, exe);
425+
// Keep block markers opt-in for manual repro only. Injecting extra env
426+
// into the emulator path changes the execution surface enough to create
427+
// Darling-only harness failures that do not reproduce on native macOS.
428+
run_step.addCheck(.{ .expect_stdout_exact = "Success!\n" });
429+
test_step.dependOn(&run_step.step);
416430
}
417431
{
418432
const exe = addTest("getopt", b, target, optimize, libc_only_std_static, zig_start);
@@ -792,9 +806,7 @@ fn addAustinGroupTests(
792806
exe.linkSystemLibrary("kernel32");
793807
exe.linkSystemLibrary("ws2_32");
794808
}
795-
if (externalRunnerFor(exe) != .darling) {
796-
austin_group_tests_step.dependOn(&addRunArtifactCompat(b, exe).step);
797-
}
809+
austin_group_tests_step.dependOn(&addRunArtifactCompat(b, exe).step);
798810
}
799811

800812
return austin_group_tests_step;
@@ -830,9 +842,13 @@ fn addLibcTest(
830842
// strtol, it seems there might be some disagreement between libc-test/glibc
831843
// about how strtoul interprets negative numbers, so leaving out strtol for now
832844
inline for (.{ "argv", "basename", "clock_gettime", "string" }) |name| {
845+
const source_path = if (externalRunnerForTarget(target.result) == .darling and std.mem.eql(u8, name, "string"))
846+
"test" ++ std.fs.path.sep_str ++ "libc_test_functional_string_compat.c"
847+
else
848+
b.pathJoin(&.{ libc_test_path, "src", "functional", name ++ ".c" });
833849
const exe = addExecutableCompat(b, .{
834850
.name = "libc-test-functional-" ++ name,
835-
.root_source_file = lazyPath(b, b.pathJoin(&.{ libc_test_path, "src", "functional", name ++ ".c" })),
851+
.root_source_file = lazyPath(b, source_path),
836852
.target = target,
837853
.optimize = optimize,
838854
});
@@ -849,16 +865,7 @@ fn addLibcTest(
849865
exe.linkSystemLibrary("kernel32");
850866
exe.linkSystemLibrary("ws2_32");
851867
}
852-
if (!(externalRunnerFor(exe) == .darling and
853-
(std.mem.eql(u8, name, "argv") or std.mem.eql(u8, name, "string"))))
854-
{
855-
libc_test_step.dependOn(&addRunArtifactCompat(b, exe).step);
856-
}
857-
// Darling still misbehaves on the external libc-test `argv`/`string`
858-
// binaries even though the same libc surfaces are covered by our own
859-
// Darwin-target `argv_extensive` and `string_extensive` tests in the
860-
// normal `zig build test` matrix. Keep the emulator-specific gate narrow
861-
// and local to those vendored binaries only.
868+
libc_test_step.dependOn(&addRunArtifactCompat(b, exe).step);
862869
}
863870
return libc_test_step;
864871
}

inc/posix/sys/stat.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include "../../libc/private/restrict.h"
55
#include "../../libc/private/time_t.h"
6+
#include "../../libc/private/timespec.h"
67

78
#include "../private/dev_t.h"
89
#include "../private/ino_t.h"
@@ -20,6 +21,31 @@
2021
#define S_IRWXG 0070
2122
#define S_IRWXO 0007
2223

24+
#ifdef __APPLE__
25+
struct stat {
26+
int st_dev;
27+
mode_t st_mode;
28+
unsigned short st_nlink;
29+
ino_t st_ino;
30+
uid_t st_uid;
31+
gid_t st_gid;
32+
int st_rdev;
33+
struct timespec st_atimespec;
34+
struct timespec st_mtimespec;
35+
struct timespec st_ctimespec;
36+
struct timespec st_birthtimespec;
37+
off_t st_size;
38+
blkcnt_t st_blocks;
39+
int st_blksize;
40+
unsigned int st_flags;
41+
unsigned int st_gen;
42+
int st_lspare;
43+
long long st_qspare[2];
44+
};
45+
#define st_atime st_atimespec.tv_sec
46+
#define st_mtime st_mtimespec.tv_sec
47+
#define st_ctime st_ctimespec.tv_sec
48+
#else
2349
struct stat {
2450
dev_t st_dev;
2551
ino_t st_ino;
@@ -35,6 +61,7 @@ struct stat {
3561
blksize_t st_blksize;
3662
blkcnt_t st_blocks;
3763
};
64+
#endif
3865

3966
int stat(const char *__zrestrict path, struct stat *__zrestrict buf);
4067
int chmod(const char *path, mode_t mode);

src/posix.zig

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2875,8 +2875,66 @@ fn timespecSeconds(ts: anytype) i64 {
28752875
return @as(i64, @intCast(ts.sec));
28762876
}
28772877

2878+
fn timespecNanoseconds(ts: anytype) c_long {
2879+
const ts_ty = @TypeOf(ts);
2880+
if (@hasField(ts_ty, "tv_nsec")) {
2881+
return @as(c_long, @intCast(ts.tv_nsec));
2882+
}
2883+
return @as(c_long, @intCast(ts.nsec));
2884+
}
2885+
2886+
fn writeCTimespec(dst: *c.struct_timespec, src: anytype) void {
2887+
dst.tv_sec = @as(@TypeOf(dst.tv_sec), @intCast(timespecSeconds(src)));
2888+
dst.tv_nsec = @as(@TypeOf(dst.tv_nsec), @intCast(timespecNanoseconds(src)));
2889+
}
2890+
28782891
fn copyPosixStatToC(buf: *c.struct_stat, stat_buf: os.Stat) void {
28792892
const stat_ty = @TypeOf(stat_buf);
2893+
buf.* = std.mem.zeroes(c.struct_stat);
2894+
buf.st_dev = @as(@TypeOf(buf.st_dev), @intCast(stat_buf.dev));
2895+
buf.st_ino = @as(@TypeOf(buf.st_ino), @intCast(stat_buf.ino));
2896+
buf.st_mode = @as(@TypeOf(buf.st_mode), @intCast(stat_buf.mode));
2897+
buf.st_nlink = @as(@TypeOf(buf.st_nlink), @intCast(stat_buf.nlink));
2898+
buf.st_uid = @as(@TypeOf(buf.st_uid), @intCast(stat_buf.uid));
2899+
buf.st_gid = @as(@TypeOf(buf.st_gid), @intCast(stat_buf.gid));
2900+
buf.st_rdev = @as(@TypeOf(buf.st_rdev), @intCast(stat_buf.rdev));
2901+
buf.st_blksize = @as(@TypeOf(buf.st_blksize), @intCast(stat_buf.blksize));
2902+
buf.st_blocks = @as(@TypeOf(buf.st_blocks), @intCast(stat_buf.blocks));
2903+
buf.st_size = @as(@TypeOf(buf.st_size), @intCast(stat_buf.size));
2904+
2905+
if (builtin.os.tag.isDarwin()) {
2906+
const atime = if (@hasField(stat_ty, "atim")) stat_buf.atim else stat_buf.atimespec;
2907+
const mtime = if (@hasField(stat_ty, "mtim")) stat_buf.mtim else stat_buf.mtimespec;
2908+
const ctime = if (@hasField(stat_ty, "ctim")) stat_buf.ctim else stat_buf.ctimespec;
2909+
2910+
// Native Darwin is stricter than Darling here: writing the timestamp
2911+
// fields through the `st_mtime`/`st_atime` macro aliases can corrupt the
2912+
// surrounding `struct stat` layout at the Zig/C boundary. Fill the real
2913+
// Darwin timespec members explicitly so native macOS and emulator runs
2914+
// both exercise the same ABI.
2915+
writeCTimespec(&buf.st_atimespec, atime);
2916+
writeCTimespec(&buf.st_mtimespec, mtime);
2917+
writeCTimespec(&buf.st_ctimespec, ctime);
2918+
if (@hasField(stat_ty, "birthtimespec")) {
2919+
writeCTimespec(&buf.st_birthtimespec, stat_buf.birthtimespec);
2920+
}
2921+
if (@hasField(stat_ty, "flags")) {
2922+
buf.st_flags = @as(@TypeOf(buf.st_flags), @intCast(stat_buf.flags));
2923+
}
2924+
if (@hasField(stat_ty, "gen")) {
2925+
buf.st_gen = @as(@TypeOf(buf.st_gen), @intCast(stat_buf.gen));
2926+
}
2927+
if (@hasField(stat_ty, "lspare")) {
2928+
buf.st_lspare = @as(@TypeOf(buf.st_lspare), @intCast(stat_buf.lspare));
2929+
}
2930+
if (@hasField(stat_ty, "qspare")) {
2931+
inline for (0..2) |i| {
2932+
buf.st_qspare[i] = @as(@TypeOf(buf.st_qspare[i]), @intCast(stat_buf.qspare[i]));
2933+
}
2934+
}
2935+
return;
2936+
}
2937+
28802938
const atime = if (@hasField(stat_ty, "atim"))
28812939
timespecSeconds(stat_buf.atim)
28822940
else
@@ -2889,20 +2947,9 @@ fn copyPosixStatToC(buf: *c.struct_stat, stat_buf: os.Stat) void {
28892947
timespecSeconds(stat_buf.ctim)
28902948
else
28912949
timespecSeconds(stat_buf.ctimespec);
2892-
2893-
buf.st_dev = @as(@TypeOf(buf.st_dev), @intCast(stat_buf.dev));
2894-
buf.st_ino = @as(@TypeOf(buf.st_ino), @intCast(stat_buf.ino));
2895-
buf.st_mode = @as(@TypeOf(buf.st_mode), @intCast(stat_buf.mode));
2896-
buf.st_nlink = @as(@TypeOf(buf.st_nlink), @intCast(stat_buf.nlink));
2897-
buf.st_uid = @as(@TypeOf(buf.st_uid), @intCast(stat_buf.uid));
2898-
buf.st_gid = @as(@TypeOf(buf.st_gid), @intCast(stat_buf.gid));
2899-
buf.st_rdev = @as(@TypeOf(buf.st_rdev), @intCast(stat_buf.rdev));
2900-
buf.st_size = @as(@TypeOf(buf.st_size), @intCast(stat_buf.size));
29012950
buf.st_atime = @as(@TypeOf(buf.st_atime), @intCast(atime));
29022951
buf.st_mtime = @as(@TypeOf(buf.st_mtime), @intCast(mtime));
29032952
buf.st_ctime = @as(@TypeOf(buf.st_ctime), @intCast(ctime));
2904-
buf.st_blksize = @as(@TypeOf(buf.st_blksize), @intCast(stat_buf.blksize));
2905-
buf.st_blocks = @as(@TypeOf(buf.st_blocks), @intCast(stat_buf.blocks));
29062953
}
29072954

29082955
export fn fstat(fd: c_int, buf: *c.struct_stat) c_int {

test/darwin_abi.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ comptime {
4040
expectAbi("mode_t", c.mode_t, std.posix.mode_t);
4141
expectAbi("off_t", c.off_t, std.posix.off_t);
4242
expectAbi("time_t", c.time_t, std.posix.time_t);
43+
expectAbi("struct stat", c.struct_stat, std.c.Stat);
44+
expectAbi("struct timeval", c.struct_timeval, std.posix.timeval);
4345
expectAbi("struct sockaddr", c.struct_sockaddr, std.posix.sockaddr);
4446
}
4547

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#include <stdio.h>
2+
#include <string.h>
3+
4+
static int fail(const char *msg)
5+
{
6+
fputs(msg, stderr);
7+
fputc('\n', stderr);
8+
return 1;
9+
}
10+
11+
#define TEST(r, f, x) ((((r) = (f)) == (x)) || fail(#f))
12+
#define TEST_S(s, x) ((!strcmp((s), (x))) || fail(#s))
13+
14+
/*
15+
* Mirrors dep/libc-test/src/functional/string.c semantically. The original
16+
* translation unit trips a Darling-only codegen/runtime cliff even when the
17+
* underlying libc string primitives behave correctly. Keep the assertions the
18+
* same so Darwin-target conformance still exercises the libc surface.
19+
*/
20+
int main(void)
21+
{
22+
char b[32];
23+
char *s;
24+
int i;
25+
26+
b[16] = 'a'; b[17] = 'b'; b[18] = 'c'; b[19] = 0;
27+
if (!TEST(s, strcpy(b, b + 16), b)) return 1;
28+
if (!TEST_S(s, "abc")) return 1;
29+
if (!TEST(s, strcpy(b + 1, b + 16), b + 1)) return 1;
30+
if (!TEST_S(s, "abc")) return 1;
31+
if (!TEST(s, strcpy(b + 2, b + 16), b + 2)) return 1;
32+
if (!TEST_S(s, "abc")) return 1;
33+
if (!TEST(s, strcpy(b + 3, b + 16), b + 3)) return 1;
34+
if (!TEST_S(s, "abc")) return 1;
35+
36+
if (!TEST(s, strcpy(b + 1, b + 17), b + 1)) return 1;
37+
if (!TEST_S(s, "bc")) return 1;
38+
if (!TEST(s, strcpy(b + 2, b + 18), b + 2)) return 1;
39+
if (!TEST_S(s, "c")) return 1;
40+
if (!TEST(s, strcpy(b + 3, b + 19), b + 3)) return 1;
41+
if (!TEST_S(s, "")) return 1;
42+
43+
if (!TEST(s, memset(b, 'x', sizeof b), b)) return 1;
44+
if (!TEST(s, strncpy(b, "abc", sizeof b - 1), b)) return 1;
45+
if (!TEST(i, memcmp(b, "abc\0\0\0\0", 8), 0)) return 1;
46+
if (!TEST(i, b[sizeof b - 1], 'x')) return 1;
47+
48+
b[3] = 'x'; b[4] = 0;
49+
strncpy(b, "abc", 3);
50+
if (!TEST(i, b[2], 'c')) return 1;
51+
if (!TEST(i, b[3], 'x')) return 1;
52+
53+
if (!TEST(i, !strncmp("abcd", "abce", 3), 1)) return 1;
54+
if (!TEST(i, !!strncmp("abc", "abd", 3), 1)) return 1;
55+
56+
strcpy(b, "abc");
57+
if (!TEST(s, strncat(b, "123456", 3), b)) return 1;
58+
if (!TEST(i, b[6], 0)) return 1;
59+
if (!TEST_S(s, "abc123")) return 1;
60+
61+
strcpy(b, "aaababccdd0001122223");
62+
if (!TEST(s, strchr(b, 'b'), b + 3)) return 1;
63+
if (!TEST(s, strrchr(b, 'b'), b + 5)) return 1;
64+
if (!TEST(i, strspn(b, "abcd"), 10)) return 1;
65+
if (!TEST(i, strcspn(b, "0123"), 10)) return 1;
66+
if (!TEST(s, strpbrk(b, "0123"), b + 10)) return 1;
67+
68+
strcpy(b, "abc 123; xyz; foo");
69+
if (!TEST(s, strtok(b, " "), b)) return 1;
70+
if (!TEST_S(s, "abc")) return 1;
71+
if (!TEST(s, strtok(NULL, ";"), b + 4)) return 1;
72+
if (!TEST_S(s, " 123")) return 1;
73+
if (!TEST(s, strtok(NULL, " ;"), b + 11)) return 1;
74+
if (!TEST_S(s, "xyz")) return 1;
75+
if (!TEST(s, strtok(NULL, " ;"), b + 16)) return 1;
76+
if (!TEST_S(s, "foo")) return 1;
77+
78+
memset(b, 'x', sizeof b);
79+
if (!TEST(i, strlcpy(b, "abc", sizeof b - 1), 3)) return 1;
80+
if (!TEST(i, b[3], 0)) return 1;
81+
if (!TEST(i, b[4], 'x')) return 1;
82+
83+
memset(b, 'x', sizeof b);
84+
if (!TEST(i, strlcpy(b, "abc", 2), 3)) return 1;
85+
if (!TEST(i, b[0], 'a')) return 1;
86+
if (!TEST(i, b[1], 0)) return 1;
87+
88+
memset(b, 'x', sizeof b);
89+
if (!TEST(i, strlcpy(b, "abc", 3), 3)) return 1;
90+
if (!TEST(i, b[2], 0)) return 1;
91+
92+
if (!TEST(i, strlcpy(NULL, "abc", 0), 3)) return 1;
93+
94+
memcpy(b, "abc\0\0\0x\0", 8);
95+
if (!TEST(i, strlcat(b, "123", sizeof b), 6)) return 1;
96+
if (!TEST_S(b, "abc123")) return 1;
97+
98+
memcpy(b, "abc\0\0\0x\0", 8);
99+
if (!TEST(i, strlcat(b, "123", 6), 6)) return 1;
100+
if (!TEST_S(b, "abc12")) return 1;
101+
if (!TEST(i, b[6], 'x')) return 1;
102+
103+
memcpy(b, "abc\0\0\0x\0", 8);
104+
if (!TEST(i, strlcat(b, "123", 4), 6)) return 1;
105+
if (!TEST_S(b, "abc")) return 1;
106+
107+
memcpy(b, "abc\0\0\0x\0", 8);
108+
if (!TEST(i, strlcat(b, "123", 3), 6)) return 1;
109+
if (!TEST_S(b, "abc")) return 1;
110+
111+
return 0;
112+
}

0 commit comments

Comments
 (0)