diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index abdfee284f..95f32ed6d4 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -10,6 +10,7 @@ const ArenaAllocator = std.heap.ArenaAllocator; const Dir = std.fs.Dir; const File = std.fs.File; const tmpDir = testing.tmpDir; +const SymLinkFlags = std.fs.Dir.SymLinkFlags; const PathType = enum { relative, @@ -119,6 +120,25 @@ fn testWithAllSupportedPathTypes(test_func: anytype) !void { } } +// For use in test setup. If the symlink creation fails on Windows with +// AccessDenied, then make the test failure silent (it is not a Zig failure). +fn setupSymlink(dir: Dir, target: []const u8, link: []const u8, flags: SymLinkFlags) !void { + return dir.symLink(target, link, flags) catch |err| switch (err) { + // Symlink requires admin privileges on windows, so this test can legitimately fail. + error.AccessDenied => if (builtin.os.tag == .windows) return error.SkipZigTest else return err, + else => return err, + }; +} + +// For use in test setup. If the symlink creation fails on Windows with +// AccessDenied, then make the test failure silent (it is not a Zig failure). +fn setupSymlinkAbsolute(target: []const u8, link: []const u8, flags: SymLinkFlags) !void { + return fs.symLinkAbsolute(target, link, flags) catch |err| switch (err) { + error.AccessDenied => if (builtin.os.tag == .windows) return error.SkipZigTest else return err, + else => return err, + }; +} + test "Dir.readLink" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { @@ -128,31 +148,33 @@ test "Dir.readLink" { 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"); - } + // test 1: symlink to a file + try setupSymlink(ctx.dir, file_target_path, "symlink1", .{}); + try testReadLink(ctx.dir, file_target_path, "symlink1"); + + // test 2: symlink to a directory (can be different on Windows) + try setupSymlink(ctx.dir, dir_target_path, "symlink2", .{ .is_directory = true }); + try testReadLink(ctx.dir, dir_target_path, "symlink2"); + + // test 3: relative path symlink + const parent_file = ".." ++ fs.path.sep_str ++ "target.txt"; + var subdir = try ctx.dir.makeOpenPath("subdir", .{}); + defer subdir.close(); + try setupSymlink(subdir, parent_file, "relative-link.txt", .{}); + try testReadLink(subdir, parent_file, "relative-link.txt"); } }.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..]); + const actual = try dir.readLink(symlink_path, buffer[0..]); + try testing.expectEqualStrings(target_path, actual); +} + +fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void { + var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; + const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]); try testing.expectEqualStrings(target_path, given); } @@ -169,11 +191,7 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { const dir_target_path = try ctx.transformPath("subdir"); try ctx.dir.makeDir(dir_target_path); - ctx.dir.symLink(dir_target_path, "symlink", .{ .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 setupSymlink(ctx.dir, dir_target_path, "symlink", .{ .is_directory = true }); var symlink = switch (builtin.target.os.tag) { .windows => windows_symlink: { @@ -238,23 +256,6 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { }.impl); } -test "relative symlink to parent directory" { - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - var subdir = try tmp.dir.makeOpenPath("subdir", .{}); - defer subdir.close(); - - const expected_link_name = ".." ++ std.fs.path.sep_str ++ "b.txt"; - - try subdir.symLink(expected_link_name, "a.txt", .{}); - - var buf: [1000]u8 = undefined; - const link_name = try subdir.readLink("a.txt", &buf); - - try testing.expectEqualStrings(expected_link_name, link_name); -} - test "openDir" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { @@ -373,33 +374,19 @@ test "readLinkAbsolute" { const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink1" }); // Create symbolic link by path - fs.symLinkAbsolute(target_path, symlink_path, .{}) 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 setupSymlinkAbsolute(target_path, symlink_path, .{}); try testReadLinkAbsolute(target_path, symlink_path); } { const target_path = try fs.path.join(allocator, &.{ base_path, "subdir" }); const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink2" }); - // Create symbolic link by path - fs.symLinkAbsolute(target_path, symlink_path, .{ .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, - }; + // Create symbolic link to a directory by path + try setupSymlinkAbsolute(target_path, symlink_path, .{ .is_directory = true }); try testReadLinkAbsolute(target_path, symlink_path); } } -fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void { - var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; - const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]); - try testing.expectEqualStrings(target_path, given); -} - test "Dir.Iterator" { var tmp_dir = tmpDir(.{ .iterate = true }); defer tmp_dir.cleanup(); @@ -1005,11 +992,7 @@ test "deleteTree does not follow symlinks" { var a = try tmp.dir.makeOpenPath("a", .{}); defer a.close(); - a.symLink("../b", "b", .{ .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 setupSymlink(a, "../b", "b", .{ .is_directory = true }); } try tmp.dir.deleteTree("a"); @@ -1024,11 +1007,7 @@ test "deleteTree on a symlink" { // Symlink to a file try tmp.dir.writeFile("file", ""); - tmp.dir.symLink("file", "filelink", .{}) 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 setupSymlink(tmp.dir, "file", "filelink", .{}); try tmp.dir.deleteTree("filelink"); try testing.expectError(error.FileNotFound, tmp.dir.access("filelink", .{})); @@ -1036,11 +1015,7 @@ test "deleteTree on a symlink" { // Symlink to a directory try tmp.dir.makePath("dir"); - tmp.dir.symLink("dir", "dirlink", .{ .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 setupSymlink(tmp.dir, "dir", "dirlink", .{ .is_directory = true }); try tmp.dir.deleteTree("dirlink"); try testing.expectError(error.FileNotFound, tmp.dir.access("dirlink", .{})); @@ -1123,7 +1098,7 @@ test "makepath through existing valid symlink" { defer tmp.cleanup(); try tmp.dir.makeDir("realfolder"); - try tmp.dir.symLink("." ++ fs.path.sep_str ++ "realfolder", "working-symlink", .{}); + try setupSymlink(tmp.dir, "." ++ fs.path.sep_str ++ "realfolder", "working-symlink", .{}); try tmp.dir.makePath("working-symlink" ++ fs.path.sep_str ++ "in-realfolder");