blob d51fe3db (83555B) - Raw
1 const std = @import("../std.zig"); 2 const builtin = @import("builtin"); 3 const testing = std.testing; 4 const fs = std.fs; 5 const mem = std.mem; 6 const wasi = std.os.wasi; 7 const native_os = builtin.os.tag; 8 const windows = std.os.windows; 9 const posix = std.posix; 10 11 const ArenaAllocator = std.heap.ArenaAllocator; 12 const Dir = std.fs.Dir; 13 const File = std.fs.File; 14 const tmpDir = testing.tmpDir; 15 const SymLinkFlags = std.fs.Dir.SymLinkFlags; 16 17 const PathType = enum { 18 relative, 19 absolute, 20 unc, 21 22 pub fn isSupported(self: PathType, target_os: std.Target.Os) bool { 23 return switch (self) { 24 .relative => true, 25 .absolute => std.os.isGetFdPathSupportedOnTarget(target_os), 26 .unc => target_os.tag == .windows, 27 }; 28 } 29 30 pub const TransformError = posix.RealPathError || error{OutOfMemory}; 31 pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8; 32 33 pub fn getTransformFn(comptime path_type: PathType) TransformFn { 34 switch (path_type) { 35 .relative => return struct { 36 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 { 37 _ = allocator; 38 _ = dir; 39 return relative_path; 40 } 41 }.transform, 42 .absolute => return struct { 43 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 { 44 // The final path may not actually exist which would cause realpath to fail. 45 // So instead, we get the path of the dir and join it with the relative path. 46 var fd_path_buf: [fs.max_path_bytes]u8 = undefined; 47 const dir_path = try std.os.getFdPath(dir.fd, &fd_path_buf); 48 return fs.path.joinZ(allocator, &.{ dir_path, relative_path }); 49 } 50 }.transform, 51 .unc => return struct { 52 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 { 53 // Any drive absolute path (C:\foo) can be converted into a UNC path by 54 // using '127.0.0.1' as the server name and '<drive letter>$' as the share name. 55 var fd_path_buf: [fs.max_path_bytes]u8 = undefined; 56 const dir_path = try std.os.getFdPath(dir.fd, &fd_path_buf); 57 const windows_path_type = windows.getUnprefixedPathType(u8, dir_path); 58 switch (windows_path_type) { 59 .unc_absolute => return fs.path.joinZ(allocator, &.{ dir_path, relative_path }), 60 .drive_absolute => { 61 // `C:\<...>` -> `\\127.0.0.1\C$\<...>` 62 const prepended = "\\\\127.0.0.1\\"; 63 var path = try fs.path.joinZ(allocator, &.{ prepended, dir_path, relative_path }); 64 path[prepended.len + 1] = '$'; 65 return path; 66 }, 67 else => unreachable, 68 } 69 } 70 }.transform, 71 } 72 } 73 }; 74 75 const TestContext = struct { 76 path_type: PathType, 77 path_sep: u8, 78 arena: ArenaAllocator, 79 tmp: testing.TmpDir, 80 dir: std.fs.Dir, 81 transform_fn: *const PathType.TransformFn, 82 83 pub fn init(path_type: PathType, path_sep: u8, allocator: mem.Allocator, transform_fn: *const PathType.TransformFn) TestContext { 84 const tmp = tmpDir(.{ .iterate = true }); 85 return .{ 86 .path_type = path_type, 87 .path_sep = path_sep, 88 .arena = ArenaAllocator.init(allocator), 89 .tmp = tmp, 90 .dir = tmp.dir, 91 .transform_fn = transform_fn, 92 }; 93 } 94 95 pub fn deinit(self: *TestContext) void { 96 self.arena.deinit(); 97 self.tmp.cleanup(); 98 } 99 100 /// Returns the `relative_path` transformed into the TestContext's `path_type`, 101 /// with any supported path separators replaced by `path_sep`. 102 /// The result is allocated by the TestContext's arena and will be free'd during 103 /// `TestContext.deinit`. 104 pub fn transformPath(self: *TestContext, relative_path: [:0]const u8) ![:0]const u8 { 105 const allocator = self.arena.allocator(); 106 const transformed_path = try self.transform_fn(allocator, self.dir, relative_path); 107 if (native_os == .windows) { 108 const transformed_sep_path = try allocator.dupeZ(u8, transformed_path); 109 std.mem.replaceScalar(u8, transformed_sep_path, switch (self.path_sep) { 110 '/' => '\\', 111 '\\' => '/', 112 else => unreachable, 113 }, self.path_sep); 114 return transformed_sep_path; 115 } 116 return transformed_path; 117 } 118 119 /// Replaces any path separators with the canonical path separator for the platform 120 /// (e.g. all path separators are converted to `\` on Windows). 121 /// If path separators are replaced, then the result is allocated by the 122 /// TestContext's arena and will be free'd during `TestContext.deinit`. 123 pub fn toCanonicalPathSep(self: *TestContext, path: [:0]const u8) ![:0]const u8 { 124 if (native_os == .windows) { 125 const allocator = self.arena.allocator(); 126 const transformed_sep_path = try allocator.dupeZ(u8, path); 127 std.mem.replaceScalar(u8, transformed_sep_path, '/', '\\'); 128 return transformed_sep_path; 129 } 130 return path; 131 } 132 }; 133 134 /// `test_func` must be a function that takes a `*TestContext` as a parameter and returns `!void`. 135 /// `test_func` will be called once for each PathType that the current target supports, 136 /// and will be passed a TestContext that can transform a relative path into the path type under test. 137 /// The TestContext will also create a tmp directory for you (and will clean it up for you too). 138 fn testWithAllSupportedPathTypes(test_func: anytype) !void { 139 try testWithPathTypeIfSupported(.relative, '/', test_func); 140 try testWithPathTypeIfSupported(.absolute, '/', test_func); 141 try testWithPathTypeIfSupported(.unc, '/', test_func); 142 try testWithPathTypeIfSupported(.relative, '\\', test_func); 143 try testWithPathTypeIfSupported(.absolute, '\\', test_func); 144 try testWithPathTypeIfSupported(.unc, '\\', test_func); 145 } 146 147 fn testWithPathTypeIfSupported(comptime path_type: PathType, comptime path_sep: u8, test_func: anytype) !void { 148 if (!(comptime path_type.isSupported(builtin.os))) return; 149 if (!(comptime fs.path.isSep(path_sep))) return; 150 151 var ctx = TestContext.init(path_type, path_sep, testing.allocator, path_type.getTransformFn()); 152 defer ctx.deinit(); 153 154 try test_func(&ctx); 155 } 156 157 // For use in test setup. If the symlink creation fails on Windows with 158 // AccessDenied, then make the test failure silent (it is not a Zig failure). 159 fn setupSymlink(dir: Dir, target: []const u8, link: []const u8, flags: SymLinkFlags) !void { 160 return dir.symLink(target, link, flags) catch |err| switch (err) { 161 // Symlink requires admin privileges on windows, so this test can legitimately fail. 162 error.AccessDenied => if (native_os == .windows) return error.SkipZigTest else return err, 163 else => return err, 164 }; 165 } 166 167 // For use in test setup. If the symlink creation fails on Windows with 168 // AccessDenied, then make the test failure silent (it is not a Zig failure). 169 fn setupSymlinkAbsolute(target: []const u8, link: []const u8, flags: SymLinkFlags) !void { 170 return fs.symLinkAbsolute(target, link, flags) catch |err| switch (err) { 171 error.AccessDenied => if (native_os == .windows) return error.SkipZigTest else return err, 172 else => return err, 173 }; 174 } 175 176 test "Dir.readLink" { 177 try testWithAllSupportedPathTypes(struct { 178 fn impl(ctx: *TestContext) !void { 179 // Create some targets 180 const file_target_path = try ctx.transformPath("file.txt"); 181 try ctx.dir.writeFile(.{ .sub_path = file_target_path, .data = "nonsense" }); 182 const dir_target_path = try ctx.transformPath("subdir"); 183 try ctx.dir.makeDir(dir_target_path); 184 185 // On Windows, symlink targets always use the canonical path separator 186 const canonical_file_target_path = try ctx.toCanonicalPathSep(file_target_path); 187 const canonical_dir_target_path = try ctx.toCanonicalPathSep(dir_target_path); 188 189 // test 1: symlink to a file 190 try setupSymlink(ctx.dir, file_target_path, "symlink1", .{}); 191 try testReadLink(ctx.dir, canonical_file_target_path, "symlink1"); 192 193 // test 2: symlink to a directory (can be different on Windows) 194 try setupSymlink(ctx.dir, dir_target_path, "symlink2", .{ .is_directory = true }); 195 try testReadLink(ctx.dir, canonical_dir_target_path, "symlink2"); 196 197 // test 3: relative path symlink 198 const parent_file = ".." ++ fs.path.sep_str ++ "target.txt"; 199 const canonical_parent_file = try ctx.toCanonicalPathSep(parent_file); 200 var subdir = try ctx.dir.makeOpenPath("subdir", .{}); 201 defer subdir.close(); 202 try setupSymlink(subdir, canonical_parent_file, "relative-link.txt", .{}); 203 try testReadLink(subdir, canonical_parent_file, "relative-link.txt"); 204 } 205 }.impl); 206 } 207 208 fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void { 209 var buffer: [fs.max_path_bytes]u8 = undefined; 210 const actual = try dir.readLink(symlink_path, buffer[0..]); 211 try testing.expectEqualStrings(target_path, actual); 212 } 213 214 fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void { 215 var buffer: [fs.max_path_bytes]u8 = undefined; 216 const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]); 217 try testing.expectEqualStrings(target_path, given); 218 } 219 220 test "File.stat on a File that is a symlink returns Kind.sym_link" { 221 // This test requires getting a file descriptor of a symlink which 222 // is not possible on all targets 223 switch (builtin.target.os.tag) { 224 .windows, .linux => {}, 225 else => return error.SkipZigTest, 226 } 227 228 try testWithAllSupportedPathTypes(struct { 229 fn impl(ctx: *TestContext) !void { 230 const dir_target_path = try ctx.transformPath("subdir"); 231 try ctx.dir.makeDir(dir_target_path); 232 233 try setupSymlink(ctx.dir, dir_target_path, "symlink", .{ .is_directory = true }); 234 235 var symlink = switch (builtin.target.os.tag) { 236 .windows => windows_symlink: { 237 const sub_path_w = try windows.cStrToPrefixedFileW(ctx.dir.fd, "symlink"); 238 239 var result = Dir{ 240 .fd = undefined, 241 }; 242 243 const path_len_bytes = @as(u16, @intCast(sub_path_w.span().len * 2)); 244 var nt_name = windows.UNICODE_STRING{ 245 .Length = path_len_bytes, 246 .MaximumLength = path_len_bytes, 247 .Buffer = @constCast(&sub_path_w.data), 248 }; 249 var attr = windows.OBJECT_ATTRIBUTES{ 250 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), 251 .RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else ctx.dir.fd, 252 .Attributes = 0, 253 .ObjectName = &nt_name, 254 .SecurityDescriptor = null, 255 .SecurityQualityOfService = null, 256 }; 257 var io: windows.IO_STATUS_BLOCK = undefined; 258 const rc = windows.ntdll.NtCreateFile( 259 &result.fd, 260 windows.STANDARD_RIGHTS_READ | windows.FILE_READ_ATTRIBUTES | windows.FILE_READ_EA | windows.SYNCHRONIZE | windows.FILE_TRAVERSE, 261 &attr, 262 &io, 263 null, 264 windows.FILE_ATTRIBUTE_NORMAL, 265 windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE | windows.FILE_SHARE_DELETE, 266 windows.FILE_OPEN, 267 // FILE_OPEN_REPARSE_POINT is the important thing here 268 windows.FILE_OPEN_REPARSE_POINT | windows.FILE_DIRECTORY_FILE | windows.FILE_SYNCHRONOUS_IO_NONALERT | windows.FILE_OPEN_FOR_BACKUP_INTENT, 269 null, 270 0, 271 ); 272 273 switch (rc) { 274 .SUCCESS => break :windows_symlink result, 275 else => return windows.unexpectedStatus(rc), 276 } 277 }, 278 .linux => linux_symlink: { 279 const sub_path_c = try posix.toPosixPath("symlink"); 280 // the O_NOFOLLOW | O_PATH combination can obtain a fd to a symlink 281 // note that if O_DIRECTORY is set, then this will error with ENOTDIR 282 const flags: posix.O = .{ 283 .NOFOLLOW = true, 284 .PATH = true, 285 .ACCMODE = .RDONLY, 286 .CLOEXEC = true, 287 }; 288 const fd = try posix.openatZ(ctx.dir.fd, &sub_path_c, flags, 0); 289 break :linux_symlink Dir{ .fd = fd }; 290 }, 291 else => unreachable, 292 }; 293 defer symlink.close(); 294 295 const stat = try symlink.stat(); 296 try testing.expectEqual(File.Kind.sym_link, stat.kind); 297 } 298 }.impl); 299 } 300 301 test "openDir" { 302 try testWithAllSupportedPathTypes(struct { 303 fn impl(ctx: *TestContext) !void { 304 const allocator = ctx.arena.allocator(); 305 const subdir_path = try ctx.transformPath("subdir"); 306 try ctx.dir.makeDir(subdir_path); 307 308 for ([_][]const u8{ "", ".", ".." }) |sub_path| { 309 const dir_path = try fs.path.join(allocator, &.{ subdir_path, sub_path }); 310 var dir = try ctx.dir.openDir(dir_path, .{}); 311 defer dir.close(); 312 } 313 } 314 }.impl); 315 } 316 317 test "accessAbsolute" { 318 if (native_os == .wasi) return error.SkipZigTest; 319 320 var tmp = tmpDir(.{}); 321 defer tmp.cleanup(); 322 323 const base_path = try tmp.dir.realpathAlloc(testing.allocator, "."); 324 defer testing.allocator.free(base_path); 325 326 try fs.accessAbsolute(base_path, .{}); 327 } 328 329 test "openDirAbsolute" { 330 if (native_os == .wasi) return error.SkipZigTest; 331 332 var tmp = tmpDir(.{}); 333 defer tmp.cleanup(); 334 335 const tmp_ino = (try tmp.dir.stat()).inode; 336 337 try tmp.dir.makeDir("subdir"); 338 const sub_path = try tmp.dir.realpathAlloc(testing.allocator, "subdir"); 339 defer testing.allocator.free(sub_path); 340 341 // Can open sub_path 342 var tmp_sub = try fs.openDirAbsolute(sub_path, .{}); 343 defer tmp_sub.close(); 344 345 const sub_ino = (try tmp_sub.stat()).inode; 346 347 { 348 // Can open sub_path + ".." 349 const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, ".." }); 350 defer testing.allocator.free(dir_path); 351 352 var dir = try fs.openDirAbsolute(dir_path, .{}); 353 defer dir.close(); 354 355 const ino = (try dir.stat()).inode; 356 try testing.expectEqual(tmp_ino, ino); 357 } 358 359 { 360 // Can open sub_path + "." 361 const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, "." }); 362 defer testing.allocator.free(dir_path); 363 364 var dir = try fs.openDirAbsolute(dir_path, .{}); 365 defer dir.close(); 366 367 const ino = (try dir.stat()).inode; 368 try testing.expectEqual(sub_ino, ino); 369 } 370 371 { 372 // Can open subdir + "..", with some extra "." 373 const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, ".", "..", "." }); 374 defer testing.allocator.free(dir_path); 375 376 var dir = try fs.openDirAbsolute(dir_path, .{}); 377 defer dir.close(); 378 379 const ino = (try dir.stat()).inode; 380 try testing.expectEqual(tmp_ino, ino); 381 } 382 } 383 384 test "openDir cwd parent '..'" { 385 var dir = fs.cwd().openDir("..", .{}) catch |err| { 386 if (native_os == .wasi and err == error.PermissionDenied) { 387 return; // This is okay. WASI disallows escaping from the fs sandbox 388 } 389 return err; 390 }; 391 defer dir.close(); 392 } 393 394 test "openDir non-cwd parent '..'" { 395 switch (native_os) { 396 .wasi, .netbsd, .openbsd => return error.SkipZigTest, 397 else => {}, 398 } 399 400 var tmp = tmpDir(.{}); 401 defer tmp.cleanup(); 402 403 var subdir = try tmp.dir.makeOpenPath("subdir", .{}); 404 defer subdir.close(); 405 406 var dir = try subdir.openDir("..", .{}); 407 defer dir.close(); 408 409 const expected_path = try tmp.dir.realpathAlloc(testing.allocator, "."); 410 defer testing.allocator.free(expected_path); 411 412 const actual_path = try dir.realpathAlloc(testing.allocator, "."); 413 defer testing.allocator.free(actual_path); 414 415 try testing.expectEqualStrings(expected_path, actual_path); 416 } 417 418 test "readLinkAbsolute" { 419 if (native_os == .wasi) return error.SkipZigTest; 420 421 var tmp = tmpDir(.{}); 422 defer tmp.cleanup(); 423 424 // Create some targets 425 try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = "nonsense" }); 426 try tmp.dir.makeDir("subdir"); 427 428 // Get base abs path 429 var arena = ArenaAllocator.init(testing.allocator); 430 defer arena.deinit(); 431 const allocator = arena.allocator(); 432 433 const base_path = try tmp.dir.realpathAlloc(allocator, "."); 434 435 { 436 const target_path = try fs.path.join(allocator, &.{ base_path, "file.txt" }); 437 const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink1" }); 438 439 // Create symbolic link by path 440 try setupSymlinkAbsolute(target_path, symlink_path, .{}); 441 try testReadLinkAbsolute(target_path, symlink_path); 442 } 443 { 444 const target_path = try fs.path.join(allocator, &.{ base_path, "subdir" }); 445 const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink2" }); 446 447 // Create symbolic link to a directory by path 448 try setupSymlinkAbsolute(target_path, symlink_path, .{ .is_directory = true }); 449 try testReadLinkAbsolute(target_path, symlink_path); 450 } 451 } 452 453 test "Dir.Iterator" { 454 var tmp_dir = tmpDir(.{ .iterate = true }); 455 defer tmp_dir.cleanup(); 456 457 // First, create a couple of entries to iterate over. 458 const file = try tmp_dir.dir.createFile("some_file", .{}); 459 file.close(); 460 461 try tmp_dir.dir.makeDir("some_dir"); 462 463 var arena = ArenaAllocator.init(testing.allocator); 464 defer arena.deinit(); 465 const allocator = arena.allocator(); 466 467 var entries = std.array_list.Managed(Dir.Entry).init(allocator); 468 469 // Create iterator. 470 var iter = tmp_dir.dir.iterate(); 471 while (try iter.next()) |entry| { 472 // We cannot just store `entry` as on Windows, we're re-using the name buffer 473 // which means we'll actually share the `name` pointer between entries! 474 const name = try allocator.dupe(u8, entry.name); 475 try entries.append(Dir.Entry{ .name = name, .kind = entry.kind }); 476 } 477 478 try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' 479 try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file })); 480 try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory })); 481 } 482 483 test "Dir.Iterator many entries" { 484 var tmp_dir = tmpDir(.{ .iterate = true }); 485 defer tmp_dir.cleanup(); 486 487 const num = 1024; 488 var i: usize = 0; 489 var buf: [4]u8 = undefined; // Enough to store "1024". 490 while (i < num) : (i += 1) { 491 const name = try std.fmt.bufPrint(&buf, "{}", .{i}); 492 const file = try tmp_dir.dir.createFile(name, .{}); 493 file.close(); 494 } 495 496 var arena = ArenaAllocator.init(testing.allocator); 497 defer arena.deinit(); 498 const allocator = arena.allocator(); 499 500 var entries = std.array_list.Managed(Dir.Entry).init(allocator); 501 502 // Create iterator. 503 var iter = tmp_dir.dir.iterate(); 504 while (try iter.next()) |entry| { 505 // We cannot just store `entry` as on Windows, we're re-using the name buffer 506 // which means we'll actually share the `name` pointer between entries! 507 const name = try allocator.dupe(u8, entry.name); 508 try entries.append(.{ .name = name, .kind = entry.kind }); 509 } 510 511 i = 0; 512 while (i < num) : (i += 1) { 513 const name = try std.fmt.bufPrint(&buf, "{}", .{i}); 514 try testing.expect(contains(&entries, .{ .name = name, .kind = .file })); 515 } 516 } 517 518 test "Dir.Iterator twice" { 519 var tmp_dir = tmpDir(.{ .iterate = true }); 520 defer tmp_dir.cleanup(); 521 522 // First, create a couple of entries to iterate over. 523 const file = try tmp_dir.dir.createFile("some_file", .{}); 524 file.close(); 525 526 try tmp_dir.dir.makeDir("some_dir"); 527 528 var arena = ArenaAllocator.init(testing.allocator); 529 defer arena.deinit(); 530 const allocator = arena.allocator(); 531 532 var i: u8 = 0; 533 while (i < 2) : (i += 1) { 534 var entries = std.array_list.Managed(Dir.Entry).init(allocator); 535 536 // Create iterator. 537 var iter = tmp_dir.dir.iterate(); 538 while (try iter.next()) |entry| { 539 // We cannot just store `entry` as on Windows, we're re-using the name buffer 540 // which means we'll actually share the `name` pointer between entries! 541 const name = try allocator.dupe(u8, entry.name); 542 try entries.append(Dir.Entry{ .name = name, .kind = entry.kind }); 543 } 544 545 try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' 546 try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file })); 547 try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory })); 548 } 549 } 550 551 test "Dir.Iterator reset" { 552 var tmp_dir = tmpDir(.{ .iterate = true }); 553 defer tmp_dir.cleanup(); 554 555 // First, create a couple of entries to iterate over. 556 const file = try tmp_dir.dir.createFile("some_file", .{}); 557 file.close(); 558 559 try tmp_dir.dir.makeDir("some_dir"); 560 561 var arena = ArenaAllocator.init(testing.allocator); 562 defer arena.deinit(); 563 const allocator = arena.allocator(); 564 565 // Create iterator. 566 var iter = tmp_dir.dir.iterate(); 567 568 var i: u8 = 0; 569 while (i < 2) : (i += 1) { 570 var entries = std.array_list.Managed(Dir.Entry).init(allocator); 571 572 while (try iter.next()) |entry| { 573 // We cannot just store `entry` as on Windows, we're re-using the name buffer 574 // which means we'll actually share the `name` pointer between entries! 575 const name = try allocator.dupe(u8, entry.name); 576 try entries.append(.{ .name = name, .kind = entry.kind }); 577 } 578 579 try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' 580 try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file })); 581 try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory })); 582 583 iter.reset(); 584 } 585 } 586 587 test "Dir.Iterator but dir is deleted during iteration" { 588 var tmp = std.testing.tmpDir(.{}); 589 defer tmp.cleanup(); 590 591 // Create directory and setup an iterator for it 592 var subdir = try tmp.dir.makeOpenPath("subdir", .{ .iterate = true }); 593 defer subdir.close(); 594 595 var iterator = subdir.iterate(); 596 597 // Create something to iterate over within the subdir 598 try tmp.dir.makePath("subdir" ++ fs.path.sep_str ++ "b"); 599 600 // Then, before iterating, delete the directory that we're iterating. 601 // This is a contrived reproduction, but this could happen outside of the program, in another thread, etc. 602 // If we get an error while trying to delete, we can skip this test (this will happen on platforms 603 // like Windows which will give FileBusy if the directory is currently open for iteration). 604 tmp.dir.deleteTree("subdir") catch return error.SkipZigTest; 605 606 // Now, when we try to iterate, the next call should return null immediately. 607 const entry = try iterator.next(); 608 try std.testing.expect(entry == null); 609 610 // On Linux, we can opt-in to receiving a more specific error by calling `nextLinux` 611 if (native_os == .linux) { 612 try std.testing.expectError(error.DirNotFound, iterator.nextLinux()); 613 } 614 } 615 616 fn entryEql(lhs: Dir.Entry, rhs: Dir.Entry) bool { 617 return mem.eql(u8, lhs.name, rhs.name) and lhs.kind == rhs.kind; 618 } 619 620 fn contains(entries: *const std.array_list.Managed(Dir.Entry), el: Dir.Entry) bool { 621 for (entries.items) |entry| { 622 if (entryEql(entry, el)) return true; 623 } 624 return false; 625 } 626 627 test "Dir.realpath smoke test" { 628 if (!comptime std.os.isGetFdPathSupportedOnTarget(builtin.os)) return error.SkipZigTest; 629 630 try testWithAllSupportedPathTypes(struct { 631 fn impl(ctx: *TestContext) !void { 632 const allocator = ctx.arena.allocator(); 633 const test_file_path = try ctx.transformPath("test_file"); 634 const test_dir_path = try ctx.transformPath("test_dir"); 635 var buf: [fs.max_path_bytes]u8 = undefined; 636 637 // FileNotFound if the path doesn't exist 638 try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_file_path)); 639 try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_file_path, &buf)); 640 try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_dir_path)); 641 try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_dir_path, &buf)); 642 643 // Now create the file and dir 644 try ctx.dir.writeFile(.{ .sub_path = test_file_path, .data = "" }); 645 try ctx.dir.makeDir(test_dir_path); 646 647 const base_path = try ctx.transformPath("."); 648 const base_realpath = try ctx.dir.realpathAlloc(allocator, base_path); 649 const expected_file_path = try fs.path.join( 650 allocator, 651 &.{ base_realpath, "test_file" }, 652 ); 653 const expected_dir_path = try fs.path.join( 654 allocator, 655 &.{ base_realpath, "test_dir" }, 656 ); 657 658 // First, test non-alloc version 659 { 660 const file_path = try ctx.dir.realpath(test_file_path, &buf); 661 try testing.expectEqualStrings(expected_file_path, file_path); 662 663 const dir_path = try ctx.dir.realpath(test_dir_path, &buf); 664 try testing.expectEqualStrings(expected_dir_path, dir_path); 665 } 666 667 // Next, test alloc version 668 { 669 const file_path = try ctx.dir.realpathAlloc(allocator, test_file_path); 670 try testing.expectEqualStrings(expected_file_path, file_path); 671 672 const dir_path = try ctx.dir.realpathAlloc(allocator, test_dir_path); 673 try testing.expectEqualStrings(expected_dir_path, dir_path); 674 } 675 } 676 }.impl); 677 } 678 679 test "readAllAlloc" { 680 var tmp_dir = tmpDir(.{}); 681 defer tmp_dir.cleanup(); 682 683 var file = try tmp_dir.dir.createFile("test_file", .{ .read = true }); 684 defer file.close(); 685 686 const buf1 = try file.readToEndAlloc(testing.allocator, 1024); 687 defer testing.allocator.free(buf1); 688 try testing.expectEqual(@as(usize, 0), buf1.len); 689 690 const write_buf: []const u8 = "this is a test.\nthis is a test.\nthis is a test.\nthis is a test.\n"; 691 try file.writeAll(write_buf); 692 try file.seekTo(0); 693 694 // max_bytes > file_size 695 const buf2 = try file.readToEndAlloc(testing.allocator, 1024); 696 defer testing.allocator.free(buf2); 697 try testing.expectEqual(write_buf.len, buf2.len); 698 try testing.expectEqualStrings(write_buf, buf2); 699 try file.seekTo(0); 700 701 // max_bytes == file_size 702 const buf3 = try file.readToEndAlloc(testing.allocator, write_buf.len); 703 defer testing.allocator.free(buf3); 704 try testing.expectEqual(write_buf.len, buf3.len); 705 try testing.expectEqualStrings(write_buf, buf3); 706 try file.seekTo(0); 707 708 // max_bytes < file_size 709 try testing.expectError(error.FileTooBig, file.readToEndAlloc(testing.allocator, write_buf.len - 1)); 710 } 711 712 test "Dir.statFile" { 713 try testWithAllSupportedPathTypes(struct { 714 fn impl(ctx: *TestContext) !void { 715 const test_file_name = try ctx.transformPath("test_file"); 716 717 try testing.expectError(error.FileNotFound, ctx.dir.statFile(test_file_name)); 718 719 try ctx.dir.writeFile(.{ .sub_path = test_file_name, .data = "" }); 720 721 const stat = try ctx.dir.statFile(test_file_name); 722 try testing.expectEqual(File.Kind.file, stat.kind); 723 } 724 }.impl); 725 } 726 727 test "statFile on dangling symlink" { 728 try testWithAllSupportedPathTypes(struct { 729 fn impl(ctx: *TestContext) !void { 730 const symlink_name = try ctx.transformPath("dangling-symlink"); 731 const symlink_target = "." ++ fs.path.sep_str ++ "doesnotexist"; 732 733 try setupSymlink(ctx.dir, symlink_target, symlink_name, .{}); 734 735 try std.testing.expectError(error.FileNotFound, ctx.dir.statFile(symlink_name)); 736 } 737 }.impl); 738 } 739 740 test "directory operations on files" { 741 try testWithAllSupportedPathTypes(struct { 742 fn impl(ctx: *TestContext) !void { 743 const test_file_name = try ctx.transformPath("test_file"); 744 745 var file = try ctx.dir.createFile(test_file_name, .{ .read = true }); 746 file.close(); 747 748 try testing.expectError(error.PathAlreadyExists, ctx.dir.makeDir(test_file_name)); 749 try testing.expectError(error.NotDir, ctx.dir.openDir(test_file_name, .{})); 750 try testing.expectError(error.NotDir, ctx.dir.deleteDir(test_file_name)); 751 752 if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) { 753 try testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(test_file_name)); 754 try testing.expectError(error.NotDir, fs.deleteDirAbsolute(test_file_name)); 755 } 756 757 // ensure the file still exists and is a file as a sanity check 758 file = try ctx.dir.openFile(test_file_name, .{}); 759 const stat = try file.stat(); 760 try testing.expectEqual(File.Kind.file, stat.kind); 761 file.close(); 762 } 763 }.impl); 764 } 765 766 test "file operations on directories" { 767 // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759 768 if (native_os == .freebsd) return error.SkipZigTest; 769 770 try testWithAllSupportedPathTypes(struct { 771 fn impl(ctx: *TestContext) !void { 772 const test_dir_name = try ctx.transformPath("test_dir"); 773 774 try ctx.dir.makeDir(test_dir_name); 775 776 try testing.expectError(error.IsDir, ctx.dir.createFile(test_dir_name, .{})); 777 try testing.expectError(error.IsDir, ctx.dir.deleteFile(test_dir_name)); 778 switch (native_os) { 779 .dragonfly, .netbsd => { 780 // no error when reading a directory. See https://github.com/ziglang/zig/issues/5732 781 const buf = try ctx.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize)); 782 testing.allocator.free(buf); 783 }, 784 .wasi => { 785 // WASI return EBADF, which gets mapped to NotOpenForReading. 786 // See https://github.com/bytecodealliance/wasmtime/issues/1935 787 try testing.expectError(error.NotOpenForReading, ctx.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize))); 788 }, 789 else => { 790 try testing.expectError(error.IsDir, ctx.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize))); 791 }, 792 } 793 794 if (native_os == .wasi and builtin.link_libc) { 795 // wasmtime unexpectedly succeeds here, see https://github.com/ziglang/zig/issues/20747 796 const handle = try ctx.dir.openFile(test_dir_name, .{ .mode = .read_write }); 797 handle.close(); 798 } else { 799 // Note: The `.mode = .read_write` is necessary to ensure the error occurs on all platforms. 800 // TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732 801 try testing.expectError(error.IsDir, ctx.dir.openFile(test_dir_name, .{ .mode = .read_write })); 802 } 803 804 if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) { 805 try testing.expectError(error.IsDir, fs.createFileAbsolute(test_dir_name, .{})); 806 try testing.expectError(error.IsDir, fs.deleteFileAbsolute(test_dir_name)); 807 } 808 809 // ensure the directory still exists as a sanity check 810 var dir = try ctx.dir.openDir(test_dir_name, .{}); 811 dir.close(); 812 } 813 }.impl); 814 } 815 816 test "makeOpenPath parent dirs do not exist" { 817 var tmp_dir = tmpDir(.{}); 818 defer tmp_dir.cleanup(); 819 820 var dir = try tmp_dir.dir.makeOpenPath("root_dir/parent_dir/some_dir", .{}); 821 dir.close(); 822 823 // double check that the full directory structure was created 824 var dir_verification = try tmp_dir.dir.openDir("root_dir/parent_dir/some_dir", .{}); 825 dir_verification.close(); 826 } 827 828 test "deleteDir" { 829 try testWithAllSupportedPathTypes(struct { 830 fn impl(ctx: *TestContext) !void { 831 const test_dir_path = try ctx.transformPath("test_dir"); 832 const test_file_path = try ctx.transformPath("test_dir" ++ fs.path.sep_str ++ "test_file"); 833 834 // deleting a non-existent directory 835 try testing.expectError(error.FileNotFound, ctx.dir.deleteDir(test_dir_path)); 836 837 // deleting a non-empty directory 838 try ctx.dir.makeDir(test_dir_path); 839 try ctx.dir.writeFile(.{ .sub_path = test_file_path, .data = "" }); 840 try testing.expectError(error.DirNotEmpty, ctx.dir.deleteDir(test_dir_path)); 841 842 // deleting an empty directory 843 try ctx.dir.deleteFile(test_file_path); 844 try ctx.dir.deleteDir(test_dir_path); 845 } 846 }.impl); 847 } 848 849 test "Dir.rename files" { 850 try testWithAllSupportedPathTypes(struct { 851 fn impl(ctx: *TestContext) !void { 852 // Rename on Windows can hit intermittent AccessDenied errors 853 // when certain conditions are true about the host system. 854 // For now, skip this test when the path type is UNC to avoid them. 855 // See https://github.com/ziglang/zig/issues/17134 856 if (ctx.path_type == .unc) return; 857 858 const missing_file_path = try ctx.transformPath("missing_file_name"); 859 const something_else_path = try ctx.transformPath("something_else"); 860 861 try testing.expectError(error.FileNotFound, ctx.dir.rename(missing_file_path, something_else_path)); 862 863 // Renaming files 864 const test_file_name = try ctx.transformPath("test_file"); 865 const renamed_test_file_name = try ctx.transformPath("test_file_renamed"); 866 var file = try ctx.dir.createFile(test_file_name, .{ .read = true }); 867 file.close(); 868 try ctx.dir.rename(test_file_name, renamed_test_file_name); 869 870 // Ensure the file was renamed 871 try testing.expectError(error.FileNotFound, ctx.dir.openFile(test_file_name, .{})); 872 file = try ctx.dir.openFile(renamed_test_file_name, .{}); 873 file.close(); 874 875 // Rename to self succeeds 876 try ctx.dir.rename(renamed_test_file_name, renamed_test_file_name); 877 878 // Rename to existing file succeeds 879 const existing_file_path = try ctx.transformPath("existing_file"); 880 var existing_file = try ctx.dir.createFile(existing_file_path, .{ .read = true }); 881 existing_file.close(); 882 try ctx.dir.rename(renamed_test_file_name, existing_file_path); 883 884 try testing.expectError(error.FileNotFound, ctx.dir.openFile(renamed_test_file_name, .{})); 885 file = try ctx.dir.openFile(existing_file_path, .{}); 886 file.close(); 887 } 888 }.impl); 889 } 890 891 test "Dir.rename directories" { 892 try testWithAllSupportedPathTypes(struct { 893 fn impl(ctx: *TestContext) !void { 894 // Rename on Windows can hit intermittent AccessDenied errors 895 // when certain conditions are true about the host system. 896 // For now, skip this test when the path type is UNC to avoid them. 897 // See https://github.com/ziglang/zig/issues/17134 898 if (ctx.path_type == .unc) return; 899 900 const test_dir_path = try ctx.transformPath("test_dir"); 901 const test_dir_renamed_path = try ctx.transformPath("test_dir_renamed"); 902 903 // Renaming directories 904 try ctx.dir.makeDir(test_dir_path); 905 try ctx.dir.rename(test_dir_path, test_dir_renamed_path); 906 907 // Ensure the directory was renamed 908 try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{})); 909 var dir = try ctx.dir.openDir(test_dir_renamed_path, .{}); 910 911 // Put a file in the directory 912 var file = try dir.createFile("test_file", .{ .read = true }); 913 file.close(); 914 dir.close(); 915 916 const test_dir_renamed_again_path = try ctx.transformPath("test_dir_renamed_again"); 917 try ctx.dir.rename(test_dir_renamed_path, test_dir_renamed_again_path); 918 919 // Ensure the directory was renamed and the file still exists in it 920 try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_renamed_path, .{})); 921 dir = try ctx.dir.openDir(test_dir_renamed_again_path, .{}); 922 file = try dir.openFile("test_file", .{}); 923 file.close(); 924 dir.close(); 925 } 926 }.impl); 927 } 928 929 test "Dir.rename directory onto empty dir" { 930 // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364 931 if (native_os == .windows) return error.SkipZigTest; 932 933 try testWithAllSupportedPathTypes(struct { 934 fn impl(ctx: *TestContext) !void { 935 const test_dir_path = try ctx.transformPath("test_dir"); 936 const target_dir_path = try ctx.transformPath("target_dir_path"); 937 938 try ctx.dir.makeDir(test_dir_path); 939 try ctx.dir.makeDir(target_dir_path); 940 try ctx.dir.rename(test_dir_path, target_dir_path); 941 942 // Ensure the directory was renamed 943 try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{})); 944 var dir = try ctx.dir.openDir(target_dir_path, .{}); 945 dir.close(); 946 } 947 }.impl); 948 } 949 950 test "Dir.rename directory onto non-empty dir" { 951 // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364 952 if (native_os == .windows) return error.SkipZigTest; 953 954 try testWithAllSupportedPathTypes(struct { 955 fn impl(ctx: *TestContext) !void { 956 const test_dir_path = try ctx.transformPath("test_dir"); 957 const target_dir_path = try ctx.transformPath("target_dir_path"); 958 959 try ctx.dir.makeDir(test_dir_path); 960 961 var target_dir = try ctx.dir.makeOpenPath(target_dir_path, .{}); 962 var file = try target_dir.createFile("test_file", .{ .read = true }); 963 file.close(); 964 target_dir.close(); 965 966 // Rename should fail with PathAlreadyExists if target_dir is non-empty 967 try testing.expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, target_dir_path)); 968 969 // Ensure the directory was not renamed 970 var dir = try ctx.dir.openDir(test_dir_path, .{}); 971 dir.close(); 972 } 973 }.impl); 974 } 975 976 test "Dir.rename file <-> dir" { 977 // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364 978 if (native_os == .windows) return error.SkipZigTest; 979 980 try testWithAllSupportedPathTypes(struct { 981 fn impl(ctx: *TestContext) !void { 982 const test_file_path = try ctx.transformPath("test_file"); 983 const test_dir_path = try ctx.transformPath("test_dir"); 984 985 var file = try ctx.dir.createFile(test_file_path, .{ .read = true }); 986 file.close(); 987 try ctx.dir.makeDir(test_dir_path); 988 try testing.expectError(error.IsDir, ctx.dir.rename(test_file_path, test_dir_path)); 989 try testing.expectError(error.NotDir, ctx.dir.rename(test_dir_path, test_file_path)); 990 } 991 }.impl); 992 } 993 994 test "rename" { 995 var tmp_dir1 = tmpDir(.{}); 996 defer tmp_dir1.cleanup(); 997 998 var tmp_dir2 = tmpDir(.{}); 999 defer tmp_dir2.cleanup(); 1000 1001 // Renaming files 1002 const test_file_name = "test_file"; 1003 const renamed_test_file_name = "test_file_renamed"; 1004 var file = try tmp_dir1.dir.createFile(test_file_name, .{ .read = true }); 1005 file.close(); 1006 try fs.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name); 1007 1008 // ensure the file was renamed 1009 try testing.expectError(error.FileNotFound, tmp_dir1.dir.openFile(test_file_name, .{})); 1010 file = try tmp_dir2.dir.openFile(renamed_test_file_name, .{}); 1011 file.close(); 1012 } 1013 1014 test "renameAbsolute" { 1015 if (native_os == .wasi) return error.SkipZigTest; 1016 1017 var tmp_dir = tmpDir(.{}); 1018 defer tmp_dir.cleanup(); 1019 1020 // Get base abs path 1021 var arena = ArenaAllocator.init(testing.allocator); 1022 defer arena.deinit(); 1023 const allocator = arena.allocator(); 1024 1025 const base_path = try tmp_dir.dir.realpathAlloc(allocator, "."); 1026 1027 try testing.expectError(error.FileNotFound, fs.renameAbsolute( 1028 try fs.path.join(allocator, &.{ base_path, "missing_file_name" }), 1029 try fs.path.join(allocator, &.{ base_path, "something_else" }), 1030 )); 1031 1032 // Renaming files 1033 const test_file_name = "test_file"; 1034 const renamed_test_file_name = "test_file_renamed"; 1035 var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true }); 1036 file.close(); 1037 try fs.renameAbsolute( 1038 try fs.path.join(allocator, &.{ base_path, test_file_name }), 1039 try fs.path.join(allocator, &.{ base_path, renamed_test_file_name }), 1040 ); 1041 1042 // ensure the file was renamed 1043 try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{})); 1044 file = try tmp_dir.dir.openFile(renamed_test_file_name, .{}); 1045 const stat = try file.stat(); 1046 try testing.expectEqual(File.Kind.file, stat.kind); 1047 file.close(); 1048 1049 // Renaming directories 1050 const test_dir_name = "test_dir"; 1051 const renamed_test_dir_name = "test_dir_renamed"; 1052 try tmp_dir.dir.makeDir(test_dir_name); 1053 try fs.renameAbsolute( 1054 try fs.path.join(allocator, &.{ base_path, test_dir_name }), 1055 try fs.path.join(allocator, &.{ base_path, renamed_test_dir_name }), 1056 ); 1057 1058 // ensure the directory was renamed 1059 try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir(test_dir_name, .{})); 1060 var dir = try tmp_dir.dir.openDir(renamed_test_dir_name, .{}); 1061 dir.close(); 1062 } 1063 1064 test "openSelfExe" { 1065 if (native_os == .wasi) return error.SkipZigTest; 1066 1067 const self_exe_file = try std.fs.openSelfExe(.{}); 1068 self_exe_file.close(); 1069 } 1070 1071 test "selfExePath" { 1072 if (native_os == .wasi) return error.SkipZigTest; 1073 1074 var buf: [fs.max_path_bytes]u8 = undefined; 1075 const buf_self_exe_path = try std.fs.selfExePath(&buf); 1076 const alloc_self_exe_path = try std.fs.selfExePathAlloc(testing.allocator); 1077 defer testing.allocator.free(alloc_self_exe_path); 1078 try testing.expectEqualSlices(u8, buf_self_exe_path, alloc_self_exe_path); 1079 } 1080 1081 test "deleteTree does not follow symlinks" { 1082 var tmp = tmpDir(.{}); 1083 defer tmp.cleanup(); 1084 1085 try tmp.dir.makePath("b"); 1086 { 1087 var a = try tmp.dir.makeOpenPath("a", .{}); 1088 defer a.close(); 1089 1090 try setupSymlink(a, "../b", "b", .{ .is_directory = true }); 1091 } 1092 1093 try tmp.dir.deleteTree("a"); 1094 1095 try testing.expectError(error.FileNotFound, tmp.dir.access("a", .{})); 1096 try tmp.dir.access("b", .{}); 1097 } 1098 1099 test "deleteTree on a symlink" { 1100 var tmp = tmpDir(.{}); 1101 defer tmp.cleanup(); 1102 1103 // Symlink to a file 1104 try tmp.dir.writeFile(.{ .sub_path = "file", .data = "" }); 1105 try setupSymlink(tmp.dir, "file", "filelink", .{}); 1106 1107 try tmp.dir.deleteTree("filelink"); 1108 try testing.expectError(error.FileNotFound, tmp.dir.access("filelink", .{})); 1109 try tmp.dir.access("file", .{}); 1110 1111 // Symlink to a directory 1112 try tmp.dir.makePath("dir"); 1113 try setupSymlink(tmp.dir, "dir", "dirlink", .{ .is_directory = true }); 1114 1115 try tmp.dir.deleteTree("dirlink"); 1116 try testing.expectError(error.FileNotFound, tmp.dir.access("dirlink", .{})); 1117 try tmp.dir.access("dir", .{}); 1118 } 1119 1120 test "makePath, put some files in it, deleteTree" { 1121 try testWithAllSupportedPathTypes(struct { 1122 fn impl(ctx: *TestContext) !void { 1123 const allocator = ctx.arena.allocator(); 1124 const dir_path = try ctx.transformPath("os_test_tmp"); 1125 1126 try ctx.dir.makePath(try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); 1127 try ctx.dir.writeFile(.{ 1128 .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }), 1129 .data = "nonsense", 1130 }); 1131 try ctx.dir.writeFile(.{ 1132 .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }), 1133 .data = "blah", 1134 }); 1135 1136 try ctx.dir.deleteTree(dir_path); 1137 try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{})); 1138 } 1139 }.impl); 1140 } 1141 1142 test "makePath, put some files in it, deleteTreeMinStackSize" { 1143 try testWithAllSupportedPathTypes(struct { 1144 fn impl(ctx: *TestContext) !void { 1145 const allocator = ctx.arena.allocator(); 1146 const dir_path = try ctx.transformPath("os_test_tmp"); 1147 1148 try ctx.dir.makePath(try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); 1149 try ctx.dir.writeFile(.{ 1150 .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }), 1151 .data = "nonsense", 1152 }); 1153 try ctx.dir.writeFile(.{ 1154 .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }), 1155 .data = "blah", 1156 }); 1157 1158 try ctx.dir.deleteTreeMinStackSize(dir_path); 1159 try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{})); 1160 } 1161 }.impl); 1162 } 1163 1164 test "makePath in a directory that no longer exists" { 1165 if (native_os == .windows) return error.SkipZigTest; // Windows returns FileBusy if attempting to remove an open dir 1166 1167 var tmp = tmpDir(.{}); 1168 defer tmp.cleanup(); 1169 try tmp.parent_dir.deleteTree(&tmp.sub_path); 1170 1171 try testing.expectError(error.FileNotFound, tmp.dir.makePath("sub-path")); 1172 } 1173 1174 test "makePath but sub_path contains pre-existing file" { 1175 var tmp = tmpDir(.{}); 1176 defer tmp.cleanup(); 1177 1178 try tmp.dir.makeDir("foo"); 1179 try tmp.dir.writeFile(.{ .sub_path = "foo/bar", .data = "" }); 1180 1181 try testing.expectError(error.NotDir, tmp.dir.makePath("foo/bar/baz")); 1182 } 1183 1184 fn expectDir(dir: Dir, path: []const u8) !void { 1185 var d = try dir.openDir(path, .{}); 1186 d.close(); 1187 } 1188 1189 test "makepath existing directories" { 1190 var tmp = tmpDir(.{}); 1191 defer tmp.cleanup(); 1192 1193 try tmp.dir.makeDir("A"); 1194 var tmpA = try tmp.dir.openDir("A", .{}); 1195 defer tmpA.close(); 1196 try tmpA.makeDir("B"); 1197 1198 const testPath = "A" ++ fs.path.sep_str ++ "B" ++ fs.path.sep_str ++ "C"; 1199 try tmp.dir.makePath(testPath); 1200 1201 try expectDir(tmp.dir, testPath); 1202 } 1203 1204 test "makepath through existing valid symlink" { 1205 var tmp = tmpDir(.{}); 1206 defer tmp.cleanup(); 1207 1208 try tmp.dir.makeDir("realfolder"); 1209 try setupSymlink(tmp.dir, "." ++ fs.path.sep_str ++ "realfolder", "working-symlink", .{}); 1210 1211 try tmp.dir.makePath("working-symlink" ++ fs.path.sep_str ++ "in-realfolder"); 1212 1213 try expectDir(tmp.dir, "realfolder" ++ fs.path.sep_str ++ "in-realfolder"); 1214 } 1215 1216 test "makepath relative walks" { 1217 var tmp = tmpDir(.{}); 1218 defer tmp.cleanup(); 1219 1220 const relPath = try fs.path.join(testing.allocator, &.{ 1221 "first", "..", "second", "..", "third", "..", "first", "A", "..", "B", "..", "C", 1222 }); 1223 defer testing.allocator.free(relPath); 1224 1225 try tmp.dir.makePath(relPath); 1226 1227 // How .. is handled is different on Windows than non-Windows 1228 switch (native_os) { 1229 .windows => { 1230 // On Windows, .. is resolved before passing the path to NtCreateFile, 1231 // meaning everything except `first/C` drops out. 1232 try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "C"); 1233 try testing.expectError(error.FileNotFound, tmp.dir.access("second", .{})); 1234 try testing.expectError(error.FileNotFound, tmp.dir.access("third", .{})); 1235 }, 1236 else => { 1237 try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "A"); 1238 try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "B"); 1239 try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "C"); 1240 try expectDir(tmp.dir, "second"); 1241 try expectDir(tmp.dir, "third"); 1242 }, 1243 } 1244 } 1245 1246 test "makepath ignores '.'" { 1247 var tmp = tmpDir(.{}); 1248 defer tmp.cleanup(); 1249 1250 // Path to create, with "." elements: 1251 const dotPath = try fs.path.join(testing.allocator, &.{ 1252 "first", ".", "second", ".", "third", 1253 }); 1254 defer testing.allocator.free(dotPath); 1255 1256 // Path to expect to find: 1257 const expectedPath = try fs.path.join(testing.allocator, &.{ 1258 "first", "second", "third", 1259 }); 1260 defer testing.allocator.free(expectedPath); 1261 1262 try tmp.dir.makePath(dotPath); 1263 1264 try expectDir(tmp.dir, expectedPath); 1265 } 1266 1267 fn testFilenameLimits(iterable_dir: Dir, maxed_filename: []const u8) !void { 1268 // setup, create a dir and a nested file both with maxed filenames, and walk the dir 1269 { 1270 var maxed_dir = try iterable_dir.makeOpenPath(maxed_filename, .{}); 1271 defer maxed_dir.close(); 1272 1273 try maxed_dir.writeFile(.{ .sub_path = maxed_filename, .data = "" }); 1274 1275 var walker = try iterable_dir.walk(testing.allocator); 1276 defer walker.deinit(); 1277 1278 var count: usize = 0; 1279 while (try walker.next()) |entry| { 1280 try testing.expectEqualStrings(maxed_filename, entry.basename); 1281 count += 1; 1282 } 1283 try testing.expectEqual(@as(usize, 2), count); 1284 } 1285 1286 // ensure that we can delete the tree 1287 try iterable_dir.deleteTree(maxed_filename); 1288 } 1289 1290 test "max file name component lengths" { 1291 var tmp = tmpDir(.{ .iterate = true }); 1292 defer tmp.cleanup(); 1293 1294 if (native_os == .windows) { 1295 // U+FFFF is the character with the largest code point that is encoded as a single 1296 // UTF-16 code unit, so Windows allows for NAME_MAX of them. 1297 const maxed_windows_filename = ("\u{FFFF}".*) ** windows.NAME_MAX; 1298 try testFilenameLimits(tmp.dir, &maxed_windows_filename); 1299 } else if (native_os == .wasi) { 1300 // On WASI, the maxed filename depends on the host OS, so in order for this test to 1301 // work on any host, we need to use a length that will work for all platforms 1302 // (i.e. the minimum max_name_bytes of all supported platforms). 1303 const maxed_wasi_filename = [_]u8{'1'} ** 255; 1304 try testFilenameLimits(tmp.dir, &maxed_wasi_filename); 1305 } else { 1306 const maxed_ascii_filename = [_]u8{'1'} ** std.fs.max_name_bytes; 1307 try testFilenameLimits(tmp.dir, &maxed_ascii_filename); 1308 } 1309 } 1310 1311 test "writev, readv" { 1312 var tmp = tmpDir(.{}); 1313 defer tmp.cleanup(); 1314 1315 const line1 = "line1\n"; 1316 const line2 = "line2\n"; 1317 1318 var buf1: [line1.len]u8 = undefined; 1319 var buf2: [line2.len]u8 = undefined; 1320 var write_vecs = [_]posix.iovec_const{ 1321 .{ 1322 .base = line1, 1323 .len = line1.len, 1324 }, 1325 .{ 1326 .base = line2, 1327 .len = line2.len, 1328 }, 1329 }; 1330 var read_vecs = [_]posix.iovec{ 1331 .{ 1332 .base = &buf2, 1333 .len = buf2.len, 1334 }, 1335 .{ 1336 .base = &buf1, 1337 .len = buf1.len, 1338 }, 1339 }; 1340 1341 var src_file = try tmp.dir.createFile("test.txt", .{ .read = true }); 1342 defer src_file.close(); 1343 1344 try src_file.writevAll(&write_vecs); 1345 try testing.expectEqual(@as(u64, line1.len + line2.len), try src_file.getEndPos()); 1346 try src_file.seekTo(0); 1347 const read = try src_file.readvAll(&read_vecs); 1348 try testing.expectEqual(@as(usize, line1.len + line2.len), read); 1349 try testing.expectEqualStrings(&buf1, "line2\n"); 1350 try testing.expectEqualStrings(&buf2, "line1\n"); 1351 } 1352 1353 test "pwritev, preadv" { 1354 var tmp = tmpDir(.{}); 1355 defer tmp.cleanup(); 1356 1357 const line1 = "line1\n"; 1358 const line2 = "line2\n"; 1359 1360 var buf1: [line1.len]u8 = undefined; 1361 var buf2: [line2.len]u8 = undefined; 1362 var write_vecs = [_]posix.iovec_const{ 1363 .{ 1364 .base = line1, 1365 .len = line1.len, 1366 }, 1367 .{ 1368 .base = line2, 1369 .len = line2.len, 1370 }, 1371 }; 1372 var read_vecs = [_]posix.iovec{ 1373 .{ 1374 .base = &buf2, 1375 .len = buf2.len, 1376 }, 1377 .{ 1378 .base = &buf1, 1379 .len = buf1.len, 1380 }, 1381 }; 1382 1383 var src_file = try tmp.dir.createFile("test.txt", .{ .read = true }); 1384 defer src_file.close(); 1385 1386 try src_file.pwritevAll(&write_vecs, 16); 1387 try testing.expectEqual(@as(u64, 16 + line1.len + line2.len), try src_file.getEndPos()); 1388 const read = try src_file.preadvAll(&read_vecs, 16); 1389 try testing.expectEqual(@as(usize, line1.len + line2.len), read); 1390 try testing.expectEqualStrings(&buf1, "line2\n"); 1391 try testing.expectEqualStrings(&buf2, "line1\n"); 1392 } 1393 1394 test "setEndPos" { 1395 // https://github.com/ziglang/zig/issues/20747 (open fd does not have write permission) 1396 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; 1397 if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23806 1398 1399 var tmp = tmpDir(.{}); 1400 defer tmp.cleanup(); 1401 1402 const file_name = "afile.txt"; 1403 try tmp.dir.writeFile(.{ .sub_path = file_name, .data = "ninebytes" }); 1404 const f = try tmp.dir.openFile(file_name, .{ .mode = .read_write }); 1405 defer f.close(); 1406 1407 const initial_size = try f.getEndPos(); 1408 var buffer: [32]u8 = undefined; 1409 1410 { 1411 try f.setEndPos(initial_size); 1412 try testing.expectEqual(initial_size, try f.getEndPos()); 1413 try testing.expectEqual(initial_size, try f.preadAll(&buffer, 0)); 1414 try testing.expectEqualStrings("ninebytes", buffer[0..@intCast(initial_size)]); 1415 } 1416 1417 { 1418 const larger = initial_size + 4; 1419 try f.setEndPos(larger); 1420 try testing.expectEqual(larger, try f.getEndPos()); 1421 try testing.expectEqual(larger, try f.preadAll(&buffer, 0)); 1422 try testing.expectEqualStrings("ninebytes\x00\x00\x00\x00", buffer[0..@intCast(larger)]); 1423 } 1424 1425 { 1426 const smaller = initial_size - 5; 1427 try f.setEndPos(smaller); 1428 try testing.expectEqual(smaller, try f.getEndPos()); 1429 try testing.expectEqual(smaller, try f.preadAll(&buffer, 0)); 1430 try testing.expectEqualStrings("nine", buffer[0..@intCast(smaller)]); 1431 } 1432 1433 try f.setEndPos(0); 1434 try testing.expectEqual(0, try f.getEndPos()); 1435 try testing.expectEqual(0, try f.preadAll(&buffer, 0)); 1436 1437 // Invalid file length should error gracefully. Actual limit is host 1438 // and file-system dependent, but 1PB should fail on filesystems like 1439 // EXT4 and NTFS. But XFS or Btrfs support up to 8EiB files. 1440 f.setEndPos(0x4_0000_0000_0000) catch |err| if (err != error.FileTooBig) { 1441 return err; 1442 }; 1443 1444 f.setEndPos(std.math.maxInt(u63)) catch |err| if (err != error.FileTooBig) { 1445 return err; 1446 }; 1447 1448 try testing.expectError(error.FileTooBig, f.setEndPos(std.math.maxInt(u63) + 1)); 1449 try testing.expectError(error.FileTooBig, f.setEndPos(std.math.maxInt(u64))); 1450 } 1451 1452 test "access file" { 1453 try testWithAllSupportedPathTypes(struct { 1454 fn impl(ctx: *TestContext) !void { 1455 const dir_path = try ctx.transformPath("os_test_tmp"); 1456 const file_path = try ctx.transformPath("os_test_tmp" ++ fs.path.sep_str ++ "file.txt"); 1457 1458 try ctx.dir.makePath(dir_path); 1459 try testing.expectError(error.FileNotFound, ctx.dir.access(file_path, .{})); 1460 1461 try ctx.dir.writeFile(.{ .sub_path = file_path, .data = "" }); 1462 try ctx.dir.access(file_path, .{}); 1463 try ctx.dir.deleteTree(dir_path); 1464 } 1465 }.impl); 1466 } 1467 1468 test "sendfile" { 1469 var tmp = tmpDir(.{}); 1470 defer tmp.cleanup(); 1471 1472 try tmp.dir.makePath("os_test_tmp"); 1473 1474 var dir = try tmp.dir.openDir("os_test_tmp", .{}); 1475 defer dir.close(); 1476 1477 const line1 = "line1\n"; 1478 const line2 = "second line\n"; 1479 var vecs = [_]posix.iovec_const{ 1480 .{ 1481 .base = line1, 1482 .len = line1.len, 1483 }, 1484 .{ 1485 .base = line2, 1486 .len = line2.len, 1487 }, 1488 }; 1489 1490 var src_file = try dir.createFile("sendfile1.txt", .{ .read = true }); 1491 defer src_file.close(); 1492 1493 try src_file.writevAll(&vecs); 1494 1495 var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true }); 1496 defer dest_file.close(); 1497 1498 const header1 = "header1\n"; 1499 const header2 = "second header\n"; 1500 const trailer1 = "trailer1\n"; 1501 const trailer2 = "second trailer\n"; 1502 var headers: [2][]const u8 = .{ header1, header2 }; 1503 var trailers: [2][]const u8 = .{ trailer1, trailer2 }; 1504 1505 var written_buf: [100]u8 = undefined; 1506 var file_reader = src_file.reader(&.{}); 1507 var fallback_buffer: [50]u8 = undefined; 1508 var file_writer = dest_file.writer(&fallback_buffer); 1509 try file_writer.interface.writeVecAll(&headers); 1510 try file_reader.seekTo(1); 1511 try testing.expectEqual(10, try file_writer.interface.sendFileAll(&file_reader, .limited(10))); 1512 try file_writer.interface.writeVecAll(&trailers); 1513 try file_writer.interface.flush(); 1514 const amt = try dest_file.preadAll(&written_buf, 0); 1515 try testing.expectEqualStrings("header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n", written_buf[0..amt]); 1516 } 1517 1518 test "copyRangeAll" { 1519 var tmp = tmpDir(.{}); 1520 defer tmp.cleanup(); 1521 1522 try tmp.dir.makePath("os_test_tmp"); 1523 1524 var dir = try tmp.dir.openDir("os_test_tmp", .{}); 1525 defer dir.close(); 1526 1527 var src_file = try dir.createFile("file1.txt", .{ .read = true }); 1528 defer src_file.close(); 1529 1530 const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP"; 1531 try src_file.writeAll(data); 1532 1533 var dest_file = try dir.createFile("file2.txt", .{ .read = true }); 1534 defer dest_file.close(); 1535 1536 var written_buf: [100]u8 = undefined; 1537 _ = try src_file.copyRangeAll(0, dest_file, 0, data.len); 1538 1539 const amt = try dest_file.preadAll(&written_buf, 0); 1540 try testing.expectEqualStrings(data, written_buf[0..amt]); 1541 } 1542 1543 test "copyFile" { 1544 try testWithAllSupportedPathTypes(struct { 1545 fn impl(ctx: *TestContext) !void { 1546 const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP"; 1547 const src_file = try ctx.transformPath("tmp_test_copy_file.txt"); 1548 const dest_file = try ctx.transformPath("tmp_test_copy_file2.txt"); 1549 const dest_file2 = try ctx.transformPath("tmp_test_copy_file3.txt"); 1550 1551 try ctx.dir.writeFile(.{ .sub_path = src_file, .data = data }); 1552 defer ctx.dir.deleteFile(src_file) catch {}; 1553 1554 try ctx.dir.copyFile(src_file, ctx.dir, dest_file, .{}); 1555 defer ctx.dir.deleteFile(dest_file) catch {}; 1556 1557 try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, .{ .override_mode = File.default_mode }); 1558 defer ctx.dir.deleteFile(dest_file2) catch {}; 1559 1560 try expectFileContents(ctx.dir, dest_file, data); 1561 try expectFileContents(ctx.dir, dest_file2, data); 1562 } 1563 }.impl); 1564 } 1565 1566 fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void { 1567 const contents = try dir.readFileAlloc(testing.allocator, file_path, 1000); 1568 defer testing.allocator.free(contents); 1569 1570 try testing.expectEqualSlices(u8, data, contents); 1571 } 1572 1573 test "AtomicFile" { 1574 try testWithAllSupportedPathTypes(struct { 1575 fn impl(ctx: *TestContext) !void { 1576 const allocator = ctx.arena.allocator(); 1577 const test_out_file = try ctx.transformPath("tmp_atomic_file_test_dest.txt"); 1578 const test_content = 1579 \\ hello! 1580 \\ this is a test file 1581 ; 1582 1583 { 1584 var buffer: [100]u8 = undefined; 1585 var af = try ctx.dir.atomicFile(test_out_file, .{ .write_buffer = &buffer }); 1586 defer af.deinit(); 1587 try af.file_writer.interface.writeAll(test_content); 1588 try af.finish(); 1589 } 1590 const content = try ctx.dir.readFileAlloc(allocator, test_out_file, 9999); 1591 try testing.expectEqualStrings(test_content, content); 1592 1593 try ctx.dir.deleteFile(test_out_file); 1594 } 1595 }.impl); 1596 } 1597 1598 test "open file with exclusive nonblocking lock twice" { 1599 if (native_os == .wasi) return error.SkipZigTest; 1600 1601 try testWithAllSupportedPathTypes(struct { 1602 fn impl(ctx: *TestContext) !void { 1603 const filename = try ctx.transformPath("file_nonblocking_lock_test.txt"); 1604 1605 const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true }); 1606 defer file1.close(); 1607 1608 const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true }); 1609 try testing.expectError(error.WouldBlock, file2); 1610 } 1611 }.impl); 1612 } 1613 1614 test "open file with shared and exclusive nonblocking lock" { 1615 if (native_os == .wasi) return error.SkipZigTest; 1616 1617 try testWithAllSupportedPathTypes(struct { 1618 fn impl(ctx: *TestContext) !void { 1619 const filename = try ctx.transformPath("file_nonblocking_lock_test.txt"); 1620 1621 const file1 = try ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true }); 1622 defer file1.close(); 1623 1624 const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true }); 1625 try testing.expectError(error.WouldBlock, file2); 1626 } 1627 }.impl); 1628 } 1629 1630 test "open file with exclusive and shared nonblocking lock" { 1631 if (native_os == .wasi) return error.SkipZigTest; 1632 1633 try testWithAllSupportedPathTypes(struct { 1634 fn impl(ctx: *TestContext) !void { 1635 const filename = try ctx.transformPath("file_nonblocking_lock_test.txt"); 1636 1637 const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true }); 1638 defer file1.close(); 1639 1640 const file2 = ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true }); 1641 try testing.expectError(error.WouldBlock, file2); 1642 } 1643 }.impl); 1644 } 1645 1646 test "open file with exclusive lock twice, make sure second lock waits" { 1647 if (builtin.single_threaded) return error.SkipZigTest; 1648 1649 try testWithAllSupportedPathTypes(struct { 1650 fn impl(ctx: *TestContext) !void { 1651 const filename = try ctx.transformPath("file_lock_test.txt"); 1652 1653 const file = try ctx.dir.createFile(filename, .{ .lock = .exclusive }); 1654 errdefer file.close(); 1655 1656 const S = struct { 1657 fn checkFn(dir: *fs.Dir, path: []const u8, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void { 1658 started.set(); 1659 const file1 = try dir.createFile(path, .{ .lock = .exclusive }); 1660 1661 locked.set(); 1662 file1.close(); 1663 } 1664 }; 1665 1666 var started = std.Thread.ResetEvent{}; 1667 var locked = std.Thread.ResetEvent{}; 1668 1669 const t = try std.Thread.spawn(.{}, S.checkFn, .{ 1670 &ctx.dir, 1671 filename, 1672 &started, 1673 &locked, 1674 }); 1675 defer t.join(); 1676 1677 // Wait for the spawned thread to start trying to acquire the exclusive file lock. 1678 // Then wait a bit to make sure that can't acquire it since we currently hold the file lock. 1679 started.wait(); 1680 try testing.expectError(error.Timeout, locked.timedWait(10 * std.time.ns_per_ms)); 1681 1682 // Release the file lock which should unlock the thread to lock it and set the locked event. 1683 file.close(); 1684 locked.wait(); 1685 } 1686 }.impl); 1687 } 1688 1689 test "open file with exclusive nonblocking lock twice (absolute paths)" { 1690 if (native_os == .wasi) return error.SkipZigTest; 1691 1692 var random_bytes: [12]u8 = undefined; 1693 std.crypto.random.bytes(&random_bytes); 1694 1695 var random_b64: [fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined; 1696 _ = fs.base64_encoder.encode(&random_b64, &random_bytes); 1697 1698 const sub_path = random_b64 ++ "-zig-test-absolute-paths.txt"; 1699 1700 const gpa = testing.allocator; 1701 1702 const cwd = try std.process.getCwdAlloc(gpa); 1703 defer gpa.free(cwd); 1704 1705 const filename = try fs.path.resolve(gpa, &.{ cwd, sub_path }); 1706 defer gpa.free(filename); 1707 1708 defer fs.deleteFileAbsolute(filename) catch {}; // createFileAbsolute can leave files on failures 1709 const file1 = try fs.createFileAbsolute(filename, .{ 1710 .lock = .exclusive, 1711 .lock_nonblocking = true, 1712 }); 1713 1714 const file2 = fs.createFileAbsolute(filename, .{ 1715 .lock = .exclusive, 1716 .lock_nonblocking = true, 1717 }); 1718 file1.close(); 1719 try testing.expectError(error.WouldBlock, file2); 1720 } 1721 1722 test "read from locked file" { 1723 try testWithAllSupportedPathTypes(struct { 1724 fn impl(ctx: *TestContext) !void { 1725 const filename = try ctx.transformPath("read_lock_file_test.txt"); 1726 1727 { 1728 const f = try ctx.dir.createFile(filename, .{ .read = true }); 1729 defer f.close(); 1730 var buffer: [1]u8 = undefined; 1731 _ = try f.readAll(&buffer); 1732 } 1733 { 1734 const f = try ctx.dir.createFile(filename, .{ 1735 .read = true, 1736 .lock = .exclusive, 1737 }); 1738 defer f.close(); 1739 const f2 = try ctx.dir.openFile(filename, .{}); 1740 defer f2.close(); 1741 var buffer: [1]u8 = undefined; 1742 if (builtin.os.tag == .windows) { 1743 try std.testing.expectError(error.LockViolation, f2.readAll(&buffer)); 1744 } else { 1745 try std.testing.expectEqual(0, f2.readAll(&buffer)); 1746 } 1747 } 1748 } 1749 }.impl); 1750 } 1751 1752 test "walker" { 1753 var tmp = tmpDir(.{ .iterate = true }); 1754 defer tmp.cleanup(); 1755 1756 // iteration order of walker is undefined, so need lookup maps to check against 1757 1758 const expected_paths = std.StaticStringMap(void).initComptime(.{ 1759 .{"dir1"}, 1760 .{"dir2"}, 1761 .{"dir3"}, 1762 .{"dir4"}, 1763 .{"dir3" ++ fs.path.sep_str ++ "sub1"}, 1764 .{"dir3" ++ fs.path.sep_str ++ "sub2"}, 1765 .{"dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1"}, 1766 }); 1767 1768 const expected_basenames = std.StaticStringMap(void).initComptime(.{ 1769 .{"dir1"}, 1770 .{"dir2"}, 1771 .{"dir3"}, 1772 .{"dir4"}, 1773 .{"sub1"}, 1774 .{"sub2"}, 1775 .{"subsub1"}, 1776 }); 1777 1778 for (expected_paths.keys()) |key| { 1779 try tmp.dir.makePath(key); 1780 } 1781 1782 var walker = try tmp.dir.walk(testing.allocator); 1783 defer walker.deinit(); 1784 1785 var num_walked: usize = 0; 1786 while (try walker.next()) |entry| { 1787 testing.expect(expected_basenames.has(entry.basename)) catch |err| { 1788 std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)}); 1789 return err; 1790 }; 1791 testing.expect(expected_paths.has(entry.path)) catch |err| { 1792 std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); 1793 return err; 1794 }; 1795 // make sure that the entry.dir is the containing dir 1796 var entry_dir = try entry.dir.openDir(entry.basename, .{}); 1797 defer entry_dir.close(); 1798 num_walked += 1; 1799 } 1800 try testing.expectEqual(expected_paths.kvs.len, num_walked); 1801 } 1802 1803 test "walker without fully iterating" { 1804 var tmp = tmpDir(.{ .iterate = true }); 1805 defer tmp.cleanup(); 1806 1807 var walker = try tmp.dir.walk(testing.allocator); 1808 defer walker.deinit(); 1809 1810 // Create 2 directories inside the tmp directory, but then only iterate once before breaking. 1811 // This ensures that walker doesn't try to close the initial directory when not fully iterating. 1812 1813 try tmp.dir.makePath("a"); 1814 try tmp.dir.makePath("b"); 1815 1816 var num_walked: usize = 0; 1817 while (try walker.next()) |_| { 1818 num_walked += 1; 1819 break; 1820 } 1821 try testing.expectEqual(@as(usize, 1), num_walked); 1822 } 1823 1824 test "'.' and '..' in fs.Dir functions" { 1825 if (native_os == .windows and builtin.cpu.arch == .aarch64) { 1826 // https://github.com/ziglang/zig/issues/17134 1827 return error.SkipZigTest; 1828 } 1829 1830 try testWithAllSupportedPathTypes(struct { 1831 fn impl(ctx: *TestContext) !void { 1832 const subdir_path = try ctx.transformPath("./subdir"); 1833 const file_path = try ctx.transformPath("./subdir/../file"); 1834 const copy_path = try ctx.transformPath("./subdir/../copy"); 1835 const rename_path = try ctx.transformPath("./subdir/../rename"); 1836 const update_path = try ctx.transformPath("./subdir/../update"); 1837 1838 try ctx.dir.makeDir(subdir_path); 1839 try ctx.dir.access(subdir_path, .{}); 1840 var created_subdir = try ctx.dir.openDir(subdir_path, .{}); 1841 created_subdir.close(); 1842 1843 const created_file = try ctx.dir.createFile(file_path, .{}); 1844 created_file.close(); 1845 try ctx.dir.access(file_path, .{}); 1846 1847 try ctx.dir.copyFile(file_path, ctx.dir, copy_path, .{}); 1848 try ctx.dir.rename(copy_path, rename_path); 1849 const renamed_file = try ctx.dir.openFile(rename_path, .{}); 1850 renamed_file.close(); 1851 try ctx.dir.deleteFile(rename_path); 1852 1853 try ctx.dir.writeFile(.{ .sub_path = update_path, .data = "something" }); 1854 const prev_status = try ctx.dir.updateFile(file_path, ctx.dir, update_path, .{}); 1855 try testing.expectEqual(fs.Dir.PrevStatus.stale, prev_status); 1856 1857 try ctx.dir.deleteDir(subdir_path); 1858 } 1859 }.impl); 1860 } 1861 1862 test "'.' and '..' in absolute functions" { 1863 if (native_os == .wasi) return error.SkipZigTest; 1864 1865 var tmp = tmpDir(.{}); 1866 defer tmp.cleanup(); 1867 1868 var arena = ArenaAllocator.init(testing.allocator); 1869 defer arena.deinit(); 1870 const allocator = arena.allocator(); 1871 1872 const base_path = try tmp.dir.realpathAlloc(allocator, "."); 1873 1874 const subdir_path = try fs.path.join(allocator, &.{ base_path, "./subdir" }); 1875 try fs.makeDirAbsolute(subdir_path); 1876 try fs.accessAbsolute(subdir_path, .{}); 1877 var created_subdir = try fs.openDirAbsolute(subdir_path, .{}); 1878 created_subdir.close(); 1879 1880 const created_file_path = try fs.path.join(allocator, &.{ subdir_path, "../file" }); 1881 const created_file = try fs.createFileAbsolute(created_file_path, .{}); 1882 created_file.close(); 1883 try fs.accessAbsolute(created_file_path, .{}); 1884 1885 const copied_file_path = try fs.path.join(allocator, &.{ subdir_path, "../copy" }); 1886 try fs.copyFileAbsolute(created_file_path, copied_file_path, .{}); 1887 const renamed_file_path = try fs.path.join(allocator, &.{ subdir_path, "../rename" }); 1888 try fs.renameAbsolute(copied_file_path, renamed_file_path); 1889 const renamed_file = try fs.openFileAbsolute(renamed_file_path, .{}); 1890 renamed_file.close(); 1891 try fs.deleteFileAbsolute(renamed_file_path); 1892 1893 const update_file_path = try fs.path.join(allocator, &.{ subdir_path, "../update" }); 1894 const update_file = try fs.createFileAbsolute(update_file_path, .{}); 1895 try update_file.writeAll("something"); 1896 update_file.close(); 1897 const prev_status = try fs.updateFileAbsolute(created_file_path, update_file_path, .{}); 1898 try testing.expectEqual(fs.Dir.PrevStatus.stale, prev_status); 1899 1900 try fs.deleteDirAbsolute(subdir_path); 1901 } 1902 1903 test "chmod" { 1904 if (native_os == .windows or native_os == .wasi) 1905 return error.SkipZigTest; 1906 1907 var tmp = tmpDir(.{}); 1908 defer tmp.cleanup(); 1909 1910 const file = try tmp.dir.createFile("test_file", .{ .mode = 0o600 }); 1911 defer file.close(); 1912 try testing.expectEqual(@as(File.Mode, 0o600), (try file.stat()).mode & 0o7777); 1913 1914 try file.chmod(0o644); 1915 try testing.expectEqual(@as(File.Mode, 0o644), (try file.stat()).mode & 0o7777); 1916 1917 try tmp.dir.makeDir("test_dir"); 1918 var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true }); 1919 defer dir.close(); 1920 1921 try dir.chmod(0o700); 1922 try testing.expectEqual(@as(File.Mode, 0o700), (try dir.stat()).mode & 0o7777); 1923 } 1924 1925 test "chown" { 1926 if (native_os == .windows or native_os == .wasi) 1927 return error.SkipZigTest; 1928 1929 var tmp = tmpDir(.{}); 1930 defer tmp.cleanup(); 1931 1932 const file = try tmp.dir.createFile("test_file", .{}); 1933 defer file.close(); 1934 try file.chown(null, null); 1935 1936 try tmp.dir.makeDir("test_dir"); 1937 1938 var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true }); 1939 defer dir.close(); 1940 try dir.chown(null, null); 1941 } 1942 1943 test "delete a setAsCwd directory on Windows" { 1944 if (native_os != .windows) return error.SkipZigTest; 1945 1946 var tmp = tmpDir(.{}); 1947 // Set tmp dir as current working directory. 1948 try tmp.dir.setAsCwd(); 1949 tmp.dir.close(); 1950 try testing.expectError(error.FileBusy, tmp.parent_dir.deleteTree(&tmp.sub_path)); 1951 // Now set the parent dir as the current working dir for clean up. 1952 try tmp.parent_dir.setAsCwd(); 1953 try tmp.parent_dir.deleteTree(&tmp.sub_path); 1954 // Close the parent "tmp" so we don't leak the HANDLE. 1955 tmp.parent_dir.close(); 1956 } 1957 1958 test "invalid UTF-8/WTF-8 paths" { 1959 const expected_err = switch (native_os) { 1960 .wasi => error.InvalidUtf8, 1961 .windows => error.InvalidWtf8, 1962 else => return error.SkipZigTest, 1963 }; 1964 1965 try testWithAllSupportedPathTypes(struct { 1966 fn impl(ctx: *TestContext) !void { 1967 // This is both invalid UTF-8 and WTF-8, since \xFF is an invalid start byte 1968 const invalid_path = try ctx.transformPath("\xFF"); 1969 1970 try testing.expectError(expected_err, ctx.dir.openFile(invalid_path, .{})); 1971 try testing.expectError(expected_err, ctx.dir.openFileZ(invalid_path, .{})); 1972 1973 try testing.expectError(expected_err, ctx.dir.createFile(invalid_path, .{})); 1974 try testing.expectError(expected_err, ctx.dir.createFileZ(invalid_path, .{})); 1975 1976 try testing.expectError(expected_err, ctx.dir.makeDir(invalid_path)); 1977 try testing.expectError(expected_err, ctx.dir.makeDirZ(invalid_path)); 1978 1979 try testing.expectError(expected_err, ctx.dir.makePath(invalid_path)); 1980 try testing.expectError(expected_err, ctx.dir.makeOpenPath(invalid_path, .{})); 1981 1982 try testing.expectError(expected_err, ctx.dir.openDir(invalid_path, .{})); 1983 try testing.expectError(expected_err, ctx.dir.openDirZ(invalid_path, .{})); 1984 1985 try testing.expectError(expected_err, ctx.dir.deleteFile(invalid_path)); 1986 try testing.expectError(expected_err, ctx.dir.deleteFileZ(invalid_path)); 1987 1988 try testing.expectError(expected_err, ctx.dir.deleteDir(invalid_path)); 1989 try testing.expectError(expected_err, ctx.dir.deleteDirZ(invalid_path)); 1990 1991 try testing.expectError(expected_err, ctx.dir.rename(invalid_path, invalid_path)); 1992 try testing.expectError(expected_err, ctx.dir.renameZ(invalid_path, invalid_path)); 1993 1994 try testing.expectError(expected_err, ctx.dir.symLink(invalid_path, invalid_path, .{})); 1995 try testing.expectError(expected_err, ctx.dir.symLinkZ(invalid_path, invalid_path, .{})); 1996 if (native_os == .wasi) { 1997 try testing.expectError(expected_err, ctx.dir.symLinkWasi(invalid_path, invalid_path, .{})); 1998 } 1999 2000 try testing.expectError(expected_err, ctx.dir.readLink(invalid_path, &[_]u8{})); 2001 try testing.expectError(expected_err, ctx.dir.readLinkZ(invalid_path, &[_]u8{})); 2002 if (native_os == .wasi) { 2003 try testing.expectError(expected_err, ctx.dir.readLinkWasi(invalid_path, &[_]u8{})); 2004 } 2005 2006 try testing.expectError(expected_err, ctx.dir.readFile(invalid_path, &[_]u8{})); 2007 try testing.expectError(expected_err, ctx.dir.readFileAlloc(testing.allocator, invalid_path, 0)); 2008 2009 try testing.expectError(expected_err, ctx.dir.deleteTree(invalid_path)); 2010 try testing.expectError(expected_err, ctx.dir.deleteTreeMinStackSize(invalid_path)); 2011 2012 try testing.expectError(expected_err, ctx.dir.writeFile(.{ .sub_path = invalid_path, .data = "" })); 2013 2014 try testing.expectError(expected_err, ctx.dir.access(invalid_path, .{})); 2015 try testing.expectError(expected_err, ctx.dir.accessZ(invalid_path, .{})); 2016 2017 try testing.expectError(expected_err, ctx.dir.updateFile(invalid_path, ctx.dir, invalid_path, .{})); 2018 try testing.expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, .{})); 2019 2020 try testing.expectError(expected_err, ctx.dir.statFile(invalid_path)); 2021 2022 if (native_os != .wasi) { 2023 try testing.expectError(expected_err, ctx.dir.realpath(invalid_path, &[_]u8{})); 2024 try testing.expectError(expected_err, ctx.dir.realpathZ(invalid_path, &[_]u8{})); 2025 try testing.expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path)); 2026 } 2027 2028 try testing.expectError(expected_err, fs.rename(ctx.dir, invalid_path, ctx.dir, invalid_path)); 2029 try testing.expectError(expected_err, fs.renameZ(ctx.dir, invalid_path, ctx.dir, invalid_path)); 2030 2031 if (native_os != .wasi and ctx.path_type != .relative) { 2032 try testing.expectError(expected_err, fs.updateFileAbsolute(invalid_path, invalid_path, .{})); 2033 try testing.expectError(expected_err, fs.copyFileAbsolute(invalid_path, invalid_path, .{})); 2034 try testing.expectError(expected_err, fs.makeDirAbsolute(invalid_path)); 2035 try testing.expectError(expected_err, fs.makeDirAbsoluteZ(invalid_path)); 2036 try testing.expectError(expected_err, fs.deleteDirAbsolute(invalid_path)); 2037 try testing.expectError(expected_err, fs.deleteDirAbsoluteZ(invalid_path)); 2038 try testing.expectError(expected_err, fs.renameAbsolute(invalid_path, invalid_path)); 2039 try testing.expectError(expected_err, fs.renameAbsoluteZ(invalid_path, invalid_path)); 2040 try testing.expectError(expected_err, fs.openDirAbsolute(invalid_path, .{})); 2041 try testing.expectError(expected_err, fs.openDirAbsoluteZ(invalid_path, .{})); 2042 try testing.expectError(expected_err, fs.openFileAbsolute(invalid_path, .{})); 2043 try testing.expectError(expected_err, fs.openFileAbsoluteZ(invalid_path, .{})); 2044 try testing.expectError(expected_err, fs.accessAbsolute(invalid_path, .{})); 2045 try testing.expectError(expected_err, fs.accessAbsoluteZ(invalid_path, .{})); 2046 try testing.expectError(expected_err, fs.createFileAbsolute(invalid_path, .{})); 2047 try testing.expectError(expected_err, fs.createFileAbsoluteZ(invalid_path, .{})); 2048 try testing.expectError(expected_err, fs.deleteFileAbsolute(invalid_path)); 2049 try testing.expectError(expected_err, fs.deleteFileAbsoluteZ(invalid_path)); 2050 try testing.expectError(expected_err, fs.deleteTreeAbsolute(invalid_path)); 2051 var readlink_buf: [fs.max_path_bytes]u8 = undefined; 2052 try testing.expectError(expected_err, fs.readLinkAbsolute(invalid_path, &readlink_buf)); 2053 try testing.expectError(expected_err, fs.readLinkAbsoluteZ(invalid_path, &readlink_buf)); 2054 try testing.expectError(expected_err, fs.symLinkAbsolute(invalid_path, invalid_path, .{})); 2055 try testing.expectError(expected_err, fs.symLinkAbsoluteZ(invalid_path, invalid_path, .{})); 2056 try testing.expectError(expected_err, fs.realpathAlloc(testing.allocator, invalid_path)); 2057 } 2058 } 2059 }.impl); 2060 } 2061 2062 test "read file non vectored" { 2063 var tmp_dir = testing.tmpDir(.{}); 2064 defer tmp_dir.cleanup(); 2065 2066 const contents = "hello, world!\n"; 2067 2068 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); 2069 defer file.close(); 2070 { 2071 var file_writer: std.fs.File.Writer = .init(file, &.{}); 2072 try file_writer.interface.writeAll(contents); 2073 try file_writer.interface.flush(); 2074 } 2075 2076 var file_reader: std.fs.File.Reader = .init(file, &.{}); 2077 2078 var write_buffer: [100]u8 = undefined; 2079 var w: std.Io.Writer = .fixed(&write_buffer); 2080 2081 var i: usize = 0; 2082 while (true) { 2083 i += file_reader.interface.stream(&w, .limited(3)) catch |err| switch (err) { 2084 error.EndOfStream => break, 2085 else => |e| return e, 2086 }; 2087 } 2088 try testing.expectEqualStrings(contents, w.buffered()); 2089 try testing.expectEqual(contents.len, i); 2090 } 2091 2092 test "seek keeping partial buffer" { 2093 var tmp_dir = testing.tmpDir(.{}); 2094 defer tmp_dir.cleanup(); 2095 2096 const contents = "0123456789"; 2097 2098 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); 2099 defer file.close(); 2100 { 2101 var file_writer: std.fs.File.Writer = .init(file, &.{}); 2102 try file_writer.interface.writeAll(contents); 2103 try file_writer.interface.flush(); 2104 } 2105 2106 var read_buffer: [3]u8 = undefined; 2107 var file_reader: std.fs.File.Reader = .init(file, &read_buffer); 2108 2109 try testing.expectEqual(0, file_reader.logicalPos()); 2110 2111 var buf: [4]u8 = undefined; 2112 try file_reader.interface.readSliceAll(&buf); 2113 2114 if (file_reader.interface.bufferedLen() != 3) { 2115 // Pass the test if the OS doesn't give us vectored reads. 2116 return; 2117 } 2118 2119 try testing.expectEqual(4, file_reader.logicalPos()); 2120 try testing.expectEqual(7, file_reader.pos); 2121 try file_reader.seekTo(6); 2122 try testing.expectEqual(6, file_reader.logicalPos()); 2123 try testing.expectEqual(7, file_reader.pos); 2124 2125 try testing.expectEqualStrings("0123", &buf); 2126 2127 const n = try file_reader.interface.readSliceShort(&buf); 2128 try testing.expectEqual(4, n); 2129 2130 try testing.expectEqualStrings("6789", &buf); 2131 }