| @@ -28,10 +28,9 @@ fn getRealtimeClock() struct { sec: i64, nsec: i64 } { |
| 28 | 28 | /// Replaces std.time.nanoTimestamp() which was removed in Zig 0.16. |
| 29 | 29 | pub fn nanoTimestamp() i128 { |
| 30 | 30 | if (comptime builtin.os.tag == .windows) { |
| 31 | | - const ft = std.os.windows.GetSystemTimeAsFileTime(); |
| 32 | | - const EPOCH_DIFF: i128 = 11644473600 * 1_000_000_000; |
| 33 | | - const intervals: i128 = @as(i128, @as(u64, ft.dwHighDateTime) << 32 | @as(u64, ft.dwLowDateTime)); |
| 34 | | - return intervals * 100 - EPOCH_DIFF; |
| 31 | + const intervals: i128 = @intCast(std.os.windows.ntdll.RtlGetSystemTimePrecise()); |
| 32 | + const epoch_ns: i128 = std.time.epoch.windows * std.time.ns_per_s; |
| 33 | + return intervals * 100 + epoch_ns; |
| 35 | 34 | } else { |
| 36 | 35 | const clock = getRealtimeClock(); |
| 37 | 36 | return @as(i128, clock.sec) * 1_000_000_000 + @as(i128, clock.nsec); |
| @@ -41,15 +40,7 @@ pub fn nanoTimestamp() i128 { |
| 41 | 40 | /// Get current wall-clock time in milliseconds since Unix epoch. |
| 42 | 41 | /// Replaces std.time.milliTimestamp() which was removed in Zig 0.16. |
| 43 | 42 | pub fn milliTimestamp() i64 { |
| 44 | | - if (comptime builtin.os.tag == .windows) { |
| 45 | | - const ft = std.os.windows.GetSystemTimeAsFileTime(); |
| 46 | | - const EPOCH_DIFF: i64 = 11644473600000; |
| 47 | | - const intervals: i64 = @bitCast(@as(u64, ft.dwHighDateTime) << 32 | @as(u64, ft.dwLowDateTime)); |
| 48 | | - return @divFloor(intervals, 10000) - EPOCH_DIFF; |
| 49 | | - } else { |
| 50 | | - const clock = getRealtimeClock(); |
| 51 | | - return @as(i64, clock.sec) * 1000 + @divFloor(@as(i64, clock.nsec), 1_000_000); |
| 52 | | - } |
| 43 | + return @intCast(@divFloor(nanoTimestamp(), std.time.ns_per_ms)); |
| 53 | 44 | } |
| 54 | 45 | |
| 55 | 46 | // ============================================================ |
| @@ -59,6 +50,12 @@ pub fn milliTimestamp() i64 { |
| 59 | 50 | /// Sleep for the given number of nanoseconds. |
| 60 | 51 | /// Replaces std.Thread.sleep() which was removed in Zig 0.16. |
| 61 | 52 | pub fn sleep(ns: u64) void { |
| 53 | + if (comptime builtin.os.tag == .windows) { |
| 54 | + var interval: std.os.windows.LARGE_INTEGER = -@as(i64, @intCast(@max(ns / 100, 1))); |
| 55 | + _ = std.os.windows.ntdll.NtDelayExecution(.FALSE, &interval); |
| 56 | + return; |
| 57 | + } |
| 58 | + |
| 62 | 59 | const s: isize = @intCast(ns / std.time.ns_per_s); |
| 63 | 60 | const remaining_ns: isize = @intCast(ns % std.time.ns_per_s); |
| 64 | 61 | var ts: std.c.timespec = .{ .sec = s, .nsec = remaining_ns }; |
| @@ -97,123 +94,58 @@ pub const Mutex = struct { |
| 97 | 94 | }; |
| 98 | 95 | |
| 99 | 96 | // ============================================================ |
| 100 | | -// File descriptor close wrapper |
| 97 | +// File I/O helpers (std.fs.cwd() removed, needs std.Io now) |
| 101 | 98 | // ============================================================ |
| 102 | 99 | |
| 103 | | -/// Close a file descriptor using libc. |
| 104 | | -/// Replaces std.posix.close() which was removed in Zig 0.16. |
| 105 | | -fn closeFd(fd: std.posix.fd_t) void { |
| 106 | | - _ = std.c.close(fd); |
| 100 | +fn initIo(allocator: std.mem.Allocator) std.Io.Threaded { |
| 101 | + return .init(allocator, .{ .environ = currentEnviron() }); |
| 107 | 102 | } |
| 108 | 103 | |
| 109 | | -// ============================================================ |
| 110 | | -// File I/O helpers (std.fs.cwd() removed, needs std.Io now) |
| 111 | | -// ============================================================ |
| 104 | +fn currentEnviron() std.process.Environ { |
| 105 | + return if (comptime builtin.os.tag == .windows) |
| 106 | + .{ .block = .global } |
| 107 | + else |
| 108 | + .{ .block = .{ .slice = std.mem.span(std.c.environ) } }; |
| 109 | +} |
| 112 | 110 | |
| 113 | | -/// Read entire file contents using POSIX APIs. |
| 111 | +/// Read entire file contents using portable Zig I/O. |
| 114 | 112 | /// Replaces std.fs.cwd().openFile() + file.readToEndAlloc(). |
| 115 | 113 | pub fn readFileAlloc(allocator: std.mem.Allocator, path: []const u8) ![]const u8 { |
| 116 | | - const path_z = try allocator.dupeSentinel(u8, path, 0); |
| 117 | | - defer allocator.free(path_z); |
| 118 | | - |
| 119 | | - const fd = std.posix.openatZ(std.posix.AT.FDCWD, path_z, .{}, 0) catch |err| { |
| 120 | | - if (err == error.FileNotFound) return error.FileNotFound; |
| 121 | | - return err; |
| 122 | | - }; |
| 123 | | - defer closeFd(fd); |
| 124 | | - |
| 125 | | - // Read file in chunks |
| 126 | | - var result = std.ArrayList(u8).empty; |
| 127 | | - errdefer result.deinit(allocator); |
| 128 | | - |
| 129 | | - var buf: [4096]u8 = undefined; |
| 130 | | - while (true) { |
| 131 | | - const n = std.posix.read(fd, &buf) catch |err| return err; |
| 132 | | - if (n == 0) break; |
| 133 | | - try result.appendSlice(allocator, buf[0..n]); |
| 134 | | - } |
| 135 | | - |
| 136 | | - return result.toOwnedSlice(allocator); |
| 114 | + var threaded = initIo(allocator); |
| 115 | + defer threaded.deinit(); |
| 116 | + return std.Io.Dir.cwd().readFileAlloc(threaded.io(), path, allocator, .unlimited); |
| 137 | 117 | } |
| 138 | 118 | |
| 139 | | -/// Write content to a file using POSIX APIs. |
| 119 | +/// Write content to a file using portable Zig I/O. |
| 140 | 120 | /// Replaces std.fs.cwd().createFile() + file.writeAll(). |
| 141 | 121 | pub fn writeFile(allocator: std.mem.Allocator, path: []const u8, content: []const u8) !void { |
| 142 | | - const path_z = try allocator.dupeSentinel(u8, path, 0); |
| 143 | | - defer allocator.free(path_z); |
| 144 | | - |
| 145 | | - const fd = std.posix.openatZ(std.posix.AT.FDCWD, path_z, .{ |
| 146 | | - .ACCMODE = .WRONLY, |
| 147 | | - .CREAT = true, |
| 148 | | - .TRUNC = true, |
| 149 | | - }, 0o644) catch |err| return err; |
| 150 | | - defer closeFd(fd); |
| 151 | | - |
| 152 | | - var remaining = content; |
| 153 | | - while (remaining.len > 0) { |
| 154 | | - const rc = std.c.write(fd, remaining.ptr, remaining.len); |
| 155 | | - if (rc < 0) return error.Unexpected; |
| 156 | | - const written: usize = @intCast(rc); |
| 157 | | - remaining = remaining[written..]; |
| 158 | | - } |
| 122 | + var threaded = initIo(allocator); |
| 123 | + defer threaded.deinit(); |
| 124 | + try std.Io.Dir.cwd().writeFile(threaded.io(), .{ .sub_path = path, .data = content }); |
| 159 | 125 | } |
| 160 | 126 | |
| 161 | | -/// Delete a file using POSIX APIs. |
| 127 | +/// Delete a file using portable Zig I/O. |
| 162 | 128 | /// Replaces std.fs.cwd().deleteFile(). |
| 163 | 129 | pub fn deleteFile(allocator: std.mem.Allocator, path: []const u8) !void { |
| 164 | | - const path_z = try allocator.dupeSentinel(u8, path, 0); |
| 165 | | - defer allocator.free(path_z); |
| 166 | | - const rc = std.c.unlink(path_z); |
| 167 | | - if (rc != 0) { |
| 168 | | - switch (std.c.errno(rc)) { |
| 169 | | - .NOENT => return error.FileNotFound, |
| 170 | | - else => return error.Unexpected, |
| 171 | | - } |
| 172 | | - } |
| 130 | + var threaded = initIo(allocator); |
| 131 | + defer threaded.deinit(); |
| 132 | + try std.Io.Dir.cwd().deleteFile(threaded.io(), path); |
| 173 | 133 | } |
| 174 | 134 | |
| 175 | | -/// Delete an empty directory using POSIX APIs. |
| 135 | +/// Delete an empty directory using portable Zig I/O. |
| 176 | 136 | /// Replaces std.fs.cwd().deleteDir(). |
| 177 | 137 | pub fn deleteDir(allocator: std.mem.Allocator, path: []const u8) !void { |
| 178 | | - const path_z = try allocator.dupeSentinel(u8, path, 0); |
| 179 | | - defer allocator.free(path_z); |
| 180 | | - const rc = std.c.rmdir(path_z); |
| 181 | | - if (rc != 0) { |
| 182 | | - switch (std.c.errno(rc)) { |
| 183 | | - .NOENT => return error.FileNotFound, |
| 184 | | - else => return error.Unexpected, |
| 185 | | - } |
| 186 | | - } |
| 138 | + var threaded = initIo(allocator); |
| 139 | + defer threaded.deinit(); |
| 140 | + try std.Io.Dir.cwd().deleteDir(threaded.io(), path); |
| 187 | 141 | } |
| 188 | 142 | |
| 189 | | -/// Create directories recursively using POSIX APIs. |
| 143 | +/// Create directories recursively using portable Zig I/O. |
| 190 | 144 | /// Replaces std.fs.cwd().makePath(). |
| 191 | 145 | pub fn makePath(allocator: std.mem.Allocator, path: []const u8) !void { |
| 192 | | - const path_z = try allocator.dupeSentinel(u8, path, 0); |
| 193 | | - defer allocator.free(path_z); |
| 194 | | - |
| 195 | | - const rc = std.c.mkdir(path_z, 0o755); |
| 196 | | - if (rc == 0) return; |
| 197 | | - |
| 198 | | - switch (std.c.errno(rc)) { |
| 199 | | - .EXIST => return, // Already exists |
| 200 | | - .NOENT => { |
| 201 | | - // Parent doesn't exist, find and create it |
| 202 | | - if (std.mem.lastIndexOfScalar(u8, path, '/')) |sep| { |
| 203 | | - if (sep > 0) { |
| 204 | | - try makePath(allocator, path[0..sep]); |
| 205 | | - // Retry creating the directory |
| 206 | | - const retry_z = try allocator.dupeSentinel(u8, path, 0); |
| 207 | | - defer allocator.free(retry_z); |
| 208 | | - const rc2 = std.c.mkdir(retry_z, 0o755); |
| 209 | | - if (rc2 != 0 and std.c.errno(rc2) != .EXIST) { |
| 210 | | - return error.Unexpected; |
| 211 | | - } |
| 212 | | - } |
| 213 | | - } |
| 214 | | - }, |
| 215 | | - else => return error.Unexpected, |
| 216 | | - } |
| 146 | + var threaded = initIo(allocator); |
| 147 | + defer threaded.deinit(); |
| 148 | + try std.Io.Dir.cwd().createDirPath(threaded.io(), path); |
| 217 | 149 | } |
| 218 | 150 | |
| 219 | 151 | // ============================================================ |
| @@ -226,56 +158,44 @@ pub const DirEntry = struct { |
| 226 | 158 | kind: enum { file, directory, sym_link, other }, |
| 227 | 159 | }; |
| 228 | 160 | |
| 229 | | -/// A simple directory iterator using POSIX APIs. |
| 161 | +/// A simple directory iterator using portable Zig I/O. |
| 230 | 162 | /// Replaces std.fs.openDirAbsolute() + dir.iterate() which was removed in Zig 0.16. |
| 231 | 163 | pub const DirIterator = struct { |
| 232 | | - dir: *std.c.DIR, |
| 233 | | - path_buf: [1024]u8, |
| 164 | + threaded: *std.Io.Threaded, |
| 165 | + dir: std.Io.Dir, |
| 166 | + iterator: std.Io.Dir.Iterator, |
| 234 | 167 | |
| 235 | 168 | pub fn open(dir_path: []const u8) !DirIterator { |
| 236 | | - var path_buf: [1024:0]u8 = @splat(0); |
| 237 | | - if (dir_path.len >= path_buf.len) return error.NameTooLong; |
| 238 | | - @memcpy(path_buf[0..dir_path.len], dir_path); |
| 169 | + const threaded = try std.heap.page_allocator.create(std.Io.Threaded); |
| 170 | + errdefer std.heap.page_allocator.destroy(threaded); |
| 171 | + threaded.* = initIo(std.heap.page_allocator); |
| 172 | + errdefer threaded.deinit(); |
| 239 | 173 | |
| 240 | | - const dir = std.c.opendir(&path_buf); |
| 241 | | - if (dir == null) return error.FileNotFound; |
| 174 | + const dir = try std.Io.Dir.cwd().openDir(threaded.io(), dir_path, .{ .iterate = true }); |
| 242 | 175 | return .{ |
| 243 | | - .dir = dir.?, |
| 244 | | - .path_buf = undefined, |
| 176 | + .threaded = threaded, |
| 177 | + .dir = dir, |
| 178 | + .iterator = dir.iterateAssumeFirstIteration(), |
| 245 | 179 | }; |
| 246 | 180 | } |
| 247 | 181 | |
| 248 | 182 | pub fn next(self: *DirIterator) !?DirEntry { |
| 249 | | - while (true) { |
| 250 | | - const entry_opt = std.c.readdir(self.dir); |
| 251 | | - if (entry_opt == null) return null; |
| 252 | | - const entry = entry_opt.?; |
| 253 | | - |
| 254 | | - // Get name as slice |
| 255 | | - const name_ptr: [*]const u8 = @ptrCast(&entry.name); |
| 256 | | - const name_len = if (@hasField(@TypeOf(entry.*), "namlen")) |
| 257 | | - entry.namlen |
| 258 | | - else |
| 259 | | - std.mem.indexOfScalar(u8, name_ptr[0..256], 0) orelse 256; |
| 260 | | - const name = name_ptr[0..name_len]; |
| 261 | | - |
| 262 | | - // Skip . and .. |
| 263 | | - if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue; |
| 264 | | - |
| 265 | | - return .{ |
| 266 | | - .name = name, |
| 267 | | - .kind = switch (entry.type) { |
| 268 | | - std.c.DT.DIR => .directory, |
| 269 | | - std.c.DT.REG => .file, |
| 270 | | - std.c.DT.LNK => .sym_link, |
| 271 | | - else => .other, |
| 272 | | - }, |
| 273 | | - }; |
| 274 | | - } |
| 183 | + const entry = try self.iterator.next(self.threaded.io()) orelse return null; |
| 184 | + return .{ |
| 185 | + .name = entry.name, |
| 186 | + .kind = switch (entry.kind) { |
| 187 | + .directory => .directory, |
| 188 | + .file => .file, |
| 189 | + .sym_link => .sym_link, |
| 190 | + else => .other, |
| 191 | + }, |
| 192 | + }; |
| 275 | 193 | } |
| 276 | 194 | |
| 277 | 195 | pub fn close(self: *DirIterator) void { |
| 278 | | - _ = std.c.closedir(self.dir); |
| 196 | + self.dir.close(self.threaded.io()); |
| 197 | + self.threaded.deinit(); |
| 198 | + std.heap.page_allocator.destroy(self.threaded); |
| 279 | 199 | } |
| 280 | 200 | }; |
| 281 | 201 | |
| @@ -298,7 +218,8 @@ pub const SpawnResult = union(enum) { |
| 298 | 218 | }; |
| 299 | 219 | |
| 300 | 220 | /// Spawn a child process and wait for it to complete. |
| 301 | | -/// Replaces std.process.Child.init() + spawnAndWait() which was removed in Zig 0.16. |
| 221 | +/// Uses Zig's portable process API, including PATH lookup and Windows process |
| 222 | +/// creation, while preserving the compatibility result used by callers. |
| 302 | 223 | pub fn spawnAndWait( |
| 303 | 224 | allocator: std.mem.Allocator, |
| 304 | 225 | argv: []const []const u8, |
| @@ -307,105 +228,30 @@ pub fn spawnAndWait( |
| 307 | 228 | ) !SpawnResult { |
| 308 | 229 | if (argv.len == 0) return error.InvalidArgument; |
| 309 | 230 | |
| 310 | | - // Convert argv to null-terminated C strings |
| 311 | | - const c_argv = try allocator.alloc(?[*:0]const u8, argv.len + 1); |
| 312 | | - defer allocator.free(c_argv); |
| 313 | | - |
| 314 | | - for (argv, 0..) |arg, i| { |
| 315 | | - const z = try allocator.dupeSentinel(u8, arg, 0); |
| 316 | | - c_argv[i] = z.ptr; |
| 317 | | - } |
| 318 | | - c_argv[argv.len] = null; |
| 319 | | - |
| 320 | | - // Fork |
| 321 | | - const pid = std.c.fork(); |
| 322 | | - if (pid < 0) return error.ForkFailed; |
| 323 | | - |
| 324 | | - if (pid == 0) { |
| 325 | | - // Child process |
| 326 | | - // Handle stdout |
| 327 | | - switch (stdout_behavior) { |
| 328 | | - .Ignore => { |
| 329 | | - const dev_null: [*:0]const u8 = "/dev/null"; |
| 330 | | - const null_fd = std.posix.openatZ(std.posix.AT.FDCWD, dev_null, .{ .ACCMODE = .WRONLY }, 0) catch std.process.exit(127); |
| 331 | | - if (std.c.dup2(null_fd, 1) < 0) std.process.exit(127); |
| 332 | | - closeFd(null_fd); |
| 333 | | - }, |
| 334 | | - else => {}, |
| 335 | | - } |
| 336 | | - |
| 337 | | - // Handle stderr |
| 338 | | - switch (stderr_behavior) { |
| 339 | | - .Ignore => { |
| 340 | | - const dev_null: [*:0]const u8 = "/dev/null"; |
| 341 | | - const null_fd = std.posix.openatZ(std.posix.AT.FDCWD, dev_null, .{ .ACCMODE = .WRONLY }, 0) catch std.process.exit(127); |
| 342 | | - if (std.c.dup2(null_fd, 2) < 0) std.process.exit(127); |
| 343 | | - closeFd(null_fd); |
| 344 | | - }, |
| 345 | | - else => {}, |
| 346 | | - } |
| 347 | | - |
| 348 | | - // Exec - use execve with PATH lookup and current environment |
| 349 | | - const envp: [*:null]const ?[*:0]const u8 = @ptrCast(std.c.environ); |
| 350 | | - |
| 351 | | - // Try direct execution first |
| 352 | | - _ = std.c.execve(c_argv[0].?, @ptrCast(c_argv.ptr), envp); |
| 353 | | - |
| 354 | | - // If direct exec failed, try PATH lookup |
| 355 | | - const cmd = std.mem.sliceTo(c_argv[0].?, 0); |
| 356 | | - if (std.mem.indexOfScalar(u8, cmd, '/') == null) { |
| 357 | | - // Search PATH |
| 358 | | - var path_search: ?[*:0]const u8 = null; |
| 359 | | - var env_idx: usize = 0; |
| 360 | | - while (std.c.environ[env_idx]) |env_entry| : (env_idx += 1) { |
| 361 | | - const entry = std.mem.sliceTo(env_entry, 0); |
| 362 | | - if (std.mem.startsWith(u8, entry, "PATH=")) { |
| 363 | | - path_search = @ptrCast(env_entry + 5); |
| 364 | | - break; |
| 365 | | - } |
| 366 | | - } |
| 367 | | - |
| 368 | | - if (path_search) |path_val| { |
| 369 | | - const path_str = std.mem.sliceTo(path_val, 0); |
| 370 | | - var path_iter = std.mem.splitScalar(u8, path_str, ':'); |
| 371 | | - while (path_iter.next()) |dir| { |
| 372 | | - var full_path: [1024:0]u8 = @splat(0); |
| 373 | | - if (dir.len + 1 + cmd.len < full_path.len) { |
| 374 | | - @memcpy(full_path[0..dir.len], dir); |
| 375 | | - full_path[dir.len] = '/'; |
| 376 | | - @memcpy(full_path[dir.len + 1 ..][0..cmd.len], cmd); |
| 377 | | - _ = std.c.execve(&full_path, @ptrCast(c_argv.ptr), envp); |
| 378 | | - } |
| 379 | | - } |
| 380 | | - } |
| 381 | | - } |
| 382 | | - std.process.exit(127); |
| 383 | | - } |
| 384 | | - |
| 385 | | - // Parent process - free the duped strings |
| 386 | | - for (argv, 0..) |_, i| { |
| 387 | | - const z: [*:0]const u8 = c_argv[i].?; |
| 388 | | - const len = std.mem.len(z); |
| 389 | | - allocator.free(z[0 .. len + 1]); |
| 390 | | - } |
| 391 | | - |
| 392 | | - // Wait for child |
| 393 | | - var status: c_int = 0; |
| 394 | | - const wait_result = std.c.waitpid(pid, &status, 0); |
| 395 | | - if (wait_result < 0) return error.WaitFailed; |
| 396 | | - |
| 397 | | - const ustatus: u32 = @bitCast(status); |
| 398 | | - |
| 399 | | - // Check if exited normally (WIFEXITED) |
| 400 | | - if (ustatus & 0x7f == 0) { |
| 401 | | - // WEXITSTATUS |
| 402 | | - const exit_code: u8 = @intCast((ustatus >> 8) & 0xff); |
| 403 | | - return .{ .Exited = exit_code }; |
| 404 | | - } |
| 231 | + var threaded = initIo(allocator); |
| 232 | + defer threaded.deinit(); |
| 233 | + const io = threaded.io(); |
| 234 | + |
| 235 | + var child = try std.process.spawn(io, .{ |
| 236 | + .argv = argv, |
| 237 | + .stdout = stdBehavior(stdout_behavior), |
| 238 | + .stderr = stdBehavior(stderr_behavior), |
| 239 | + .create_no_window = false, |
| 240 | + }); |
| 241 | + const term = try child.wait(io); |
| 242 | + return switch (term) { |
| 243 | + .exited => |code| .{ .Exited = code }, |
| 244 | + .signal => |signal| .{ .Signal = @backingInt(signal) }, |
| 245 | + .stopped => |signal| .{ .Signal = @backingInt(signal) }, |
| 246 | + .unknown => |status| .{ .Unknown = status }, |
| 247 | + }; |
| 248 | +} |
| 405 | 249 | |
| 406 | | - // Signal |
| 407 | | - const sig = ustatus & 0x7f; |
| 408 | | - return .{ .Signal = sig }; |
| 250 | +fn stdBehavior(behavior: StdBehavior) std.process.SpawnOptions.StdIo { |
| 251 | + return switch (behavior) { |
| 252 | + .Inherit, .Pipe => .inherit, |
| 253 | + .Ignore => .ignore, |
| 254 | + }; |
| 409 | 255 | } |
| 410 | 256 | |
| 411 | 257 | // ============================================================ |
| @@ -452,10 +298,33 @@ pub const ArrayListWriter = struct { |
| 452 | 298 | } |
| 453 | 299 | }; |
| 454 | 300 | |
| 455 | | -// ============================================================ |
| 456 | | -// Signal handler type |
| 457 | | -// ============================================================ |
| 301 | +test "portable file helpers preserve nested paths" { |
| 302 | + const allocator = std.testing.allocator; |
| 303 | + const nonce = nanoTimestamp(); |
| 304 | + const dir_path = try std.fmt.allocPrint(allocator, ".zig-cache/compat-{d}", .{nonce}); |
| 305 | + defer allocator.free(dir_path); |
| 306 | + const file_path = try std.fs.path.join(allocator, &.{ dir_path, "roundtrip.txt" }); |
| 307 | + defer allocator.free(file_path); |
| 308 | + |
| 309 | + try makePath(allocator, dir_path); |
| 310 | + defer deleteDir(allocator, dir_path) catch {}; |
| 311 | + defer deleteFile(allocator, file_path) catch {}; |
| 312 | + |
| 313 | + try writeFile(allocator, file_path, "portable"); |
| 314 | + const contents = try readFileAlloc(allocator, file_path); |
| 315 | + defer allocator.free(contents); |
| 316 | + try std.testing.expectEqualStrings("portable", contents); |
| 317 | +} |
| 458 | 318 | |
| 459 | | -/// The signal type used in signal handlers. |
| 460 | | -/// In Zig 0.16, this changed from c_int to std.posix.SIG enum. |
| 461 | | -pub const SignalType = std.posix.SIG; |
| 319 | +test "portable process helper preserves exit codes" { |
| 320 | + const argv: []const []const u8 = if (comptime builtin.os.tag == .windows) |
| 321 | + &.{ "cmd.exe", "/C", "exit", "7" } |
| 322 | + else |
| 323 | + &.{ "sh", "-c", "exit 7" }; |
| 324 | + |
| 325 | + const result = try spawnAndWait(std.testing.allocator, argv, .Ignore, .Ignore); |
| 326 | + switch (result) { |
| 327 | + .Exited => |code| try std.testing.expectEqual(@as(u8, 7), code), |
| 328 | + else => return error.UnexpectedTermination, |
| 329 | + } |
| 330 | +} |