authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-09-17 21:19:01-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-09-17 21:19:01-07:00
log220c6795233bfdc9b93dfb3364b21705b1e6c903
tree8d59b2842374a10ceac6648f37a954b19dfa33f5
parent09bc118c9f2349f162f181487f8e5a01eb5e734c
parentbd4617033e09979398e65907a1d9c7f1b6e1d124
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #25197 from rootbeer/24380-flaky-sigset-test

Re-enable std.posix "sigset_t bits" test

9 files changed, 485 insertions(+), 387 deletions(-)

lib/std/Thread.zig+37
......@@ -1637,3 +1637,40 @@ test detach {
16371637 event.wait();
16381638 try std.testing.expectEqual(value, 1);
16391639}
1640
1641test "Thread.getCpuCount" {
1642 if (native_os == .wasi) return error.SkipZigTest;
1643
1644 const cpu_count = try Thread.getCpuCount();
1645 try std.testing.expect(cpu_count >= 1);
1646}
1647
1648fn testThreadIdFn(thread_id: *Thread.Id) void {
1649 thread_id.* = Thread.getCurrentId();
1650}
1651
1652test "Thread.getCurrentId" {
1653 if (builtin.single_threaded) return error.SkipZigTest;
1654
1655 var thread_current_id: Thread.Id = undefined;
1656 const thread = try Thread.spawn(.{}, testThreadIdFn, .{&thread_current_id});
1657 thread.join();
1658 try std.testing.expect(Thread.getCurrentId() != thread_current_id);
1659}
1660
1661test "thread local storage" {
1662 if (builtin.single_threaded) return error.SkipZigTest;
1663
1664 const thread1 = try Thread.spawn(.{}, testTls, .{});
1665 const thread2 = try Thread.spawn(.{}, testTls, .{});
1666 try testTls();
1667 thread1.join();
1668 thread2.join();
1669}
1670
1671threadlocal var x: i32 = 1234;
1672fn testTls() !void {
1673 if (x != 1234) return error.TlsBadStartValue;
1674 x += 1;
1675 if (x != 1235) return error.TlsBadEndValue;
1676}
lib/std/posix/test.zig+3-387
......@@ -7,7 +7,6 @@ const expectError = testing.expectError;
77const fs = std.fs;
88const mem = std.mem;
99const elf = std.elf;
10const Thread = std.Thread;
1110const linux = std.os.linux;
1211
1312const a = std.testing.allocator;
......@@ -18,6 +17,9 @@ const AtomicOrder = std.builtin.AtomicOrder;
1817const native_os = builtin.target.os.tag;
1918const tmpDir = std.testing.tmpDir;
2019
20// NOTE: several additional tests are in test/standalone/posix/. Any tests that mutate
21// process-wide POSIX state (cwd, signals, etc) cannot be Zig unit tests and should be over there.
22
2123// https://github.com/ziglang/zig/issues/20288
2224test "WTF-8 to WTF-16 conversion buffer overflows" {
2325 if (native_os != .windows) return error.SkipZigTest;
......@@ -40,68 +42,6 @@ test "check WASI CWD" {
4042 }
4143}
4244
43test "chdir absolute parent" {
44 if (native_os == .wasi) return error.SkipZigTest;
45
46 // Restore default CWD at end of test.
47 const orig_cwd = try fs.cwd().openDir(".", .{});
48 defer orig_cwd.setAsCwd() catch unreachable;
49
50 // Get current working directory path
51 var old_cwd_buf: [fs.max_path_bytes]u8 = undefined;
52 const old_cwd = try posix.getcwd(old_cwd_buf[0..]);
53
54 {
55 // Firstly, changing to itself should have no effect
56 try posix.chdir(old_cwd);
57 var new_cwd_buf: [fs.max_path_bytes]u8 = undefined;
58 const new_cwd = try posix.getcwd(new_cwd_buf[0..]);
59 try expect(mem.eql(u8, old_cwd, new_cwd));
60 }
61
62 // Next, change current working directory to one level above
63 const parent = fs.path.dirname(old_cwd) orelse unreachable; // old_cwd should be absolute
64 try posix.chdir(parent);
65
66 var new_cwd_buf: [fs.max_path_bytes]u8 = undefined;
67 const new_cwd = try posix.getcwd(new_cwd_buf[0..]);
68 try expect(mem.eql(u8, parent, new_cwd));
69}
70
71test "chdir relative" {
72 if (native_os == .wasi) return error.SkipZigTest;
73
74 var tmp = tmpDir(.{});
75 defer tmp.cleanup();
76
77 // Restore default CWD at end of test.
78 const orig_cwd = try fs.cwd().openDir(".", .{});
79 defer orig_cwd.setAsCwd() catch unreachable;
80
81 // Use the tmpDir parent_dir as the "base" for the test. Then cd into the child
82 try tmp.parent_dir.setAsCwd();
83
84 // Capture base working directory path, to build expected full path
85 var base_cwd_buf: [fs.max_path_bytes]u8 = undefined;
86 const base_cwd = try posix.getcwd(base_cwd_buf[0..]);
87
88 const dir_name = &tmp.sub_path;
89 const expected_path = try fs.path.resolve(a, &.{ base_cwd, dir_name });
90 defer a.free(expected_path);
91
92 // change current working directory to new directory
93 try posix.chdir(dir_name);
94
95 var new_cwd_buf: [fs.max_path_bytes]u8 = undefined;
96 const new_cwd = try posix.getcwd(new_cwd_buf[0..]);
97
98 // On Windows, fs.path.resolve returns an uppercase drive letter, but the drive letter returned by getcwd may be lowercase
99 const resolved_cwd = try fs.path.resolve(a, &.{new_cwd});
100 defer a.free(resolved_cwd);
101
102 try expect(mem.eql(u8, expected_path, resolved_cwd));
103}
104
10545test "open smoke test" {
10646 if (native_os == .wasi) return error.SkipZigTest;
10747 if (native_os == .windows) return error.SkipZigTest;
......@@ -227,45 +167,6 @@ test "openat smoke test" {
227167 }
228168}
229169
230test "symlink with relative paths" {
231 if (native_os == .wasi) return error.SkipZigTest; // Can symlink, but can't change into tmpDir
232
233 var tmp = tmpDir(.{});
234 defer tmp.cleanup();
235
236 const target_name = "symlink-target";
237 const symlink_name = "symlinker";
238
239 // Restore default CWD at end of test.
240 const orig_cwd = try fs.cwd().openDir(".", .{});
241 defer orig_cwd.setAsCwd() catch unreachable;
242
243 // Create the target file
244 try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "nonsense" });
245
246 // Want to test relative paths, so cd into the tmpdir for this test
247 try tmp.dir.setAsCwd();
248
249 if (native_os == .windows) {
250 const wtarget_name = try std.unicode.wtf8ToWtf16LeAllocZ(a, target_name);
251 const wsymlink_name = try std.unicode.wtf8ToWtf16LeAllocZ(a, symlink_name);
252 defer a.free(wtarget_name);
253 defer a.free(wsymlink_name);
254
255 std.os.windows.CreateSymbolicLink(tmp.dir.fd, wsymlink_name, wtarget_name, false) catch |err| switch (err) {
256 // Symlink requires admin privileges on windows, so this test can legitimately fail.
257 error.AccessDenied => return error.SkipZigTest,
258 else => return err,
259 };
260 } else {
261 try posix.symlink(target_name, symlink_name);
262 }
263
264 var buffer: [fs.max_path_bytes]u8 = undefined;
265 const given = try posix.readlink(symlink_name, buffer[0..]);
266 try expect(mem.eql(u8, target_name, given));
267}
268
269170test "readlink on Windows" {
270171 if (native_os != .windows) return error.SkipZigTest;
271172
......@@ -280,55 +181,6 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {
280181 try expect(mem.eql(u8, target_path, given));
281182}
282183
283test "link with relative paths" {
284 if (native_os == .wasi) return error.SkipZigTest; // Can link, but can't change into tmpDir
285 if ((builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) and builtin.os.tag == .linux and !builtin.link_libc) return error.SkipZigTest; // No `fstat()`.
286 if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest; // `nstat.nlink` assertion is failing with LLVM 20+ for unclear reasons.
287
288 switch (native_os) {
289 .wasi, .linux, .solaris, .illumos => {},
290 else => return error.SkipZigTest,
291 }
292
293 var tmp = tmpDir(.{});
294 defer tmp.cleanup();
295
296 // Restore default CWD at end of test.
297 const orig_cwd = try fs.cwd().openDir(".", .{});
298 defer orig_cwd.setAsCwd() catch unreachable;
299
300 const target_name = "link-target";
301 const link_name = "newlink";
302
303 try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "example" });
304
305 // Test 1: create the relative link from inside tmp
306 try tmp.dir.setAsCwd();
307 try posix.link(target_name, link_name);
308
309 // Verify
310 const efd = try tmp.dir.openFile(target_name, .{});
311 defer efd.close();
312
313 const nfd = try tmp.dir.openFile(link_name, .{});
314 defer nfd.close();
315
316 {
317 const estat = try posix.fstat(efd.handle);
318 const nstat = try posix.fstat(nfd.handle);
319 try testing.expectEqual(estat.ino, nstat.ino);
320 try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink);
321 }
322
323 // Test 2: Remove the link and see the stats update
324 try posix.unlink(link_name);
325
326 {
327 const estat = try posix.fstat(efd.handle);
328 try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink);
329 }
330}
331
332184test "linkat with different directories" {
333185 if ((builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) and builtin.os.tag == .linux and !builtin.link_libc) return error.SkipZigTest; // No `fstatat()`.
334186 if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest; // `nstat.nlink` assertion is failing with LLVM 20+ for unclear reasons.
......@@ -446,70 +298,6 @@ test "readlinkat" {
446298 try expect(mem.eql(u8, "file.txt", read_link));
447299}
448300
449fn testThreadIdFn(thread_id: *Thread.Id) void {
450 thread_id.* = Thread.getCurrentId();
451}
452
453test "Thread.getCurrentId" {
454 if (builtin.single_threaded) return error.SkipZigTest;
455
456 var thread_current_id: Thread.Id = undefined;
457 const thread = try Thread.spawn(.{}, testThreadIdFn, .{&thread_current_id});
458 thread.join();
459 try expect(Thread.getCurrentId() != thread_current_id);
460}
461
462test "spawn threads" {
463 if (builtin.single_threaded) return error.SkipZigTest;
464
465 var shared_ctx: i32 = 1;
466
467 const thread1 = try Thread.spawn(.{}, start1, .{});
468 const thread2 = try Thread.spawn(.{}, start2, .{&shared_ctx});
469 const thread3 = try Thread.spawn(.{}, start2, .{&shared_ctx});
470 const thread4 = try Thread.spawn(.{}, start2, .{&shared_ctx});
471
472 thread1.join();
473 thread2.join();
474 thread3.join();
475 thread4.join();
476
477 try expect(shared_ctx == 4);
478}
479
480fn start1() u8 {
481 return 0;
482}
483
484fn start2(ctx: *i32) u8 {
485 _ = @atomicRmw(i32, ctx, AtomicRmwOp.Add, 1, AtomicOrder.seq_cst);
486 return 0;
487}
488
489test "cpu count" {
490 if (native_os == .wasi) return error.SkipZigTest;
491
492 const cpu_count = try Thread.getCpuCount();
493 try expect(cpu_count >= 1);
494}
495
496test "thread local storage" {
497 if (builtin.single_threaded) return error.SkipZigTest;
498
499 const thread1 = try Thread.spawn(.{}, testTls, .{});
500 const thread2 = try Thread.spawn(.{}, testTls, .{});
501 try testTls();
502 thread1.join();
503 thread2.join();
504}
505
506threadlocal var x: i32 = 1234;
507fn testTls() !void {
508 if (x != 1234) return error.TlsBadStartValue;
509 x += 1;
510 if (x != 1235) return error.TlsBadEndValue;
511}
512
513301test "getrandom" {
514302 var buf_a: [50]u8 = undefined;
515303 var buf_b: [50]u8 = undefined;
......@@ -520,12 +308,6 @@ test "getrandom" {
520308 try expect(!mem.eql(u8, &buf_a, &buf_b));
521309}
522310
523test "getcwd" {
524 // at least call it so it gets compiled
525 var buf: [std.fs.max_path_bytes]u8 = undefined;
526 _ = posix.getcwd(&buf) catch undefined;
527}
528
529311test "getuid" {
530312 if (native_os == .windows or native_os == .wasi) return error.SkipZigTest;
531313 _ = posix.getuid();
......@@ -737,24 +519,6 @@ test "mmap" {
737519 }
738520}
739521
740test "getenv" {
741 if (native_os == .wasi and !builtin.link_libc) {
742 // std.posix.getenv is not supported on WASI due to the need of allocation
743 return error.SkipZigTest;
744 }
745
746 if (native_os == .windows) {
747 try expect(std.process.getenvW(&[_:0]u16{ 'B', 'O', 'G', 'U', 'S', 0x11, 0x22, 0x33, 0x44, 0x55 }) == null);
748 } else {
749 try expect(posix.getenv("") == null);
750 try expect(posix.getenv("BOGUSDOESNOTEXISTENVVAR") == null);
751 if (builtin.link_libc) {
752 try testing.expectEqualStrings(posix.getenv("USER") orelse "", mem.span(std.c.getenv("USER") orelse ""));
753 }
754 try expect(posix.getenvZ("BOGUSDOESNOTEXISTENVVAR") == null);
755 }
756}
757
758522test "fcntl" {
759523 if (native_os == .windows or native_os == .wasi)
760524 return error.SkipZigTest;
......@@ -939,154 +703,6 @@ test "sigset add/del" {
939703 }
940704}
941705
942test "sigaction" {
943 if (native_os == .wasi or native_os == .windows)
944 return error.SkipZigTest;
945
946 // https://github.com/ziglang/zig/issues/15381
947 if (native_os == .macos and builtin.target.cpu.arch == .x86_64) {
948 return error.SkipZigTest;
949 }
950
951 const test_signo = posix.SIG.URG; // URG only because it is ignored by default in debuggers
952
953 const S = struct {
954 var handler_called_count: u32 = 0;
955
956 fn handler(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
957 _ = ctx_ptr;
958 // Check that we received the correct signal.
959 const info_sig = switch (native_os) {
960 .netbsd => info.info.signo,
961 else => info.signo,
962 };
963 if (sig == test_signo and sig == info_sig) {
964 handler_called_count += 1;
965 }
966 }
967 };
968
969 var sa: posix.Sigaction = .{
970 .handler = .{ .sigaction = &S.handler },
971 .mask = posix.sigemptyset(),
972 .flags = posix.SA.SIGINFO | posix.SA.RESETHAND,
973 };
974
975 var old_sa: posix.Sigaction = undefined;
976
977 // Install the new signal handler.
978 posix.sigaction(test_signo, &sa, null);
979
980 // Check that we can read it back correctly.
981 posix.sigaction(test_signo, null, &old_sa);
982 try testing.expectEqual(&S.handler, old_sa.handler.sigaction.?);
983 try testing.expect((old_sa.flags & posix.SA.SIGINFO) != 0);
984
985 // Invoke the handler.
986 try posix.raise(test_signo);
987 try testing.expectEqual(1, S.handler_called_count);
988
989 // Check if passing RESETHAND correctly reset the handler to SIG_DFL
990 posix.sigaction(test_signo, null, &old_sa);
991 try testing.expectEqual(posix.SIG.DFL, old_sa.handler.handler);
992
993 // Reinstall the signal w/o RESETHAND and re-raise
994 sa.flags = posix.SA.SIGINFO;
995 posix.sigaction(test_signo, &sa, null);
996 try posix.raise(test_signo);
997 try testing.expectEqual(2, S.handler_called_count);
998
999 // Now set the signal to ignored
1000 sa.handler = .{ .handler = posix.SIG.IGN };
1001 sa.flags = 0;
1002 posix.sigaction(test_signo, &sa, null);
1003
1004 // Re-raise to ensure handler is actually ignored
1005 try posix.raise(test_signo);
1006 try testing.expectEqual(2, S.handler_called_count);
1007
1008 // Ensure that ignored state is returned when querying
1009 posix.sigaction(test_signo, null, &old_sa);
1010 try testing.expectEqual(posix.SIG.IGN, old_sa.handler.handler);
1011}
1012
1013test "sigset_t bits" {
1014 if (native_os == .wasi or native_os == .windows)
1015 return error.SkipZigTest;
1016
1017 if (true) {
1018 // https://github.com/ziglang/zig/issues/24380
1019 return error.SkipZigTest;
1020 }
1021
1022 const S = struct {
1023 var expected_sig: i32 = undefined;
1024 var handler_called_count: u32 = 0;
1025
1026 fn handler(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
1027 _ = ctx_ptr;
1028
1029 const info_sig = switch (native_os) {
1030 .netbsd => info.info.signo,
1031 else => info.signo,
1032 };
1033 if (sig == expected_sig and sig == info_sig) {
1034 handler_called_count += 1;
1035 }
1036 }
1037 };
1038
1039 const self_pid = posix.system.getpid();
1040
1041 // To check that sigset_t mapping matches kernel (think u32/u64 mismatches on
1042 // big-endian), try sending a blocked signal to make sure the mask matches the
1043 // signal. (Send URG and CHLD because they're ignored by default in the
1044 // debugger, vs. USR1 or other named signals)
1045 inline for ([_]usize{ posix.SIG.URG, posix.SIG.CHLD, 62, 94, 126 }) |test_signo| {
1046 if (test_signo >= posix.NSIG) continue;
1047
1048 S.expected_sig = test_signo;
1049 S.handler_called_count = 0;
1050
1051 const sa: posix.Sigaction = .{
1052 .handler = .{ .sigaction = &S.handler },
1053 .mask = posix.sigemptyset(),
1054 .flags = posix.SA.SIGINFO | posix.SA.RESETHAND,
1055 };
1056
1057 var old_sa: posix.Sigaction = undefined;
1058
1059 // Install the new signal handler.
1060 posix.sigaction(test_signo, &sa, &old_sa);
1061
1062 // block the signal and see that its delayed until unblocked
1063 var block_one: posix.sigset_t = posix.sigemptyset();
1064 posix.sigaddset(&block_one, test_signo);
1065 posix.sigprocmask(posix.SIG.BLOCK, &block_one, null);
1066
1067 // qemu maps target signals to host signals 1-to-1, so targets
1068 // with more signals than the host will fail to send the signal.
1069 const rc = posix.system.kill(self_pid, test_signo);
1070 switch (posix.errno(rc)) {
1071 .SUCCESS => {
1072 // See that the signal is blocked, then unblocked
1073 try testing.expectEqual(0, S.handler_called_count);
1074 posix.sigprocmask(posix.SIG.UNBLOCK, &block_one, null);
1075 try testing.expectEqual(1, S.handler_called_count);
1076 },
1077 .INVAL => {
1078 // Signal won't get delviered. Just clean up.
1079 posix.sigprocmask(posix.SIG.UNBLOCK, &block_one, null);
1080 try testing.expectEqual(0, S.handler_called_count);
1081 },
1082 else => |errno| return posix.unexpectedErrno(errno),
1083 }
1084
1085 // Restore original handler
1086 posix.sigaction(test_signo, &old_sa, null);
1087 }
1088}
1089
1090706test "dup & dup2" {
1091707 switch (native_os) {
1092708 .linux, .solaris, .illumos => {},
test/standalone/build.zig.zon+3
......@@ -214,6 +214,9 @@
214214 .tsan = .{
215215 .path = "tsan",
216216 },
217 .posix = .{
218 .path = "posix",
219 },
217220 },
218221 .paths = .{
219222 "build.zig",
test/standalone/posix/README.md created+8
......@@ -0,0 +1,8 @@
1## Zig standalone POSIX tests
2
3This directory is just for std.posix-related test cases that depend on
4process-wide state like the current-working directory, signal handlers,
5fork, the main thread, environment variables, etc. Most tests (e.g,
6around file descriptors, etc) are with the unit tests in
7`lib/std/posix/test.zig`. New tests should be with the unit tests, unless
8there is a specific reason they cannot.
test/standalone/posix/build.zig created+80
......@@ -0,0 +1,80 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4const Case = struct {
5 src_path: []const u8,
6 set_env_vars: bool = false,
7};
8
9const cases = [_]Case{
10 .{
11 .src_path = "cwd.zig",
12 },
13 .{
14 .src_path = "getenv.zig",
15 .set_env_vars = true,
16 },
17 .{
18 .src_path = "sigaction.zig",
19 },
20 .{
21 .src_path = "relpaths.zig",
22 },
23};
24
25pub fn build(b: *std.Build) void {
26 const test_step = b.step("test", "Run POSIX standalone test cases");
27 b.default_step = test_step;
28
29 const optimize = b.standardOptimizeOption(.{});
30
31 const default_target = b.resolveTargetQuery(.{});
32
33 // Run each test case built against libc-less, glibc, and musl.
34 for (cases) |case| {
35 const run_def = run_exe(b, optimize, &case, default_target, false);
36 test_step.dependOn(&run_def.step);
37
38 if (default_target.result.os.tag == .linux) {
39 const gnu_target = b.resolveTargetQuery(.{ .abi = .gnu });
40 const musl_target = b.resolveTargetQuery(.{ .abi = .musl });
41
42 const run_gnu = run_exe(b, optimize, &case, gnu_target, true);
43 const run_musl = run_exe(b, optimize, &case, musl_target, true);
44
45 test_step.dependOn(&run_gnu.step);
46 test_step.dependOn(&run_musl.step);
47 } else {
48 const run_libc = run_exe(b, optimize, &case, default_target, true);
49 test_step.dependOn(&run_libc.step);
50 }
51 }
52}
53
54fn run_exe(b: *std.Build, optimize: std.builtin.OptimizeMode, case: *const Case, target: std.Build.ResolvedTarget, link_libc: bool) *std.Build.Step.Run {
55 const exe_name = b.fmt("test-posix-{s}{s}{s}", .{
56 std.fs.path.stem(case.src_path),
57 if (link_libc) "-libc" else "",
58 if (link_libc and target.result.isGnuLibC()) "-gnu" else if (link_libc and target.result.isMuslLibC()) "-musl" else "",
59 });
60
61 const exe = b.addExecutable(.{
62 .name = exe_name,
63 .root_module = b.createModule(.{
64 .root_source_file = b.path(case.src_path),
65 .link_libc = link_libc,
66 .optimize = optimize,
67 .target = target,
68 }),
69 });
70
71 const run_cmd = b.addRunArtifact(exe);
72
73 if (case.set_env_vars) {
74 run_cmd.setEnvironmentVariable("ZIG_TEST_POSIX_1EQ", "test=variable");
75 run_cmd.setEnvironmentVariable("ZIG_TEST_POSIX_3EQ", "=test=variable=");
76 run_cmd.setEnvironmentVariable("ZIG_TEST_POSIX_EMPTY", "");
77 }
78
79 return run_cmd;
80}
test/standalone/posix/cwd.zig created+75
......@@ -0,0 +1,75 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4const path_max = std.fs.max_path_bytes;
5
6pub fn main() !void {
7 if (builtin.target.os.tag == .wasi) {
8 // WASI doesn't support changing the working directory at all.
9 return;
10 }
11
12 var Allocator = std.heap.DebugAllocator(.{}){};
13 const a = Allocator.allocator();
14 defer std.debug.assert(Allocator.deinit() == .ok);
15
16 try test_chdir_self();
17 try test_chdir_absolute();
18 try test_chdir_relative(a);
19}
20
21// get current working directory and expect it to match given path
22fn expect_cwd(expected_cwd: []const u8) !void {
23 var cwd_buf: [path_max]u8 = undefined;
24 const actual_cwd = try std.posix.getcwd(cwd_buf[0..]);
25 try std.testing.expectEqualStrings(actual_cwd, expected_cwd);
26}
27
28fn test_chdir_self() !void {
29 var old_cwd_buf: [path_max]u8 = undefined;
30 const old_cwd = try std.posix.getcwd(old_cwd_buf[0..]);
31
32 // Try changing to the current directory
33 try std.posix.chdir(old_cwd);
34 try expect_cwd(old_cwd);
35}
36
37fn test_chdir_absolute() !void {
38 var old_cwd_buf: [path_max]u8 = undefined;
39 const old_cwd = try std.posix.getcwd(old_cwd_buf[0..]);
40
41 const parent = std.fs.path.dirname(old_cwd) orelse unreachable; // old_cwd should be absolute
42
43 // Try changing to the parent via a full path
44 try std.posix.chdir(parent);
45
46 try expect_cwd(parent);
47}
48
49fn test_chdir_relative(a: std.mem.Allocator) !void {
50 var tmp = std.testing.tmpDir(.{});
51 defer tmp.cleanup();
52
53 // Use the tmpDir parent_dir as the "base" for the test. Then cd into the child
54 try tmp.parent_dir.setAsCwd();
55
56 // Capture base working directory path, to build expected full path
57 var base_cwd_buf: [path_max]u8 = undefined;
58 const base_cwd = try std.posix.getcwd(base_cwd_buf[0..]);
59
60 const relative_dir_name = &tmp.sub_path;
61 const expected_path = try std.fs.path.resolve(a, &.{ base_cwd, relative_dir_name });
62 defer a.free(expected_path);
63
64 // change current working directory to new test directory
65 try std.posix.chdir(relative_dir_name);
66
67 var new_cwd_buf: [path_max]u8 = undefined;
68 const new_cwd = try std.posix.getcwd(new_cwd_buf[0..]);
69
70 // On Windows, fs.path.resolve returns an uppercase drive letter, but the drive letter returned by getcwd may be lowercase
71 const resolved_cwd = try std.fs.path.resolve(a, &.{new_cwd});
72 defer a.free(resolved_cwd);
73
74 try std.testing.expectEqualStrings(expected_path, resolved_cwd);
75}
test/standalone/posix/getenv.zig created+32
......@@ -0,0 +1,32 @@
1// test getting environment variables
2
3const std = @import("std");
4const builtin = @import("builtin");
5
6pub fn main() !void {
7 if (builtin.target.os.tag == .windows) {
8 return; // Windows env strings are WTF-16, so not supported by Zig's std.posix.getenv()
9 }
10
11 if (builtin.target.os.tag == .wasi and !builtin.link_libc) {
12 return; // std.posix.getenv is not supported on WASI due to the need of allocation
13 }
14
15 // Test some unset env vars:
16
17 try std.testing.expectEqual(std.posix.getenv(""), null);
18 try std.testing.expectEqual(std.posix.getenv("BOGUSDOESNOTEXISTENVVAR"), null);
19 try std.testing.expectEqual(std.posix.getenvZ("BOGUSDOESNOTEXISTENVVAR"), null);
20
21 if (builtin.link_libc) {
22 // Test if USER matches what C library sees
23 const expected = std.mem.span(std.c.getenv("USER") orelse "");
24 const actual = std.posix.getenv("USER") orelse "";
25 try std.testing.expectEqualStrings(expected, actual);
26 }
27
28 // env vars set by our build.zig run step:
29 try std.testing.expectEqualStrings("", std.posix.getenv("ZIG_TEST_POSIX_EMPTY") orelse "invalid");
30 try std.testing.expectEqualStrings("test=variable", std.posix.getenv("ZIG_TEST_POSIX_1EQ") orelse "invalid");
31 try std.testing.expectEqualStrings("=test=variable=", std.posix.getenv("ZIG_TEST_POSIX_3EQ") orelse "invalid");
32}
test/standalone/posix/relpaths.zig created+94
......@@ -0,0 +1,94 @@
1// Test relative paths through POSIX APIS. These tests have to change the cwd, so
2// they shouldn't be Zig unit tests.
3
4const std = @import("std");
5const builtin = @import("builtin");
6
7pub fn main() !void {
8 if (builtin.target.os.tag == .wasi) return; // Can link, but can't change into tmpDir
9
10 var Allocator = std.heap.DebugAllocator(.{}){};
11 const a = Allocator.allocator();
12 defer std.debug.assert(Allocator.deinit() == .ok);
13
14 var tmp = std.testing.tmpDir(.{});
15 defer tmp.cleanup();
16
17 // Want to test relative paths, so cd into the tmpdir for these tests
18 try tmp.dir.setAsCwd();
19
20 try test_symlink(a, tmp);
21 try test_link(tmp);
22}
23
24fn test_symlink(a: std.mem.Allocator, tmp: std.testing.TmpDir) !void {
25 const target_name = "symlink-target";
26 const symlink_name = "symlinker";
27
28 // Create the target file
29 try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "nonsense" });
30
31 if (builtin.target.os.tag == .windows) {
32 const wtarget_name = try std.unicode.wtf8ToWtf16LeAllocZ(a, target_name);
33 const wsymlink_name = try std.unicode.wtf8ToWtf16LeAllocZ(a, symlink_name);
34 defer a.free(wtarget_name);
35 defer a.free(wsymlink_name);
36
37 std.os.windows.CreateSymbolicLink(tmp.dir.fd, wsymlink_name, wtarget_name, false) catch |err| switch (err) {
38 // Symlink requires admin privileges on windows, so this test can legitimately fail.
39 error.AccessDenied => return,
40 else => return err,
41 };
42 } else {
43 try std.posix.symlink(target_name, symlink_name);
44 }
45
46 var buffer: [std.fs.max_path_bytes]u8 = undefined;
47 const given = try std.posix.readlink(symlink_name, buffer[0..]);
48 try std.testing.expectEqualStrings(target_name, given);
49}
50
51fn test_link(tmp: std.testing.TmpDir) !void {
52 switch (builtin.target.os.tag) {
53 .linux, .solaris, .illumos => {},
54 else => return,
55 }
56
57 if ((builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) and builtin.target.os.tag == .linux and !builtin.link_libc) {
58 return; // No `fstat()`.
59 }
60
61 if (builtin.cpu.arch.isMIPS64()) {
62 return; // `nstat.nlink` assertion is failing with LLVM 20+ for unclear reasons.
63 }
64
65 const target_name = "link-target";
66 const link_name = "newlink";
67
68 try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "example" });
69
70 // Test 1: create the relative link from inside tmp
71 try std.posix.link(target_name, link_name);
72
73 // Verify
74 const efd = try tmp.dir.openFile(target_name, .{});
75 defer efd.close();
76
77 const nfd = try tmp.dir.openFile(link_name, .{});
78 defer nfd.close();
79
80 {
81 const estat = try std.posix.fstat(efd.handle);
82 const nstat = try std.posix.fstat(nfd.handle);
83 try std.testing.expectEqual(estat.ino, nstat.ino);
84 try std.testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink);
85 }
86
87 // Test 2: Remove the link and see the stats update
88 try std.posix.unlink(link_name);
89
90 {
91 const estat = try std.posix.fstat(efd.handle);
92 try std.testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink);
93 }
94}
test/standalone/posix/sigaction.zig created+153
......@@ -0,0 +1,153 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4const native_os = builtin.target.os.tag;
5
6pub fn main() !void {
7 if (native_os == .wasi or native_os == .windows) {
8 return; // no sigaction
9 }
10
11 try test_sigaction();
12 try test_sigset_bits();
13}
14
15fn test_sigaction() !void {
16 if (native_os == .macos and builtin.target.cpu.arch == .x86_64) {
17 return; // https://github.com/ziglang/zig/issues/15381
18 }
19
20 const test_signo = std.posix.SIG.URG; // URG only because it is ignored by default in debuggers
21
22 const S = struct {
23 var handler_called_count: u32 = 0;
24
25 fn handler(sig: i32, info: *const std.posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
26 _ = ctx_ptr;
27 // Check that we received the correct signal.
28 const info_sig = switch (native_os) {
29 .netbsd => info.info.signo,
30 else => info.signo,
31 };
32 if (sig == test_signo and sig == info_sig) {
33 handler_called_count += 1;
34 }
35 }
36 };
37
38 var sa: std.posix.Sigaction = .{
39 .handler = .{ .sigaction = &S.handler },
40 .mask = std.posix.sigemptyset(),
41 .flags = std.posix.SA.SIGINFO | std.posix.SA.RESETHAND,
42 };
43
44 var old_sa: std.posix.Sigaction = undefined;
45
46 // Install the new signal handler.
47 std.posix.sigaction(test_signo, &sa, null);
48
49 // Check that we can read it back correctly.
50 std.posix.sigaction(test_signo, null, &old_sa);
51 try std.testing.expectEqual(&S.handler, old_sa.handler.sigaction.?);
52 try std.testing.expect((old_sa.flags & std.posix.SA.SIGINFO) != 0);
53
54 // Invoke the handler.
55 try std.posix.raise(test_signo);
56 try std.testing.expectEqual(1, S.handler_called_count);
57
58 // Check if passing RESETHAND correctly reset the handler to SIG_DFL
59 std.posix.sigaction(test_signo, null, &old_sa);
60 try std.testing.expectEqual(std.posix.SIG.DFL, old_sa.handler.handler);
61
62 // Reinstall the signal w/o RESETHAND and re-raise
63 sa.flags = std.posix.SA.SIGINFO;
64 std.posix.sigaction(test_signo, &sa, null);
65 try std.posix.raise(test_signo);
66 try std.testing.expectEqual(2, S.handler_called_count);
67
68 // Now set the signal to ignored
69 sa.handler = .{ .handler = std.posix.SIG.IGN };
70 sa.flags = 0;
71 std.posix.sigaction(test_signo, &sa, null);
72
73 // Re-raise to ensure handler is actually ignored
74 try std.posix.raise(test_signo);
75 try std.testing.expectEqual(2, S.handler_called_count);
76
77 // Ensure that ignored state is returned when querying
78 std.posix.sigaction(test_signo, null, &old_sa);
79 try std.testing.expectEqual(std.posix.SIG.IGN, old_sa.handler.handler);
80}
81
82fn test_sigset_bits() !void {
83 const NO_SIG: i32 = 0;
84
85 const S = struct {
86 var expected_sig: i32 = undefined;
87 var seen_sig: i32 = NO_SIG;
88
89 fn handler(sig: i32, info: *const std.posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
90 _ = ctx_ptr;
91
92 const info_sig = switch (native_os) {
93 .netbsd => info.info.signo,
94 else => info.signo,
95 };
96 if (seen_sig == NO_SIG and sig == expected_sig and sig == info_sig) {
97 seen_sig = sig;
98 }
99 }
100 };
101
102 // Assume this is a single-threaded process where the current thread has the
103 // 'pid' thread id. (The sigprocmask calls are thread-private state.)
104 const self_tid = std.posix.system.getpid();
105
106 // To check that sigset_t mapping matches kernel (think u32/u64 mismatches on
107 // big-endian), try sending a blocked signal to make sure the mask matches the
108 // signal. (Send URG and CHLD because they're ignored by default in the
109 // debugger, vs. USR1 or other named signals)
110 inline for ([_]i32{ std.posix.SIG.URG, std.posix.SIG.CHLD, 62, 94, 126 }) |test_signo| {
111 if (test_signo >= std.posix.NSIG) continue;
112
113 S.expected_sig = test_signo;
114 S.seen_sig = NO_SIG;
115
116 const sa: std.posix.Sigaction = .{
117 .handler = .{ .sigaction = &S.handler },
118 .mask = std.posix.sigemptyset(),
119 .flags = std.posix.SA.SIGINFO | std.posix.SA.RESETHAND,
120 };
121
122 var old_sa: std.posix.Sigaction = undefined;
123
124 // Install the new signal handler.
125 std.posix.sigaction(test_signo, &sa, &old_sa);
126
127 // block the signal and see that its delayed until unblocked
128 var block_one: std.posix.sigset_t = std.posix.sigemptyset();
129 std.posix.sigaddset(&block_one, test_signo);
130 std.posix.sigprocmask(std.posix.SIG.BLOCK, &block_one, null);
131
132 // qemu maps target signals to host signals 1-to-1, so targets
133 // with more signals than the host will fail to send the signal.
134 const rc = std.posix.system.kill(self_tid, test_signo);
135 switch (std.posix.errno(rc)) {
136 .SUCCESS => {
137 // See that the signal is blocked, then unblocked
138 try std.testing.expectEqual(NO_SIG, S.seen_sig);
139 std.posix.sigprocmask(std.posix.SIG.UNBLOCK, &block_one, null);
140 try std.testing.expectEqual(test_signo, S.seen_sig);
141 },
142 .INVAL => {
143 // Signal won't get delviered. Just clean up.
144 std.posix.sigprocmask(std.posix.SIG.UNBLOCK, &block_one, null);
145 try std.testing.expectEqual(NO_SIG, S.seen_sig);
146 },
147 else => |errno| return std.posix.unexpectedErrno(errno),
148 }
149
150 // Restore original handler
151 std.posix.sigaction(test_signo, &old_sa, null);
152 }
153}