authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-01-18 15:37:16-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-01-18 15:37:16-08:00
log22b9d8987dd667842de0b6848043a2bd53864569
tree9f89cf3d86ab0839d2265d4a10b15e9f11098586
parent14efbbfd89c7e034436faa87a201a35324b9dff3
parentff5613873f4492e25c5779183284a29e7be2f551
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #17499 from rootbeer/makepath-dangling-symlink

std.fs: tests for makePath and symlinks

1 files changed, 67 insertions(+), 79 deletions(-)

lib/std/fs/test.zig+67-79
...@@ -10,6 +10,7 @@ const ArenaAllocator = std.heap.ArenaAllocator;...@@ -10,6 +10,7 @@ const ArenaAllocator = std.heap.ArenaAllocator;
10const Dir = std.fs.Dir;10const Dir = std.fs.Dir;
11const File = std.fs.File;11const File = std.fs.File;
12const tmpDir = testing.tmpDir;12const tmpDir = testing.tmpDir;
13const SymLinkFlags = std.fs.Dir.SymLinkFlags;
1314
14const PathType = enum {15const 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}
121122
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).
125fn 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).
135fn 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
122test "Dir.readLink" {142test "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);
130150
131 {151 // test 1: symlink to a file
132 // Create symbolic link by path152 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 path161 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}
152168
153fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {169fn 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
175fn 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}
158180
...@@ -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);
171193
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 };
177195
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}
240258
241test "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
258test "openDir" {259test "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}
319320
320test "openDir cwd parent .." {321test "openDir cwd parent '..'" {
321 if (builtin.os.tag == .wasi) return error.SkipZigTest;322 if (builtin.os.tag == .wasi) return error.SkipZigTest;
322323
323 var dir = try fs.cwd().openDir("..", .{});324 var dir = try fs.cwd().openDir("..", .{});
324 defer dir.close();325 defer dir.close();
325}326}
326327
327test "openDir non-cwd parent .." {328test "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" });
374375
375 // Create symbolic link by path376 // 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" });
386383
387 // Create symbolic link by path384 // 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}
396389
397fn 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
403test "Dir.Iterator" {390test "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}
676663
664test "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
677test "directory operations on files" {677test "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();
10071007
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 }
10141010
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" {
10241020
1025 // Symlink to a file1021 // 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 };
10321024
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" {
10361028
1037 // Symlink to a directory1029 // 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 };
10441032
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();
11241112
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", .{});
11271115
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");
11291117
...@@ -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);
15861574
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 {};
15921580
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}
16761664
1677test ". and .. in fs.Dir functions" {1665test "'.' 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;
16791667
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}
17161704
1717test ". and .. in absolute functions" {1705test "'.' and '..' in absolute functions" {
1718 if (builtin.os.tag == .wasi) return error.SkipZigTest;1706 if (builtin.os.tag == .wasi) return error.SkipZigTest;
17191707
1720 var tmp = tmpDir(.{});1708 var tmp = tmpDir(.{});