mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 05:44:20 +00:00
Merge pull request #25197 from rootbeer/24380-flaky-sigset-test
Re-enable std.posix "sigset_t bits" test
This commit is contained in:
commit
220c679523
9 changed files with 485 additions and 387 deletions
|
|
@ -1637,3 +1637,40 @@ test detach {
|
|||
event.wait();
|
||||
try std.testing.expectEqual(value, 1);
|
||||
}
|
||||
|
||||
test "Thread.getCpuCount" {
|
||||
if (native_os == .wasi) return error.SkipZigTest;
|
||||
|
||||
const cpu_count = try Thread.getCpuCount();
|
||||
try std.testing.expect(cpu_count >= 1);
|
||||
}
|
||||
|
||||
fn testThreadIdFn(thread_id: *Thread.Id) void {
|
||||
thread_id.* = Thread.getCurrentId();
|
||||
}
|
||||
|
||||
test "Thread.getCurrentId" {
|
||||
if (builtin.single_threaded) return error.SkipZigTest;
|
||||
|
||||
var thread_current_id: Thread.Id = undefined;
|
||||
const thread = try Thread.spawn(.{}, testThreadIdFn, .{&thread_current_id});
|
||||
thread.join();
|
||||
try std.testing.expect(Thread.getCurrentId() != thread_current_id);
|
||||
}
|
||||
|
||||
test "thread local storage" {
|
||||
if (builtin.single_threaded) return error.SkipZigTest;
|
||||
|
||||
const thread1 = try Thread.spawn(.{}, testTls, .{});
|
||||
const thread2 = try Thread.spawn(.{}, testTls, .{});
|
||||
try testTls();
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
}
|
||||
|
||||
threadlocal var x: i32 = 1234;
|
||||
fn testTls() !void {
|
||||
if (x != 1234) return error.TlsBadStartValue;
|
||||
x += 1;
|
||||
if (x != 1235) return error.TlsBadEndValue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ const expectError = testing.expectError;
|
|||
const fs = std.fs;
|
||||
const mem = std.mem;
|
||||
const elf = std.elf;
|
||||
const Thread = std.Thread;
|
||||
const linux = std.os.linux;
|
||||
|
||||
const a = std.testing.allocator;
|
||||
|
|
@ -18,6 +17,9 @@ const AtomicOrder = std.builtin.AtomicOrder;
|
|||
const native_os = builtin.target.os.tag;
|
||||
const tmpDir = std.testing.tmpDir;
|
||||
|
||||
// NOTE: several additional tests are in test/standalone/posix/. Any tests that mutate
|
||||
// process-wide POSIX state (cwd, signals, etc) cannot be Zig unit tests and should be over there.
|
||||
|
||||
// https://github.com/ziglang/zig/issues/20288
|
||||
test "WTF-8 to WTF-16 conversion buffer overflows" {
|
||||
if (native_os != .windows) return error.SkipZigTest;
|
||||
|
|
@ -40,68 +42,6 @@ test "check WASI CWD" {
|
|||
}
|
||||
}
|
||||
|
||||
test "chdir absolute parent" {
|
||||
if (native_os == .wasi) return error.SkipZigTest;
|
||||
|
||||
// Restore default CWD at end of test.
|
||||
const orig_cwd = try fs.cwd().openDir(".", .{});
|
||||
defer orig_cwd.setAsCwd() catch unreachable;
|
||||
|
||||
// Get current working directory path
|
||||
var old_cwd_buf: [fs.max_path_bytes]u8 = undefined;
|
||||
const old_cwd = try posix.getcwd(old_cwd_buf[0..]);
|
||||
|
||||
{
|
||||
// Firstly, changing to itself should have no effect
|
||||
try posix.chdir(old_cwd);
|
||||
var new_cwd_buf: [fs.max_path_bytes]u8 = undefined;
|
||||
const new_cwd = try posix.getcwd(new_cwd_buf[0..]);
|
||||
try expect(mem.eql(u8, old_cwd, new_cwd));
|
||||
}
|
||||
|
||||
// Next, change current working directory to one level above
|
||||
const parent = fs.path.dirname(old_cwd) orelse unreachable; // old_cwd should be absolute
|
||||
try posix.chdir(parent);
|
||||
|
||||
var new_cwd_buf: [fs.max_path_bytes]u8 = undefined;
|
||||
const new_cwd = try posix.getcwd(new_cwd_buf[0..]);
|
||||
try expect(mem.eql(u8, parent, new_cwd));
|
||||
}
|
||||
|
||||
test "chdir relative" {
|
||||
if (native_os == .wasi) return error.SkipZigTest;
|
||||
|
||||
var tmp = tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
// Restore default CWD at end of test.
|
||||
const orig_cwd = try fs.cwd().openDir(".", .{});
|
||||
defer orig_cwd.setAsCwd() catch unreachable;
|
||||
|
||||
// Use the tmpDir parent_dir as the "base" for the test. Then cd into the child
|
||||
try tmp.parent_dir.setAsCwd();
|
||||
|
||||
// Capture base working directory path, to build expected full path
|
||||
var base_cwd_buf: [fs.max_path_bytes]u8 = undefined;
|
||||
const base_cwd = try posix.getcwd(base_cwd_buf[0..]);
|
||||
|
||||
const dir_name = &tmp.sub_path;
|
||||
const expected_path = try fs.path.resolve(a, &.{ base_cwd, dir_name });
|
||||
defer a.free(expected_path);
|
||||
|
||||
// change current working directory to new directory
|
||||
try posix.chdir(dir_name);
|
||||
|
||||
var new_cwd_buf: [fs.max_path_bytes]u8 = undefined;
|
||||
const new_cwd = try posix.getcwd(new_cwd_buf[0..]);
|
||||
|
||||
// On Windows, fs.path.resolve returns an uppercase drive letter, but the drive letter returned by getcwd may be lowercase
|
||||
const resolved_cwd = try fs.path.resolve(a, &.{new_cwd});
|
||||
defer a.free(resolved_cwd);
|
||||
|
||||
try expect(mem.eql(u8, expected_path, resolved_cwd));
|
||||
}
|
||||
|
||||
test "open smoke test" {
|
||||
if (native_os == .wasi) return error.SkipZigTest;
|
||||
if (native_os == .windows) return error.SkipZigTest;
|
||||
|
|
@ -227,45 +167,6 @@ test "openat smoke test" {
|
|||
}
|
||||
}
|
||||
|
||||
test "symlink with relative paths" {
|
||||
if (native_os == .wasi) return error.SkipZigTest; // Can symlink, but can't change into tmpDir
|
||||
|
||||
var tmp = tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
const target_name = "symlink-target";
|
||||
const symlink_name = "symlinker";
|
||||
|
||||
// Restore default CWD at end of test.
|
||||
const orig_cwd = try fs.cwd().openDir(".", .{});
|
||||
defer orig_cwd.setAsCwd() catch unreachable;
|
||||
|
||||
// Create the target file
|
||||
try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "nonsense" });
|
||||
|
||||
// Want to test relative paths, so cd into the tmpdir for this test
|
||||
try tmp.dir.setAsCwd();
|
||||
|
||||
if (native_os == .windows) {
|
||||
const wtarget_name = try std.unicode.wtf8ToWtf16LeAllocZ(a, target_name);
|
||||
const wsymlink_name = try std.unicode.wtf8ToWtf16LeAllocZ(a, symlink_name);
|
||||
defer a.free(wtarget_name);
|
||||
defer a.free(wsymlink_name);
|
||||
|
||||
std.os.windows.CreateSymbolicLink(tmp.dir.fd, wsymlink_name, wtarget_name, false) catch |err| switch (err) {
|
||||
// Symlink requires admin privileges on windows, so this test can legitimately fail.
|
||||
error.AccessDenied => return error.SkipZigTest,
|
||||
else => return err,
|
||||
};
|
||||
} else {
|
||||
try posix.symlink(target_name, symlink_name);
|
||||
}
|
||||
|
||||
var buffer: [fs.max_path_bytes]u8 = undefined;
|
||||
const given = try posix.readlink(symlink_name, buffer[0..]);
|
||||
try expect(mem.eql(u8, target_name, given));
|
||||
}
|
||||
|
||||
test "readlink on Windows" {
|
||||
if (native_os != .windows) return error.SkipZigTest;
|
||||
|
||||
|
|
@ -280,55 +181,6 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {
|
|||
try expect(mem.eql(u8, target_path, given));
|
||||
}
|
||||
|
||||
test "link with relative paths" {
|
||||
if (native_os == .wasi) return error.SkipZigTest; // Can link, but can't change into tmpDir
|
||||
if ((builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) and builtin.os.tag == .linux and !builtin.link_libc) return error.SkipZigTest; // No `fstat()`.
|
||||
if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest; // `nstat.nlink` assertion is failing with LLVM 20+ for unclear reasons.
|
||||
|
||||
switch (native_os) {
|
||||
.wasi, .linux, .solaris, .illumos => {},
|
||||
else => return error.SkipZigTest,
|
||||
}
|
||||
|
||||
var tmp = tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
// Restore default CWD at end of test.
|
||||
const orig_cwd = try fs.cwd().openDir(".", .{});
|
||||
defer orig_cwd.setAsCwd() catch unreachable;
|
||||
|
||||
const target_name = "link-target";
|
||||
const link_name = "newlink";
|
||||
|
||||
try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "example" });
|
||||
|
||||
// Test 1: create the relative link from inside tmp
|
||||
try tmp.dir.setAsCwd();
|
||||
try posix.link(target_name, link_name);
|
||||
|
||||
// Verify
|
||||
const efd = try tmp.dir.openFile(target_name, .{});
|
||||
defer efd.close();
|
||||
|
||||
const nfd = try tmp.dir.openFile(link_name, .{});
|
||||
defer nfd.close();
|
||||
|
||||
{
|
||||
const estat = try posix.fstat(efd.handle);
|
||||
const nstat = try posix.fstat(nfd.handle);
|
||||
try testing.expectEqual(estat.ino, nstat.ino);
|
||||
try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink);
|
||||
}
|
||||
|
||||
// Test 2: Remove the link and see the stats update
|
||||
try posix.unlink(link_name);
|
||||
|
||||
{
|
||||
const estat = try posix.fstat(efd.handle);
|
||||
try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink);
|
||||
}
|
||||
}
|
||||
|
||||
test "linkat with different directories" {
|
||||
if ((builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) and builtin.os.tag == .linux and !builtin.link_libc) return error.SkipZigTest; // No `fstatat()`.
|
||||
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" {
|
|||
try expect(mem.eql(u8, "file.txt", read_link));
|
||||
}
|
||||
|
||||
fn testThreadIdFn(thread_id: *Thread.Id) void {
|
||||
thread_id.* = Thread.getCurrentId();
|
||||
}
|
||||
|
||||
test "Thread.getCurrentId" {
|
||||
if (builtin.single_threaded) return error.SkipZigTest;
|
||||
|
||||
var thread_current_id: Thread.Id = undefined;
|
||||
const thread = try Thread.spawn(.{}, testThreadIdFn, .{&thread_current_id});
|
||||
thread.join();
|
||||
try expect(Thread.getCurrentId() != thread_current_id);
|
||||
}
|
||||
|
||||
test "spawn threads" {
|
||||
if (builtin.single_threaded) return error.SkipZigTest;
|
||||
|
||||
var shared_ctx: i32 = 1;
|
||||
|
||||
const thread1 = try Thread.spawn(.{}, start1, .{});
|
||||
const thread2 = try Thread.spawn(.{}, start2, .{&shared_ctx});
|
||||
const thread3 = try Thread.spawn(.{}, start2, .{&shared_ctx});
|
||||
const thread4 = try Thread.spawn(.{}, start2, .{&shared_ctx});
|
||||
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
thread3.join();
|
||||
thread4.join();
|
||||
|
||||
try expect(shared_ctx == 4);
|
||||
}
|
||||
|
||||
fn start1() u8 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn start2(ctx: *i32) u8 {
|
||||
_ = @atomicRmw(i32, ctx, AtomicRmwOp.Add, 1, AtomicOrder.seq_cst);
|
||||
return 0;
|
||||
}
|
||||
|
||||
test "cpu count" {
|
||||
if (native_os == .wasi) return error.SkipZigTest;
|
||||
|
||||
const cpu_count = try Thread.getCpuCount();
|
||||
try expect(cpu_count >= 1);
|
||||
}
|
||||
|
||||
test "thread local storage" {
|
||||
if (builtin.single_threaded) return error.SkipZigTest;
|
||||
|
||||
const thread1 = try Thread.spawn(.{}, testTls, .{});
|
||||
const thread2 = try Thread.spawn(.{}, testTls, .{});
|
||||
try testTls();
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
}
|
||||
|
||||
threadlocal var x: i32 = 1234;
|
||||
fn testTls() !void {
|
||||
if (x != 1234) return error.TlsBadStartValue;
|
||||
x += 1;
|
||||
if (x != 1235) return error.TlsBadEndValue;
|
||||
}
|
||||
|
||||
test "getrandom" {
|
||||
var buf_a: [50]u8 = undefined;
|
||||
var buf_b: [50]u8 = undefined;
|
||||
|
|
@ -520,12 +308,6 @@ test "getrandom" {
|
|||
try expect(!mem.eql(u8, &buf_a, &buf_b));
|
||||
}
|
||||
|
||||
test "getcwd" {
|
||||
// at least call it so it gets compiled
|
||||
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
_ = posix.getcwd(&buf) catch undefined;
|
||||
}
|
||||
|
||||
test "getuid" {
|
||||
if (native_os == .windows or native_os == .wasi) return error.SkipZigTest;
|
||||
_ = posix.getuid();
|
||||
|
|
@ -737,24 +519,6 @@ test "mmap" {
|
|||
}
|
||||
}
|
||||
|
||||
test "getenv" {
|
||||
if (native_os == .wasi and !builtin.link_libc) {
|
||||
// std.posix.getenv is not supported on WASI due to the need of allocation
|
||||
return error.SkipZigTest;
|
||||
}
|
||||
|
||||
if (native_os == .windows) {
|
||||
try expect(std.process.getenvW(&[_:0]u16{ 'B', 'O', 'G', 'U', 'S', 0x11, 0x22, 0x33, 0x44, 0x55 }) == null);
|
||||
} else {
|
||||
try expect(posix.getenv("") == null);
|
||||
try expect(posix.getenv("BOGUSDOESNOTEXISTENVVAR") == null);
|
||||
if (builtin.link_libc) {
|
||||
try testing.expectEqualStrings(posix.getenv("USER") orelse "", mem.span(std.c.getenv("USER") orelse ""));
|
||||
}
|
||||
try expect(posix.getenvZ("BOGUSDOESNOTEXISTENVVAR") == null);
|
||||
}
|
||||
}
|
||||
|
||||
test "fcntl" {
|
||||
if (native_os == .windows or native_os == .wasi)
|
||||
return error.SkipZigTest;
|
||||
|
|
@ -939,154 +703,6 @@ test "sigset add/del" {
|
|||
}
|
||||
}
|
||||
|
||||
test "sigaction" {
|
||||
if (native_os == .wasi or native_os == .windows)
|
||||
return error.SkipZigTest;
|
||||
|
||||
// https://github.com/ziglang/zig/issues/15381
|
||||
if (native_os == .macos and builtin.target.cpu.arch == .x86_64) {
|
||||
return error.SkipZigTest;
|
||||
}
|
||||
|
||||
const test_signo = posix.SIG.URG; // URG only because it is ignored by default in debuggers
|
||||
|
||||
const S = struct {
|
||||
var handler_called_count: u32 = 0;
|
||||
|
||||
fn handler(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
|
||||
_ = ctx_ptr;
|
||||
// Check that we received the correct signal.
|
||||
const info_sig = switch (native_os) {
|
||||
.netbsd => info.info.signo,
|
||||
else => info.signo,
|
||||
};
|
||||
if (sig == test_signo and sig == info_sig) {
|
||||
handler_called_count += 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var sa: posix.Sigaction = .{
|
||||
.handler = .{ .sigaction = &S.handler },
|
||||
.mask = posix.sigemptyset(),
|
||||
.flags = posix.SA.SIGINFO | posix.SA.RESETHAND,
|
||||
};
|
||||
|
||||
var old_sa: posix.Sigaction = undefined;
|
||||
|
||||
// Install the new signal handler.
|
||||
posix.sigaction(test_signo, &sa, null);
|
||||
|
||||
// Check that we can read it back correctly.
|
||||
posix.sigaction(test_signo, null, &old_sa);
|
||||
try testing.expectEqual(&S.handler, old_sa.handler.sigaction.?);
|
||||
try testing.expect((old_sa.flags & posix.SA.SIGINFO) != 0);
|
||||
|
||||
// Invoke the handler.
|
||||
try posix.raise(test_signo);
|
||||
try testing.expectEqual(1, S.handler_called_count);
|
||||
|
||||
// Check if passing RESETHAND correctly reset the handler to SIG_DFL
|
||||
posix.sigaction(test_signo, null, &old_sa);
|
||||
try testing.expectEqual(posix.SIG.DFL, old_sa.handler.handler);
|
||||
|
||||
// Reinstall the signal w/o RESETHAND and re-raise
|
||||
sa.flags = posix.SA.SIGINFO;
|
||||
posix.sigaction(test_signo, &sa, null);
|
||||
try posix.raise(test_signo);
|
||||
try testing.expectEqual(2, S.handler_called_count);
|
||||
|
||||
// Now set the signal to ignored
|
||||
sa.handler = .{ .handler = posix.SIG.IGN };
|
||||
sa.flags = 0;
|
||||
posix.sigaction(test_signo, &sa, null);
|
||||
|
||||
// Re-raise to ensure handler is actually ignored
|
||||
try posix.raise(test_signo);
|
||||
try testing.expectEqual(2, S.handler_called_count);
|
||||
|
||||
// Ensure that ignored state is returned when querying
|
||||
posix.sigaction(test_signo, null, &old_sa);
|
||||
try testing.expectEqual(posix.SIG.IGN, old_sa.handler.handler);
|
||||
}
|
||||
|
||||
test "sigset_t bits" {
|
||||
if (native_os == .wasi or native_os == .windows)
|
||||
return error.SkipZigTest;
|
||||
|
||||
if (true) {
|
||||
// https://github.com/ziglang/zig/issues/24380
|
||||
return error.SkipZigTest;
|
||||
}
|
||||
|
||||
const S = struct {
|
||||
var expected_sig: i32 = undefined;
|
||||
var handler_called_count: u32 = 0;
|
||||
|
||||
fn handler(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
|
||||
_ = ctx_ptr;
|
||||
|
||||
const info_sig = switch (native_os) {
|
||||
.netbsd => info.info.signo,
|
||||
else => info.signo,
|
||||
};
|
||||
if (sig == expected_sig and sig == info_sig) {
|
||||
handler_called_count += 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const self_pid = posix.system.getpid();
|
||||
|
||||
// To check that sigset_t mapping matches kernel (think u32/u64 mismatches on
|
||||
// big-endian), try sending a blocked signal to make sure the mask matches the
|
||||
// signal. (Send URG and CHLD because they're ignored by default in the
|
||||
// debugger, vs. USR1 or other named signals)
|
||||
inline for ([_]usize{ posix.SIG.URG, posix.SIG.CHLD, 62, 94, 126 }) |test_signo| {
|
||||
if (test_signo >= posix.NSIG) continue;
|
||||
|
||||
S.expected_sig = test_signo;
|
||||
S.handler_called_count = 0;
|
||||
|
||||
const sa: posix.Sigaction = .{
|
||||
.handler = .{ .sigaction = &S.handler },
|
||||
.mask = posix.sigemptyset(),
|
||||
.flags = posix.SA.SIGINFO | posix.SA.RESETHAND,
|
||||
};
|
||||
|
||||
var old_sa: posix.Sigaction = undefined;
|
||||
|
||||
// Install the new signal handler.
|
||||
posix.sigaction(test_signo, &sa, &old_sa);
|
||||
|
||||
// block the signal and see that its delayed until unblocked
|
||||
var block_one: posix.sigset_t = posix.sigemptyset();
|
||||
posix.sigaddset(&block_one, test_signo);
|
||||
posix.sigprocmask(posix.SIG.BLOCK, &block_one, null);
|
||||
|
||||
// qemu maps target signals to host signals 1-to-1, so targets
|
||||
// with more signals than the host will fail to send the signal.
|
||||
const rc = posix.system.kill(self_pid, test_signo);
|
||||
switch (posix.errno(rc)) {
|
||||
.SUCCESS => {
|
||||
// See that the signal is blocked, then unblocked
|
||||
try testing.expectEqual(0, S.handler_called_count);
|
||||
posix.sigprocmask(posix.SIG.UNBLOCK, &block_one, null);
|
||||
try testing.expectEqual(1, S.handler_called_count);
|
||||
},
|
||||
.INVAL => {
|
||||
// Signal won't get delviered. Just clean up.
|
||||
posix.sigprocmask(posix.SIG.UNBLOCK, &block_one, null);
|
||||
try testing.expectEqual(0, S.handler_called_count);
|
||||
},
|
||||
else => |errno| return posix.unexpectedErrno(errno),
|
||||
}
|
||||
|
||||
// Restore original handler
|
||||
posix.sigaction(test_signo, &old_sa, null);
|
||||
}
|
||||
}
|
||||
|
||||
test "dup & dup2" {
|
||||
switch (native_os) {
|
||||
.linux, .solaris, .illumos => {},
|
||||
|
|
|
|||
|
|
@ -214,6 +214,9 @@
|
|||
.tsan = .{
|
||||
.path = "tsan",
|
||||
},
|
||||
.posix = .{
|
||||
.path = "posix",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
|
|
|
|||
8
test/standalone/posix/README.md
Normal file
8
test/standalone/posix/README.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
## Zig standalone POSIX tests
|
||||
|
||||
This directory is just for std.posix-related test cases that depend on
|
||||
process-wide state like the current-working directory, signal handlers,
|
||||
fork, the main thread, environment variables, etc. Most tests (e.g,
|
||||
around file descriptors, etc) are with the unit tests in
|
||||
`lib/std/posix/test.zig`. New tests should be with the unit tests, unless
|
||||
there is a specific reason they cannot.
|
||||
80
test/standalone/posix/build.zig
Normal file
80
test/standalone/posix/build.zig
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const Case = struct {
|
||||
src_path: []const u8,
|
||||
set_env_vars: bool = false,
|
||||
};
|
||||
|
||||
const cases = [_]Case{
|
||||
.{
|
||||
.src_path = "cwd.zig",
|
||||
},
|
||||
.{
|
||||
.src_path = "getenv.zig",
|
||||
.set_env_vars = true,
|
||||
},
|
||||
.{
|
||||
.src_path = "sigaction.zig",
|
||||
},
|
||||
.{
|
||||
.src_path = "relpaths.zig",
|
||||
},
|
||||
};
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const test_step = b.step("test", "Run POSIX standalone test cases");
|
||||
b.default_step = test_step;
|
||||
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const default_target = b.resolveTargetQuery(.{});
|
||||
|
||||
// Run each test case built against libc-less, glibc, and musl.
|
||||
for (cases) |case| {
|
||||
const run_def = run_exe(b, optimize, &case, default_target, false);
|
||||
test_step.dependOn(&run_def.step);
|
||||
|
||||
if (default_target.result.os.tag == .linux) {
|
||||
const gnu_target = b.resolveTargetQuery(.{ .abi = .gnu });
|
||||
const musl_target = b.resolveTargetQuery(.{ .abi = .musl });
|
||||
|
||||
const run_gnu = run_exe(b, optimize, &case, gnu_target, true);
|
||||
const run_musl = run_exe(b, optimize, &case, musl_target, true);
|
||||
|
||||
test_step.dependOn(&run_gnu.step);
|
||||
test_step.dependOn(&run_musl.step);
|
||||
} else {
|
||||
const run_libc = run_exe(b, optimize, &case, default_target, true);
|
||||
test_step.dependOn(&run_libc.step);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_exe(b: *std.Build, optimize: std.builtin.OptimizeMode, case: *const Case, target: std.Build.ResolvedTarget, link_libc: bool) *std.Build.Step.Run {
|
||||
const exe_name = b.fmt("test-posix-{s}{s}{s}", .{
|
||||
std.fs.path.stem(case.src_path),
|
||||
if (link_libc) "-libc" else "",
|
||||
if (link_libc and target.result.isGnuLibC()) "-gnu" else if (link_libc and target.result.isMuslLibC()) "-musl" else "",
|
||||
});
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = exe_name,
|
||||
.root_module = b.createModule(.{
|
||||
.root_source_file = b.path(case.src_path),
|
||||
.link_libc = link_libc,
|
||||
.optimize = optimize,
|
||||
.target = target,
|
||||
}),
|
||||
});
|
||||
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
|
||||
if (case.set_env_vars) {
|
||||
run_cmd.setEnvironmentVariable("ZIG_TEST_POSIX_1EQ", "test=variable");
|
||||
run_cmd.setEnvironmentVariable("ZIG_TEST_POSIX_3EQ", "=test=variable=");
|
||||
run_cmd.setEnvironmentVariable("ZIG_TEST_POSIX_EMPTY", "");
|
||||
}
|
||||
|
||||
return run_cmd;
|
||||
}
|
||||
75
test/standalone/posix/cwd.zig
Normal file
75
test/standalone/posix/cwd.zig
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const path_max = std.fs.max_path_bytes;
|
||||
|
||||
pub fn main() !void {
|
||||
if (builtin.target.os.tag == .wasi) {
|
||||
// WASI doesn't support changing the working directory at all.
|
||||
return;
|
||||
}
|
||||
|
||||
var Allocator = std.heap.DebugAllocator(.{}){};
|
||||
const a = Allocator.allocator();
|
||||
defer std.debug.assert(Allocator.deinit() == .ok);
|
||||
|
||||
try test_chdir_self();
|
||||
try test_chdir_absolute();
|
||||
try test_chdir_relative(a);
|
||||
}
|
||||
|
||||
// get current working directory and expect it to match given path
|
||||
fn expect_cwd(expected_cwd: []const u8) !void {
|
||||
var cwd_buf: [path_max]u8 = undefined;
|
||||
const actual_cwd = try std.posix.getcwd(cwd_buf[0..]);
|
||||
try std.testing.expectEqualStrings(actual_cwd, expected_cwd);
|
||||
}
|
||||
|
||||
fn test_chdir_self() !void {
|
||||
var old_cwd_buf: [path_max]u8 = undefined;
|
||||
const old_cwd = try std.posix.getcwd(old_cwd_buf[0..]);
|
||||
|
||||
// Try changing to the current directory
|
||||
try std.posix.chdir(old_cwd);
|
||||
try expect_cwd(old_cwd);
|
||||
}
|
||||
|
||||
fn test_chdir_absolute() !void {
|
||||
var old_cwd_buf: [path_max]u8 = undefined;
|
||||
const old_cwd = try std.posix.getcwd(old_cwd_buf[0..]);
|
||||
|
||||
const parent = std.fs.path.dirname(old_cwd) orelse unreachable; // old_cwd should be absolute
|
||||
|
||||
// Try changing to the parent via a full path
|
||||
try std.posix.chdir(parent);
|
||||
|
||||
try expect_cwd(parent);
|
||||
}
|
||||
|
||||
fn test_chdir_relative(a: std.mem.Allocator) !void {
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
// Use the tmpDir parent_dir as the "base" for the test. Then cd into the child
|
||||
try tmp.parent_dir.setAsCwd();
|
||||
|
||||
// Capture base working directory path, to build expected full path
|
||||
var base_cwd_buf: [path_max]u8 = undefined;
|
||||
const base_cwd = try std.posix.getcwd(base_cwd_buf[0..]);
|
||||
|
||||
const relative_dir_name = &tmp.sub_path;
|
||||
const expected_path = try std.fs.path.resolve(a, &.{ base_cwd, relative_dir_name });
|
||||
defer a.free(expected_path);
|
||||
|
||||
// change current working directory to new test directory
|
||||
try std.posix.chdir(relative_dir_name);
|
||||
|
||||
var new_cwd_buf: [path_max]u8 = undefined;
|
||||
const new_cwd = try std.posix.getcwd(new_cwd_buf[0..]);
|
||||
|
||||
// On Windows, fs.path.resolve returns an uppercase drive letter, but the drive letter returned by getcwd may be lowercase
|
||||
const resolved_cwd = try std.fs.path.resolve(a, &.{new_cwd});
|
||||
defer a.free(resolved_cwd);
|
||||
|
||||
try std.testing.expectEqualStrings(expected_path, resolved_cwd);
|
||||
}
|
||||
32
test/standalone/posix/getenv.zig
Normal file
32
test/standalone/posix/getenv.zig
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// test getting environment variables
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
pub fn main() !void {
|
||||
if (builtin.target.os.tag == .windows) {
|
||||
return; // Windows env strings are WTF-16, so not supported by Zig's std.posix.getenv()
|
||||
}
|
||||
|
||||
if (builtin.target.os.tag == .wasi and !builtin.link_libc) {
|
||||
return; // std.posix.getenv is not supported on WASI due to the need of allocation
|
||||
}
|
||||
|
||||
// Test some unset env vars:
|
||||
|
||||
try std.testing.expectEqual(std.posix.getenv(""), null);
|
||||
try std.testing.expectEqual(std.posix.getenv("BOGUSDOESNOTEXISTENVVAR"), null);
|
||||
try std.testing.expectEqual(std.posix.getenvZ("BOGUSDOESNOTEXISTENVVAR"), null);
|
||||
|
||||
if (builtin.link_libc) {
|
||||
// Test if USER matches what C library sees
|
||||
const expected = std.mem.span(std.c.getenv("USER") orelse "");
|
||||
const actual = std.posix.getenv("USER") orelse "";
|
||||
try std.testing.expectEqualStrings(expected, actual);
|
||||
}
|
||||
|
||||
// env vars set by our build.zig run step:
|
||||
try std.testing.expectEqualStrings("", std.posix.getenv("ZIG_TEST_POSIX_EMPTY") orelse "invalid");
|
||||
try std.testing.expectEqualStrings("test=variable", std.posix.getenv("ZIG_TEST_POSIX_1EQ") orelse "invalid");
|
||||
try std.testing.expectEqualStrings("=test=variable=", std.posix.getenv("ZIG_TEST_POSIX_3EQ") orelse "invalid");
|
||||
}
|
||||
94
test/standalone/posix/relpaths.zig
Normal file
94
test/standalone/posix/relpaths.zig
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
// Test relative paths through POSIX APIS. These tests have to change the cwd, so
|
||||
// they shouldn't be Zig unit tests.
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
pub fn main() !void {
|
||||
if (builtin.target.os.tag == .wasi) return; // Can link, but can't change into tmpDir
|
||||
|
||||
var Allocator = std.heap.DebugAllocator(.{}){};
|
||||
const a = Allocator.allocator();
|
||||
defer std.debug.assert(Allocator.deinit() == .ok);
|
||||
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
// Want to test relative paths, so cd into the tmpdir for these tests
|
||||
try tmp.dir.setAsCwd();
|
||||
|
||||
try test_symlink(a, tmp);
|
||||
try test_link(tmp);
|
||||
}
|
||||
|
||||
fn test_symlink(a: std.mem.Allocator, tmp: std.testing.TmpDir) !void {
|
||||
const target_name = "symlink-target";
|
||||
const symlink_name = "symlinker";
|
||||
|
||||
// Create the target file
|
||||
try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "nonsense" });
|
||||
|
||||
if (builtin.target.os.tag == .windows) {
|
||||
const wtarget_name = try std.unicode.wtf8ToWtf16LeAllocZ(a, target_name);
|
||||
const wsymlink_name = try std.unicode.wtf8ToWtf16LeAllocZ(a, symlink_name);
|
||||
defer a.free(wtarget_name);
|
||||
defer a.free(wsymlink_name);
|
||||
|
||||
std.os.windows.CreateSymbolicLink(tmp.dir.fd, wsymlink_name, wtarget_name, false) catch |err| switch (err) {
|
||||
// Symlink requires admin privileges on windows, so this test can legitimately fail.
|
||||
error.AccessDenied => return,
|
||||
else => return err,
|
||||
};
|
||||
} else {
|
||||
try std.posix.symlink(target_name, symlink_name);
|
||||
}
|
||||
|
||||
var buffer: [std.fs.max_path_bytes]u8 = undefined;
|
||||
const given = try std.posix.readlink(symlink_name, buffer[0..]);
|
||||
try std.testing.expectEqualStrings(target_name, given);
|
||||
}
|
||||
|
||||
fn test_link(tmp: std.testing.TmpDir) !void {
|
||||
switch (builtin.target.os.tag) {
|
||||
.linux, .solaris, .illumos => {},
|
||||
else => return,
|
||||
}
|
||||
|
||||
if ((builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) and builtin.target.os.tag == .linux and !builtin.link_libc) {
|
||||
return; // No `fstat()`.
|
||||
}
|
||||
|
||||
if (builtin.cpu.arch.isMIPS64()) {
|
||||
return; // `nstat.nlink` assertion is failing with LLVM 20+ for unclear reasons.
|
||||
}
|
||||
|
||||
const target_name = "link-target";
|
||||
const link_name = "newlink";
|
||||
|
||||
try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "example" });
|
||||
|
||||
// Test 1: create the relative link from inside tmp
|
||||
try std.posix.link(target_name, link_name);
|
||||
|
||||
// Verify
|
||||
const efd = try tmp.dir.openFile(target_name, .{});
|
||||
defer efd.close();
|
||||
|
||||
const nfd = try tmp.dir.openFile(link_name, .{});
|
||||
defer nfd.close();
|
||||
|
||||
{
|
||||
const estat = try std.posix.fstat(efd.handle);
|
||||
const nstat = try std.posix.fstat(nfd.handle);
|
||||
try std.testing.expectEqual(estat.ino, nstat.ino);
|
||||
try std.testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink);
|
||||
}
|
||||
|
||||
// Test 2: Remove the link and see the stats update
|
||||
try std.posix.unlink(link_name);
|
||||
|
||||
{
|
||||
const estat = try std.posix.fstat(efd.handle);
|
||||
try std.testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink);
|
||||
}
|
||||
}
|
||||
153
test/standalone/posix/sigaction.zig
Normal file
153
test/standalone/posix/sigaction.zig
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const native_os = builtin.target.os.tag;
|
||||
|
||||
pub fn main() !void {
|
||||
if (native_os == .wasi or native_os == .windows) {
|
||||
return; // no sigaction
|
||||
}
|
||||
|
||||
try test_sigaction();
|
||||
try test_sigset_bits();
|
||||
}
|
||||
|
||||
fn test_sigaction() !void {
|
||||
if (native_os == .macos and builtin.target.cpu.arch == .x86_64) {
|
||||
return; // https://github.com/ziglang/zig/issues/15381
|
||||
}
|
||||
|
||||
const test_signo = std.posix.SIG.URG; // URG only because it is ignored by default in debuggers
|
||||
|
||||
const S = struct {
|
||||
var handler_called_count: u32 = 0;
|
||||
|
||||
fn handler(sig: i32, info: *const std.posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
|
||||
_ = ctx_ptr;
|
||||
// Check that we received the correct signal.
|
||||
const info_sig = switch (native_os) {
|
||||
.netbsd => info.info.signo,
|
||||
else => info.signo,
|
||||
};
|
||||
if (sig == test_signo and sig == info_sig) {
|
||||
handler_called_count += 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var sa: std.posix.Sigaction = .{
|
||||
.handler = .{ .sigaction = &S.handler },
|
||||
.mask = std.posix.sigemptyset(),
|
||||
.flags = std.posix.SA.SIGINFO | std.posix.SA.RESETHAND,
|
||||
};
|
||||
|
||||
var old_sa: std.posix.Sigaction = undefined;
|
||||
|
||||
// Install the new signal handler.
|
||||
std.posix.sigaction(test_signo, &sa, null);
|
||||
|
||||
// Check that we can read it back correctly.
|
||||
std.posix.sigaction(test_signo, null, &old_sa);
|
||||
try std.testing.expectEqual(&S.handler, old_sa.handler.sigaction.?);
|
||||
try std.testing.expect((old_sa.flags & std.posix.SA.SIGINFO) != 0);
|
||||
|
||||
// Invoke the handler.
|
||||
try std.posix.raise(test_signo);
|
||||
try std.testing.expectEqual(1, S.handler_called_count);
|
||||
|
||||
// Check if passing RESETHAND correctly reset the handler to SIG_DFL
|
||||
std.posix.sigaction(test_signo, null, &old_sa);
|
||||
try std.testing.expectEqual(std.posix.SIG.DFL, old_sa.handler.handler);
|
||||
|
||||
// Reinstall the signal w/o RESETHAND and re-raise
|
||||
sa.flags = std.posix.SA.SIGINFO;
|
||||
std.posix.sigaction(test_signo, &sa, null);
|
||||
try std.posix.raise(test_signo);
|
||||
try std.testing.expectEqual(2, S.handler_called_count);
|
||||
|
||||
// Now set the signal to ignored
|
||||
sa.handler = .{ .handler = std.posix.SIG.IGN };
|
||||
sa.flags = 0;
|
||||
std.posix.sigaction(test_signo, &sa, null);
|
||||
|
||||
// Re-raise to ensure handler is actually ignored
|
||||
try std.posix.raise(test_signo);
|
||||
try std.testing.expectEqual(2, S.handler_called_count);
|
||||
|
||||
// Ensure that ignored state is returned when querying
|
||||
std.posix.sigaction(test_signo, null, &old_sa);
|
||||
try std.testing.expectEqual(std.posix.SIG.IGN, old_sa.handler.handler);
|
||||
}
|
||||
|
||||
fn test_sigset_bits() !void {
|
||||
const NO_SIG: i32 = 0;
|
||||
|
||||
const S = struct {
|
||||
var expected_sig: i32 = undefined;
|
||||
var seen_sig: i32 = NO_SIG;
|
||||
|
||||
fn handler(sig: i32, info: *const std.posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
|
||||
_ = ctx_ptr;
|
||||
|
||||
const info_sig = switch (native_os) {
|
||||
.netbsd => info.info.signo,
|
||||
else => info.signo,
|
||||
};
|
||||
if (seen_sig == NO_SIG and sig == expected_sig and sig == info_sig) {
|
||||
seen_sig = sig;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Assume this is a single-threaded process where the current thread has the
|
||||
// 'pid' thread id. (The sigprocmask calls are thread-private state.)
|
||||
const self_tid = std.posix.system.getpid();
|
||||
|
||||
// To check that sigset_t mapping matches kernel (think u32/u64 mismatches on
|
||||
// big-endian), try sending a blocked signal to make sure the mask matches the
|
||||
// signal. (Send URG and CHLD because they're ignored by default in the
|
||||
// debugger, vs. USR1 or other named signals)
|
||||
inline for ([_]i32{ std.posix.SIG.URG, std.posix.SIG.CHLD, 62, 94, 126 }) |test_signo| {
|
||||
if (test_signo >= std.posix.NSIG) continue;
|
||||
|
||||
S.expected_sig = test_signo;
|
||||
S.seen_sig = NO_SIG;
|
||||
|
||||
const sa: std.posix.Sigaction = .{
|
||||
.handler = .{ .sigaction = &S.handler },
|
||||
.mask = std.posix.sigemptyset(),
|
||||
.flags = std.posix.SA.SIGINFO | std.posix.SA.RESETHAND,
|
||||
};
|
||||
|
||||
var old_sa: std.posix.Sigaction = undefined;
|
||||
|
||||
// Install the new signal handler.
|
||||
std.posix.sigaction(test_signo, &sa, &old_sa);
|
||||
|
||||
// block the signal and see that its delayed until unblocked
|
||||
var block_one: std.posix.sigset_t = std.posix.sigemptyset();
|
||||
std.posix.sigaddset(&block_one, test_signo);
|
||||
std.posix.sigprocmask(std.posix.SIG.BLOCK, &block_one, null);
|
||||
|
||||
// qemu maps target signals to host signals 1-to-1, so targets
|
||||
// with more signals than the host will fail to send the signal.
|
||||
const rc = std.posix.system.kill(self_tid, test_signo);
|
||||
switch (std.posix.errno(rc)) {
|
||||
.SUCCESS => {
|
||||
// See that the signal is blocked, then unblocked
|
||||
try std.testing.expectEqual(NO_SIG, S.seen_sig);
|
||||
std.posix.sigprocmask(std.posix.SIG.UNBLOCK, &block_one, null);
|
||||
try std.testing.expectEqual(test_signo, S.seen_sig);
|
||||
},
|
||||
.INVAL => {
|
||||
// Signal won't get delviered. Just clean up.
|
||||
std.posix.sigprocmask(std.posix.SIG.UNBLOCK, &block_one, null);
|
||||
try std.testing.expectEqual(NO_SIG, S.seen_sig);
|
||||
},
|
||||
else => |errno| return std.posix.unexpectedErrno(errno),
|
||||
}
|
||||
|
||||
// Restore original handler
|
||||
std.posix.sigaction(test_signo, &old_sa, null);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue