authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-01 23:20:50+00:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-07-01 23:20:50+00:00
log6f98ef09e31768e3356598ef30e60fe028a0e70c
tree7da2236639e2c739b7419d329cd05e5b1d491ad1
parentd2a4e5e226f2041e4a21e9353e1323b84017b3b1
parent74c245aea94ebcf05651a6f3420d8c3a7145f9d7
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #5717 from squeek502/fs-dir-file-ops

Add tests for using file operations on directories

2 files changed, 60 insertions(+), 4 deletions(-)

lib/std/fs.zig+23-4
...@@ -1111,10 +1111,18 @@ pub const Dir = struct {...@@ -1111,10 +1111,18 @@ pub const Dir = struct {
1111 /// Delete a file name and possibly the file it refers to, based on an open directory handle.1111 /// Delete a file name and possibly the file it refers to, based on an open directory handle.
1112 /// Asserts that the path parameter has no null bytes.1112 /// Asserts that the path parameter has no null bytes.
1113 pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {1113 pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
1114 os.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) {1114 if (builtin.os.tag == .windows) {
1115 error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR1115 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
1116 else => |e| return e,1116 return self.deleteFileW(sub_path_w.span().ptr);
1117 };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 }
11191127
1120 pub const deleteFileC = @compileError("deprecated: renamed to deleteFileZ");1128 pub const deleteFileC = @compileError("deprecated: renamed to deleteFileZ");
...@@ -1123,6 +1131,17 @@ pub const Dir = struct {...@@ -1123,6 +1131,17 @@ pub const Dir = struct {
1123 pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void {1131 pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void {
1124 os.unlinkatZ(self.fd, sub_path_c, 0) catch |err| switch (err) {1132 os.unlinkatZ(self.fd, sub_path_c, 0) catch |err| switch (err) {
1125 error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR1133 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 else => |e| return e,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,6 +73,43 @@ test "directory operations on files" {
73 file.close();73 file.close();
74}74}
7575
76test "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
76test "openSelfExe" {113test "openSelfExe" {
77 if (builtin.os.tag == .wasi) return error.SkipZigTest;114 if (builtin.os.tag == .wasi) return error.SkipZigTest;
78115