blob 6b9c19c4 (13295B) - Raw
1 const std = @import("../std.zig"); 2 const os = std.os; 3 const testing = std.testing; 4 const expect = testing.expect; 5 const expectEqual = testing.expectEqual; 6 const io = std.io; 7 const fs = std.fs; 8 const mem = std.mem; 9 const elf = std.elf; 10 const File = std.fs.File; 11 const Thread = std.Thread; 12 13 const a = std.testing.allocator; 14 15 const builtin = @import("builtin"); 16 const AtomicRmwOp = builtin.AtomicRmwOp; 17 const AtomicOrder = builtin.AtomicOrder; 18 const tmpDir = std.testing.tmpDir; 19 const Dir = std.fs.Dir; 20 const ArenaAllocator = std.heap.ArenaAllocator; 21 22 test "fstatat" { 23 // enable when `fstat` and `fstatat` are implemented on Windows 24 if (builtin.os.tag == .windows) return error.SkipZigTest; 25 26 var tmp = tmpDir(.{}); 27 defer tmp.cleanup(); 28 29 // create dummy file 30 const contents = "nonsense"; 31 try tmp.dir.writeFile("file.txt", contents); 32 33 // fetch file's info on the opened fd directly 34 const file = try tmp.dir.openFile("file.txt", .{}); 35 const stat = try os.fstat(file.handle); 36 defer file.close(); 37 38 // now repeat but using `fstatat` instead 39 const flags = if (builtin.os.tag == .wasi) 0x0 else os.AT_SYMLINK_NOFOLLOW; 40 const statat = try os.fstatat(tmp.dir.fd, "file.txt", flags); 41 expectEqual(stat, statat); 42 } 43 44 test "readlink" { 45 if (builtin.os.tag == .wasi) return error.SkipZigTest; 46 47 // First, try relative paths 48 { 49 var cwd = fs.cwd(); 50 try cwd.writeFile("file.txt", "nonsense"); 51 try os.symlink("file.txt", "symlinked"); 52 53 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; 54 const given = try os.readlink("symlinked", buffer[0..]); 55 expect(mem.eql(u8, "file.txt", given)); 56 57 try cwd.deleteFile("file.txt"); 58 try cwd.deleteFile("symlinked"); 59 } 60 61 // Next, let's try fully-qualified paths 62 { 63 var tmp = tmpDir(.{}); 64 // defer tmp.cleanup(); 65 66 // create file 67 try tmp.dir.writeFile("file.txt", "nonsense"); 68 69 // get paths 70 // TODO: use Dir's realpath function once that exists 71 var arena = ArenaAllocator.init(testing.allocator); 72 defer arena.deinit(); 73 74 const base_path = blk: { 75 const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] }); 76 break :blk try fs.realpathAlloc(&arena.allocator, relative_path); 77 }; 78 const target_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "file.txt" }); 79 const symlink_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "symlinked" }); 80 std.debug.warn("\ntarget_path={}\n", .{target_path}); 81 std.debug.warn("symlink_path={}\n", .{symlink_path}); 82 83 // create symbolic link by path 84 try os.symlink(target_path, symlink_path); 85 86 // now, read the link and verify 87 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; 88 const given = try os.readlink(symlink_path, buffer[0..]); 89 expect(mem.eql(u8, symlink_path, given)); 90 } 91 } 92 93 test "readlinkat" { 94 // enable when `readlinkat` and `symlinkat` are implemented on Windows 95 if (builtin.os.tag == .windows) return error.SkipZigTest; 96 97 var tmp = tmpDir(.{}); 98 defer tmp.cleanup(); 99 100 // create file 101 try tmp.dir.writeFile("file.txt", "nonsense"); 102 103 // create a symbolic link 104 try os.symlinkat("file.txt", tmp.dir.fd, "link"); 105 106 // read the link 107 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; 108 const read_link = try os.readlinkat(tmp.dir.fd, "link", buffer[0..]); 109 expect(mem.eql(u8, "file.txt", read_link)); 110 } 111 112 fn testThreadIdFn(thread_id: *Thread.Id) void { 113 thread_id.* = Thread.getCurrentId(); 114 } 115 116 test "std.Thread.getCurrentId" { 117 if (builtin.single_threaded) return error.SkipZigTest; 118 119 var thread_current_id: Thread.Id = undefined; 120 const thread = try Thread.spawn(&thread_current_id, testThreadIdFn); 121 const thread_id = thread.handle(); 122 thread.wait(); 123 if (Thread.use_pthreads) { 124 expect(thread_current_id == thread_id); 125 } else if (builtin.os.tag == .windows) { 126 expect(Thread.getCurrentId() != thread_current_id); 127 } else { 128 // If the thread completes very quickly, then thread_id can be 0. See the 129 // documentation comments for `std.Thread.handle`. 130 expect(thread_id == 0 or thread_current_id == thread_id); 131 } 132 } 133 134 test "spawn threads" { 135 if (builtin.single_threaded) return error.SkipZigTest; 136 137 var shared_ctx: i32 = 1; 138 139 const thread1 = try Thread.spawn({}, start1); 140 const thread2 = try Thread.spawn(&shared_ctx, start2); 141 const thread3 = try Thread.spawn(&shared_ctx, start2); 142 const thread4 = try Thread.spawn(&shared_ctx, start2); 143 144 thread1.wait(); 145 thread2.wait(); 146 thread3.wait(); 147 thread4.wait(); 148 149 expect(shared_ctx == 4); 150 } 151 152 fn start1(ctx: void) u8 { 153 return 0; 154 } 155 156 fn start2(ctx: *i32) u8 { 157 _ = @atomicRmw(i32, ctx, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); 158 return 0; 159 } 160 161 test "cpu count" { 162 if (builtin.os.tag == .wasi) return error.SkipZigTest; 163 164 const cpu_count = try Thread.cpuCount(); 165 expect(cpu_count >= 1); 166 } 167 168 test "thread local storage" { 169 if (builtin.single_threaded) return error.SkipZigTest; 170 const thread1 = try Thread.spawn({}, testTls); 171 const thread2 = try Thread.spawn({}, testTls); 172 testTls({}); 173 thread1.wait(); 174 thread2.wait(); 175 } 176 177 threadlocal var x: i32 = 1234; 178 fn testTls(context: void) void { 179 if (x != 1234) @panic("bad start value"); 180 x += 1; 181 if (x != 1235) @panic("bad end value"); 182 } 183 184 test "getrandom" { 185 var buf_a: [50]u8 = undefined; 186 var buf_b: [50]u8 = undefined; 187 try os.getrandom(&buf_a); 188 try os.getrandom(&buf_b); 189 // If this test fails the chance is significantly higher that there is a bug than 190 // that two sets of 50 bytes were equal. 191 expect(!mem.eql(u8, &buf_a, &buf_b)); 192 } 193 194 test "getcwd" { 195 if (builtin.os.tag == .wasi) return error.SkipZigTest; 196 197 // at least call it so it gets compiled 198 var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; 199 _ = os.getcwd(&buf) catch undefined; 200 } 201 202 test "sigaltstack" { 203 if (builtin.os.tag == .windows or builtin.os.tag == .wasi) return error.SkipZigTest; 204 205 var st: os.stack_t = undefined; 206 try os.sigaltstack(null, &st); 207 // Setting a stack size less than MINSIGSTKSZ returns ENOMEM 208 st.ss_flags = 0; 209 st.ss_size = 1; 210 testing.expectError(error.SizeTooSmall, os.sigaltstack(&st, null)); 211 } 212 213 // If the type is not available use void to avoid erroring out when `iter_fn` is 214 // analyzed 215 const dl_phdr_info = if (@hasDecl(os, "dl_phdr_info")) os.dl_phdr_info else c_void; 216 217 const IterFnError = error{ 218 MissingPtLoadSegment, 219 MissingLoad, 220 BadElfMagic, 221 FailedConsistencyCheck, 222 }; 223 224 fn iter_fn(info: *dl_phdr_info, size: usize, counter: *usize) IterFnError!void { 225 // Count how many libraries are loaded 226 counter.* += @as(usize, 1); 227 228 // The image should contain at least a PT_LOAD segment 229 if (info.dlpi_phnum < 1) return error.MissingPtLoadSegment; 230 231 // Quick & dirty validation of the phdr pointers, make sure we're not 232 // pointing to some random gibberish 233 var i: usize = 0; 234 var found_load = false; 235 while (i < info.dlpi_phnum) : (i += 1) { 236 const phdr = info.dlpi_phdr[i]; 237 238 if (phdr.p_type != elf.PT_LOAD) continue; 239 240 const reloc_addr = info.dlpi_addr + phdr.p_vaddr; 241 // Find the ELF header 242 const elf_header = @intToPtr(*elf.Ehdr, reloc_addr - phdr.p_offset); 243 // Validate the magic 244 if (!mem.eql(u8, elf_header.e_ident[0..4], "\x7fELF")) return error.BadElfMagic; 245 // Consistency check 246 if (elf_header.e_phnum != info.dlpi_phnum) return error.FailedConsistencyCheck; 247 248 found_load = true; 249 break; 250 } 251 252 if (!found_load) return error.MissingLoad; 253 } 254 255 test "dl_iterate_phdr" { 256 if (builtin.os.tag == .windows or builtin.os.tag == .wasi or builtin.os.tag == .macosx) 257 return error.SkipZigTest; 258 259 var counter: usize = 0; 260 try os.dl_iterate_phdr(&counter, IterFnError, iter_fn); 261 expect(counter != 0); 262 } 263 264 test "gethostname" { 265 if (builtin.os.tag == .windows or builtin.os.tag == .wasi) 266 return error.SkipZigTest; 267 268 var buf: [os.HOST_NAME_MAX]u8 = undefined; 269 const hostname = try os.gethostname(&buf); 270 expect(hostname.len != 0); 271 } 272 273 test "pipe" { 274 if (builtin.os.tag == .windows or builtin.os.tag == .wasi) 275 return error.SkipZigTest; 276 277 var fds = try os.pipe(); 278 expect((try os.write(fds[1], "hello")) == 5); 279 var buf: [16]u8 = undefined; 280 expect((try os.read(fds[0], buf[0..])) == 5); 281 testing.expectEqualSlices(u8, buf[0..5], "hello"); 282 os.close(fds[1]); 283 os.close(fds[0]); 284 } 285 286 test "argsAlloc" { 287 var args = try std.process.argsAlloc(std.testing.allocator); 288 std.process.argsFree(std.testing.allocator, args); 289 } 290 291 test "memfd_create" { 292 // memfd_create is linux specific. 293 if (builtin.os.tag != .linux) return error.SkipZigTest; 294 const fd = std.os.memfd_create("test", 0) catch |err| switch (err) { 295 // Related: https://github.com/ziglang/zig/issues/4019 296 error.SystemOutdated => return error.SkipZigTest, 297 else => |e| return e, 298 }; 299 defer std.os.close(fd); 300 expect((try std.os.write(fd, "test")) == 4); 301 try std.os.lseek_SET(fd, 0); 302 303 var buf: [10]u8 = undefined; 304 const bytes_read = try std.os.read(fd, &buf); 305 expect(bytes_read == 4); 306 expect(mem.eql(u8, buf[0..4], "test")); 307 } 308 309 test "mmap" { 310 if (builtin.os.tag == .windows or builtin.os.tag == .wasi) 311 return error.SkipZigTest; 312 313 var tmp = tmpDir(.{}); 314 defer tmp.cleanup(); 315 316 // Simple mmap() call with non page-aligned size 317 { 318 const data = try os.mmap( 319 null, 320 1234, 321 os.PROT_READ | os.PROT_WRITE, 322 os.MAP_ANONYMOUS | os.MAP_PRIVATE, 323 -1, 324 0, 325 ); 326 defer os.munmap(data); 327 328 testing.expectEqual(@as(usize, 1234), data.len); 329 330 // By definition the data returned by mmap is zero-filled 331 testing.expect(mem.eql(u8, data, &[_]u8{0x00} ** 1234)); 332 333 // Make sure the memory is writeable as requested 334 std.mem.set(u8, data, 0x55); 335 testing.expect(mem.eql(u8, data, &[_]u8{0x55} ** 1234)); 336 } 337 338 const test_out_file = "os_tmp_test"; 339 // Must be a multiple of 4096 so that the test works with mmap2 340 const alloc_size = 8 * 4096; 341 342 // Create a file used for testing mmap() calls with a file descriptor 343 { 344 const file = try tmp.dir.createFile(test_out_file, .{}); 345 defer file.close(); 346 347 const stream = file.outStream(); 348 349 var i: u32 = 0; 350 while (i < alloc_size / @sizeOf(u32)) : (i += 1) { 351 try stream.writeIntNative(u32, i); 352 } 353 } 354 355 // Map the whole file 356 { 357 const file = try tmp.dir.openFile(test_out_file, .{}); 358 defer file.close(); 359 360 const data = try os.mmap( 361 null, 362 alloc_size, 363 os.PROT_READ, 364 os.MAP_PRIVATE, 365 file.handle, 366 0, 367 ); 368 defer os.munmap(data); 369 370 var mem_stream = io.fixedBufferStream(data); 371 const stream = mem_stream.inStream(); 372 373 var i: u32 = 0; 374 while (i < alloc_size / @sizeOf(u32)) : (i += 1) { 375 testing.expectEqual(i, try stream.readIntNative(u32)); 376 } 377 } 378 379 // Map the upper half of the file 380 { 381 const file = try tmp.dir.openFile(test_out_file, .{}); 382 defer file.close(); 383 384 const data = try os.mmap( 385 null, 386 alloc_size / 2, 387 os.PROT_READ, 388 os.MAP_PRIVATE, 389 file.handle, 390 alloc_size / 2, 391 ); 392 defer os.munmap(data); 393 394 var mem_stream = io.fixedBufferStream(data); 395 const stream = mem_stream.inStream(); 396 397 var i: u32 = alloc_size / 2 / @sizeOf(u32); 398 while (i < alloc_size / @sizeOf(u32)) : (i += 1) { 399 testing.expectEqual(i, try stream.readIntNative(u32)); 400 } 401 } 402 403 try tmp.dir.deleteFile(test_out_file); 404 } 405 406 test "getenv" { 407 if (builtin.os.tag == .windows) { 408 expect(os.getenvW(&[_:0]u16{ 'B', 'O', 'G', 'U', 'S', 0x11, 0x22, 0x33, 0x44, 0x55 }) == null); 409 } else { 410 expect(os.getenvZ("BOGUSDOESNOTEXISTENVVAR") == null); 411 } 412 } 413 414 test "fcntl" { 415 if (builtin.os.tag == .windows or builtin.os.tag == .wasi) 416 return error.SkipZigTest; 417 418 var tmp = tmpDir(.{}); 419 defer tmp.cleanup(); 420 421 const test_out_file = "os_tmp_test"; 422 423 const file = try tmp.dir.createFile(test_out_file, .{}); 424 defer { 425 file.close(); 426 tmp.dir.deleteFile(test_out_file) catch {}; 427 } 428 429 // Note: The test assumes createFile opens the file with O_CLOEXEC 430 { 431 const flags = try os.fcntl(file.handle, os.F_GETFD, 0); 432 expect((flags & os.FD_CLOEXEC) != 0); 433 } 434 { 435 _ = try os.fcntl(file.handle, os.F_SETFD, 0); 436 const flags = try os.fcntl(file.handle, os.F_GETFD, 0); 437 expect((flags & os.FD_CLOEXEC) == 0); 438 } 439 { 440 _ = try os.fcntl(file.handle, os.F_SETFD, os.FD_CLOEXEC); 441 const flags = try os.fcntl(file.handle, os.F_GETFD, 0); 442 expect((flags & os.FD_CLOEXEC) != 0); 443 } 444 }