Commit 8f5f1ff25a

Ryan Liptak <squeek502@hotmail.com>
2023-08-17 09:58:44
fs tests: Test multiple different path types in most tests
(which path types will depend on which the target supports)
1 parent 3819e69
Changed files (2)
lib
lib/std/fs/test.zig
@@ -13,38 +13,167 @@ const File = std.fs.File;
 const tmpDir = testing.tmpDir;
 const tmpIterableDir = testing.tmpIterableDir;
 
-test "Dir.readLink" {
-    var tmp = tmpDir(.{});
-    defer tmp.cleanup();
-
-    // Create some targets
-    try tmp.dir.writeFile("file.txt", "nonsense");
-    try tmp.dir.makeDir("subdir");
+const PathType = enum {
+    relative,
+    absolute,
+    unc,
+
+    pub fn isSupported(self: PathType, target_os: std.Target.Os) bool {
+        return switch (self) {
+            .relative => true,
+            .absolute => std.os.isGetFdPathSupportedOnTarget(target_os),
+            .unc => target_os.tag == .windows,
+        };
+    }
 
-    {
-        // Create symbolic link by path
-        tmp.dir.symLink("file.txt", "symlink1", .{}) catch |err| switch (err) {
-            // Symlink requires admin privileges on windows, so this test can legitimately fail.
-            error.AccessDenied => return error.SkipZigTest,
-            else => return err,
+    pub const TransformError = std.os.RealPathError || error{OutOfMemory};
+    pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8;
+
+    pub fn getTransformFn(comptime path_type: PathType) TransformFn {
+        switch (path_type) {
+            .relative => return struct {
+                fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
+                    _ = allocator;
+                    _ = dir;
+                    return relative_path;
+                }
+            }.transform,
+            .absolute => return struct {
+                fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
+                    // The final path may not actually exist which would cause realpath to fail.
+                    // So instead, we get the path of the dir and join it with the relative path.
+                    var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
+                    const dir_path = try os.getFdPath(dir.fd, &fd_path_buf);
+                    return fs.path.join(allocator, &.{ dir_path, relative_path });
+                }
+            }.transform,
+            .unc => return struct {
+                fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
+                    // Any drive absolute path (C:\foo) can be converted into a UNC path by
+                    // using 'localhost' as the server name and '<drive letter>$' as the share name.
+                    var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
+                    const dir_path = try os.getFdPath(dir.fd, &fd_path_buf);
+                    const windows_path_type = std.os.windows.getUnprefixedPathType(u8, dir_path);
+                    switch (windows_path_type) {
+                        .unc_absolute => return fs.path.join(allocator, &.{ dir_path, relative_path }),
+                        .drive_absolute => {
+                            // `C:\<...>` -> `\\localhost\C$\<...>`
+                            const prepended = "\\\\localhost\\";
+                            var path = try fs.path.join(allocator, &.{ prepended, dir_path, relative_path });
+                            path[prepended.len + 1] = '$';
+                            return path;
+                        },
+                        else => unreachable,
+                    }
+                }
+            }.transform,
+        }
+    }
+};
+
+const TestContext = struct {
+    path_type: PathType,
+    arena: ArenaAllocator,
+    tmp: testing.TmpIterableDir,
+    dir: std.fs.Dir,
+    iterable_dir: std.fs.IterableDir,
+    transform_fn: *const PathType.TransformFn,
+
+    pub fn init(path_type: PathType, allocator: mem.Allocator, transform_fn: *const PathType.TransformFn) TestContext {
+        var tmp = tmpIterableDir(.{});
+        return .{
+            .path_type = path_type,
+            .arena = ArenaAllocator.init(allocator),
+            .tmp = tmp,
+            .dir = tmp.iterable_dir.dir,
+            .iterable_dir = tmp.iterable_dir,
+            .transform_fn = transform_fn,
         };
-        try testReadLink(tmp.dir, "file.txt", "symlink1");
     }
-    {
-        // Create symbolic link by path
-        tmp.dir.symLink("subdir", "symlink2", .{ .is_directory = true }) catch |err| switch (err) {
-            // Symlink requires admin privileges on windows, so this test can legitimately fail.
-            error.AccessDenied => return error.SkipZigTest,
-            else => return err,
+
+    pub fn deinit(self: *TestContext) void {
+        self.arena.deinit();
+        self.tmp.cleanup();
+    }
+
+    /// Returns the `relative_path` transformed into the TestContext's `path_type`.
+    /// The result is allocated by the TestContext's arena and will be free'd during
+    /// `TestContext.deinit`.
+    pub fn transformPath(self: *TestContext, relative_path: []const u8) ![]const u8 {
+        return self.transform_fn(self.arena.allocator(), self.dir, relative_path);
+    }
+};
+
+/// `test_func` must be a function that takes a `*TestContext` as a parameter and returns `!void`.
+/// `test_func` will be called once for each PathType that the current target supports,
+/// and will be passed a TestContext that can transform a relative path into the path type under test.
+/// The TestContext will also create a tmp directory for you (and will clean it up for you too).
+fn testWithAllSupportedPathTypes(test_func: anytype) !void {
+    inline for (@typeInfo(PathType).Enum.fields) |enum_field| {
+        const path_type = @field(PathType, enum_field.name);
+        if (!(comptime path_type.isSupported(builtin.os))) continue;
+
+        var ctx = TestContext.init(path_type, testing.allocator, path_type.getTransformFn());
+        defer ctx.deinit();
+
+        test_func(&ctx) catch |err| {
+            std.debug.print("path type: {s}\n", .{enum_field.name});
+            return err;
         };
-        try testReadLink(tmp.dir, "subdir", "symlink2");
     }
 }
 
+test "Dir.readLink" {
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            // Create some targets
+            const file_target_path = try ctx.transformPath("file.txt");
+            try ctx.dir.writeFile(file_target_path, "nonsense");
+            const dir_target_path = try ctx.transformPath("subdir");
+            try ctx.dir.makeDir(dir_target_path);
+
+            {
+                // Create symbolic link by path
+                ctx.dir.symLink(file_target_path, "symlink1", .{}) catch |err| switch (err) {
+                    // Symlink requires admin privileges on windows, so this test can legitimately fail.
+                    error.AccessDenied => return error.SkipZigTest,
+                    else => return err,
+                };
+                try testReadLink(ctx.dir, file_target_path, "symlink1");
+            }
+            {
+                // Create symbolic link by path
+                ctx.dir.symLink(dir_target_path, "symlink2", .{ .is_directory = true }) catch |err| switch (err) {
+                    // Symlink requires admin privileges on windows, so this test can legitimately fail.
+                    error.AccessDenied => return error.SkipZigTest,
+                    else => return err,
+                };
+                try testReadLink(ctx.dir, dir_target_path, "symlink2");
+            }
+        }
+    }.impl);
+}
+
 fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {
     var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
     const given = try dir.readLink(symlink_path, buffer[0..]);
-    try testing.expect(mem.eql(u8, target_path, given));
+    try testing.expectEqualStrings(target_path, given);
+}
+
+test "openDir" {
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const subdir_path = try ctx.transformPath("subdir");
+            try ctx.dir.makeDir(subdir_path);
+
+            for ([_][]const u8{ "", ".", ".." }) |sub_path| {
+                const dir_path = try fs.path.join(testing.allocator, &[_][]const u8{ subdir_path, sub_path });
+                defer testing.allocator.free(dir_path);
+                var dir = try ctx.dir.openDir(dir_path, .{});
+                defer dir.close();
+            }
+        }
+    }.impl);
 }
 
 test "accessAbsolute" {
@@ -349,53 +478,59 @@ fn contains(entries: *const std.ArrayList(IterableDir.Entry), el: IterableDir.En
 }
 
 test "Dir.realpath smoke test" {
-    switch (builtin.os.tag) {
-        .linux, .windows, .macos, .ios, .watchos, .tvos, .solaris => {},
-        else => return error.SkipZigTest,
-    }
-
-    var tmp_dir = tmpDir(.{});
-    defer tmp_dir.cleanup();
-
-    var file = try tmp_dir.dir.createFile("test_file", .{ .lock = .shared });
-    // We need to close the file immediately as otherwise on Windows we'll end up
-    // with a sharing violation.
-    file.close();
-
-    try tmp_dir.dir.makeDir("test_dir");
-
-    var arena = ArenaAllocator.init(testing.allocator);
-    defer arena.deinit();
-    const allocator = arena.allocator();
-
-    const base_path = blk: {
-        const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp_dir.sub_path[0..] });
-        break :blk try fs.realpathAlloc(allocator, relative_path);
-    };
-
-    // First, test non-alloc version
-    {
-        var buf1: [fs.MAX_PATH_BYTES]u8 = undefined;
-
-        const file_path = try tmp_dir.dir.realpath("test_file", buf1[0..]);
-        const expected_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "test_file" });
-        try testing.expectEqualStrings(expected_file_path, file_path);
-
-        const dir_path = try tmp_dir.dir.realpath("test_dir", buf1[0..]);
-        const expected_dir_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "test_dir" });
-        try testing.expectEqualStrings(expected_dir_path, dir_path);
-    }
-
-    // Next, test alloc version
-    {
-        const file_path = try tmp_dir.dir.realpathAlloc(allocator, "test_file");
-        const expected_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "test_file" });
-        try testing.expectEqualStrings(expected_file_path, file_path);
-
-        const dir_path = try tmp_dir.dir.realpathAlloc(allocator, "test_dir");
-        const expected_dir_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "test_dir" });
-        try testing.expectEqualStrings(expected_dir_path, dir_path);
-    }
+    if (!comptime std.os.isGetFdPathSupportedOnTarget(builtin.os)) return error.SkipZigTest;
+
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const test_file_path = try ctx.transformPath("test_file");
+            const test_dir_path = try ctx.transformPath("test_dir");
+            var buf: [fs.MAX_PATH_BYTES]u8 = undefined;
+
+            // FileNotFound if the path doesn't exist
+            try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(testing.allocator, test_file_path));
+            try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_file_path, &buf));
+            try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(testing.allocator, test_dir_path));
+            try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_dir_path, &buf));
+
+            // Now create the file and dir
+            try ctx.dir.writeFile(test_file_path, "");
+            try ctx.dir.makeDir(test_dir_path);
+
+            const base_path = try ctx.transformPath(".");
+            const base_realpath = try ctx.dir.realpathAlloc(testing.allocator, base_path);
+            defer testing.allocator.free(base_realpath);
+            const expected_file_path = try fs.path.join(
+                testing.allocator,
+                &[_][]const u8{ base_realpath, "test_file" },
+            );
+            defer testing.allocator.free(expected_file_path);
+            const expected_dir_path = try fs.path.join(
+                testing.allocator,
+                &[_][]const u8{ base_realpath, "test_dir" },
+            );
+            defer testing.allocator.free(expected_dir_path);
+
+            // First, test non-alloc version
+            {
+                const file_path = try ctx.dir.realpath(test_file_path, &buf);
+                try testing.expectEqualStrings(expected_file_path, file_path);
+
+                const dir_path = try ctx.dir.realpath(test_dir_path, &buf);
+                try testing.expectEqualStrings(expected_dir_path, dir_path);
+            }
+
+            // Next, test alloc version
+            {
+                const file_path = try ctx.dir.realpathAlloc(testing.allocator, test_file_path);
+                defer testing.allocator.free(file_path);
+                try testing.expectEqualStrings(expected_file_path, file_path);
+
+                const dir_path = try ctx.dir.realpathAlloc(testing.allocator, test_dir_path);
+                defer testing.allocator.free(dir_path);
+                try testing.expectEqualStrings(expected_dir_path, dir_path);
+            }
+        }
+    }.impl);
 }
 
 test "readAllAlloc" {
@@ -432,211 +567,221 @@ test "readAllAlloc" {
 }
 
 test "directory operations on files" {
-    var tmp_dir = tmpDir(.{});
-    defer tmp_dir.cleanup();
-
-    const test_file_name = "test_file";
-
-    var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true });
-    file.close();
-
-    try testing.expectError(error.PathAlreadyExists, tmp_dir.dir.makeDir(test_file_name));
-    try testing.expectError(error.NotDir, tmp_dir.dir.openDir(test_file_name, .{}));
-    try testing.expectError(error.NotDir, tmp_dir.dir.deleteDir(test_file_name));
-
-    switch (builtin.os.tag) {
-        .wasi, .freebsd, .netbsd, .openbsd, .dragonfly => {},
-        else => {
-            const absolute_path = try tmp_dir.dir.realpathAlloc(testing.allocator, test_file_name);
-            defer testing.allocator.free(absolute_path);
-
-            try testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(absolute_path));
-            try testing.expectError(error.NotDir, fs.deleteDirAbsolute(absolute_path));
-        },
-    }
-
-    // ensure the file still exists and is a file as a sanity check
-    file = try tmp_dir.dir.openFile(test_file_name, .{});
-    const stat = try file.stat();
-    try testing.expect(stat.kind == .file);
-    file.close();
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const test_file_name = try ctx.transformPath("test_file");
+
+            var file = try ctx.dir.createFile(test_file_name, .{ .read = true });
+            file.close();
+
+            try testing.expectError(error.PathAlreadyExists, ctx.dir.makeDir(test_file_name));
+            try testing.expectError(error.NotDir, ctx.dir.openDir(test_file_name, .{}));
+            try testing.expectError(error.NotDir, ctx.dir.deleteDir(test_file_name));
+
+            if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) {
+                try testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(test_file_name));
+                try testing.expectError(error.NotDir, fs.deleteDirAbsolute(test_file_name));
+            }
+
+            // ensure the file still exists and is a file as a sanity check
+            file = try ctx.dir.openFile(test_file_name, .{});
+            const stat = try file.stat();
+            try testing.expect(stat.kind == .file);
+            file.close();
+        }
+    }.impl);
 }
 
 test "file operations on directories" {
     // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759
     if (builtin.os.tag == .freebsd) return error.SkipZigTest;
 
-    var tmp_dir = tmpDir(.{});
-    defer tmp_dir.cleanup();
-
-    const test_dir_name = "test_dir";
-
-    try tmp_dir.dir.makeDir(test_dir_name);
-
-    try testing.expectError(error.IsDir, tmp_dir.dir.createFile(test_dir_name, .{}));
-    try testing.expectError(error.IsDir, tmp_dir.dir.deleteFile(test_dir_name));
-    switch (builtin.os.tag) {
-        // no error when reading a directory.
-        .dragonfly, .netbsd => {},
-        // Currently, WASI will return error.Unexpected (via ENOTCAPABLE) when attempting fd_read on a directory handle.
-        // TODO: Re-enable on WASI once https://github.com/bytecodealliance/wasmtime/issues/1935 is resolved.
-        .wasi => {},
-        else => {
-            try testing.expectError(error.IsDir, tmp_dir.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize)));
-        },
-    }
-    // Note: The `.mode = .read_write` is necessary to ensure the error occurs on all platforms.
-    // TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732
-    try testing.expectError(error.IsDir, tmp_dir.dir.openFile(test_dir_name, .{ .mode = .read_write }));
-
-    switch (builtin.os.tag) {
-        .wasi, .freebsd, .netbsd, .openbsd, .dragonfly => {},
-        else => {
-            const absolute_path = try tmp_dir.dir.realpathAlloc(testing.allocator, test_dir_name);
-            defer testing.allocator.free(absolute_path);
-
-            try testing.expectError(error.IsDir, fs.createFileAbsolute(absolute_path, .{}));
-            try testing.expectError(error.IsDir, fs.deleteFileAbsolute(absolute_path));
-        },
-    }
-
-    // ensure the directory still exists as a sanity check
-    var dir = try tmp_dir.dir.openDir(test_dir_name, .{});
-    dir.close();
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const test_dir_name = try ctx.transformPath("test_dir");
+
+            try ctx.dir.makeDir(test_dir_name);
+
+            try testing.expectError(error.IsDir, ctx.dir.createFile(test_dir_name, .{}));
+            try testing.expectError(error.IsDir, ctx.dir.deleteFile(test_dir_name));
+            switch (builtin.os.tag) {
+                // no error when reading a directory.
+                .dragonfly, .netbsd => {},
+                // Currently, WASI will return error.Unexpected (via ENOTCAPABLE) when attempting fd_read on a directory handle.
+                // TODO: Re-enable on WASI once https://github.com/bytecodealliance/wasmtime/issues/1935 is resolved.
+                .wasi => {},
+                else => {
+                    try testing.expectError(error.IsDir, ctx.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize)));
+                },
+            }
+            // Note: The `.mode = .read_write` is necessary to ensure the error occurs on all platforms.
+            // TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732
+            try testing.expectError(error.IsDir, ctx.dir.openFile(test_dir_name, .{ .mode = .read_write }));
+
+            if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) {
+                try testing.expectError(error.IsDir, fs.createFileAbsolute(test_dir_name, .{}));
+                try testing.expectError(error.IsDir, fs.deleteFileAbsolute(test_dir_name));
+            }
+
+            // ensure the directory still exists as a sanity check
+            var dir = try ctx.dir.openDir(test_dir_name, .{});
+            dir.close();
+        }
+    }.impl);
 }
 
 test "deleteDir" {
-    var tmp_dir = tmpDir(.{});
-    defer tmp_dir.cleanup();
-
-    // deleting a non-existent directory
-    try testing.expectError(error.FileNotFound, tmp_dir.dir.deleteDir("test_dir"));
-
-    var dir = try tmp_dir.dir.makeOpenPath("test_dir", .{});
-    var file = try dir.createFile("test_file", .{});
-    file.close();
-    dir.close();
-
-    // deleting a non-empty directory
-    try testing.expectError(error.DirNotEmpty, tmp_dir.dir.deleteDir("test_dir"));
-
-    dir = try tmp_dir.dir.openDir("test_dir", .{});
-    try dir.deleteFile("test_file");
-    dir.close();
-
-    // deleting an empty directory
-    try tmp_dir.dir.deleteDir("test_dir");
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const test_dir_path = try ctx.transformPath("test_dir");
+            const test_file_path = try ctx.transformPath("test_dir" ++ std.fs.path.sep_str ++ "test_file");
+
+            // deleting a non-existent directory
+            try testing.expectError(error.FileNotFound, ctx.dir.deleteDir(test_dir_path));
+
+            // deleting a non-empty directory
+            try ctx.dir.makeDir(test_dir_path);
+            try ctx.dir.writeFile(test_file_path, "");
+            try testing.expectError(error.DirNotEmpty, ctx.dir.deleteDir(test_dir_path));
+
+            // deleting an empty directory
+            try ctx.dir.deleteFile(test_file_path);
+            try ctx.dir.deleteDir(test_dir_path);
+        }
+    }.impl);
 }
 
 test "Dir.rename files" {
-    var tmp_dir = tmpDir(.{});
-    defer tmp_dir.cleanup();
-
-    try testing.expectError(error.FileNotFound, tmp_dir.dir.rename("missing_file_name", "something_else"));
-
-    // Renaming files
-    const test_file_name = "test_file";
-    const renamed_test_file_name = "test_file_renamed";
-    var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true });
-    file.close();
-    try tmp_dir.dir.rename(test_file_name, renamed_test_file_name);
-
-    // Ensure the file was renamed
-    try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{}));
-    file = try tmp_dir.dir.openFile(renamed_test_file_name, .{});
-    file.close();
-
-    // Rename to self succeeds
-    try tmp_dir.dir.rename(renamed_test_file_name, renamed_test_file_name);
-
-    // Rename to existing file succeeds
-    var existing_file = try tmp_dir.dir.createFile("existing_file", .{ .read = true });
-    existing_file.close();
-    try tmp_dir.dir.rename(renamed_test_file_name, "existing_file");
-
-    try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(renamed_test_file_name, .{}));
-    file = try tmp_dir.dir.openFile("existing_file", .{});
-    file.close();
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const missing_file_path = try ctx.transformPath("missing_file_name");
+            const something_else_path = try ctx.transformPath("something_else");
+
+            try testing.expectError(error.FileNotFound, ctx.dir.rename(missing_file_path, something_else_path));
+
+            // Renaming files
+            const test_file_name = try ctx.transformPath("test_file");
+            const renamed_test_file_name = try ctx.transformPath("test_file_renamed");
+            var file = try ctx.dir.createFile(test_file_name, .{ .read = true });
+            file.close();
+            try ctx.dir.rename(test_file_name, renamed_test_file_name);
+
+            // Ensure the file was renamed
+            try testing.expectError(error.FileNotFound, ctx.dir.openFile(test_file_name, .{}));
+            file = try ctx.dir.openFile(renamed_test_file_name, .{});
+            file.close();
+
+            // Rename to self succeeds
+            try ctx.dir.rename(renamed_test_file_name, renamed_test_file_name);
+
+            // Rename to existing file succeeds
+            const existing_file_path = try ctx.transformPath("existing_file");
+            var existing_file = try ctx.dir.createFile(existing_file_path, .{ .read = true });
+            existing_file.close();
+            try ctx.dir.rename(renamed_test_file_name, existing_file_path);
+
+            try testing.expectError(error.FileNotFound, ctx.dir.openFile(renamed_test_file_name, .{}));
+            file = try ctx.dir.openFile(existing_file_path, .{});
+            file.close();
+        }
+    }.impl);
 }
 
 test "Dir.rename directories" {
-    var tmp_dir = tmpDir(.{});
-    defer tmp_dir.cleanup();
-
-    // Renaming directories
-    try tmp_dir.dir.makeDir("test_dir");
-    try tmp_dir.dir.rename("test_dir", "test_dir_renamed");
-
-    // Ensure the directory was renamed
-    try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir", .{}));
-    var dir = try tmp_dir.dir.openDir("test_dir_renamed", .{});
-
-    // Put a file in the directory
-    var file = try dir.createFile("test_file", .{ .read = true });
-    file.close();
-    dir.close();
-
-    try tmp_dir.dir.rename("test_dir_renamed", "test_dir_renamed_again");
-
-    // Ensure the directory was renamed and the file still exists in it
-    try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir_renamed", .{}));
-    dir = try tmp_dir.dir.openDir("test_dir_renamed_again", .{});
-    file = try dir.openFile("test_file", .{});
-    file.close();
-    dir.close();
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const test_dir_path = try ctx.transformPath("test_dir");
+            const test_dir_renamed_path = try ctx.transformPath("test_dir_renamed");
+
+            // Renaming directories
+            try ctx.dir.makeDir(test_dir_path);
+            try ctx.dir.rename(test_dir_path, test_dir_renamed_path);
+
+            // Ensure the directory was renamed
+            try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{}));
+            var dir = try ctx.dir.openDir(test_dir_renamed_path, .{});
+
+            // Put a file in the directory
+            var file = try dir.createFile("test_file", .{ .read = true });
+            file.close();
+            dir.close();
+
+            const test_dir_renamed_again_path = try ctx.transformPath("test_dir_renamed_again");
+            try ctx.dir.rename(test_dir_renamed_path, test_dir_renamed_again_path);
+
+            // Ensure the directory was renamed and the file still exists in it
+            try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_renamed_path, .{}));
+            dir = try ctx.dir.openDir(test_dir_renamed_again_path, .{});
+            file = try dir.openFile("test_file", .{});
+            file.close();
+            dir.close();
+        }
+    }.impl);
 }
 
 test "Dir.rename directory onto empty dir" {
     // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
     if (builtin.os.tag == .windows) return error.SkipZigTest;
 
-    var tmp_dir = testing.tmpDir(.{});
-    defer tmp_dir.cleanup();
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const test_dir_path = try ctx.transformPath("test_dir");
+            const target_dir_path = try ctx.transformPath("target_dir_path");
 
-    try tmp_dir.dir.makeDir("test_dir");
-    try tmp_dir.dir.makeDir("target_dir");
-    try tmp_dir.dir.rename("test_dir", "target_dir");
+            try ctx.dir.makeDir(test_dir_path);
+            try ctx.dir.makeDir(target_dir_path);
+            try ctx.dir.rename(test_dir_path, target_dir_path);
 
-    // Ensure the directory was renamed
-    try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir", .{}));
-    var dir = try tmp_dir.dir.openDir("target_dir", .{});
-    dir.close();
+            // Ensure the directory was renamed
+            try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{}));
+            var dir = try ctx.dir.openDir(target_dir_path, .{});
+            dir.close();
+        }
+    }.impl);
 }
 
 test "Dir.rename directory onto non-empty dir" {
     // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
     if (builtin.os.tag == .windows) return error.SkipZigTest;
 
-    var tmp_dir = testing.tmpDir(.{});
-    defer tmp_dir.cleanup();
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const test_dir_path = try ctx.transformPath("test_dir");
+            const target_dir_path = try ctx.transformPath("target_dir_path");
 
-    try tmp_dir.dir.makeDir("test_dir");
+            try ctx.dir.makeDir(test_dir_path);
 
-    var target_dir = try tmp_dir.dir.makeOpenPath("target_dir", .{});
-    var file = try target_dir.createFile("test_file", .{ .read = true });
-    file.close();
-    target_dir.close();
+            var target_dir = try ctx.dir.makeOpenPath(target_dir_path, .{});
+            var file = try target_dir.createFile("test_file", .{ .read = true });
+            file.close();
+            target_dir.close();
 
-    // Rename should fail with PathAlreadyExists if target_dir is non-empty
-    try testing.expectError(error.PathAlreadyExists, tmp_dir.dir.rename("test_dir", "target_dir"));
+            // Rename should fail with PathAlreadyExists if target_dir is non-empty
+            try testing.expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, target_dir_path));
 
-    // Ensure the directory was not renamed
-    var dir = try tmp_dir.dir.openDir("test_dir", .{});
-    dir.close();
+            // Ensure the directory was not renamed
+            var dir = try ctx.dir.openDir(test_dir_path, .{});
+            dir.close();
+        }
+    }.impl);
 }
 
 test "Dir.rename file <-> dir" {
     // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
     if (builtin.os.tag == .windows) return error.SkipZigTest;
 
-    var tmp_dir = tmpDir(.{});
-    defer tmp_dir.cleanup();
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const test_file_path = try ctx.transformPath("test_file");
+            const test_dir_path = try ctx.transformPath("test_dir");
 
-    var file = try tmp_dir.dir.createFile("test_file", .{ .read = true });
-    file.close();
-    try tmp_dir.dir.makeDir("test_dir");
-    try testing.expectError(error.IsDir, tmp_dir.dir.rename("test_file", "test_dir"));
-    try testing.expectError(error.NotDir, tmp_dir.dir.rename("test_dir", "test_file"));
+            var file = try ctx.dir.createFile(test_file_path, .{ .read = true });
+            file.close();
+            try ctx.dir.makeDir(test_dir_path);
+            try testing.expectError(error.IsDir, ctx.dir.rename(test_file_path, test_dir_path));
+            try testing.expectError(error.NotDir, ctx.dir.rename(test_dir_path, test_file_path));
+        }
+    }.impl);
 }
 
 test "rename" {
@@ -720,35 +865,33 @@ test "openSelfExe" {
 }
 
 test "makePath, put some files in it, deleteTree" {
-    var tmp = tmpDir(.{});
-    defer tmp.cleanup();
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const dir_path = try ctx.transformPath("os_test_tmp");
 
-    try tmp.dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");
-    try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");
-    try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");
-    try tmp.dir.deleteTree("os_test_tmp");
-    if (tmp.dir.openDir("os_test_tmp", .{})) |dir| {
-        _ = dir;
-        @panic("expected error");
-    } else |err| {
-        try testing.expect(err == error.FileNotFound);
-    }
+            try ctx.dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");
+            try ctx.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");
+            try ctx.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");
+
+            try ctx.dir.deleteTree(dir_path);
+            try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{}));
+        }
+    }.impl);
 }
 
 test "makePath, put some files in it, deleteTreeMinStackSize" {
-    var tmp = tmpDir(.{});
-    defer tmp.cleanup();
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const dir_path = try ctx.transformPath("os_test_tmp");
 
-    try tmp.dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");
-    try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");
-    try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");
-    try tmp.dir.deleteTreeMinStackSize("os_test_tmp");
-    if (tmp.dir.openDir("os_test_tmp", .{})) |dir| {
-        _ = dir;
-        @panic("expected error");
-    } else |err| {
-        try testing.expect(err == error.FileNotFound);
-    }
+            try ctx.dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");
+            try ctx.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");
+            try ctx.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");
+
+            try ctx.dir.deleteTreeMinStackSize(dir_path);
+            try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{}));
+        }
+    }.impl);
 }
 
 test "makePath in a directory that no longer exists" {
@@ -789,9 +932,9 @@ test "max file name component lengths" {
     defer tmp.cleanup();
 
     if (builtin.os.tag == .windows) {
-        // € is the character with the largest codepoint that is encoded as a single u16 in UTF-16,
-        // so Windows allows for NAME_MAX of them
-        const maxed_windows_filename = ("€".*) ** std.os.windows.NAME_MAX;
+        // U+FFFF is the character with the largest code point that is encoded as a single
+        // UTF-16 code unit, so Windows allows for NAME_MAX of them.
+        const maxed_windows_filename = ("\u{FFFF}".*) ** std.os.windows.NAME_MAX;
         try testFilenameLimits(tmp.iterable_dir, &maxed_windows_filename);
     } else if (builtin.os.tag == .wasi) {
         // On WASI, the maxed filename depends on the host OS, so in order for this test to
@@ -889,20 +1032,19 @@ test "pwritev, preadv" {
 }
 
 test "access file" {
-    var tmp = tmpDir(.{});
-    defer tmp.cleanup();
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const dir_path = try ctx.transformPath("os_test_tmp");
+            const file_path = try ctx.transformPath("os_test_tmp" ++ fs.path.sep_str ++ "file.txt");
 
-    try tmp.dir.makePath("os_test_tmp");
-    if (tmp.dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{})) |ok| {
-        _ = ok;
-        @panic("expected error");
-    } else |err| {
-        try testing.expect(err == error.FileNotFound);
-    }
+            try ctx.dir.makePath(dir_path);
+            try testing.expectError(error.FileNotFound, ctx.dir.access(file_path, .{}));
 
-    try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", "");
-    try tmp.dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{});
-    try tmp.dir.deleteTree("os_test_tmp");
+            try ctx.dir.writeFile(file_path, "");
+            try ctx.dir.access(file_path, .{});
+            try ctx.dir.deleteTree(dir_path);
+        }
+    }.impl);
 }
 
 test "sendfile" {
@@ -996,26 +1138,27 @@ test "copyRangeAll" {
     try testing.expect(mem.eql(u8, written_buf[0..amt], data));
 }
 
-test "fs.copyFile" {
-    const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";
-    const src_file = "tmp_test_copy_file.txt";
-    const dest_file = "tmp_test_copy_file2.txt";
-    const dest_file2 = "tmp_test_copy_file3.txt";
-
-    var tmp = tmpDir(.{});
-    defer tmp.cleanup();
+test "copyFile" {
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";
+            const src_file = try ctx.transformPath("tmp_test_copy_file.txt");
+            const dest_file = try ctx.transformPath("tmp_test_copy_file2.txt");
+            const dest_file2 = try ctx.transformPath("tmp_test_copy_file3.txt");
 
-    try tmp.dir.writeFile(src_file, data);
-    defer tmp.dir.deleteFile(src_file) catch {};
+            try ctx.dir.writeFile(src_file, data);
+            defer ctx.dir.deleteFile(src_file) catch {};
 
-    try tmp.dir.copyFile(src_file, tmp.dir, dest_file, .{});
-    defer tmp.dir.deleteFile(dest_file) catch {};
+            try ctx.dir.copyFile(src_file, ctx.dir, dest_file, .{});
+            defer ctx.dir.deleteFile(dest_file) catch {};
 
-    try tmp.dir.copyFile(src_file, tmp.dir, dest_file2, .{ .override_mode = File.default_mode });
-    defer tmp.dir.deleteFile(dest_file2) catch {};
+            try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, .{ .override_mode = File.default_mode });
+            defer ctx.dir.deleteFile(dest_file2) catch {};
 
-    try expectFileContents(tmp.dir, dest_file, data);
-    try expectFileContents(tmp.dir, dest_file2, data);
+            try expectFileContents(ctx.dir, dest_file, data);
+            try expectFileContents(ctx.dir, dest_file2, data);
+        }
+    }.impl);
 }
 
 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 {
 }
 
 test "AtomicFile" {
-    const test_out_file = "tmp_atomic_file_test_dest.txt";
-    const test_content =
-        \\ hello!
-        \\ this is a test file
-    ;
-
-    var tmp = tmpDir(.{});
-    defer tmp.cleanup();
-
-    {
-        var af = try tmp.dir.atomicFile(test_out_file, .{});
-        defer af.deinit();
-        try af.file.writeAll(test_content);
-        try af.finish();
-    }
-    const content = try tmp.dir.readFileAlloc(testing.allocator, test_out_file, 9999);
-    defer testing.allocator.free(content);
-    try testing.expect(mem.eql(u8, content, test_content));
-
-    try tmp.dir.deleteFile(test_out_file);
-}
-
-test "realpath" {
-    if (builtin.os.tag == .wasi) return error.SkipZigTest;
-
-    var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
-    try testing.expectError(error.FileNotFound, fs.realpath("definitely_bogus_does_not_exist1234", &buf));
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const test_out_file = try ctx.transformPath("tmp_atomic_file_test_dest.txt");
+            const test_content =
+                \\ hello!
+                \\ this is a test file
+            ;
+
+            {
+                var af = try ctx.dir.atomicFile(test_out_file, .{});
+                defer af.deinit();
+                try af.file.writeAll(test_content);
+                try af.finish();
+            }
+            const content = try ctx.dir.readFileAlloc(testing.allocator, test_out_file, 9999);
+            defer testing.allocator.free(content);
+            try testing.expect(mem.eql(u8, content, test_content));
+
+            try ctx.dir.deleteFile(test_out_file);
+        }
+    }.impl);
 }
 
 test "open file with exclusive nonblocking lock twice" {
     if (builtin.os.tag == .wasi) return error.SkipZigTest;
 
-    const filename = "file_nonblocking_lock_test.txt";
-
-    var tmp = tmpDir(.{});
-    defer tmp.cleanup();
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
 
-    const file1 = try tmp.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
-    defer file1.close();
+            const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
+            defer file1.close();
 
-    const file2 = tmp.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
-    try testing.expectError(error.WouldBlock, file2);
+            const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
+            try testing.expectError(error.WouldBlock, file2);
+        }
+    }.impl);
 }
 
 test "open file with shared and exclusive nonblocking lock" {
     if (builtin.os.tag == .wasi) return error.SkipZigTest;
 
-    const filename = "file_nonblocking_lock_test.txt";
-
-    var tmp = tmpDir(.{});
-    defer tmp.cleanup();
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
 
-    const file1 = try tmp.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
-    defer file1.close();
+            const file1 = try ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
+            defer file1.close();
 
-    const file2 = tmp.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
-    try testing.expectError(error.WouldBlock, file2);
+            const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
+            try testing.expectError(error.WouldBlock, file2);
+        }
+    }.impl);
 }
 
 test "open file with exclusive and shared nonblocking lock" {
     if (builtin.os.tag == .wasi) return error.SkipZigTest;
 
-    const filename = "file_nonblocking_lock_test.txt";
-
-    var tmp = tmpDir(.{});
-    defer tmp.cleanup();
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
 
-    const file1 = try tmp.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
-    defer file1.close();
+            const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
+            defer file1.close();
 
-    const file2 = tmp.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
-    try testing.expectError(error.WouldBlock, file2);
+            const file2 = ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
+            try testing.expectError(error.WouldBlock, file2);
+        }
+    }.impl);
 }
 
 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" {
         return error.SkipZigTest;
     }
 
-    const filename = "file_lock_test.txt";
-
-    var tmp = tmpDir(.{});
-    defer tmp.cleanup();
-
-    const file = try tmp.dir.createFile(filename, .{ .lock = .exclusive });
-    errdefer file.close();
-
-    const S = struct {
-        fn checkFn(dir: *fs.Dir, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void {
-            started.set();
-            const file1 = try dir.createFile(filename, .{ .lock = .exclusive });
-
-            locked.set();
-            file1.close();
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const filename = try ctx.transformPath("file_lock_test.txt");
+
+            const file = try ctx.dir.createFile(filename, .{ .lock = .exclusive });
+            errdefer file.close();
+
+            const S = struct {
+                fn checkFn(dir: *fs.Dir, path: []const u8, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void {
+                    started.set();
+                    const file1 = try dir.createFile(path, .{ .lock = .exclusive });
+
+                    locked.set();
+                    file1.close();
+                }
+            };
+
+            var started = std.Thread.ResetEvent{};
+            var locked = std.Thread.ResetEvent{};
+
+            const t = try std.Thread.spawn(.{}, S.checkFn, .{
+                &ctx.dir,
+                filename,
+                &started,
+                &locked,
+            });
+            defer t.join();
+
+            // Wait for the spawned thread to start trying to acquire the exclusive file lock.
+            // Then wait a bit to make sure that can't acquire it since we currently hold the file lock.
+            started.wait();
+            try testing.expectError(error.Timeout, locked.timedWait(10 * std.time.ns_per_ms));
+
+            // Release the file lock which should unlock the thread to lock it and set the locked event.
+            file.close();
+            locked.wait();
         }
-    };
-
-    var started = std.Thread.ResetEvent{};
-    var locked = std.Thread.ResetEvent{};
-
-    const t = try std.Thread.spawn(.{}, S.checkFn, .{
-        &tmp.dir,
-        &started,
-        &locked,
-    });
-    defer t.join();
-
-    // Wait for the spawned thread to start trying to acquire the exclusive file lock.
-    // Then wait a bit to make sure that can't acquire it since we currently hold the file lock.
-    started.wait();
-    try testing.expectError(error.Timeout, locked.timedWait(10 * std.time.ns_per_ms));
-
-    // Release the file lock which should unlock the thread to lock it and set the locked event.
-    file.close();
-    locked.wait();
+    }.impl);
 }
 
 test "open file with exclusive nonblocking lock twice (absolute paths)" {
@@ -1259,29 +1401,36 @@ test "walker without fully iterating" {
 test ". and .. in fs.Dir functions" {
     if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
 
-    var tmp = tmpDir(.{});
-    defer tmp.cleanup();
-
-    try tmp.dir.makeDir("./subdir");
-    try tmp.dir.access("./subdir", .{});
-    var created_subdir = try tmp.dir.openDir("./subdir", .{});
-    created_subdir.close();
-
-    const created_file = try tmp.dir.createFile("./subdir/../file", .{});
-    created_file.close();
-    try tmp.dir.access("./subdir/../file", .{});
-
-    try tmp.dir.copyFile("./subdir/../file", tmp.dir, "./subdir/../copy", .{});
-    try tmp.dir.rename("./subdir/../copy", "./subdir/../rename");
-    const renamed_file = try tmp.dir.openFile("./subdir/../rename", .{});
-    renamed_file.close();
-    try tmp.dir.deleteFile("./subdir/../rename");
-
-    try tmp.dir.writeFile("./subdir/../update", "something");
-    const prev_status = try tmp.dir.updateFile("./subdir/../file", tmp.dir, "./subdir/../update", .{});
-    try testing.expectEqual(fs.PrevStatus.stale, prev_status);
-
-    try tmp.dir.deleteDir("./subdir");
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            const subdir_path = try ctx.transformPath("./subdir");
+            const file_path = try ctx.transformPath("./subdir/../file");
+            const copy_path = try ctx.transformPath("./subdir/../copy");
+            const rename_path = try ctx.transformPath("./subdir/../rename");
+            const update_path = try ctx.transformPath("./subdir/../update");
+
+            try ctx.dir.makeDir(subdir_path);
+            try ctx.dir.access(subdir_path, .{});
+            var created_subdir = try ctx.dir.openDir(subdir_path, .{});
+            created_subdir.close();
+
+            const created_file = try ctx.dir.createFile(file_path, .{});
+            created_file.close();
+            try ctx.dir.access(file_path, .{});
+
+            try ctx.dir.copyFile(file_path, ctx.dir, copy_path, .{});
+            try ctx.dir.rename(copy_path, rename_path);
+            const renamed_file = try ctx.dir.openFile(rename_path, .{});
+            renamed_file.close();
+            try ctx.dir.deleteFile(rename_path);
+
+            try ctx.dir.writeFile(update_path, "something");
+            const prev_status = try ctx.dir.updateFile(file_path, ctx.dir, update_path, .{});
+            try testing.expectEqual(fs.PrevStatus.stale, prev_status);
+
+            try ctx.dir.deleteDir(subdir_path);
+        }
+    }.impl);
 }
 
 test ". and .. in absolute functions" {
lib/std/os.zig
@@ -5169,11 +5169,30 @@ pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPat
     return getFdPath(h_file, out_buffer);
 }
 
+pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool {
+    return switch (os.tag) {
+        // zig fmt: off
+        .windows,
+        .macos, .ios, .watchos, .tvos,
+        .linux,
+        .solaris,
+        .freebsd,
+        => true,
+        // zig fmt: on
+        .dragonfly => os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt,
+        .netbsd => os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) != .lt,
+        else => false,
+    };
+}
+
 /// Return canonical path of handle `fd`.
 /// This function is very host-specific and is not universally supported by all hosts.
 /// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is
 /// unsupported on WASI.
 pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
+    if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) {
+        @compileError("querying for canonical path of a handle is unsupported on this host");
+    }
     switch (builtin.os.tag) {
         .windows => {
             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 {
             }
         },
         .dragonfly => {
-            if (comptime builtin.os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) == .lt) {
-                @compileError("querying for canonical path of a handle is unsupported on this host");
-            }
             @memset(out_buffer[0..MAX_PATH_BYTES], 0);
             switch (errno(system.fcntl(fd, F.GETPATH, out_buffer))) {
                 .SUCCESS => {},
@@ -5290,9 +5306,6 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
             return out_buffer[0..len];
         },
         .netbsd => {
-            if (comptime builtin.os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) == .lt) {
-                @compileError("querying for canonical path of a handle is unsupported on this host");
-            }
             @memset(out_buffer[0..MAX_PATH_BYTES], 0);
             switch (errno(system.fcntl(fd, F.GETPATH, out_buffer))) {
                 .SUCCESS => {},
@@ -5306,7 +5319,7 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
             const len = mem.indexOfScalar(u8, out_buffer[0..], @as(u8, 0)) orelse MAX_PATH_BYTES;
             return out_buffer[0..len];
         },
-        else => @compileError("querying for canonical path of a handle is unsupported on this host"),
+        else => unreachable, // made unreachable by isGetFdPathSupportedOnTarget above
     }
 }