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;...@@ -13,38 +13,167 @@ const File = std.fs.File;
13const tmpDir = testing.tmpDir;13const tmpDir = testing.tmpDir;
14const tmpIterableDir = testing.tmpIterableDir;14const tmpIterableDir = testing.tmpIterableDir;
1515
16test "Dir.readLink" {16const PathType = enum {
17 var tmp = tmpDir(.{});17 relative,
18 defer tmp.cleanup();18 absolute,
1919 unc,
20 // Create some targets20
21 try tmp.dir.writeFile("file.txt", "nonsense");21 pub fn isSupported(self: PathType, target_os: std.Target.Os) bool {
22 try tmp.dir.makeDir("subdir");22 return switch (self) {
23 .relative => true,
24 .absolute => std.os.isGetFdPathSupportedOnTarget(target_os),
25 .unc => target_os.tag == .windows,
26 };
27 }
2328
24 {29 pub const TransformError = std.os.RealPathError || error{OutOfMemory};
25 // Create symbolic link by path30 pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8;
26 tmp.dir.symLink("file.txt", "symlink1", .{}) catch |err| switch (err) {31
27 // Symlink requires admin privileges on windows, so this test can legitimately fail.32 pub fn getTransformFn(comptime path_type: PathType) TransformFn {
28 error.AccessDenied => return error.SkipZigTest,33 switch (path_type) {
29 else => return err,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,
30 };91 };
31 try testReadLink(tmp.dir, "file.txt", "symlink1");
32 }92 }
33 {93
34 // Create symbolic link by path94 pub fn deinit(self: *TestContext) void {
35 tmp.dir.symLink("subdir", "symlink2", .{ .is_directory = true }) catch |err| switch (err) {95 self.arena.deinit();
36 // Symlink requires admin privileges on windows, so this test can legitimately fail.96 self.tmp.cleanup();
37 error.AccessDenied => return error.SkipZigTest,97 }
38 else => return err,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;
39 };122 };
40 try testReadLink(tmp.dir, "subdir", "symlink2");
41 }123 }
42}124}
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
44fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {157fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {
45 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;158 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
46 const given = try dir.readLink(symlink_path, buffer[0..]);159 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);
48}177}
49178
50test "accessAbsolute" {179test "accessAbsolute" {
...@@ -349,53 +478,59 @@ fn contains(entries: *const std.ArrayList(IterableDir.Entry), el: IterableDir.En...@@ -349,53 +478,59 @@ fn contains(entries: *const std.ArrayList(IterableDir.Entry), el: IterableDir.En
349}478}
350479
351test "Dir.realpath smoke test" {480test "Dir.realpath smoke test" {
352 switch (builtin.os.tag) {481 if (!comptime std.os.isGetFdPathSupportedOnTarget(builtin.os)) return error.SkipZigTest;
353 .linux, .windows, .macos, .ios, .watchos, .tvos, .solaris => {},482
354 else => return error.SkipZigTest,483 try testWithAllSupportedPathTypes(struct {
355 }484 fn impl(ctx: *TestContext) !void {
356485 const test_file_path = try ctx.transformPath("test_file");
357 var tmp_dir = tmpDir(.{});486 const test_dir_path = try ctx.transformPath("test_dir");
358 defer tmp_dir.cleanup();487 var buf: [fs.MAX_PATH_BYTES]u8 = undefined;
359488
360 var file = try tmp_dir.dir.createFile("test_file", .{ .lock = .shared });489 // FileNotFound if the path doesn't exist
361 // We need to close the file immediately as otherwise on Windows we'll end up490 try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(testing.allocator, test_file_path));
362 // with a sharing violation.491 try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_file_path, &buf));
363 file.close();492 try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(testing.allocator, test_dir_path));
364493 try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_dir_path, &buf));
365 try tmp_dir.dir.makeDir("test_dir");494
366495 // Now create the file and dir
367 var arena = ArenaAllocator.init(testing.allocator);496 try ctx.dir.writeFile(test_file_path, "");
368 defer arena.deinit();497 try ctx.dir.makeDir(test_dir_path);
369 const allocator = arena.allocator();498
370499 const base_path = try ctx.transformPath(".");
371 const base_path = blk: {500 const base_realpath = try ctx.dir.realpathAlloc(testing.allocator, base_path);
372 const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp_dir.sub_path[0..] });501 defer testing.allocator.free(base_realpath);
373 break :blk try fs.realpathAlloc(allocator, relative_path);502 const expected_file_path = try fs.path.join(
374 };503 testing.allocator,
375504 &[_][]const u8{ base_realpath, "test_file" },
376 // First, test non-alloc version505 );
377 {506 defer testing.allocator.free(expected_file_path);
378 var buf1: [fs.MAX_PATH_BYTES]u8 = undefined;507 const expected_dir_path = try fs.path.join(
379508 testing.allocator,
380 const file_path = try tmp_dir.dir.realpath("test_file", buf1[0..]);509 &[_][]const u8{ base_realpath, "test_dir" },
381 const expected_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "test_file" });510 );
382 try testing.expectEqualStrings(expected_file_path, file_path);511 defer testing.allocator.free(expected_dir_path);
383512
384 const dir_path = try tmp_dir.dir.realpath("test_dir", buf1[0..]);513 // First, test non-alloc version
385 const expected_dir_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "test_dir" });514 {
386 try testing.expectEqualStrings(expected_dir_path, dir_path);515 const file_path = try ctx.dir.realpath(test_file_path, &buf);
387 }516 try testing.expectEqualStrings(expected_file_path, file_path);
388517
389 // Next, test alloc version518 const dir_path = try ctx.dir.realpath(test_dir_path, &buf);
390 {519 try testing.expectEqualStrings(expected_dir_path, dir_path);
391 const file_path = try tmp_dir.dir.realpathAlloc(allocator, "test_file");520 }
392 const expected_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "test_file" });521
393 try testing.expectEqualStrings(expected_file_path, file_path);522 // Next, test alloc version
394523 {
395 const dir_path = try tmp_dir.dir.realpathAlloc(allocator, "test_dir");524 const file_path = try ctx.dir.realpathAlloc(testing.allocator, test_file_path);
396 const expected_dir_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "test_dir" });525 defer testing.allocator.free(file_path);
397 try testing.expectEqualStrings(expected_dir_path, dir_path);526 try testing.expectEqualStrings(expected_file_path, file_path);
398 }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);
399}534}
400535
401test "readAllAlloc" {536test "readAllAlloc" {
...@@ -432,211 +567,221 @@ test "readAllAlloc" {...@@ -432,211 +567,221 @@ test "readAllAlloc" {
432}567}
433568
434test "directory operations on files" {569test "directory operations on files" {
435 var tmp_dir = tmpDir(.{});570 try testWithAllSupportedPathTypes(struct {
436 defer tmp_dir.cleanup();571 fn impl(ctx: *TestContext) !void {
437572 const test_file_name = try ctx.transformPath("test_file");
438 const test_file_name = "test_file";573
439574 var file = try ctx.dir.createFile(test_file_name, .{ .read = true });
440 var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true });575 file.close();
441 file.close();576
442577 try testing.expectError(error.PathAlreadyExists, ctx.dir.makeDir(test_file_name));
443 try testing.expectError(error.PathAlreadyExists, tmp_dir.dir.makeDir(test_file_name));578 try testing.expectError(error.NotDir, ctx.dir.openDir(test_file_name, .{}));
444 try testing.expectError(error.NotDir, tmp_dir.dir.openDir(test_file_name, .{}));579 try testing.expectError(error.NotDir, ctx.dir.deleteDir(test_file_name));
445 try testing.expectError(error.NotDir, tmp_dir.dir.deleteDir(test_file_name));580
446581 if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) {
447 switch (builtin.os.tag) {582 try testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(test_file_name));
448 .wasi, .freebsd, .netbsd, .openbsd, .dragonfly => {},583 try testing.expectError(error.NotDir, fs.deleteDirAbsolute(test_file_name));
449 else => {584 }
450 const absolute_path = try tmp_dir.dir.realpathAlloc(testing.allocator, test_file_name);585
451 defer testing.allocator.free(absolute_path);586 // ensure the file still exists and is a file as a sanity check
452587 file = try ctx.dir.openFile(test_file_name, .{});
453 try testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(absolute_path));588 const stat = try file.stat();
454 try testing.expectError(error.NotDir, fs.deleteDirAbsolute(absolute_path));589 try testing.expect(stat.kind == .file);
455 },590 file.close();
456 }591 }
457592 }.impl);
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();
463}593}
464594
465test "file operations on directories" {595test "file operations on directories" {
466 // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759596 // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759
467 if (builtin.os.tag == .freebsd) return error.SkipZigTest;597 if (builtin.os.tag == .freebsd) return error.SkipZigTest;
468598
469 var tmp_dir = tmpDir(.{});599 try testWithAllSupportedPathTypes(struct {
470 defer tmp_dir.cleanup();600 fn impl(ctx: *TestContext) !void {
471601 const test_dir_name = try ctx.transformPath("test_dir");
472 const test_dir_name = "test_dir";602
473603 try ctx.dir.makeDir(test_dir_name);
474 try tmp_dir.dir.makeDir(test_dir_name);604
475605 try testing.expectError(error.IsDir, ctx.dir.createFile(test_dir_name, .{}));
476 try testing.expectError(error.IsDir, tmp_dir.dir.createFile(test_dir_name, .{}));606 try testing.expectError(error.IsDir, ctx.dir.deleteFile(test_dir_name));
477 try testing.expectError(error.IsDir, tmp_dir.dir.deleteFile(test_dir_name));607 switch (builtin.os.tag) {
478 switch (builtin.os.tag) {608 // no error when reading a directory.
479 // no error when reading a directory.609 .dragonfly, .netbsd => {},
480 .dragonfly, .netbsd => {},610 // Currently, WASI will return error.Unexpected (via ENOTCAPABLE) when attempting fd_read on a directory handle.
481 // 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.
482 // TODO: Re-enable on WASI once https://github.com/bytecodealliance/wasmtime/issues/1935 is resolved.612 .wasi => {},
483 .wasi => {},613 else => {
484 else => {614 try testing.expectError(error.IsDir, ctx.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize)));
485 try testing.expectError(error.IsDir, tmp_dir.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize)));615 },
486 },616 }
487 }617 // Note: The `.mode = .read_write` is necessary to ensure the error occurs on all platforms.
488 // 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
489 // TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732619 try testing.expectError(error.IsDir, ctx.dir.openFile(test_dir_name, .{ .mode = .read_write }));
490 try testing.expectError(error.IsDir, tmp_dir.dir.openFile(test_dir_name, .{ .mode = .read_write }));620
491621 if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) {
492 switch (builtin.os.tag) {622 try testing.expectError(error.IsDir, fs.createFileAbsolute(test_dir_name, .{}));
493 .wasi, .freebsd, .netbsd, .openbsd, .dragonfly => {},623 try testing.expectError(error.IsDir, fs.deleteFileAbsolute(test_dir_name));
494 else => {624 }
495 const absolute_path = try tmp_dir.dir.realpathAlloc(testing.allocator, test_dir_name);625
496 defer testing.allocator.free(absolute_path);626 // ensure the directory still exists as a sanity check
497627 var dir = try ctx.dir.openDir(test_dir_name, .{});
498 try testing.expectError(error.IsDir, fs.createFileAbsolute(absolute_path, .{}));628 dir.close();
499 try testing.expectError(error.IsDir, fs.deleteFileAbsolute(absolute_path));629 }
500 },630 }.impl);
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();
506}631}
507632
508test "deleteDir" {633test "deleteDir" {
509 var tmp_dir = tmpDir(.{});634 try testWithAllSupportedPathTypes(struct {
510 defer tmp_dir.cleanup();635 fn impl(ctx: *TestContext) !void {
511636 const test_dir_path = try ctx.transformPath("test_dir");
512 // deleting a non-existent directory637 const test_file_path = try ctx.transformPath("test_dir" ++ std.fs.path.sep_str ++ "test_file");
513 try testing.expectError(error.FileNotFound, tmp_dir.dir.deleteDir("test_dir"));638
514639 // deleting a non-existent directory
515 var dir = try tmp_dir.dir.makeOpenPath("test_dir", .{});640 try testing.expectError(error.FileNotFound, ctx.dir.deleteDir(test_dir_path));
516 var file = try dir.createFile("test_file", .{});641
517 file.close();642 // deleting a non-empty directory
518 dir.close();643 try ctx.dir.makeDir(test_dir_path);
519644 try ctx.dir.writeFile(test_file_path, "");
520 // deleting a non-empty directory645 try testing.expectError(error.DirNotEmpty, ctx.dir.deleteDir(test_dir_path));
521 try testing.expectError(error.DirNotEmpty, tmp_dir.dir.deleteDir("test_dir"));646
522647 // deleting an empty directory
523 dir = try tmp_dir.dir.openDir("test_dir", .{});648 try ctx.dir.deleteFile(test_file_path);
524 try dir.deleteFile("test_file");649 try ctx.dir.deleteDir(test_dir_path);
525 dir.close();650 }
526651 }.impl);
527 // deleting an empty directory
528 try tmp_dir.dir.deleteDir("test_dir");
529}652}
530653
531test "Dir.rename files" {654test "Dir.rename files" {
532 var tmp_dir = tmpDir(.{});655 try testWithAllSupportedPathTypes(struct {
533 defer tmp_dir.cleanup();656 fn impl(ctx: *TestContext) !void {
534657 const missing_file_path = try ctx.transformPath("missing_file_name");
535 try testing.expectError(error.FileNotFound, tmp_dir.dir.rename("missing_file_name", "something_else"));658 const something_else_path = try ctx.transformPath("something_else");
536659
537 // Renaming files660 try testing.expectError(error.FileNotFound, ctx.dir.rename(missing_file_path, something_else_path));
538 const test_file_name = "test_file";661
539 const renamed_test_file_name = "test_file_renamed";662 // Renaming files
540 var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true });663 const test_file_name = try ctx.transformPath("test_file");
541 file.close();664 const renamed_test_file_name = try ctx.transformPath("test_file_renamed");
542 try tmp_dir.dir.rename(test_file_name, renamed_test_file_name);665 var file = try ctx.dir.createFile(test_file_name, .{ .read = true });
543666 file.close();
544 // Ensure the file was renamed667 try ctx.dir.rename(test_file_name, renamed_test_file_name);
545 try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{}));668
546 file = try tmp_dir.dir.openFile(renamed_test_file_name, .{});669 // Ensure the file was renamed
547 file.close();670 try testing.expectError(error.FileNotFound, ctx.dir.openFile(test_file_name, .{}));
548671 file = try ctx.dir.openFile(renamed_test_file_name, .{});
549 // Rename to self succeeds672 file.close();
550 try tmp_dir.dir.rename(renamed_test_file_name, renamed_test_file_name);673
551674 // Rename to self succeeds
552 // Rename to existing file succeeds675 try ctx.dir.rename(renamed_test_file_name, renamed_test_file_name);
553 var existing_file = try tmp_dir.dir.createFile("existing_file", .{ .read = true });676
554 existing_file.close();677 // Rename to existing file succeeds
555 try tmp_dir.dir.rename(renamed_test_file_name, "existing_file");678 const existing_file_path = try ctx.transformPath("existing_file");
556679 var existing_file = try ctx.dir.createFile(existing_file_path, .{ .read = true });
557 try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(renamed_test_file_name, .{}));680 existing_file.close();
558 file = try tmp_dir.dir.openFile("existing_file", .{});681 try ctx.dir.rename(renamed_test_file_name, existing_file_path);
559 file.close();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);
560}688}
561689
562test "Dir.rename directories" {690test "Dir.rename directories" {
563 var tmp_dir = tmpDir(.{});691 try testWithAllSupportedPathTypes(struct {
564 defer tmp_dir.cleanup();692 fn impl(ctx: *TestContext) !void {
565693 const test_dir_path = try ctx.transformPath("test_dir");
566 // Renaming directories694 const test_dir_renamed_path = try ctx.transformPath("test_dir_renamed");
567 try tmp_dir.dir.makeDir("test_dir");695
568 try tmp_dir.dir.rename("test_dir", "test_dir_renamed");696 // Renaming directories
569697 try ctx.dir.makeDir(test_dir_path);
570 // Ensure the directory was renamed698 try ctx.dir.rename(test_dir_path, test_dir_renamed_path);
571 try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir", .{}));699
572 var dir = try tmp_dir.dir.openDir("test_dir_renamed", .{});700 // Ensure the directory was renamed
573701 try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{}));
574 // Put a file in the directory702 var dir = try ctx.dir.openDir(test_dir_renamed_path, .{});
575 var file = try dir.createFile("test_file", .{ .read = true });703
576 file.close();704 // Put a file in the directory
577 dir.close();705 var file = try dir.createFile("test_file", .{ .read = true });
578706 file.close();
579 try tmp_dir.dir.rename("test_dir_renamed", "test_dir_renamed_again");707 dir.close();
580708
581 // Ensure the directory was renamed and the file still exists in it709 const test_dir_renamed_again_path = try ctx.transformPath("test_dir_renamed_again");
582 try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir_renamed", .{}));710 try ctx.dir.rename(test_dir_renamed_path, test_dir_renamed_again_path);
583 dir = try tmp_dir.dir.openDir("test_dir_renamed_again", .{});711
584 file = try dir.openFile("test_file", .{});712 // Ensure the directory was renamed and the file still exists in it
585 file.close();713 try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_renamed_path, .{}));
586 dir.close();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);
587}720}
588721
589test "Dir.rename directory onto empty dir" {722test "Dir.rename directory onto empty dir" {
590 // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364723 // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
591 if (builtin.os.tag == .windows) return error.SkipZigTest;724 if (builtin.os.tag == .windows) return error.SkipZigTest;
592725
593 var tmp_dir = testing.tmpDir(.{});726 try testWithAllSupportedPathTypes(struct {
594 defer tmp_dir.cleanup();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");731 try ctx.dir.makeDir(test_dir_path);
597 try tmp_dir.dir.makeDir("target_dir");732 try ctx.dir.makeDir(target_dir_path);
598 try tmp_dir.dir.rename("test_dir", "target_dir");733 try ctx.dir.rename(test_dir_path, target_dir_path);
599734
600 // Ensure the directory was renamed735 // Ensure the directory was renamed
601 try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir", .{}));736 try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{}));
602 var dir = try tmp_dir.dir.openDir("target_dir", .{});737 var dir = try ctx.dir.openDir(target_dir_path, .{});
603 dir.close();738 dir.close();
739 }
740 }.impl);
604}741}
605742
606test "Dir.rename directory onto non-empty dir" {743test "Dir.rename directory onto non-empty dir" {
607 // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364744 // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
608 if (builtin.os.tag == .windows) return error.SkipZigTest;745 if (builtin.os.tag == .windows) return error.SkipZigTest;
609746
610 var tmp_dir = testing.tmpDir(.{});747 try testWithAllSupportedPathTypes(struct {
611 defer tmp_dir.cleanup();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", .{});754 var target_dir = try ctx.dir.makeOpenPath(target_dir_path, .{});
616 var file = try target_dir.createFile("test_file", .{ .read = true });755 var file = try target_dir.createFile("test_file", .{ .read = true });
617 file.close();756 file.close();
618 target_dir.close();757 target_dir.close();
619758
620 // Rename should fail with PathAlreadyExists if target_dir is non-empty759 // 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"));760 try testing.expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, target_dir_path));
622761
623 // Ensure the directory was not renamed762 // Ensure the directory was not renamed
624 var dir = try tmp_dir.dir.openDir("test_dir", .{});763 var dir = try ctx.dir.openDir(test_dir_path, .{});
625 dir.close();764 dir.close();
765 }
766 }.impl);
626}767}
627768
628test "Dir.rename file <-> dir" {769test "Dir.rename file <-> dir" {
629 // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364770 // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
630 if (builtin.os.tag == .windows) return error.SkipZigTest;771 if (builtin.os.tag == .windows) return error.SkipZigTest;
631772
632 var tmp_dir = tmpDir(.{});773 try testWithAllSupportedPathTypes(struct {
633 defer tmp_dir.cleanup();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 });778 var file = try ctx.dir.createFile(test_file_path, .{ .read = true });
636 file.close();779 file.close();
637 try tmp_dir.dir.makeDir("test_dir");780 try ctx.dir.makeDir(test_dir_path);
638 try testing.expectError(error.IsDir, tmp_dir.dir.rename("test_file", "test_dir"));781 try testing.expectError(error.IsDir, ctx.dir.rename(test_file_path, test_dir_path));
639 try testing.expectError(error.NotDir, tmp_dir.dir.rename("test_dir", "test_file"));782 try testing.expectError(error.NotDir, ctx.dir.rename(test_dir_path, test_file_path));
783 }
784 }.impl);
640}785}
641786
642test "rename" {787test "rename" {
...@@ -720,35 +865,33 @@ test "openSelfExe" {...@@ -720,35 +865,33 @@ test "openSelfExe" {
720}865}
721866
722test "makePath, put some files in it, deleteTree" {867test "makePath, put some files in it, deleteTree" {
723 var tmp = tmpDir(.{});868 try testWithAllSupportedPathTypes(struct {
724 defer tmp.cleanup();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");872 try ctx.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");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");
728 try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");874 try ctx.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");875
730 if (tmp.dir.openDir("os_test_tmp", .{})) |dir| {876 try ctx.dir.deleteTree(dir_path);
731 _ = dir;877 try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{}));
732 @panic("expected error");878 }
733 } else |err| {879 }.impl);
734 try testing.expect(err == error.FileNotFound);
735 }
736}880}
737881
738test "makePath, put some files in it, deleteTreeMinStackSize" {882test "makePath, put some files in it, deleteTreeMinStackSize" {
739 var tmp = tmpDir(.{});883 try testWithAllSupportedPathTypes(struct {
740 defer tmp.cleanup();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");887 try ctx.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");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");
744 try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");889 try ctx.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");890
746 if (tmp.dir.openDir("os_test_tmp", .{})) |dir| {891 try ctx.dir.deleteTreeMinStackSize(dir_path);
747 _ = dir;892 try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{}));
748 @panic("expected error");893 }
749 } else |err| {894 }.impl);
750 try testing.expect(err == error.FileNotFound);
751 }
752}895}
753896
754test "makePath in a directory that no longer exists" {897test "makePath in a directory that no longer exists" {
...@@ -789,9 +932,9 @@ test "max file name component lengths" {...@@ -789,9 +932,9 @@ test "max file name component lengths" {
789 defer tmp.cleanup();932 defer tmp.cleanup();
790933
791 if (builtin.os.tag == .windows) {934 if (builtin.os.tag == .windows) {
792 // € is the character with the largest codepoint that is encoded as a single u16 in UTF-16,935 // U+FFFF is the character with the largest code point that is encoded as a single
793 // so Windows allows for NAME_MAX of them936 // UTF-16 code unit, so Windows allows for NAME_MAX of them.
794 const maxed_windows_filename = ("€".*) ** std.os.windows.NAME_MAX;937 const maxed_windows_filename = ("\u{FFFF}".*) ** std.os.windows.NAME_MAX;
795 try testFilenameLimits(tmp.iterable_dir, &maxed_windows_filename);938 try testFilenameLimits(tmp.iterable_dir, &maxed_windows_filename);
796 } else if (builtin.os.tag == .wasi) {939 } else if (builtin.os.tag == .wasi) {
797 // On WASI, the maxed filename depends on the host OS, so in order for this test to940 // On WASI, the maxed filename depends on the host OS, so in order for this test to
...@@ -889,20 +1032,19 @@ test "pwritev, preadv" {...@@ -889,20 +1032,19 @@ test "pwritev, preadv" {
889}1032}
8901033
891test "access file" {1034test "access file" {
892 var tmp = tmpDir(.{});1035 try testWithAllSupportedPathTypes(struct {
893 defer tmp.cleanup();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");1040 try ctx.dir.makePath(dir_path);
896 if (tmp.dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{})) |ok| {1041 try testing.expectError(error.FileNotFound, ctx.dir.access(file_path, .{}));
897 _ = ok;
898 @panic("expected error");
899 } else |err| {
900 try testing.expect(err == error.FileNotFound);
901 }
9021042
903 try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", "");1043 try ctx.dir.writeFile(file_path, "");
904 try tmp.dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{});1044 try ctx.dir.access(file_path, .{});
905 try tmp.dir.deleteTree("os_test_tmp");1045 try ctx.dir.deleteTree(dir_path);
1046 }
1047 }.impl);
906}1048}
9071049
908test "sendfile" {1050test "sendfile" {
...@@ -996,26 +1138,27 @@ test "copyRangeAll" {...@@ -996,26 +1138,27 @@ test "copyRangeAll" {
996 try testing.expect(mem.eql(u8, written_buf[0..amt], data));1138 try testing.expect(mem.eql(u8, written_buf[0..amt], data));
997}1139}
9981140
999test "fs.copyFile" {1141test "copyFile" {
1000 const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";1142 try testWithAllSupportedPathTypes(struct {
1001 const src_file = "tmp_test_copy_file.txt";1143 fn impl(ctx: *TestContext) !void {
1002 const dest_file = "tmp_test_copy_file2.txt";1144 const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";
1003 const dest_file2 = "tmp_test_copy_file3.txt";1145 const src_file = try ctx.transformPath("tmp_test_copy_file.txt");
10041146 const dest_file = try ctx.transformPath("tmp_test_copy_file2.txt");
1005 var tmp = tmpDir(.{});1147 const dest_file2 = try ctx.transformPath("tmp_test_copy_file3.txt");
1006 defer tmp.cleanup();
10071148
1008 try tmp.dir.writeFile(src_file, data);1149 try ctx.dir.writeFile(src_file, data);
1009 defer tmp.dir.deleteFile(src_file) catch {};1150 defer ctx.dir.deleteFile(src_file) catch {};
10101151
1011 try tmp.dir.copyFile(src_file, tmp.dir, dest_file, .{});1152 try ctx.dir.copyFile(src_file, ctx.dir, dest_file, .{});
1012 defer tmp.dir.deleteFile(dest_file) catch {};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 });1155 try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, .{ .override_mode = File.default_mode });
1015 defer tmp.dir.deleteFile(dest_file2) catch {};1156 defer ctx.dir.deleteFile(dest_file2) catch {};
10161157
1017 try expectFileContents(tmp.dir, dest_file, data);1158 try expectFileContents(ctx.dir, dest_file, data);
1018 try expectFileContents(tmp.dir, dest_file2, data);1159 try expectFileContents(ctx.dir, dest_file2, data);
1160 }
1161 }.impl);
1019}1162}
10201163
1021fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void {1164fn 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 {...@@ -1026,78 +1169,75 @@ fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void {
1026}1169}
10271170
1028test "AtomicFile" {1171test "AtomicFile" {
1029 const test_out_file = "tmp_atomic_file_test_dest.txt";1172 try testWithAllSupportedPathTypes(struct {
1030 const test_content =1173 fn impl(ctx: *TestContext) !void {
1031 \\ hello!1174 const test_out_file = try ctx.transformPath("tmp_atomic_file_test_dest.txt");
1032 \\ this is a test file1175 const test_content =
1033 ;1176 \\ hello!
10341177 \\ this is a test file
1035 var tmp = tmpDir(.{});1178 ;
1036 defer tmp.cleanup();1179
10371180 {
1038 {1181 var af = try ctx.dir.atomicFile(test_out_file, .{});
1039 var af = try tmp.dir.atomicFile(test_out_file, .{});1182 defer af.deinit();
1040 defer af.deinit();1183 try af.file.writeAll(test_content);
1041 try af.file.writeAll(test_content);1184 try af.finish();
1042 try af.finish();1185 }
1043 }1186 const content = try ctx.dir.readFileAlloc(testing.allocator, test_out_file, 9999);
1044 const content = try tmp.dir.readFileAlloc(testing.allocator, test_out_file, 9999);1187 defer testing.allocator.free(content);
1045 defer testing.allocator.free(content);1188 try testing.expect(mem.eql(u8, content, test_content));
1046 try testing.expect(mem.eql(u8, content, test_content));1189
10471190 try ctx.dir.deleteFile(test_out_file);
1048 try tmp.dir.deleteFile(test_out_file);1191 }
1049}1192 }.impl);
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));
1056}1193}
10571194
1058test "open file with exclusive nonblocking lock twice" {1195test "open file with exclusive nonblocking lock twice" {
1059 if (builtin.os.tag == .wasi) return error.SkipZigTest;1196 if (builtin.os.tag == .wasi) return error.SkipZigTest;
10601197
1061 const filename = "file_nonblocking_lock_test.txt";1198 try testWithAllSupportedPathTypes(struct {
10621199 fn impl(ctx: *TestContext) !void {
1063 var tmp = tmpDir(.{});1200 const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
1064 defer tmp.cleanup();
10651201
1066 const file1 = try tmp.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });1202 const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1067 defer file1.close();1203 defer file1.close();
10681204
1069 const file2 = tmp.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });1205 const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1070 try testing.expectError(error.WouldBlock, file2);1206 try testing.expectError(error.WouldBlock, file2);
1207 }
1208 }.impl);
1071}1209}
10721210
1073test "open file with shared and exclusive nonblocking lock" {1211test "open file with shared and exclusive nonblocking lock" {
1074 if (builtin.os.tag == .wasi) return error.SkipZigTest;1212 if (builtin.os.tag == .wasi) return error.SkipZigTest;
10751213
1076 const filename = "file_nonblocking_lock_test.txt";1214 try testWithAllSupportedPathTypes(struct {
10771215 fn impl(ctx: *TestContext) !void {
1078 var tmp = tmpDir(.{});1216 const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
1079 defer tmp.cleanup();
10801217
1081 const file1 = try tmp.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });1218 const file1 = try ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
1082 defer file1.close();1219 defer file1.close();
10831220
1084 const file2 = tmp.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });1221 const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1085 try testing.expectError(error.WouldBlock, file2);1222 try testing.expectError(error.WouldBlock, file2);
1223 }
1224 }.impl);
1086}1225}
10871226
1088test "open file with exclusive and shared nonblocking lock" {1227test "open file with exclusive and shared nonblocking lock" {
1089 if (builtin.os.tag == .wasi) return error.SkipZigTest;1228 if (builtin.os.tag == .wasi) return error.SkipZigTest;
10901229
1091 const filename = "file_nonblocking_lock_test.txt";1230 try testWithAllSupportedPathTypes(struct {
10921231 fn impl(ctx: *TestContext) !void {
1093 var tmp = tmpDir(.{});1232 const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
1094 defer tmp.cleanup();
10951233
1096 const file1 = try tmp.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });1234 const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1097 defer file1.close();1235 defer file1.close();
10981236
1099 const file2 = tmp.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });1237 const file2 = ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
1100 try testing.expectError(error.WouldBlock, file2);1238 try testing.expectError(error.WouldBlock, file2);
1239 }
1240 }.impl);
1101}1241}
11021242
1103test "open file with exclusive lock twice, make sure second lock waits" {1243test "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" {...@@ -1108,42 +1248,44 @@ test "open file with exclusive lock twice, make sure second lock waits" {
1108 return error.SkipZigTest;1248 return error.SkipZigTest;
1109 }1249 }
11101250
1111 const filename = "file_lock_test.txt";1251 try testWithAllSupportedPathTypes(struct {
11121252 fn impl(ctx: *TestContext) !void {
1113 var tmp = tmpDir(.{});1253 const filename = try ctx.transformPath("file_lock_test.txt");
1114 defer tmp.cleanup();1254
11151255 const file = try ctx.dir.createFile(filename, .{ .lock = .exclusive });
1116 const file = try tmp.dir.createFile(filename, .{ .lock = .exclusive });1256 errdefer file.close();
1117 errdefer file.close();1257
11181258 const S = struct {
1119 const S = struct {1259 fn checkFn(dir: *fs.Dir, path: []const u8, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void {
1120 fn checkFn(dir: *fs.Dir, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void {1260 started.set();
1121 started.set();1261 const file1 = try dir.createFile(path, .{ .lock = .exclusive });
1122 const file1 = try dir.createFile(filename, .{ .lock = .exclusive });1262
11231263 locked.set();
1124 locked.set();1264 file1.close();
1125 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();
1126 }1287 }
1127 };1288 }.impl);
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();
1147}1289}
11481290
1149test "open file with exclusive nonblocking lock twice (absolute paths)" {1291test "open file with exclusive nonblocking lock twice (absolute paths)" {
...@@ -1259,29 +1401,36 @@ test "walker without fully iterating" {...@@ -1259,29 +1401,36 @@ test "walker without fully iterating" {
1259test ". and .. in fs.Dir functions" {1401test ". and .. in fs.Dir functions" {
1260 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;1402 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
12611403
1262 var tmp = tmpDir(.{});1404 try testWithAllSupportedPathTypes(struct {
1263 defer tmp.cleanup();1405 fn impl(ctx: *TestContext) !void {
12641406 const subdir_path = try ctx.transformPath("./subdir");
1265 try tmp.dir.makeDir("./subdir");1407 const file_path = try ctx.transformPath("./subdir/../file");
1266 try tmp.dir.access("./subdir", .{});1408 const copy_path = try ctx.transformPath("./subdir/../copy");
1267 var created_subdir = try tmp.dir.openDir("./subdir", .{});1409 const rename_path = try ctx.transformPath("./subdir/../rename");
1268 created_subdir.close();1410 const update_path = try ctx.transformPath("./subdir/../update");
12691411
1270 const created_file = try tmp.dir.createFile("./subdir/../file", .{});1412 try ctx.dir.makeDir(subdir_path);
1271 created_file.close();1413 try ctx.dir.access(subdir_path, .{});
1272 try tmp.dir.access("./subdir/../file", .{});1414 var created_subdir = try ctx.dir.openDir(subdir_path, .{});
12731415 created_subdir.close();
1274 try tmp.dir.copyFile("./subdir/../file", tmp.dir, "./subdir/../copy", .{});1416
1275 try tmp.dir.rename("./subdir/../copy", "./subdir/../rename");1417 const created_file = try ctx.dir.createFile(file_path, .{});
1276 const renamed_file = try tmp.dir.openFile("./subdir/../rename", .{});1418 created_file.close();
1277 renamed_file.close();1419 try ctx.dir.access(file_path, .{});
1278 try tmp.dir.deleteFile("./subdir/../rename");1420
12791421 try ctx.dir.copyFile(file_path, ctx.dir, copy_path, .{});
1280 try tmp.dir.writeFile("./subdir/../update", "something");1422 try ctx.dir.rename(copy_path, rename_path);
1281 const prev_status = try tmp.dir.updateFile("./subdir/../file", tmp.dir, "./subdir/../update", .{});1423 const renamed_file = try ctx.dir.openFile(rename_path, .{});
1282 try testing.expectEqual(fs.PrevStatus.stale, prev_status);1424 renamed_file.close();
12831425 try ctx.dir.deleteFile(rename_path);
1284 try tmp.dir.deleteDir("./subdir");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);
1285}1434}
12861435
1287test ". and .. in absolute functions" {1436test ". 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...@@ -5169,11 +5169,30 @@ pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPat
5169 return getFdPath(h_file, out_buffer);5169 return getFdPath(h_file, out_buffer);
5170}5170}
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
5172/// Return canonical path of handle `fd`.5188/// Return canonical path of handle `fd`.
5173/// This function is very host-specific and is not universally supported by all hosts.5189/// This function is very host-specific and is not universally supported by all hosts.
5174/// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is5190/// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is
5175/// unsupported on WASI.5191/// unsupported on WASI.
5176pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {5192pub 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 }
5177 switch (builtin.os.tag) {5196 switch (builtin.os.tag) {
5178 .windows => {5197 .windows => {
5179 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;5198 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 {...@@ -5276,9 +5295,6 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
5276 }5295 }
5277 },5296 },
5278 .dragonfly => {5297 .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 }
5282 @memset(out_buffer[0..MAX_PATH_BYTES], 0);5298 @memset(out_buffer[0..MAX_PATH_BYTES], 0);
5283 switch (errno(system.fcntl(fd, F.GETPATH, out_buffer))) {5299 switch (errno(system.fcntl(fd, F.GETPATH, out_buffer))) {
5284 .SUCCESS => {},5300 .SUCCESS => {},
...@@ -5290,9 +5306,6 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {...@@ -5290,9 +5306,6 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
5290 return out_buffer[0..len];5306 return out_buffer[0..len];
5291 },5307 },
5292 .netbsd => {5308 .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 }
5296 @memset(out_buffer[0..MAX_PATH_BYTES], 0);5309 @memset(out_buffer[0..MAX_PATH_BYTES], 0);
5297 switch (errno(system.fcntl(fd, F.GETPATH, out_buffer))) {5310 switch (errno(system.fcntl(fd, F.GETPATH, out_buffer))) {
5298 .SUCCESS => {},5311 .SUCCESS => {},
...@@ -5306,7 +5319,7 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {...@@ -5306,7 +5319,7 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
5306 const len = mem.indexOfScalar(u8, out_buffer[0..], @as(u8, 0)) orelse MAX_PATH_BYTES;5319 const len = mem.indexOfScalar(u8, out_buffer[0..], @as(u8, 0)) orelse MAX_PATH_BYTES;
5307 return out_buffer[0..len];5320 return out_buffer[0..len];
5308 },5321 },
5309 else => @compileError("querying for canonical path of a handle is unsupported on this host"),5322 else => unreachable, // made unreachable by isGetFdPathSupportedOnTarget above
5310 }5323 }
5311}5324}
53125325