Skip to content

Commit 22b9d89

Browse files
authored
Merge pull request #17499 from rootbeer/makepath-dangling-symlink
std.fs: tests for makePath and symlinks
2 parents 14efbbf + ff56138 commit 22b9d89

File tree

1 file changed

+67
-79
lines changed

1 file changed

+67
-79
lines changed

lib/std/fs/test.zig

Lines changed: 67 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const ArenaAllocator = std.heap.ArenaAllocator;
1010
const Dir = std.fs.Dir;
1111
const File = std.fs.File;
1212
const tmpDir = testing.tmpDir;
13+
const SymLinkFlags = std.fs.Dir.SymLinkFlags;
1314

1415
const 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).
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+
122142
test "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

153169
fn 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+
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..]);
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

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-
258259
test "openDir" {
259260
try testWithAllSupportedPathTypes(struct {
260261
fn impl(ctx: *TestContext) !void {
@@ -317,14 +318,14 @@ test "openDirAbsolute" {
317318
}
318319
}
319320

320-
test "openDir cwd parent .." {
321+
test "openDir cwd parent '..'" {
321322
if (builtin.os.tag == .wasi) return error.SkipZigTest;
322323

323324
var dir = try fs.cwd().openDir("..", .{});
324325
defer dir.close();
325326
}
326327

327-
test "openDir non-cwd parent .." {
328+
test "openDir non-cwd parent '..'" {
328329
switch (builtin.os.tag) {
329330
.wasi, .netbsd, .openbsd => return error.SkipZigTest,
330331
else => {},
@@ -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

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-
403390
test "Dir.Iterator" {
404391
var tmp_dir = tmpDir(.{ .iterate = true });
405392
defer tmp_dir.cleanup();
@@ -674,6 +661,19 @@ test "Dir.statFile" {
674661
}.impl);
675662
}
676663

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+
677677
test "directory operations on files" {
678678
try testWithAllSupportedPathTypes(struct {
679679
fn impl(ctx: *TestContext) !void {
@@ -1005,11 +1005,7 @@ test "deleteTree does not follow symlinks" {
10051005
var a = try tmp.dir.makeOpenPath("a", .{});
10061006
defer a.close();
10071007

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-
};
1008+
try setupSymlink(a, "../b", "b", .{ .is_directory = true });
10131009
}
10141010

10151011
try tmp.dir.deleteTree("a");
@@ -1024,23 +1020,15 @@ test "deleteTree on a symlink" {
10241020

10251021
// Symlink to a file
10261022
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-
};
1023+
try setupSymlink(tmp.dir, "file", "filelink", .{});
10321024

10331025
try tmp.dir.deleteTree("filelink");
10341026
try testing.expectError(error.FileNotFound, tmp.dir.access("filelink", .{}));
10351027
try tmp.dir.access("file", .{});
10361028

10371029
// Symlink to a directory
10381030
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-
};
1031+
try setupSymlink(tmp.dir, "dir", "dirlink", .{ .is_directory = true });
10441032

10451033
try tmp.dir.deleteTree("dirlink");
10461034
try testing.expectError(error.FileNotFound, tmp.dir.access("dirlink", .{}));
@@ -1123,7 +1111,7 @@ test "makepath through existing valid symlink" {
11231111
defer tmp.cleanup();
11241112

11251113
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

11281116
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)" {
15841572
const filename = try fs.path.resolve(gpa, &.{ cwd, sub_path });
15851573
defer gpa.free(filename);
15861574

1575+
defer fs.deleteFileAbsolute(filename) catch {}; // createFileAbsolute can leave files on failures
15871576
const file1 = try fs.createFileAbsolute(filename, .{
15881577
.lock = .exclusive,
15891578
.lock_nonblocking = true,
15901579
});
1591-
defer fs.deleteFileAbsolute(filename) catch {};
15921580

15931581
const file2 = fs.createFileAbsolute(filename, .{
15941582
.lock = .exclusive,
@@ -1674,7 +1662,7 @@ test "walker without fully iterating" {
16741662
try testing.expectEqual(@as(usize, 1), num_walked);
16751663
}
16761664

1677-
test ". and .. in fs.Dir functions" {
1665+
test "'.' and '..' in fs.Dir functions" {
16781666
if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
16791667

16801668
if (builtin.os.tag == .windows and builtin.cpu.arch == .aarch64) {
@@ -1714,7 +1702,7 @@ test ". and .. in fs.Dir functions" {
17141702
}.impl);
17151703
}
17161704

1717-
test ". and .. in absolute functions" {
1705+
test "'.' and '..' in absolute functions" {
17181706
if (builtin.os.tag == .wasi) return error.SkipZigTest;
17191707

17201708
var tmp = tmpDir(.{});

0 commit comments

Comments
 (0)