authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2023-08-17 00:58:44-07:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2023-08-17 00:59:19-07:00
log8f5f1ff25aebba87f8f6eb9682d7f1b7cb7d0efd
tree1dfd290775706c00cbdef791531f30c36e1340bd
parent3819e69376ae18289ef9a7422c3644bc8ab288e0

fs tests: Test multiple different path types in most tests

(which path types will depend on which the target supports)

2 files changed, 567 insertions(+), 405 deletions(-)

lib/std/fs/test.zig+547-398
......@@ -13,38 +13,167 @@ const File = std.fs.File;
1313const tmpDir = testing.tmpDir;
1414const tmpIterableDir = testing.tmpIterableDir;
1515
16test "Dir.readLink" {
17 var tmp = tmpDir(.{});
18 defer tmp.cleanup();
19
20 // Create some targets
21 try tmp.dir.writeFile("file.txt", "nonsense");
22 try tmp.dir.makeDir("subdir");
16const PathType = enum {
17 relative,
18 absolute,
19 unc,
20
21 pub fn isSupported(self: PathType, target_os: std.Target.Os) bool {
22 return switch (self) {
23 .relative => true,
24 .absolute => std.os.isGetFdPathSupportedOnTarget(target_os),
25 .unc => target_os.tag == .windows,
26 };
27 }
2328
24 {
25 // Create symbolic link by path
26 tmp.dir.symLink("file.txt", "symlink1", .{}) catch |err| switch (err) {
27 // Symlink requires admin privileges on windows, so this test can legitimately fail.
28 error.AccessDenied => return error.SkipZigTest,
29 else => return err,
29 pub const TransformError = std.os.RealPathError || error{OutOfMemory};
30 pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8;
31
32 pub fn getTransformFn(comptime path_type: PathType) TransformFn {
33 switch (path_type) {
34 .relative => return struct {
35 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
36 _ = allocator;
37 _ = dir;
38 return relative_path;
39 }
40 }.transform,
41 .absolute => return struct {
42 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
43 // The final path may not actually exist which would cause realpath to fail.
44 // So instead, we get the path of the dir and join it with the relative path.
45 var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
46 const dir_path = try os.getFdPath(dir.fd, &fd_path_buf);
47 return fs.path.join(allocator, &.{ dir_path, relative_path });
48 }
49 }.transform,
50 .unc => return struct {
51 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
52 // Any drive absolute path (C:\foo) can be converted into a UNC path by
53 // using 'localhost' as the server name and '<drive letter>$' as the share name.
54 var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
55 const dir_path = try os.getFdPath(dir.fd, &fd_path_buf);
56 const windows_path_type = std.os.windows.getUnprefixedPathType(u8, dir_path);
57 switch (windows_path_type) {
58 .unc_absolute => return fs.path.join(allocator, &.{ dir_path, relative_path }),
59 .drive_absolute => {
60 // `C:\<...>` -> `\\localhost\C$\<...>`
61 const prepended = "\\\\localhost\\";
62 var path = try fs.path.join(allocator, &.{ prepended, dir_path, relative_path });
63 path[prepended.len + 1] = '$';
64 return path;
65 },
66 else => unreachable,
67 }
68 }
69 }.transform,
70 }
71 }
72};
73
74const TestContext = struct {
75 path_type: PathType,
76 arena: ArenaAllocator,
77 tmp: testing.TmpIterableDir,
78 dir: std.fs.Dir,
79 iterable_dir: std.fs.IterableDir,
80 transform_fn: *const PathType.TransformFn,
81
82 pub fn init(path_type: PathType, allocator: mem.Allocator, transform_fn: *const PathType.TransformFn) TestContext {
83 var tmp = tmpIterableDir(.{});
84 return .{
85 .path_type = path_type,
86 .arena = ArenaAllocator.init(allocator),
87 .tmp = tmp,
88 .dir = tmp.iterable_dir.dir,
89 .iterable_dir = tmp.iterable_dir,
90 .transform_fn = transform_fn,
3091 };
31 try testReadLink(tmp.dir, "file.txt", "symlink1");
3292 }
33 {
34 // Create symbolic link by path
35 tmp.dir.symLink("subdir", "symlink2", .{ .is_directory = true }) catch |err| switch (err) {
36 // Symlink requires admin privileges on windows, so this test can legitimately fail.
37 error.AccessDenied => return error.SkipZigTest,
38 else => return err,
93
94 pub fn deinit(self: *TestContext) void {
95 self.arena.deinit();
96 self.tmp.cleanup();
97 }
98
99 /// Returns the `relative_path` transformed into the TestContext's `path_type`.
100 /// The result is allocated by the TestContext's arena and will be free'd during
101 /// `TestContext.deinit`.
102 pub fn transformPath(self: *TestContext, relative_path: []const u8) ![]const u8 {
103 return self.transform_fn(self.arena.allocator(), self.dir, relative_path);
104 }
105};
106
107/// `test_func` must be a function that takes a `*TestContext` as a parameter and returns `!void`.
108/// `test_func` will be called once for each PathType that the current target supports,
109/// and will be passed a TestContext that can transform a relative path into the path type under test.
110/// The TestContext will also create a tmp directory for you (and will clean it up for you too).
111fn testWithAllSupportedPathTypes(test_func: anytype) !void {
112 inline for (@typeInfo(PathType).Enum.fields) |enum_field| {
113 const path_type = @field(PathType, enum_field.name);
114 if (!(comptime path_type.isSupported(builtin.os))) continue;
115
116 var ctx = TestContext.init(path_type, testing.allocator, path_type.getTransformFn());
117 defer ctx.deinit();
118
119 test_func(&ctx) catch |err| {
120 std.debug.print("path type: {s}\n", .{enum_field.name});
121 return err;
39122 };
40 try testReadLink(tmp.dir, "subdir", "symlink2");
41123 }
42124}
43125
126test "Dir.readLink" {
127 try testWithAllSupportedPathTypes(struct {
128 fn impl(ctx: *TestContext) !void {
129 // Create some targets
130 const file_target_path = try ctx.transformPath("file.txt");
131 try ctx.dir.writeFile(file_target_path, "nonsense");
132 const dir_target_path = try ctx.transformPath("subdir");
133 try ctx.dir.makeDir(dir_target_path);
134
135 {
136 // Create symbolic link by path
137 ctx.dir.symLink(file_target_path, "symlink1", .{}) catch |err| switch (err) {
138 // Symlink requires admin privileges on windows, so this test can legitimately fail.
139 error.AccessDenied => return error.SkipZigTest,
140 else => return err,
141 };
142 try testReadLink(ctx.dir, file_target_path, "symlink1");
143 }
144 {
145 // Create symbolic link by path
146 ctx.dir.symLink(dir_target_path, "symlink2", .{ .is_directory = true }) catch |err| switch (err) {
147 // Symlink requires admin privileges on windows, so this test can legitimately fail.
148 error.AccessDenied => return error.SkipZigTest,
149 else => return err,
150 };
151 try testReadLink(ctx.dir, dir_target_path, "symlink2");
152 }
153 }
154 }.impl);
155}
156
44157fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {
45158 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
46159 const given = try dir.readLink(symlink_path, buffer[0..]);
47 try testing.expect(mem.eql(u8, target_path, given));
160 try testing.expectEqualStrings(target_path, given);
161}
162
163test "openDir" {
164 try testWithAllSupportedPathTypes(struct {
165 fn impl(ctx: *TestContext) !void {
166 const subdir_path = try ctx.transformPath("subdir");
167 try ctx.dir.makeDir(subdir_path);
168
169 for ([_][]const u8{ "", ".", ".." }) |sub_path| {
170 const dir_path = try fs.path.join(testing.allocator, &[_][]const u8{ subdir_path, sub_path });
171 defer testing.allocator.free(dir_path);
172 var dir = try ctx.dir.openDir(dir_path, .{});
173 defer dir.close();
174 }
175 }
176 }.impl);
48177}
49178
50179test "accessAbsolute" {
......@@ -349,53 +478,59 @@ fn contains(entries: *const std.ArrayList(IterableDir.Entry), el: IterableDir.En
349478}
350479
351480test "Dir.realpath smoke test" {
352 switch (builtin.os.tag) {
353 .linux, .windows, .macos, .ios, .watchos, .tvos, .solaris => {},
354 else => return error.SkipZigTest,
355 }
356
357 var tmp_dir = tmpDir(.{});
358 defer tmp_dir.cleanup();
359
360 var file = try tmp_dir.dir.createFile("test_file", .{ .lock = .shared });
361 // We need to close the file immediately as otherwise on Windows we'll end up
362 // with a sharing violation.
363 file.close();
364
365 try tmp_dir.dir.makeDir("test_dir");
366
367 var arena = ArenaAllocator.init(testing.allocator);
368 defer arena.deinit();
369 const allocator = arena.allocator();
370
371 const base_path = blk: {
372 const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp_dir.sub_path[0..] });
373 break :blk try fs.realpathAlloc(allocator, relative_path);
374 };
375
376 // First, test non-alloc version
377 {
378 var buf1: [fs.MAX_PATH_BYTES]u8 = undefined;
379
380 const file_path = try tmp_dir.dir.realpath("test_file", buf1[0..]);
381 const expected_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "test_file" });
382 try testing.expectEqualStrings(expected_file_path, file_path);
383
384 const dir_path = try tmp_dir.dir.realpath("test_dir", buf1[0..]);
385 const expected_dir_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "test_dir" });
386 try testing.expectEqualStrings(expected_dir_path, dir_path);
387 }
388
389 // Next, test alloc version
390 {
391 const file_path = try tmp_dir.dir.realpathAlloc(allocator, "test_file");
392 const expected_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "test_file" });
393 try testing.expectEqualStrings(expected_file_path, file_path);
394
395 const dir_path = try tmp_dir.dir.realpathAlloc(allocator, "test_dir");
396 const expected_dir_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "test_dir" });
397 try testing.expectEqualStrings(expected_dir_path, dir_path);
398 }
481 if (!comptime std.os.isGetFdPathSupportedOnTarget(builtin.os)) return error.SkipZigTest;
482
483 try testWithAllSupportedPathTypes(struct {
484 fn impl(ctx: *TestContext) !void {
485 const test_file_path = try ctx.transformPath("test_file");
486 const test_dir_path = try ctx.transformPath("test_dir");
487 var buf: [fs.MAX_PATH_BYTES]u8 = undefined;
488
489 // FileNotFound if the path doesn't exist
490 try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(testing.allocator, test_file_path));
491 try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_file_path, &buf));
492 try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(testing.allocator, test_dir_path));
493 try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_dir_path, &buf));
494
495 // Now create the file and dir
496 try ctx.dir.writeFile(test_file_path, "");
497 try ctx.dir.makeDir(test_dir_path);
498
499 const base_path = try ctx.transformPath(".");
500 const base_realpath = try ctx.dir.realpathAlloc(testing.allocator, base_path);
501 defer testing.allocator.free(base_realpath);
502 const expected_file_path = try fs.path.join(
503 testing.allocator,
504 &[_][]const u8{ base_realpath, "test_file" },
505 );
506 defer testing.allocator.free(expected_file_path);
507 const expected_dir_path = try fs.path.join(
508 testing.allocator,
509 &[_][]const u8{ base_realpath, "test_dir" },
510 );
511 defer testing.allocator.free(expected_dir_path);
512
513 // First, test non-alloc version
514 {
515 const file_path = try ctx.dir.realpath(test_file_path, &buf);
516 try testing.expectEqualStrings(expected_file_path, file_path);
517
518 const dir_path = try ctx.dir.realpath(test_dir_path, &buf);
519 try testing.expectEqualStrings(expected_dir_path, dir_path);
520 }
521
522 // Next, test alloc version
523 {
524 const file_path = try ctx.dir.realpathAlloc(testing.allocator, test_file_path);
525 defer testing.allocator.free(file_path);
526 try testing.expectEqualStrings(expected_file_path, file_path);
527
528 const dir_path = try ctx.dir.realpathAlloc(testing.allocator, test_dir_path);
529 defer testing.allocator.free(dir_path);
530 try testing.expectEqualStrings(expected_dir_path, dir_path);
531 }
532 }
533 }.impl);
399534}
400535
401536test "readAllAlloc" {
......@@ -432,211 +567,221 @@ test "readAllAlloc" {
432567}
433568
434569test "directory operations on files" {
435 var tmp_dir = tmpDir(.{});
436 defer tmp_dir.cleanup();
437
438 const test_file_name = "test_file";
439
440 var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true });
441 file.close();
442
443 try testing.expectError(error.PathAlreadyExists, tmp_dir.dir.makeDir(test_file_name));
444 try testing.expectError(error.NotDir, tmp_dir.dir.openDir(test_file_name, .{}));
445 try testing.expectError(error.NotDir, tmp_dir.dir.deleteDir(test_file_name));
446
447 switch (builtin.os.tag) {
448 .wasi, .freebsd, .netbsd, .openbsd, .dragonfly => {},
449 else => {
450 const absolute_path = try tmp_dir.dir.realpathAlloc(testing.allocator, test_file_name);
451 defer testing.allocator.free(absolute_path);
452
453 try testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(absolute_path));
454 try testing.expectError(error.NotDir, fs.deleteDirAbsolute(absolute_path));
455 },
456 }
457
458 // ensure the file still exists and is a file as a sanity check
459 file = try tmp_dir.dir.openFile(test_file_name, .{});
460 const stat = try file.stat();
461 try testing.expect(stat.kind == .file);
462 file.close();
570 try testWithAllSupportedPathTypes(struct {
571 fn impl(ctx: *TestContext) !void {
572 const test_file_name = try ctx.transformPath("test_file");
573
574 var file = try ctx.dir.createFile(test_file_name, .{ .read = true });
575 file.close();
576
577 try testing.expectError(error.PathAlreadyExists, ctx.dir.makeDir(test_file_name));
578 try testing.expectError(error.NotDir, ctx.dir.openDir(test_file_name, .{}));
579 try testing.expectError(error.NotDir, ctx.dir.deleteDir(test_file_name));
580
581 if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) {
582 try testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(test_file_name));
583 try testing.expectError(error.NotDir, fs.deleteDirAbsolute(test_file_name));
584 }
585
586 // ensure the file still exists and is a file as a sanity check
587 file = try ctx.dir.openFile(test_file_name, .{});
588 const stat = try file.stat();
589 try testing.expect(stat.kind == .file);
590 file.close();
591 }
592 }.impl);
463593}
464594
465595test "file operations on directories" {
466596 // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759
467597 if (builtin.os.tag == .freebsd) return error.SkipZigTest;
468598
469 var tmp_dir = tmpDir(.{});
470 defer tmp_dir.cleanup();
471
472 const test_dir_name = "test_dir";
473
474 try tmp_dir.dir.makeDir(test_dir_name);
475
476 try testing.expectError(error.IsDir, tmp_dir.dir.createFile(test_dir_name, .{}));
477 try testing.expectError(error.IsDir, tmp_dir.dir.deleteFile(test_dir_name));
478 switch (builtin.os.tag) {
479 // no error when reading a directory.
480 .dragonfly, .netbsd => {},
481 // Currently, WASI will return error.Unexpected (via ENOTCAPABLE) when attempting fd_read on a directory handle.
482 // TODO: Re-enable on WASI once https://github.com/bytecodealliance/wasmtime/issues/1935 is resolved.
483 .wasi => {},
484 else => {
485 try testing.expectError(error.IsDir, tmp_dir.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize)));
486 },
487 }
488 // Note: The `.mode = .read_write` is necessary to ensure the error occurs on all platforms.
489 // TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732
490 try testing.expectError(error.IsDir, tmp_dir.dir.openFile(test_dir_name, .{ .mode = .read_write }));
491
492 switch (builtin.os.tag) {
493 .wasi, .freebsd, .netbsd, .openbsd, .dragonfly => {},
494 else => {
495 const absolute_path = try tmp_dir.dir.realpathAlloc(testing.allocator, test_dir_name);
496 defer testing.allocator.free(absolute_path);
497
498 try testing.expectError(error.IsDir, fs.createFileAbsolute(absolute_path, .{}));
499 try testing.expectError(error.IsDir, fs.deleteFileAbsolute(absolute_path));
500 },
501 }
502
503 // ensure the directory still exists as a sanity check
504 var dir = try tmp_dir.dir.openDir(test_dir_name, .{});
505 dir.close();
599 try testWithAllSupportedPathTypes(struct {
600 fn impl(ctx: *TestContext) !void {
601 const test_dir_name = try ctx.transformPath("test_dir");
602
603 try ctx.dir.makeDir(test_dir_name);
604
605 try testing.expectError(error.IsDir, ctx.dir.createFile(test_dir_name, .{}));
606 try testing.expectError(error.IsDir, ctx.dir.deleteFile(test_dir_name));
607 switch (builtin.os.tag) {
608 // no error when reading a directory.
609 .dragonfly, .netbsd => {},
610 // Currently, WASI will return error.Unexpected (via ENOTCAPABLE) when attempting fd_read on a directory handle.
611 // TODO: Re-enable on WASI once https://github.com/bytecodealliance/wasmtime/issues/1935 is resolved.
612 .wasi => {},
613 else => {
614 try testing.expectError(error.IsDir, ctx.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize)));
615 },
616 }
617 // Note: The `.mode = .read_write` is necessary to ensure the error occurs on all platforms.
618 // TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732
619 try testing.expectError(error.IsDir, ctx.dir.openFile(test_dir_name, .{ .mode = .read_write }));
620
621 if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) {
622 try testing.expectError(error.IsDir, fs.createFileAbsolute(test_dir_name, .{}));
623 try testing.expectError(error.IsDir, fs.deleteFileAbsolute(test_dir_name));
624 }
625
626 // ensure the directory still exists as a sanity check
627 var dir = try ctx.dir.openDir(test_dir_name, .{});
628 dir.close();
629 }
630 }.impl);
506631}
507632
508633test "deleteDir" {
509 var tmp_dir = tmpDir(.{});
510 defer tmp_dir.cleanup();
511
512 // deleting a non-existent directory
513 try testing.expectError(error.FileNotFound, tmp_dir.dir.deleteDir("test_dir"));
514
515 var dir = try tmp_dir.dir.makeOpenPath("test_dir", .{});
516 var file = try dir.createFile("test_file", .{});
517 file.close();
518 dir.close();
519
520 // deleting a non-empty directory
521 try testing.expectError(error.DirNotEmpty, tmp_dir.dir.deleteDir("test_dir"));
522
523 dir = try tmp_dir.dir.openDir("test_dir", .{});
524 try dir.deleteFile("test_file");
525 dir.close();
526
527 // deleting an empty directory
528 try tmp_dir.dir.deleteDir("test_dir");
634 try testWithAllSupportedPathTypes(struct {
635 fn impl(ctx: *TestContext) !void {
636 const test_dir_path = try ctx.transformPath("test_dir");
637 const test_file_path = try ctx.transformPath("test_dir" ++ std.fs.path.sep_str ++ "test_file");
638
639 // deleting a non-existent directory
640 try testing.expectError(error.FileNotFound, ctx.dir.deleteDir(test_dir_path));
641
642 // deleting a non-empty directory
643 try ctx.dir.makeDir(test_dir_path);
644 try ctx.dir.writeFile(test_file_path, "");
645 try testing.expectError(error.DirNotEmpty, ctx.dir.deleteDir(test_dir_path));
646
647 // deleting an empty directory
648 try ctx.dir.deleteFile(test_file_path);
649 try ctx.dir.deleteDir(test_dir_path);
650 }
651 }.impl);
529652}
530653
531654test "Dir.rename files" {
532 var tmp_dir = tmpDir(.{});
533 defer tmp_dir.cleanup();
534
535 try testing.expectError(error.FileNotFound, tmp_dir.dir.rename("missing_file_name", "something_else"));
536
537 // Renaming files
538 const test_file_name = "test_file";
539 const renamed_test_file_name = "test_file_renamed";
540 var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true });
541 file.close();
542 try tmp_dir.dir.rename(test_file_name, renamed_test_file_name);
543
544 // Ensure the file was renamed
545 try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{}));
546 file = try tmp_dir.dir.openFile(renamed_test_file_name, .{});
547 file.close();
548
549 // Rename to self succeeds
550 try tmp_dir.dir.rename(renamed_test_file_name, renamed_test_file_name);
551
552 // Rename to existing file succeeds
553 var existing_file = try tmp_dir.dir.createFile("existing_file", .{ .read = true });
554 existing_file.close();
555 try tmp_dir.dir.rename(renamed_test_file_name, "existing_file");
556
557 try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(renamed_test_file_name, .{}));
558 file = try tmp_dir.dir.openFile("existing_file", .{});
559 file.close();
655 try testWithAllSupportedPathTypes(struct {
656 fn impl(ctx: *TestContext) !void {
657 const missing_file_path = try ctx.transformPath("missing_file_name");
658 const something_else_path = try ctx.transformPath("something_else");
659
660 try testing.expectError(error.FileNotFound, ctx.dir.rename(missing_file_path, something_else_path));
661
662 // Renaming files
663 const test_file_name = try ctx.transformPath("test_file");
664 const renamed_test_file_name = try ctx.transformPath("test_file_renamed");
665 var file = try ctx.dir.createFile(test_file_name, .{ .read = true });
666 file.close();
667 try ctx.dir.rename(test_file_name, renamed_test_file_name);
668
669 // Ensure the file was renamed
670 try testing.expectError(error.FileNotFound, ctx.dir.openFile(test_file_name, .{}));
671 file = try ctx.dir.openFile(renamed_test_file_name, .{});
672 file.close();
673
674 // Rename to self succeeds
675 try ctx.dir.rename(renamed_test_file_name, renamed_test_file_name);
676
677 // Rename to existing file succeeds
678 const existing_file_path = try ctx.transformPath("existing_file");
679 var existing_file = try ctx.dir.createFile(existing_file_path, .{ .read = true });
680 existing_file.close();
681 try ctx.dir.rename(renamed_test_file_name, existing_file_path);
682
683 try testing.expectError(error.FileNotFound, ctx.dir.openFile(renamed_test_file_name, .{}));
684 file = try ctx.dir.openFile(existing_file_path, .{});
685 file.close();
686 }
687 }.impl);
560688}
561689
562690test "Dir.rename directories" {
563 var tmp_dir = tmpDir(.{});
564 defer tmp_dir.cleanup();
565
566 // Renaming directories
567 try tmp_dir.dir.makeDir("test_dir");
568 try tmp_dir.dir.rename("test_dir", "test_dir_renamed");
569
570 // Ensure the directory was renamed
571 try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir", .{}));
572 var dir = try tmp_dir.dir.openDir("test_dir_renamed", .{});
573
574 // Put a file in the directory
575 var file = try dir.createFile("test_file", .{ .read = true });
576 file.close();
577 dir.close();
578
579 try tmp_dir.dir.rename("test_dir_renamed", "test_dir_renamed_again");
580
581 // Ensure the directory was renamed and the file still exists in it
582 try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir_renamed", .{}));
583 dir = try tmp_dir.dir.openDir("test_dir_renamed_again", .{});
584 file = try dir.openFile("test_file", .{});
585 file.close();
586 dir.close();
691 try testWithAllSupportedPathTypes(struct {
692 fn impl(ctx: *TestContext) !void {
693 const test_dir_path = try ctx.transformPath("test_dir");
694 const test_dir_renamed_path = try ctx.transformPath("test_dir_renamed");
695
696 // Renaming directories
697 try ctx.dir.makeDir(test_dir_path);
698 try ctx.dir.rename(test_dir_path, test_dir_renamed_path);
699
700 // Ensure the directory was renamed
701 try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{}));
702 var dir = try ctx.dir.openDir(test_dir_renamed_path, .{});
703
704 // Put a file in the directory
705 var file = try dir.createFile("test_file", .{ .read = true });
706 file.close();
707 dir.close();
708
709 const test_dir_renamed_again_path = try ctx.transformPath("test_dir_renamed_again");
710 try ctx.dir.rename(test_dir_renamed_path, test_dir_renamed_again_path);
711
712 // Ensure the directory was renamed and the file still exists in it
713 try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_renamed_path, .{}));
714 dir = try ctx.dir.openDir(test_dir_renamed_again_path, .{});
715 file = try dir.openFile("test_file", .{});
716 file.close();
717 dir.close();
718 }
719 }.impl);
587720}
588721
589722test "Dir.rename directory onto empty dir" {
590723 // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
591724 if (builtin.os.tag == .windows) return error.SkipZigTest;
592725
593 var tmp_dir = testing.tmpDir(.{});
594 defer tmp_dir.cleanup();
726 try testWithAllSupportedPathTypes(struct {
727 fn impl(ctx: *TestContext) !void {
728 const test_dir_path = try ctx.transformPath("test_dir");
729 const target_dir_path = try ctx.transformPath("target_dir_path");
595730
596 try tmp_dir.dir.makeDir("test_dir");
597 try tmp_dir.dir.makeDir("target_dir");
598 try tmp_dir.dir.rename("test_dir", "target_dir");
731 try ctx.dir.makeDir(test_dir_path);
732 try ctx.dir.makeDir(target_dir_path);
733 try ctx.dir.rename(test_dir_path, target_dir_path);
599734
600 // Ensure the directory was renamed
601 try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir", .{}));
602 var dir = try tmp_dir.dir.openDir("target_dir", .{});
603 dir.close();
735 // Ensure the directory was renamed
736 try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{}));
737 var dir = try ctx.dir.openDir(target_dir_path, .{});
738 dir.close();
739 }
740 }.impl);
604741}
605742
606743test "Dir.rename directory onto non-empty dir" {
607744 // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
608745 if (builtin.os.tag == .windows) return error.SkipZigTest;
609746
610 var tmp_dir = testing.tmpDir(.{});
611 defer tmp_dir.cleanup();
747 try testWithAllSupportedPathTypes(struct {
748 fn impl(ctx: *TestContext) !void {
749 const test_dir_path = try ctx.transformPath("test_dir");
750 const target_dir_path = try ctx.transformPath("target_dir_path");
612751
613 try tmp_dir.dir.makeDir("test_dir");
752 try ctx.dir.makeDir(test_dir_path);
614753
615 var target_dir = try tmp_dir.dir.makeOpenPath("target_dir", .{});
616 var file = try target_dir.createFile("test_file", .{ .read = true });
617 file.close();
618 target_dir.close();
754 var target_dir = try ctx.dir.makeOpenPath(target_dir_path, .{});
755 var file = try target_dir.createFile("test_file", .{ .read = true });
756 file.close();
757 target_dir.close();
619758
620 // Rename should fail with PathAlreadyExists if target_dir is non-empty
621 try testing.expectError(error.PathAlreadyExists, tmp_dir.dir.rename("test_dir", "target_dir"));
759 // Rename should fail with PathAlreadyExists if target_dir is non-empty
760 try testing.expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, target_dir_path));
622761
623 // Ensure the directory was not renamed
624 var dir = try tmp_dir.dir.openDir("test_dir", .{});
625 dir.close();
762 // Ensure the directory was not renamed
763 var dir = try ctx.dir.openDir(test_dir_path, .{});
764 dir.close();
765 }
766 }.impl);
626767}
627768
628769test "Dir.rename file <-> dir" {
629770 // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
630771 if (builtin.os.tag == .windows) return error.SkipZigTest;
631772
632 var tmp_dir = tmpDir(.{});
633 defer tmp_dir.cleanup();
773 try testWithAllSupportedPathTypes(struct {
774 fn impl(ctx: *TestContext) !void {
775 const test_file_path = try ctx.transformPath("test_file");
776 const test_dir_path = try ctx.transformPath("test_dir");
634777
635 var file = try tmp_dir.dir.createFile("test_file", .{ .read = true });
636 file.close();
637 try tmp_dir.dir.makeDir("test_dir");
638 try testing.expectError(error.IsDir, tmp_dir.dir.rename("test_file", "test_dir"));
639 try testing.expectError(error.NotDir, tmp_dir.dir.rename("test_dir", "test_file"));
778 var file = try ctx.dir.createFile(test_file_path, .{ .read = true });
779 file.close();
780 try ctx.dir.makeDir(test_dir_path);
781 try testing.expectError(error.IsDir, ctx.dir.rename(test_file_path, test_dir_path));
782 try testing.expectError(error.NotDir, ctx.dir.rename(test_dir_path, test_file_path));
783 }
784 }.impl);
640785}
641786
642787test "rename" {
......@@ -720,35 +865,33 @@ test "openSelfExe" {
720865}
721866
722867test "makePath, put some files in it, deleteTree" {
723 var tmp = tmpDir(.{});
724 defer tmp.cleanup();
868 try testWithAllSupportedPathTypes(struct {
869 fn impl(ctx: *TestContext) !void {
870 const dir_path = try ctx.transformPath("os_test_tmp");
725871
726 try tmp.dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");
727 try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");
728 try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");
729 try tmp.dir.deleteTree("os_test_tmp");
730 if (tmp.dir.openDir("os_test_tmp", .{})) |dir| {
731 _ = dir;
732 @panic("expected error");
733 } else |err| {
734 try testing.expect(err == error.FileNotFound);
735 }
872 try ctx.dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");
873 try ctx.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");
874 try ctx.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");
875
876 try ctx.dir.deleteTree(dir_path);
877 try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{}));
878 }
879 }.impl);
736880}
737881
738882test "makePath, put some files in it, deleteTreeMinStackSize" {
739 var tmp = tmpDir(.{});
740 defer tmp.cleanup();
883 try testWithAllSupportedPathTypes(struct {
884 fn impl(ctx: *TestContext) !void {
885 const dir_path = try ctx.transformPath("os_test_tmp");
741886
742 try tmp.dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");
743 try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");
744 try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");
745 try tmp.dir.deleteTreeMinStackSize("os_test_tmp");
746 if (tmp.dir.openDir("os_test_tmp", .{})) |dir| {
747 _ = dir;
748 @panic("expected error");
749 } else |err| {
750 try testing.expect(err == error.FileNotFound);
751 }
887 try ctx.dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");
888 try ctx.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");
889 try ctx.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");
890
891 try ctx.dir.deleteTreeMinStackSize(dir_path);
892 try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{}));
893 }
894 }.impl);
752895}
753896
754897test "makePath in a directory that no longer exists" {
......@@ -789,9 +932,9 @@ test "max file name component lengths" {
789932 defer tmp.cleanup();
790933
791934 if (builtin.os.tag == .windows) {
792 // € is the character with the largest codepoint that is encoded as a single u16 in UTF-16,
793 // so Windows allows for NAME_MAX of them
794 const maxed_windows_filename = ("€".*) ** std.os.windows.NAME_MAX;
935 // U+FFFF is the character with the largest code point that is encoded as a single
936 // UTF-16 code unit, so Windows allows for NAME_MAX of them.
937 const maxed_windows_filename = ("\u{FFFF}".*) ** std.os.windows.NAME_MAX;
795938 try testFilenameLimits(tmp.iterable_dir, &maxed_windows_filename);
796939 } else if (builtin.os.tag == .wasi) {
797940 // On WASI, the maxed filename depends on the host OS, so in order for this test to
......@@ -889,20 +1032,19 @@ test "pwritev, preadv" {
8891032}
8901033
8911034test "access file" {
892 var tmp = tmpDir(.{});
893 defer tmp.cleanup();
1035 try testWithAllSupportedPathTypes(struct {
1036 fn impl(ctx: *TestContext) !void {
1037 const dir_path = try ctx.transformPath("os_test_tmp");
1038 const file_path = try ctx.transformPath("os_test_tmp" ++ fs.path.sep_str ++ "file.txt");
8941039
895 try tmp.dir.makePath("os_test_tmp");
896 if (tmp.dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{})) |ok| {
897 _ = ok;
898 @panic("expected error");
899 } else |err| {
900 try testing.expect(err == error.FileNotFound);
901 }
1040 try ctx.dir.makePath(dir_path);
1041 try testing.expectError(error.FileNotFound, ctx.dir.access(file_path, .{}));
9021042
903 try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", "");
904 try tmp.dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{});
905 try tmp.dir.deleteTree("os_test_tmp");
1043 try ctx.dir.writeFile(file_path, "");
1044 try ctx.dir.access(file_path, .{});
1045 try ctx.dir.deleteTree(dir_path);
1046 }
1047 }.impl);
9061048}
9071049
9081050test "sendfile" {
......@@ -996,26 +1138,27 @@ test "copyRangeAll" {
9961138 try testing.expect(mem.eql(u8, written_buf[0..amt], data));
9971139}
9981140
999test "fs.copyFile" {
1000 const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";
1001 const src_file = "tmp_test_copy_file.txt";
1002 const dest_file = "tmp_test_copy_file2.txt";
1003 const dest_file2 = "tmp_test_copy_file3.txt";
1004
1005 var tmp = tmpDir(.{});
1006 defer tmp.cleanup();
1141test "copyFile" {
1142 try testWithAllSupportedPathTypes(struct {
1143 fn impl(ctx: *TestContext) !void {
1144 const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";
1145 const src_file = try ctx.transformPath("tmp_test_copy_file.txt");
1146 const dest_file = try ctx.transformPath("tmp_test_copy_file2.txt");
1147 const dest_file2 = try ctx.transformPath("tmp_test_copy_file3.txt");
10071148
1008 try tmp.dir.writeFile(src_file, data);
1009 defer tmp.dir.deleteFile(src_file) catch {};
1149 try ctx.dir.writeFile(src_file, data);
1150 defer ctx.dir.deleteFile(src_file) catch {};
10101151
1011 try tmp.dir.copyFile(src_file, tmp.dir, dest_file, .{});
1012 defer tmp.dir.deleteFile(dest_file) catch {};
1152 try ctx.dir.copyFile(src_file, ctx.dir, dest_file, .{});
1153 defer ctx.dir.deleteFile(dest_file) catch {};
10131154
1014 try tmp.dir.copyFile(src_file, tmp.dir, dest_file2, .{ .override_mode = File.default_mode });
1015 defer tmp.dir.deleteFile(dest_file2) catch {};
1155 try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, .{ .override_mode = File.default_mode });
1156 defer ctx.dir.deleteFile(dest_file2) catch {};
10161157
1017 try expectFileContents(tmp.dir, dest_file, data);
1018 try expectFileContents(tmp.dir, dest_file2, data);
1158 try expectFileContents(ctx.dir, dest_file, data);
1159 try expectFileContents(ctx.dir, dest_file2, data);
1160 }
1161 }.impl);
10191162}
10201163
10211164fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void {
......@@ -1026,78 +1169,75 @@ fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void {
10261169}
10271170
10281171test "AtomicFile" {
1029 const test_out_file = "tmp_atomic_file_test_dest.txt";
1030 const test_content =
1031 \\ hello!
1032 \\ this is a test file
1033 ;
1034
1035 var tmp = tmpDir(.{});
1036 defer tmp.cleanup();
1037
1038 {
1039 var af = try tmp.dir.atomicFile(test_out_file, .{});
1040 defer af.deinit();
1041 try af.file.writeAll(test_content);
1042 try af.finish();
1043 }
1044 const content = try tmp.dir.readFileAlloc(testing.allocator, test_out_file, 9999);
1045 defer testing.allocator.free(content);
1046 try testing.expect(mem.eql(u8, content, test_content));
1047
1048 try tmp.dir.deleteFile(test_out_file);
1049}
1050
1051test "realpath" {
1052 if (builtin.os.tag == .wasi) return error.SkipZigTest;
1053
1054 var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
1055 try testing.expectError(error.FileNotFound, fs.realpath("definitely_bogus_does_not_exist1234", &buf));
1172 try testWithAllSupportedPathTypes(struct {
1173 fn impl(ctx: *TestContext) !void {
1174 const test_out_file = try ctx.transformPath("tmp_atomic_file_test_dest.txt");
1175 const test_content =
1176 \\ hello!
1177 \\ this is a test file
1178 ;
1179
1180 {
1181 var af = try ctx.dir.atomicFile(test_out_file, .{});
1182 defer af.deinit();
1183 try af.file.writeAll(test_content);
1184 try af.finish();
1185 }
1186 const content = try ctx.dir.readFileAlloc(testing.allocator, test_out_file, 9999);
1187 defer testing.allocator.free(content);
1188 try testing.expect(mem.eql(u8, content, test_content));
1189
1190 try ctx.dir.deleteFile(test_out_file);
1191 }
1192 }.impl);
10561193}
10571194
10581195test "open file with exclusive nonblocking lock twice" {
10591196 if (builtin.os.tag == .wasi) return error.SkipZigTest;
10601197
1061 const filename = "file_nonblocking_lock_test.txt";
1062
1063 var tmp = tmpDir(.{});
1064 defer tmp.cleanup();
1198 try testWithAllSupportedPathTypes(struct {
1199 fn impl(ctx: *TestContext) !void {
1200 const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
10651201
1066 const file1 = try tmp.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1067 defer file1.close();
1202 const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1203 defer file1.close();
10681204
1069 const file2 = tmp.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1070 try testing.expectError(error.WouldBlock, file2);
1205 const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1206 try testing.expectError(error.WouldBlock, file2);
1207 }
1208 }.impl);
10711209}
10721210
10731211test "open file with shared and exclusive nonblocking lock" {
10741212 if (builtin.os.tag == .wasi) return error.SkipZigTest;
10751213
1076 const filename = "file_nonblocking_lock_test.txt";
1077
1078 var tmp = tmpDir(.{});
1079 defer tmp.cleanup();
1214 try testWithAllSupportedPathTypes(struct {
1215 fn impl(ctx: *TestContext) !void {
1216 const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
10801217
1081 const file1 = try tmp.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
1082 defer file1.close();
1218 const file1 = try ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
1219 defer file1.close();
10831220
1084 const file2 = tmp.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1085 try testing.expectError(error.WouldBlock, file2);
1221 const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1222 try testing.expectError(error.WouldBlock, file2);
1223 }
1224 }.impl);
10861225}
10871226
10881227test "open file with exclusive and shared nonblocking lock" {
10891228 if (builtin.os.tag == .wasi) return error.SkipZigTest;
10901229
1091 const filename = "file_nonblocking_lock_test.txt";
1092
1093 var tmp = tmpDir(.{});
1094 defer tmp.cleanup();
1230 try testWithAllSupportedPathTypes(struct {
1231 fn impl(ctx: *TestContext) !void {
1232 const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
10951233
1096 const file1 = try tmp.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1097 defer file1.close();
1234 const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1235 defer file1.close();
10981236
1099 const file2 = tmp.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
1100 try testing.expectError(error.WouldBlock, file2);
1237 const file2 = ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
1238 try testing.expectError(error.WouldBlock, file2);
1239 }
1240 }.impl);
11011241}
11021242
11031243test "open file with exclusive lock twice, make sure second lock waits" {
......@@ -1108,42 +1248,44 @@ test "open file with exclusive lock twice, make sure second lock waits" {
11081248 return error.SkipZigTest;
11091249 }
11101250
1111 const filename = "file_lock_test.txt";
1112
1113 var tmp = tmpDir(.{});
1114 defer tmp.cleanup();
1115
1116 const file = try tmp.dir.createFile(filename, .{ .lock = .exclusive });
1117 errdefer file.close();
1118
1119 const S = struct {
1120 fn checkFn(dir: *fs.Dir, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void {
1121 started.set();
1122 const file1 = try dir.createFile(filename, .{ .lock = .exclusive });
1123
1124 locked.set();
1125 file1.close();
1251 try testWithAllSupportedPathTypes(struct {
1252 fn impl(ctx: *TestContext) !void {
1253 const filename = try ctx.transformPath("file_lock_test.txt");
1254
1255 const file = try ctx.dir.createFile(filename, .{ .lock = .exclusive });
1256 errdefer file.close();
1257
1258 const S = struct {
1259 fn checkFn(dir: *fs.Dir, path: []const u8, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void {
1260 started.set();
1261 const file1 = try dir.createFile(path, .{ .lock = .exclusive });
1262
1263 locked.set();
1264 file1.close();
1265 }
1266 };
1267
1268 var started = std.Thread.ResetEvent{};
1269 var locked = std.Thread.ResetEvent{};
1270
1271 const t = try std.Thread.spawn(.{}, S.checkFn, .{
1272 &ctx.dir,
1273 filename,
1274 &started,
1275 &locked,
1276 });
1277 defer t.join();
1278
1279 // Wait for the spawned thread to start trying to acquire the exclusive file lock.
1280 // Then wait a bit to make sure that can't acquire it since we currently hold the file lock.
1281 started.wait();
1282 try testing.expectError(error.Timeout, locked.timedWait(10 * std.time.ns_per_ms));
1283
1284 // Release the file lock which should unlock the thread to lock it and set the locked event.
1285 file.close();
1286 locked.wait();
11261287 }
1127 };
1128
1129 var started = std.Thread.ResetEvent{};
1130 var locked = std.Thread.ResetEvent{};
1131
1132 const t = try std.Thread.spawn(.{}, S.checkFn, .{
1133 &tmp.dir,
1134 &started,
1135 &locked,
1136 });
1137 defer t.join();
1138
1139 // Wait for the spawned thread to start trying to acquire the exclusive file lock.
1140 // Then wait a bit to make sure that can't acquire it since we currently hold the file lock.
1141 started.wait();
1142 try testing.expectError(error.Timeout, locked.timedWait(10 * std.time.ns_per_ms));
1143
1144 // Release the file lock which should unlock the thread to lock it and set the locked event.
1145 file.close();
1146 locked.wait();
1288 }.impl);
11471289}
11481290
11491291test "open file with exclusive nonblocking lock twice (absolute paths)" {
......@@ -1259,29 +1401,36 @@ test "walker without fully iterating" {
12591401test ". and .. in fs.Dir functions" {
12601402 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
12611403
1262 var tmp = tmpDir(.{});
1263 defer tmp.cleanup();
1264
1265 try tmp.dir.makeDir("./subdir");
1266 try tmp.dir.access("./subdir", .{});
1267 var created_subdir = try tmp.dir.openDir("./subdir", .{});
1268 created_subdir.close();
1269
1270 const created_file = try tmp.dir.createFile("./subdir/../file", .{});
1271 created_file.close();
1272 try tmp.dir.access("./subdir/../file", .{});
1273
1274 try tmp.dir.copyFile("./subdir/../file", tmp.dir, "./subdir/../copy", .{});
1275 try tmp.dir.rename("./subdir/../copy", "./subdir/../rename");
1276 const renamed_file = try tmp.dir.openFile("./subdir/../rename", .{});
1277 renamed_file.close();
1278 try tmp.dir.deleteFile("./subdir/../rename");
1279
1280 try tmp.dir.writeFile("./subdir/../update", "something");
1281 const prev_status = try tmp.dir.updateFile("./subdir/../file", tmp.dir, "./subdir/../update", .{});
1282 try testing.expectEqual(fs.PrevStatus.stale, prev_status);
1283
1284 try tmp.dir.deleteDir("./subdir");
1404 try testWithAllSupportedPathTypes(struct {
1405 fn impl(ctx: *TestContext) !void {
1406 const subdir_path = try ctx.transformPath("./subdir");
1407 const file_path = try ctx.transformPath("./subdir/../file");
1408 const copy_path = try ctx.transformPath("./subdir/../copy");
1409 const rename_path = try ctx.transformPath("./subdir/../rename");
1410 const update_path = try ctx.transformPath("./subdir/../update");
1411
1412 try ctx.dir.makeDir(subdir_path);
1413 try ctx.dir.access(subdir_path, .{});
1414 var created_subdir = try ctx.dir.openDir(subdir_path, .{});
1415 created_subdir.close();
1416
1417 const created_file = try ctx.dir.createFile(file_path, .{});
1418 created_file.close();
1419 try ctx.dir.access(file_path, .{});
1420
1421 try ctx.dir.copyFile(file_path, ctx.dir, copy_path, .{});
1422 try ctx.dir.rename(copy_path, rename_path);
1423 const renamed_file = try ctx.dir.openFile(rename_path, .{});
1424 renamed_file.close();
1425 try ctx.dir.deleteFile(rename_path);
1426
1427 try ctx.dir.writeFile(update_path, "something");
1428 const prev_status = try ctx.dir.updateFile(file_path, ctx.dir, update_path, .{});
1429 try testing.expectEqual(fs.PrevStatus.stale, prev_status);
1430
1431 try ctx.dir.deleteDir(subdir_path);
1432 }
1433 }.impl);
12851434}
12861435
12871436test ". and .. in absolute functions" {
lib/std/os.zig+20-7
......@@ -5169,11 +5169,30 @@ pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPat
51695169 return getFdPath(h_file, out_buffer);
51705170}
51715171
5172pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool {
5173 return switch (os.tag) {
5174 // zig fmt: off
5175 .windows,
5176 .macos, .ios, .watchos, .tvos,
5177 .linux,
5178 .solaris,
5179 .freebsd,
5180 => true,
5181 // zig fmt: on
5182 .dragonfly => os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt,
5183 .netbsd => os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) != .lt,
5184 else => false,
5185 };
5186}
5187
51725188/// Return canonical path of handle `fd`.
51735189/// This function is very host-specific and is not universally supported by all hosts.
51745190/// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is
51755191/// unsupported on WASI.
51765192pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
5193 if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) {
5194 @compileError("querying for canonical path of a handle is unsupported on this host");
5195 }
51775196 switch (builtin.os.tag) {
51785197 .windows => {
51795198 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
......@@ -5276,9 +5295,6 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
52765295 }
52775296 },
52785297 .dragonfly => {
5279 if (comptime builtin.os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) == .lt) {
5280 @compileError("querying for canonical path of a handle is unsupported on this host");
5281 }
52825298 @memset(out_buffer[0..MAX_PATH_BYTES], 0);
52835299 switch (errno(system.fcntl(fd, F.GETPATH, out_buffer))) {
52845300 .SUCCESS => {},
......@@ -5290,9 +5306,6 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
52905306 return out_buffer[0..len];
52915307 },
52925308 .netbsd => {
5293 if (comptime builtin.os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) == .lt) {
5294 @compileError("querying for canonical path of a handle is unsupported on this host");
5295 }
52965309 @memset(out_buffer[0..MAX_PATH_BYTES], 0);
52975310 switch (errno(system.fcntl(fd, F.GETPATH, out_buffer))) {
52985311 .SUCCESS => {},
......@@ -5306,7 +5319,7 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
53065319 const len = mem.indexOfScalar(u8, out_buffer[0..], @as(u8, 0)) orelse MAX_PATH_BYTES;
53075320 return out_buffer[0..len];
53085321 },
5309 else => @compileError("querying for canonical path of a handle is unsupported on this host"),
5322 else => unreachable, // made unreachable by isGetFdPathSupportedOnTarget above
53105323 }
53115324}
53125325