@@ -10,6 +10,7 @@ const ArenaAllocator = std.heap.ArenaAllocator;
10
10
const Dir = std .fs .Dir ;
11
11
const File = std .fs .File ;
12
12
const tmpDir = testing .tmpDir ;
13
+ const SymLinkFlags = std .fs .Dir .SymLinkFlags ;
13
14
14
15
const PathType = enum {
15
16
relative ,
@@ -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
142
test "Dir.readLink" {
123
143
try testWithAllSupportedPathTypes (struct {
124
144
fn impl (ctx : * TestContext ) ! void {
@@ -128,31 +148,33 @@ test "Dir.readLink" {
128
148
const dir_target_path = try ctx .transformPath ("subdir" );
129
149
try ctx .dir .makeDir (dir_target_path );
130
150
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" );
149
165
}
150
166
}.impl );
151
167
}
152
168
153
169
fn testReadLink (dir : Dir , target_path : []const u8 , symlink_path : []const u8 ) ! void {
154
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
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
191
const dir_target_path = try ctx .transformPath ("subdir" );
170
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 ) {
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 });
177
195
178
196
var symlink = switch (builtin .target .os .tag ) {
179
197
.windows = > windows_symlink : {
@@ -238,23 +256,6 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" {
238
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
259
test "openDir" {
259
260
try testWithAllSupportedPathTypes (struct {
260
261
fn impl (ctx : * TestContext ) ! void {
@@ -317,14 +318,14 @@ test "openDirAbsolute" {
317
318
}
318
319
}
319
320
320
- test "openDir cwd parent .. " {
321
+ test "openDir cwd parent '..' " {
321
322
if (builtin .os .tag == .wasi ) return error .SkipZigTest ;
322
323
323
324
var dir = try fs .cwd ().openDir (".." , .{});
324
325
defer dir .close ();
325
326
}
326
327
327
- test "openDir non-cwd parent .. " {
328
+ test "openDir non-cwd parent '..' " {
328
329
switch (builtin .os .tag ) {
329
330
.wasi , .netbsd , .openbsd = > return error .SkipZigTest ,
330
331
else = > {},
@@ -373,33 +374,19 @@ test "readLinkAbsolute" {
373
374
const symlink_path = try fs .path .join (allocator , &.{ base_path , "symlink1" });
374
375
375
376
// 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 , .{});
381
378
try testReadLinkAbsolute (target_path , symlink_path );
382
379
}
383
380
{
384
381
const target_path = try fs .path .join (allocator , &.{ base_path , "subdir" });
385
382
const symlink_path = try fs .path .join (allocator , &.{ base_path , "symlink2" });
386
383
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 });
393
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
390
test "Dir.Iterator" {
404
391
var tmp_dir = tmpDir (.{ .iterate = true });
405
392
defer tmp_dir .cleanup ();
@@ -674,6 +661,19 @@ test "Dir.statFile" {
674
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
677
test "directory operations on files" {
678
678
try testWithAllSupportedPathTypes (struct {
679
679
fn impl (ctx : * TestContext ) ! void {
@@ -1005,11 +1005,7 @@ test "deleteTree does not follow symlinks" {
1005
1005
var a = try tmp .dir .makeOpenPath ("a" , .{});
1006
1006
defer a .close ();
1007
1007
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 });
1013
1009
}
1014
1010
1015
1011
try tmp .dir .deleteTree ("a" );
@@ -1024,23 +1020,15 @@ test "deleteTree on a symlink" {
1024
1020
1025
1021
// Symlink to a file
1026
1022
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" , .{});
1032
1024
1033
1025
try tmp .dir .deleteTree ("filelink" );
1034
1026
try testing .expectError (error .FileNotFound , tmp .dir .access ("filelink" , .{}));
1035
1027
try tmp .dir .access ("file" , .{});
1036
1028
1037
1029
// Symlink to a directory
1038
1030
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 });
1044
1032
1045
1033
try tmp .dir .deleteTree ("dirlink" );
1046
1034
try testing .expectError (error .FileNotFound , tmp .dir .access ("dirlink" , .{}));
@@ -1123,7 +1111,7 @@ test "makepath through existing valid symlink" {
1123
1111
defer tmp .cleanup ();
1124
1112
1125
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
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
1572
const filename = try fs .path .resolve (gpa , &.{ cwd , sub_path });
1585
1573
defer gpa .free (filename );
1586
1574
1575
+ defer fs .deleteFileAbsolute (filename ) catch {}; // createFileAbsolute can leave files on failures
1587
1576
const file1 = try fs .createFileAbsolute (filename , .{
1588
1577
.lock = .exclusive ,
1589
1578
.lock_nonblocking = true ,
1590
1579
});
1591
- defer fs .deleteFileAbsolute (filename ) catch {};
1592
1580
1593
1581
const file2 = fs .createFileAbsolute (filename , .{
1594
1582
.lock = .exclusive ,
@@ -1674,7 +1662,7 @@ test "walker without fully iterating" {
1674
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
1666
if (builtin .os .tag == .wasi and builtin .link_libc ) return error .SkipZigTest ;
1679
1667
1680
1668
if (builtin .os .tag == .windows and builtin .cpu .arch == .aarch64 ) {
@@ -1714,7 +1702,7 @@ test ". and .. in fs.Dir functions" {
1714
1702
}.impl );
1715
1703
}
1716
1704
1717
- test ". and .. in absolute functions" {
1705
+ test "'.' and '..' in absolute functions" {
1718
1706
if (builtin .os .tag == .wasi ) return error .SkipZigTest ;
1719
1707
1720
1708
var tmp = tmpDir (.{});
0 commit comments