| ... | @@ -10,6 +10,7 @@ const ArenaAllocator = std.heap.ArenaAllocator; | ... | @@ -10,6 +10,7 @@ const ArenaAllocator = std.heap.ArenaAllocator; |
| 10 | const Dir = std.fs.Dir; | 10 | const Dir = std.fs.Dir; |
| 11 | const File = std.fs.File; | 11 | const File = std.fs.File; |
| 12 | const tmpDir = testing.tmpDir; | 12 | const tmpDir = testing.tmpDir; |
| | 13 | const SymLinkFlags = std.fs.Dir.SymLinkFlags; |
| 13 | | 14 | |
| 14 | const PathType = enum { | 15 | const PathType = enum { |
| 15 | relative, | 16 | relative, |
| ... | @@ -119,6 +120,25 @@ fn testWithAllSupportedPathTypes(test_func: anytype) !void { | ... | @@ -119,6 +120,25 @@ fn testWithAllSupportedPathTypes(test_func: anytype) !void { |
| 119 | } | 120 | } |
| 120 | } | 121 | } |
| 121 | | 122 | |
| | 123 | // For use in test setup. If the symlink creation fails on Windows with |
| | 124 | // AccessDenied, then make the test failure silent (it is not a Zig failure). |
| | 125 | fn setupSymlink(dir: Dir, target: []const u8, link: []const u8, flags: SymLinkFlags) !void { |
| | 126 | return dir.symLink(target, link, flags) catch |err| switch (err) { |
| | 127 | // Symlink requires admin privileges on windows, so this test can legitimately fail. |
| | 128 | error.AccessDenied => if (builtin.os.tag == .windows) return error.SkipZigTest else return err, |
| | 129 | else => return err, |
| | 130 | }; |
| | 131 | } |
| | 132 | |
| | 133 | // For use in test setup. If the symlink creation fails on Windows with |
| | 134 | // AccessDenied, then make the test failure silent (it is not a Zig failure). |
| | 135 | fn setupSymlinkAbsolute(target: []const u8, link: []const u8, flags: SymLinkFlags) !void { |
| | 136 | return fs.symLinkAbsolute(target, link, flags) catch |err| switch (err) { |
| | 137 | error.AccessDenied => if (builtin.os.tag == .windows) return error.SkipZigTest else return err, |
| | 138 | else => return err, |
| | 139 | }; |
| | 140 | } |
| | 141 | |
| 122 | test "Dir.readLink" { | 142 | test "Dir.readLink" { |
| 123 | try testWithAllSupportedPathTypes(struct { | 143 | try testWithAllSupportedPathTypes(struct { |
| 124 | fn impl(ctx: *TestContext) !void { | 144 | fn impl(ctx: *TestContext) !void { |
| ... | @@ -128,31 +148,33 @@ test "Dir.readLink" { | ... | @@ -128,31 +148,33 @@ test "Dir.readLink" { |
| 128 | const dir_target_path = try ctx.transformPath("subdir"); | 148 | const dir_target_path = try ctx.transformPath("subdir"); |
| 129 | try ctx.dir.makeDir(dir_target_path); | 149 | try ctx.dir.makeDir(dir_target_path); |
| 130 | | 150 | |
| 131 | { | 151 | // test 1: symlink to a file |
| 132 | // Create symbolic link by path | 152 | try setupSymlink(ctx.dir, file_target_path, "symlink1", .{}); |
| 133 | ctx.dir.symLink(file_target_path, "symlink1", .{}) catch |err| switch (err) { | 153 | try testReadLink(ctx.dir, file_target_path, "symlink1"); |
| 134 | // Symlink requires admin privileges on windows, so this test can legitimately fail. | 154 | |
| 135 | error.AccessDenied => return error.SkipZigTest, | 155 | // test 2: symlink to a directory (can be different on Windows) |
| 136 | else => return err, | 156 | try setupSymlink(ctx.dir, dir_target_path, "symlink2", .{ .is_directory = true }); |
| 137 | }; | 157 | try testReadLink(ctx.dir, dir_target_path, "symlink2"); |
| 138 | try testReadLink(ctx.dir, file_target_path, "symlink1"); | 158 | |
| 139 | } | 159 | // test 3: relative path symlink |
| 140 | { | 160 | const parent_file = ".." ++ fs.path.sep_str ++ "target.txt"; |
| 141 | // Create symbolic link by path | 161 | var subdir = try ctx.dir.makeOpenPath("subdir", .{}); |
| 142 | ctx.dir.symLink(dir_target_path, "symlink2", .{ .is_directory = true }) catch |err| switch (err) { | 162 | defer subdir.close(); |
| 143 | // Symlink requires admin privileges on windows, so this test can legitimately fail. | 163 | try setupSymlink(subdir, parent_file, "relative-link.txt", .{}); |
| 144 | error.AccessDenied => return error.SkipZigTest, | 164 | try testReadLink(subdir, parent_file, "relative-link.txt"); |
| 145 | else => return err, | | |
| 146 | }; | | |
| 147 | try testReadLink(ctx.dir, dir_target_path, "symlink2"); | | |
| 148 | } | | |
| 149 | } | 165 | } |
| 150 | }.impl); | 166 | }.impl); |
| 151 | } | 167 | } |
| 152 | | 168 | |
| 153 | fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void { | 169 | fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void { |
| 154 | var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; | 170 | var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; |
| 155 | const given = try dir.readLink(symlink_path, buffer[0..]); | 171 | const actual = try dir.readLink(symlink_path, buffer[0..]); |
| | 172 | try testing.expectEqualStrings(target_path, actual); |
| | 173 | } |
| | 174 | |
| | 175 | fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void { |
| | 176 | var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; |
| | 177 | const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]); |
| 156 | try testing.expectEqualStrings(target_path, given); | 178 | try testing.expectEqualStrings(target_path, given); |
| 157 | } | 179 | } |
| 158 | | 180 | |
| ... | @@ -169,11 +191,7 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { | ... | @@ -169,11 +191,7 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { |
| 169 | const dir_target_path = try ctx.transformPath("subdir"); | 191 | const dir_target_path = try ctx.transformPath("subdir"); |
| 170 | try ctx.dir.makeDir(dir_target_path); | 192 | try ctx.dir.makeDir(dir_target_path); |
| 171 | | 193 | |
| 172 | ctx.dir.symLink(dir_target_path, "symlink", .{ .is_directory = true }) catch |err| switch (err) { | 194 | try setupSymlink(ctx.dir, dir_target_path, "symlink", .{ .is_directory = true }); |
| 173 | // Symlink requires admin privileges on windows, so this test can legitimately fail. | | |
| 174 | error.AccessDenied => return error.SkipZigTest, | | |
| 175 | else => return err, | | |
| 176 | }; | | |
| 177 | | 195 | |
| 178 | var symlink = switch (builtin.target.os.tag) { | 196 | var symlink = switch (builtin.target.os.tag) { |
| 179 | .windows => windows_symlink: { | 197 | .windows => windows_symlink: { |
| ... | @@ -238,23 +256,6 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { | ... | @@ -238,23 +256,6 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { |
| 238 | }.impl); | 256 | }.impl); |
| 239 | } | 257 | } |
| 240 | | 258 | |
| 241 | test "relative symlink to parent directory" { | | |
| 242 | var tmp = tmpDir(.{}); | | |
| 243 | defer tmp.cleanup(); | | |
| 244 | | | |
| 245 | var subdir = try tmp.dir.makeOpenPath("subdir", .{}); | | |
| 246 | defer subdir.close(); | | |
| 247 | | | |
| 248 | const expected_link_name = ".." ++ std.fs.path.sep_str ++ "b.txt"; | | |
| 249 | | | |
| 250 | try subdir.symLink(expected_link_name, "a.txt", .{}); | | |
| 251 | | | |
| 252 | var buf: [1000]u8 = undefined; | | |
| 253 | const link_name = try subdir.readLink("a.txt", &buf); | | |
| 254 | | | |
| 255 | try testing.expectEqualStrings(expected_link_name, link_name); | | |
| 256 | } | | |
| 257 | | | |
| 258 | test "openDir" { | 259 | test "openDir" { |
| 259 | try testWithAllSupportedPathTypes(struct { | 260 | try testWithAllSupportedPathTypes(struct { |
| 260 | fn impl(ctx: *TestContext) !void { | 261 | fn impl(ctx: *TestContext) !void { |
| ... | @@ -317,14 +318,14 @@ test "openDirAbsolute" { | ... | @@ -317,14 +318,14 @@ test "openDirAbsolute" { |
| 317 | } | 318 | } |
| 318 | } | 319 | } |
| 319 | | 320 | |
| 320 | test "openDir cwd parent .." { | 321 | test "openDir cwd parent '..'" { |
| 321 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | 322 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 322 | | 323 | |
| 323 | var dir = try fs.cwd().openDir("..", .{}); | 324 | var dir = try fs.cwd().openDir("..", .{}); |
| 324 | defer dir.close(); | 325 | defer dir.close(); |
| 325 | } | 326 | } |
| 326 | | 327 | |
| 327 | test "openDir non-cwd parent .." { | 328 | test "openDir non-cwd parent '..'" { |
| 328 | switch (builtin.os.tag) { | 329 | switch (builtin.os.tag) { |
| 329 | .wasi, .netbsd, .openbsd => return error.SkipZigTest, | 330 | .wasi, .netbsd, .openbsd => return error.SkipZigTest, |
| 330 | else => {}, | 331 | else => {}, |
| ... | @@ -373,33 +374,19 @@ test "readLinkAbsolute" { | ... | @@ -373,33 +374,19 @@ test "readLinkAbsolute" { |
| 373 | const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink1" }); | 374 | const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink1" }); |
| 374 | | 375 | |
| 375 | // Create symbolic link by path | 376 | // Create symbolic link by path |
| 376 | fs.symLinkAbsolute(target_path, symlink_path, .{}) catch |err| switch (err) { | 377 | try setupSymlinkAbsolute(target_path, symlink_path, .{}); |
| 377 | // Symlink requires admin privileges on windows, so this test can legitimately fail. | | |
| 378 | error.AccessDenied => return error.SkipZigTest, | | |
| 379 | else => return err, | | |
| 380 | }; | | |
| 381 | try testReadLinkAbsolute(target_path, symlink_path); | 378 | try testReadLinkAbsolute(target_path, symlink_path); |
| 382 | } | 379 | } |
| 383 | { | 380 | { |
| 384 | const target_path = try fs.path.join(allocator, &.{ base_path, "subdir" }); | 381 | const target_path = try fs.path.join(allocator, &.{ base_path, "subdir" }); |
| 385 | const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink2" }); | 382 | const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink2" }); |
| 386 | | 383 | |
| 387 | // Create symbolic link by path | 384 | // Create symbolic link to a directory by path |
| 388 | fs.symLinkAbsolute(target_path, symlink_path, .{ .is_directory = true }) catch |err| switch (err) { | 385 | try setupSymlinkAbsolute(target_path, symlink_path, .{ .is_directory = true }); |
| 389 | // Symlink requires admin privileges on windows, so this test can legitimately fail. | | |
| 390 | error.AccessDenied => return error.SkipZigTest, | | |
| 391 | else => return err, | | |
| 392 | }; | | |
| 393 | try testReadLinkAbsolute(target_path, symlink_path); | 386 | try testReadLinkAbsolute(target_path, symlink_path); |
| 394 | } | 387 | } |
| 395 | } | 388 | } |
| 396 | | 389 | |
| 397 | fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void { | | |
| 398 | var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; | | |
| 399 | const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]); | | |
| 400 | try testing.expectEqualStrings(target_path, given); | | |
| 401 | } | | |
| 402 | | | |
| 403 | test "Dir.Iterator" { | 390 | test "Dir.Iterator" { |
| 404 | var tmp_dir = tmpDir(.{ .iterate = true }); | 391 | var tmp_dir = tmpDir(.{ .iterate = true }); |
| 405 | defer tmp_dir.cleanup(); | 392 | defer tmp_dir.cleanup(); |
| ... | @@ -674,6 +661,19 @@ test "Dir.statFile" { | ... | @@ -674,6 +661,19 @@ test "Dir.statFile" { |
| 674 | }.impl); | 661 | }.impl); |
| 675 | } | 662 | } |
| 676 | | 663 | |
| | 664 | test "statFile on dangling symlink" { |
| | 665 | try testWithAllSupportedPathTypes(struct { |
| | 666 | fn impl(ctx: *TestContext) !void { |
| | 667 | const symlink_name = try ctx.transformPath("dangling-symlink"); |
| | 668 | const symlink_target = "." ++ fs.path.sep_str ++ "doesnotexist"; |
| | 669 | |
| | 670 | try setupSymlink(ctx.dir, symlink_target, symlink_name, .{}); |
| | 671 | |
| | 672 | try std.testing.expectError(error.FileNotFound, ctx.dir.statFile(symlink_name)); |
| | 673 | } |
| | 674 | }.impl); |
| | 675 | } |
| | 676 | |
| 677 | test "directory operations on files" { | 677 | test "directory operations on files" { |
| 678 | try testWithAllSupportedPathTypes(struct { | 678 | try testWithAllSupportedPathTypes(struct { |
| 679 | fn impl(ctx: *TestContext) !void { | 679 | fn impl(ctx: *TestContext) !void { |
| ... | @@ -1005,11 +1005,7 @@ test "deleteTree does not follow symlinks" { | ... | @@ -1005,11 +1005,7 @@ test "deleteTree does not follow symlinks" { |
| 1005 | var a = try tmp.dir.makeOpenPath("a", .{}); | 1005 | var a = try tmp.dir.makeOpenPath("a", .{}); |
| 1006 | defer a.close(); | 1006 | defer a.close(); |
| 1007 | | 1007 | |
| 1008 | a.symLink("../b", "b", .{ .is_directory = true }) catch |err| switch (err) { | 1008 | try setupSymlink(a, "../b", "b", .{ .is_directory = true }); |
| 1009 | // Symlink requires admin privileges on windows, so this test can legitimately fail. | | |
| 1010 | error.AccessDenied => return error.SkipZigTest, | | |
| 1011 | else => return err, | | |
| 1012 | }; | | |
| 1013 | } | 1009 | } |
| 1014 | | 1010 | |
| 1015 | try tmp.dir.deleteTree("a"); | 1011 | try tmp.dir.deleteTree("a"); |
| ... | @@ -1024,11 +1020,7 @@ test "deleteTree on a symlink" { | ... | @@ -1024,11 +1020,7 @@ test "deleteTree on a symlink" { |
| 1024 | | 1020 | |
| 1025 | // Symlink to a file | 1021 | // Symlink to a file |
| 1026 | try tmp.dir.writeFile("file", ""); | 1022 | try tmp.dir.writeFile("file", ""); |
| 1027 | tmp.dir.symLink("file", "filelink", .{}) catch |err| switch (err) { | 1023 | try setupSymlink(tmp.dir, "file", "filelink", .{}); |
| 1028 | // Symlink requires admin privileges on windows, so this test can legitimately fail. | | |
| 1029 | error.AccessDenied => return error.SkipZigTest, | | |
| 1030 | else => return err, | | |
| 1031 | }; | | |
| 1032 | | 1024 | |
| 1033 | try tmp.dir.deleteTree("filelink"); | 1025 | try tmp.dir.deleteTree("filelink"); |
| 1034 | try testing.expectError(error.FileNotFound, tmp.dir.access("filelink", .{})); | 1026 | try testing.expectError(error.FileNotFound, tmp.dir.access("filelink", .{})); |
| ... | @@ -1036,11 +1028,7 @@ test "deleteTree on a symlink" { | ... | @@ -1036,11 +1028,7 @@ test "deleteTree on a symlink" { |
| 1036 | | 1028 | |
| 1037 | // Symlink to a directory | 1029 | // Symlink to a directory |
| 1038 | try tmp.dir.makePath("dir"); | 1030 | try tmp.dir.makePath("dir"); |
| 1039 | tmp.dir.symLink("dir", "dirlink", .{ .is_directory = true }) catch |err| switch (err) { | 1031 | try setupSymlink(tmp.dir, "dir", "dirlink", .{ .is_directory = true }); |
| 1040 | // Symlink requires admin privileges on windows, so this test can legitimately fail. | | |
| 1041 | error.AccessDenied => return error.SkipZigTest, | | |
| 1042 | else => return err, | | |
| 1043 | }; | | |
| 1044 | | 1032 | |
| 1045 | try tmp.dir.deleteTree("dirlink"); | 1033 | try tmp.dir.deleteTree("dirlink"); |
| 1046 | try testing.expectError(error.FileNotFound, tmp.dir.access("dirlink", .{})); | 1034 | try testing.expectError(error.FileNotFound, tmp.dir.access("dirlink", .{})); |
| ... | @@ -1123,7 +1111,7 @@ test "makepath through existing valid symlink" { | ... | @@ -1123,7 +1111,7 @@ test "makepath through existing valid symlink" { |
| 1123 | defer tmp.cleanup(); | 1111 | defer tmp.cleanup(); |
| 1124 | | 1112 | |
| 1125 | try tmp.dir.makeDir("realfolder"); | 1113 | try tmp.dir.makeDir("realfolder"); |
| 1126 | try tmp.dir.symLink("." ++ fs.path.sep_str ++ "realfolder", "working-symlink", .{}); | 1114 | try setupSymlink(tmp.dir, "." ++ fs.path.sep_str ++ "realfolder", "working-symlink", .{}); |
| 1127 | | 1115 | |
| 1128 | try tmp.dir.makePath("working-symlink" ++ fs.path.sep_str ++ "in-realfolder"); | 1116 | try tmp.dir.makePath("working-symlink" ++ fs.path.sep_str ++ "in-realfolder"); |
| 1129 | | 1117 | |
| ... | @@ -1584,11 +1572,11 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { | ... | @@ -1584,11 +1572,11 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { |
| 1584 | const filename = try fs.path.resolve(gpa, &.{ cwd, sub_path }); | 1572 | const filename = try fs.path.resolve(gpa, &.{ cwd, sub_path }); |
| 1585 | defer gpa.free(filename); | 1573 | defer gpa.free(filename); |
| 1586 | | 1574 | |
| | 1575 | defer fs.deleteFileAbsolute(filename) catch {}; // createFileAbsolute can leave files on failures |
| 1587 | const file1 = try fs.createFileAbsolute(filename, .{ | 1576 | const file1 = try fs.createFileAbsolute(filename, .{ |
| 1588 | .lock = .exclusive, | 1577 | .lock = .exclusive, |
| 1589 | .lock_nonblocking = true, | 1578 | .lock_nonblocking = true, |
| 1590 | }); | 1579 | }); |
| 1591 | defer fs.deleteFileAbsolute(filename) catch {}; | | |
| 1592 | | 1580 | |
| 1593 | const file2 = fs.createFileAbsolute(filename, .{ | 1581 | const file2 = fs.createFileAbsolute(filename, .{ |
| 1594 | .lock = .exclusive, | 1582 | .lock = .exclusive, |
| ... | @@ -1674,7 +1662,7 @@ test "walker without fully iterating" { | ... | @@ -1674,7 +1662,7 @@ test "walker without fully iterating" { |
| 1674 | try testing.expectEqual(@as(usize, 1), num_walked); | 1662 | try testing.expectEqual(@as(usize, 1), num_walked); |
| 1675 | } | 1663 | } |
| 1676 | | 1664 | |
| 1677 | test ". and .. in fs.Dir functions" { | 1665 | test "'.' and '..' in fs.Dir functions" { |
| 1678 | if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest; | 1666 | if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest; |
| 1679 | | 1667 | |
| 1680 | if (builtin.os.tag == .windows and builtin.cpu.arch == .aarch64) { | 1668 | if (builtin.os.tag == .windows and builtin.cpu.arch == .aarch64) { |
| ... | @@ -1714,7 +1702,7 @@ test ". and .. in fs.Dir functions" { | ... | @@ -1714,7 +1702,7 @@ test ". and .. in fs.Dir functions" { |
| 1714 | }.impl); | 1702 | }.impl); |
| 1715 | } | 1703 | } |
| 1716 | | 1704 | |
| 1717 | test ". and .. in absolute functions" { | 1705 | test "'.' and '..' in absolute functions" { |
| 1718 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | 1706 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 1719 | | 1707 | |
| 1720 | var tmp = tmpDir(.{}); | 1708 | var tmp = tmpDir(.{}); |