| author | |
| committer | |
| log | 6f98ef09e31768e3356598ef30e60fe028a0e70c |
| tree | 7da2236639e2c739b7419d329cd05e5b1d491ad1 |
| parent | d2a4e5e226f2041e4a21e9353e1323b84017b3b1 |
| parent | 74c245aea94ebcf05651a6f3420d8c3a7145f9d7 |
| signature |
Add tests for using file operations on directories2 files changed, 60 insertions(+), 4 deletions(-)
lib/std/fs.zig+23-4| ... | ... | @@ -1111,10 +1111,18 @@ pub const Dir = struct { |
| 1111 | 1111 | /// Delete a file name and possibly the file it refers to, based on an open directory handle. |
| 1112 | 1112 | /// Asserts that the path parameter has no null bytes. |
| 1113 | 1113 | pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void { |
| 1114 | os.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) { | |
| 1115 | error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR | |
| 1116 | else => |e| return e, | |
| 1117 | }; | |
| 1114 | if (builtin.os.tag == .windows) { | |
| 1115 | const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); | |
| 1116 | return self.deleteFileW(sub_path_w.span().ptr); | |
| 1117 | } else if (builtin.os.tag == .wasi) { | |
| 1118 | os.unlinkatWasi(self.fd, sub_path, 0) catch |err| switch (err) { | |
| 1119 | error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR | |
| 1120 | else => |e| return e, | |
| 1121 | }; | |
| 1122 | } else { | |
| 1123 | const sub_path_c = try os.toPosixPath(sub_path); | |
| 1124 | return self.deleteFileZ(&sub_path_c); | |
| 1125 | } | |
| 1118 | 1126 | } |
| 1119 | 1127 | |
| 1120 | 1128 | pub const deleteFileC = @compileError("deprecated: renamed to deleteFileZ"); |
| ... | ... | @@ -1123,6 +1131,17 @@ pub const Dir = struct { |
| 1123 | 1131 | pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void { |
| 1124 | 1132 | os.unlinkatZ(self.fd, sub_path_c, 0) catch |err| switch (err) { |
| 1125 | 1133 | error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR |
| 1134 | error.AccessDenied => |e| switch (builtin.os.tag) { | |
| 1135 | // non-Linux POSIX systems return EPERM when trying to delete a directory, so | |
| 1136 | // we need to handle that case specifically and translate the error | |
| 1137 | .macosx, .ios, .freebsd, .netbsd, .dragonfly => { | |
| 1138 | // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them) | |
| 1139 | const fstat = os.fstatatZ(self.fd, sub_path_c, os.AT_SYMLINK_NOFOLLOW) catch return e; | |
| 1140 | const is_dir = fstat.mode & os.S_IFMT == os.S_IFDIR; | |
| 1141 | return if (is_dir) error.IsDir else e; | |
| 1142 | }, | |
| 1143 | else => return e, | |
| 1144 | }, | |
| 1126 | 1145 | else => |e| return e, |
| 1127 | 1146 | }; |
| 1128 | 1147 | } |
lib/std/fs/test.zig+37| ... | ... | @@ -73,6 +73,43 @@ test "directory operations on files" { |
| 73 | 73 | file.close(); |
| 74 | 74 | } |
| 75 | 75 | |
| 76 | test "file operations on directories" { | |
| 77 | var tmp_dir = tmpDir(.{}); | |
| 78 | defer tmp_dir.cleanup(); | |
| 79 | ||
| 80 | const test_dir_name = "test_dir"; | |
| 81 | ||
| 82 | try tmp_dir.dir.makeDir(test_dir_name); | |
| 83 | ||
| 84 | testing.expectError(error.IsDir, tmp_dir.dir.createFile(test_dir_name, .{})); | |
| 85 | testing.expectError(error.IsDir, tmp_dir.dir.deleteFile(test_dir_name)); | |
| 86 | // Currently, WASI will return error.Unexpected (via ENOTCAPABLE) when attempting fd_read on a directory handle. | |
| 87 | // TODO: Re-enable on WASI once https://github.com/bytecodealliance/wasmtime/issues/1935 is resolved. | |
| 88 | if (builtin.os.tag != .wasi) { | |
| 89 | testing.expectError(error.IsDir, tmp_dir.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize))); | |
| 90 | } | |
| 91 | // Note: The `.write = true` is necessary to ensure the error occurs on all platforms. | |
| 92 | // TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732 | |
| 93 | testing.expectError(error.IsDir, tmp_dir.dir.openFile(test_dir_name, .{ .write = true })); | |
| 94 | ||
| 95 | if (builtin.os.tag != .wasi) { | |
| 96 | // TODO: use Dir's realpath function once that exists | |
| 97 | const absolute_path = blk: { | |
| 98 | const relative_path = try fs.path.join(testing.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp_dir.sub_path[0..], test_dir_name }); | |
| 99 | defer testing.allocator.free(relative_path); | |
| 100 | break :blk try fs.realpathAlloc(testing.allocator, relative_path); | |
| 101 | }; | |
| 102 | defer testing.allocator.free(absolute_path); | |
| 103 | ||
| 104 | testing.expectError(error.IsDir, fs.createFileAbsolute(absolute_path, .{})); | |
| 105 | testing.expectError(error.IsDir, fs.deleteFileAbsolute(absolute_path)); | |
| 106 | } | |
| 107 | ||
| 108 | // ensure the directory still exists as a sanity check | |
| 109 | var dir = try tmp_dir.dir.openDir(test_dir_name, .{}); | |
| 110 | dir.close(); | |
| 111 | } | |
| 112 | ||
| 76 | 113 | test "openSelfExe" { |
| 77 | 114 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 78 | 115 |