authorgravatar for pat.github@tullmann.orgPat Tullmann <pat.github@tullmann.org> 2023-10-23 15:00:17-07:00
committergravatar for pat.github@tullmann.orgPat Tullmann <pat.github@tullmann.org> 2024-01-16 14:19:48-08:00
logc36962bb197abf66e2eb94068dc2a53b098fff9d
treef846328b69fc84059424a72924d82525be5b257b
parent95a0e127b3f9528d25e0aefefc238d082a57bdc3

std/fs/test.zig: Factor out the symlink-creation wrappers

Because creation of a symlink can fail on Windows with an Access Denied error (https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-symbolic-links) any tests that need a symbolic link "skip" if they run into this problem. This change factors out a "setupSymbolicLink()" routine to make this clearer, a bit tighter, and easier to use in future tests. I also collapsed the "symlink in parent directory" test into the existing "Dir.readlink" test, because the latter uses the more comprehensive testWithAllSupportedPathTypes wrapper.

1 files changed, 49 insertions(+), 74 deletions(-)

lib/std/fs/test.zig+49-74
......@@ -10,6 +10,7 @@ const ArenaAllocator = std.heap.ArenaAllocator;
1010const Dir = std.fs.Dir;
1111const File = std.fs.File;
1212const tmpDir = testing.tmpDir;
13const SymLinkFlags = std.fs.Dir.SymLinkFlags;
1314
1415const PathType = enum {
1516 relative,
......@@ -119,6 +120,25 @@ fn testWithAllSupportedPathTypes(test_func: anytype) !void {
119120 }
120121}
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
122142test "Dir.readLink" {
123143 try testWithAllSupportedPathTypes(struct {
124144 fn impl(ctx: *TestContext) !void {
......@@ -128,31 +148,33 @@ test "Dir.readLink" {
128148 const dir_target_path = try ctx.transformPath("subdir");
129149 try ctx.dir.makeDir(dir_target_path);
130150
131 {
132 // Create symbolic link by path
133 ctx.dir.symLink(file_target_path, "symlink1", .{}) catch |err| switch (err) {
134 // Symlink requires admin privileges on windows, so this test can legitimately fail.
135 error.AccessDenied => return error.SkipZigTest,
136 else => return err,
137 };
138 try testReadLink(ctx.dir, file_target_path, "symlink1");
139 }
140 {
141 // Create symbolic link by path
142 ctx.dir.symLink(dir_target_path, "symlink2", .{ .is_directory = true }) catch |err| switch (err) {
143 // Symlink requires admin privileges on windows, so this test can legitimately fail.
144 error.AccessDenied => return error.SkipZigTest,
145 else => return err,
146 };
147 try testReadLink(ctx.dir, dir_target_path, "symlink2");
148 }
151 // test 1: symlink to a file
152 try setupSymlink(ctx.dir, file_target_path, "symlink1", .{});
153 try testReadLink(ctx.dir, file_target_path, "symlink1");
154
155 // test 2: symlink to a directory (can be different on Windows)
156 try setupSymlink(ctx.dir, dir_target_path, "symlink2", .{ .is_directory = true });
157 try testReadLink(ctx.dir, dir_target_path, "symlink2");
158
159 // test 3: relative path symlink
160 const parent_file = ".." ++ fs.path.sep_str ++ "target.txt";
161 var subdir = try ctx.dir.makeOpenPath("subdir", .{});
162 defer subdir.close();
163 try setupSymlink(subdir, parent_file, "relative-link.txt", .{});
164 try testReadLink(subdir, parent_file, "relative-link.txt");
149165 }
150166 }.impl);
151167}
152168
153169fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {
154170 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..]);
156178 try testing.expectEqualStrings(target_path, given);
157179}
158180
......@@ -169,11 +191,7 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" {
169191 const dir_target_path = try ctx.transformPath("subdir");
170192 try ctx.dir.makeDir(dir_target_path);
171193
172 ctx.dir.symLink(dir_target_path, "symlink", .{ .is_directory = true }) catch |err| switch (err) {
173 // Symlink requires admin privileges on windows, so this test can legitimately fail.
174 error.AccessDenied => return error.SkipZigTest,
175 else => return err,
176 };
194 try setupSymlink(ctx.dir, dir_target_path, "symlink", .{ .is_directory = true });
177195
178196 var symlink = switch (builtin.target.os.tag) {
179197 .windows => windows_symlink: {
......@@ -238,23 +256,6 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" {
238256 }.impl);
239257}
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
258259test "openDir" {
259260 try testWithAllSupportedPathTypes(struct {
260261 fn impl(ctx: *TestContext) !void {
......@@ -373,33 +374,19 @@ test "readLinkAbsolute" {
373374 const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink1" });
374375
375376 // Create symbolic link by path
376 fs.symLinkAbsolute(target_path, symlink_path, .{}) catch |err| switch (err) {
377 // Symlink requires admin privileges on windows, so this test can legitimately fail.
378 error.AccessDenied => return error.SkipZigTest,
379 else => return err,
380 };
377 try setupSymlinkAbsolute(target_path, symlink_path, .{});
381378 try testReadLinkAbsolute(target_path, symlink_path);
382379 }
383380 {
384381 const target_path = try fs.path.join(allocator, &.{ base_path, "subdir" });
385382 const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink2" });
386383
387 // Create symbolic link by path
388 fs.symLinkAbsolute(target_path, symlink_path, .{ .is_directory = true }) catch |err| switch (err) {
389 // Symlink requires admin privileges on windows, so this test can legitimately fail.
390 error.AccessDenied => return error.SkipZigTest,
391 else => return err,
392 };
384 // Create symbolic link to a directory by path
385 try setupSymlinkAbsolute(target_path, symlink_path, .{ .is_directory = true });
393386 try testReadLinkAbsolute(target_path, symlink_path);
394387 }
395388}
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
403390test "Dir.Iterator" {
404391 var tmp_dir = tmpDir(.{ .iterate = true });
405392 defer tmp_dir.cleanup();
......@@ -1005,11 +992,7 @@ test "deleteTree does not follow symlinks" {
1005992 var a = try tmp.dir.makeOpenPath("a", .{});
1006993 defer a.close();
1007994
1008 a.symLink("../b", "b", .{ .is_directory = true }) catch |err| switch (err) {
1009 // Symlink requires admin privileges on windows, so this test can legitimately fail.
1010 error.AccessDenied => return error.SkipZigTest,
1011 else => return err,
1012 };
995 try setupSymlink(a, "../b", "b", .{ .is_directory = true });
1013996 }
1014997
1015998 try tmp.dir.deleteTree("a");
......@@ -1024,11 +1007,7 @@ test "deleteTree on a symlink" {
10241007
10251008 // Symlink to a file
10261009 try tmp.dir.writeFile("file", "");
1027 tmp.dir.symLink("file", "filelink", .{}) catch |err| switch (err) {
1028 // Symlink requires admin privileges on windows, so this test can legitimately fail.
1029 error.AccessDenied => return error.SkipZigTest,
1030 else => return err,
1031 };
1010 try setupSymlink(tmp.dir, "file", "filelink", .{});
10321011
10331012 try tmp.dir.deleteTree("filelink");
10341013 try testing.expectError(error.FileNotFound, tmp.dir.access("filelink", .{}));
......@@ -1036,11 +1015,7 @@ test "deleteTree on a symlink" {
10361015
10371016 // Symlink to a directory
10381017 try tmp.dir.makePath("dir");
1039 tmp.dir.symLink("dir", "dirlink", .{ .is_directory = true }) catch |err| switch (err) {
1040 // Symlink requires admin privileges on windows, so this test can legitimately fail.
1041 error.AccessDenied => return error.SkipZigTest,
1042 else => return err,
1043 };
1018 try setupSymlink(tmp.dir, "dir", "dirlink", .{ .is_directory = true });
10441019
10451020 try tmp.dir.deleteTree("dirlink");
10461021 try testing.expectError(error.FileNotFound, tmp.dir.access("dirlink", .{}));
......@@ -1123,7 +1098,7 @@ test "makepath through existing valid symlink" {
11231098 defer tmp.cleanup();
11241099
11251100 try tmp.dir.makeDir("realfolder");
1126 try tmp.dir.symLink("." ++ fs.path.sep_str ++ "realfolder", "working-symlink", .{});
1101 try setupSymlink(tmp.dir, "." ++ fs.path.sep_str ++ "realfolder", "working-symlink", .{});
11271102
11281103 try tmp.dir.makePath("working-symlink" ++ fs.path.sep_str ++ "in-realfolder");
11291104