2020-04-12 05:50:38 +08:00
|
|
|
const std = @import("../std.zig");
|
2021-10-05 14:47:27 +08:00
|
|
|
const builtin = @import("builtin");
|
2020-06-21 21:18:50 +08:00
|
|
|
const testing = std.testing;
|
stdlib: Add emulated CWD to std.os for WASI targets
This adds a special CWD file descriptor, AT.FDCWD (-2), to refer to the
current working directory. The `*at(...)` functions look for this and
resolve relative paths against the stored CWD. Absolute paths are
dynamically matched against the stored Preopens.
"os.initPreopensWasi()" must be called before std.os functions will
resolve relative or absolute paths correctly. This is asserted at
runtime.
Support has been added for: `open`, `rename`, `mkdir`, `rmdir`, `chdir`,
`fchdir`, `link`, `symlink`, `unlink`, `readlink`, `fstatat`, `access`,
and `faccessat`.
This also includes limited support for `getcwd()` and `realpath()`.
These return an error if the CWD does not correspond to a Preopen with
an absolute path. They also do not currently expand symlinks.
2022-03-02 01:17:05 +08:00
|
|
|
const os = std.os;
|
2020-04-12 05:50:38 +08:00
|
|
|
const fs = std.fs;
|
2020-06-21 21:18:50 +08:00
|
|
|
const mem = std.mem;
|
2020-06-30 23:43:00 +08:00
|
|
|
const wasi = std.os.wasi;
|
2020-06-21 21:18:50 +08:00
|
|
|
|
2020-07-01 20:15:58 +08:00
|
|
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
2020-06-30 23:43:00 +08:00
|
|
|
const Dir = std.fs.Dir;
|
2020-04-12 05:50:38 +08:00
|
|
|
const File = std.fs.File;
|
2020-06-21 21:18:50 +08:00
|
|
|
const tmpDir = testing.tmpDir;
|
2023-10-24 06:00:17 +08:00
|
|
|
const SymLinkFlags = std.fs.Dir.SymLinkFlags;
|
2020-06-21 21:18:50 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
const PathType = enum {
|
|
|
|
relative,
|
|
|
|
absolute,
|
|
|
|
unc,
|
|
|
|
|
|
|
|
pub fn isSupported(self: PathType, target_os: std.Target.Os) bool {
|
|
|
|
return switch (self) {
|
|
|
|
.relative => true,
|
|
|
|
.absolute => std.os.isGetFdPathSupportedOnTarget(target_os),
|
|
|
|
.unc => target_os.tag == .windows,
|
|
|
|
};
|
|
|
|
}
|
2020-07-22 02:40:34 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
pub const TransformError = std.os.RealPathError || error{OutOfMemory};
|
|
|
|
pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8;
|
|
|
|
|
|
|
|
pub fn getTransformFn(comptime path_type: PathType) TransformFn {
|
|
|
|
switch (path_type) {
|
|
|
|
.relative => return struct {
|
|
|
|
fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
|
|
|
|
_ = allocator;
|
|
|
|
_ = dir;
|
|
|
|
return relative_path;
|
|
|
|
}
|
|
|
|
}.transform,
|
|
|
|
.absolute => return struct {
|
|
|
|
fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
|
|
|
|
// The final path may not actually exist which would cause realpath to fail.
|
|
|
|
// So instead, we get the path of the dir and join it with the relative path.
|
|
|
|
var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
|
|
|
|
const dir_path = try os.getFdPath(dir.fd, &fd_path_buf);
|
|
|
|
return fs.path.join(allocator, &.{ dir_path, relative_path });
|
|
|
|
}
|
|
|
|
}.transform,
|
|
|
|
.unc => return struct {
|
|
|
|
fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
|
|
|
|
// Any drive absolute path (C:\foo) can be converted into a UNC path by
|
2023-08-18 15:26:29 +08:00
|
|
|
// using '127.0.0.1' as the server name and '<drive letter>$' as the share name.
|
2023-08-17 15:58:44 +08:00
|
|
|
var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
|
|
|
|
const dir_path = try os.getFdPath(dir.fd, &fd_path_buf);
|
|
|
|
const windows_path_type = std.os.windows.getUnprefixedPathType(u8, dir_path);
|
|
|
|
switch (windows_path_type) {
|
|
|
|
.unc_absolute => return fs.path.join(allocator, &.{ dir_path, relative_path }),
|
|
|
|
.drive_absolute => {
|
2023-08-18 15:26:29 +08:00
|
|
|
// `C:\<...>` -> `\\127.0.0.1\C$\<...>`
|
|
|
|
const prepended = "\\\\127.0.0.1\\";
|
2023-08-17 15:58:44 +08:00
|
|
|
var path = try fs.path.join(allocator, &.{ prepended, dir_path, relative_path });
|
|
|
|
path[prepended.len + 1] = '$';
|
|
|
|
return path;
|
|
|
|
},
|
|
|
|
else => unreachable,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}.transform,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const TestContext = struct {
|
|
|
|
path_type: PathType,
|
|
|
|
arena: ArenaAllocator,
|
2023-11-23 04:12:22 +08:00
|
|
|
tmp: testing.TmpDir,
|
2023-08-17 15:58:44 +08:00
|
|
|
dir: std.fs.Dir,
|
|
|
|
transform_fn: *const PathType.TransformFn,
|
|
|
|
|
|
|
|
pub fn init(path_type: PathType, allocator: mem.Allocator, transform_fn: *const PathType.TransformFn) TestContext {
|
2023-11-23 03:35:33 +08:00
|
|
|
const tmp = tmpDir(.{ .iterate = true });
|
2023-08-17 15:58:44 +08:00
|
|
|
return .{
|
|
|
|
.path_type = path_type,
|
|
|
|
.arena = ArenaAllocator.init(allocator),
|
|
|
|
.tmp = tmp,
|
2023-11-23 04:12:22 +08:00
|
|
|
.dir = tmp.dir,
|
2023-08-17 15:58:44 +08:00
|
|
|
.transform_fn = transform_fn,
|
2020-08-28 02:55:47 +08:00
|
|
|
};
|
2020-07-22 02:40:34 +08:00
|
|
|
}
|
2023-08-17 15:58:44 +08:00
|
|
|
|
|
|
|
pub fn deinit(self: *TestContext) void {
|
|
|
|
self.arena.deinit();
|
|
|
|
self.tmp.cleanup();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the `relative_path` transformed into the TestContext's `path_type`.
|
|
|
|
/// The result is allocated by the TestContext's arena and will be free'd during
|
|
|
|
/// `TestContext.deinit`.
|
|
|
|
pub fn transformPath(self: *TestContext, relative_path: []const u8) ![]const u8 {
|
|
|
|
return self.transform_fn(self.arena.allocator(), self.dir, relative_path);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/// `test_func` must be a function that takes a `*TestContext` as a parameter and returns `!void`.
|
|
|
|
/// `test_func` will be called once for each PathType that the current target supports,
|
|
|
|
/// and will be passed a TestContext that can transform a relative path into the path type under test.
|
|
|
|
/// The TestContext will also create a tmp directory for you (and will clean it up for you too).
|
|
|
|
fn testWithAllSupportedPathTypes(test_func: anytype) !void {
|
2024-02-03 03:52:42 +08:00
|
|
|
try testWithPathTypeIfSupported(.relative, test_func);
|
|
|
|
try testWithPathTypeIfSupported(.absolute, test_func);
|
|
|
|
try testWithPathTypeIfSupported(.unc, test_func);
|
|
|
|
}
|
2023-08-17 15:58:44 +08:00
|
|
|
|
2024-02-03 03:52:42 +08:00
|
|
|
fn testWithPathTypeIfSupported(comptime path_type: PathType, test_func: anytype) !void {
|
|
|
|
if (!(comptime path_type.isSupported(builtin.os))) return;
|
2023-08-17 15:58:44 +08:00
|
|
|
|
2024-02-03 03:52:42 +08:00
|
|
|
var ctx = TestContext.init(path_type, testing.allocator, path_type.getTransformFn());
|
|
|
|
defer ctx.deinit();
|
|
|
|
|
|
|
|
try test_func(&ctx);
|
2020-07-22 02:40:34 +08:00
|
|
|
}
|
|
|
|
|
2023-10-24 06:00:17 +08:00
|
|
|
// For use in test setup. If the symlink creation fails on Windows with
|
|
|
|
// AccessDenied, then make the test failure silent (it is not a Zig failure).
|
|
|
|
fn setupSymlink(dir: Dir, target: []const u8, link: []const u8, flags: SymLinkFlags) !void {
|
|
|
|
return dir.symLink(target, link, flags) catch |err| switch (err) {
|
|
|
|
// Symlink requires admin privileges on windows, so this test can legitimately fail.
|
|
|
|
error.AccessDenied => if (builtin.os.tag == .windows) return error.SkipZigTest else return err,
|
|
|
|
else => return err,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// For use in test setup. If the symlink creation fails on Windows with
|
|
|
|
// AccessDenied, then make the test failure silent (it is not a Zig failure).
|
|
|
|
fn setupSymlinkAbsolute(target: []const u8, link: []const u8, flags: SymLinkFlags) !void {
|
|
|
|
return fs.symLinkAbsolute(target, link, flags) catch |err| switch (err) {
|
|
|
|
error.AccessDenied => if (builtin.os.tag == .windows) return error.SkipZigTest else return err,
|
|
|
|
else => return err,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
test "Dir.readLink" {
|
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
|
|
|
// Create some targets
|
|
|
|
const file_target_path = try ctx.transformPath("file.txt");
|
|
|
|
try ctx.dir.writeFile(file_target_path, "nonsense");
|
|
|
|
const dir_target_path = try ctx.transformPath("subdir");
|
|
|
|
try ctx.dir.makeDir(dir_target_path);
|
|
|
|
|
2023-10-24 06:00:17 +08:00
|
|
|
// test 1: symlink to a file
|
|
|
|
try setupSymlink(ctx.dir, file_target_path, "symlink1", .{});
|
|
|
|
try testReadLink(ctx.dir, file_target_path, "symlink1");
|
|
|
|
|
|
|
|
// test 2: symlink to a directory (can be different on Windows)
|
|
|
|
try setupSymlink(ctx.dir, dir_target_path, "symlink2", .{ .is_directory = true });
|
|
|
|
try testReadLink(ctx.dir, dir_target_path, "symlink2");
|
|
|
|
|
|
|
|
// test 3: relative path symlink
|
|
|
|
const parent_file = ".." ++ fs.path.sep_str ++ "target.txt";
|
|
|
|
var subdir = try ctx.dir.makeOpenPath("subdir", .{});
|
|
|
|
defer subdir.close();
|
|
|
|
try setupSymlink(subdir, parent_file, "relative-link.txt", .{});
|
|
|
|
try testReadLink(subdir, parent_file, "relative-link.txt");
|
2023-08-17 15:58:44 +08:00
|
|
|
}
|
|
|
|
}.impl);
|
|
|
|
}
|
|
|
|
|
2020-07-22 02:40:34 +08:00
|
|
|
fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {
|
|
|
|
var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
|
2023-10-24 06:00:17 +08:00
|
|
|
const actual = try dir.readLink(symlink_path, buffer[0..]);
|
|
|
|
try testing.expectEqualStrings(target_path, actual);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void {
|
|
|
|
var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
|
|
|
|
const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]);
|
2023-08-17 15:58:44 +08:00
|
|
|
try testing.expectEqualStrings(target_path, given);
|
|
|
|
}
|
|
|
|
|
2023-12-21 19:02:22 +08:00
|
|
|
test "File.stat on a File that is a symlink returns Kind.sym_link" {
|
|
|
|
// This test requires getting a file descriptor of a symlink which
|
|
|
|
// is not possible on all targets
|
|
|
|
switch (builtin.target.os.tag) {
|
|
|
|
.windows, .linux => {},
|
|
|
|
else => return error.SkipZigTest,
|
|
|
|
}
|
|
|
|
|
2023-12-20 15:01:57 +08:00
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
|
|
|
const dir_target_path = try ctx.transformPath("subdir");
|
|
|
|
try ctx.dir.makeDir(dir_target_path);
|
|
|
|
|
2023-10-24 06:00:17 +08:00
|
|
|
try setupSymlink(ctx.dir, dir_target_path, "symlink", .{ .is_directory = true });
|
2023-12-20 15:01:57 +08:00
|
|
|
|
2023-12-21 19:02:22 +08:00
|
|
|
var symlink = switch (builtin.target.os.tag) {
|
|
|
|
.windows => windows_symlink: {
|
|
|
|
const w = std.os.windows;
|
|
|
|
|
|
|
|
const sub_path_w = try std.os.windows.cStrToPrefixedFileW(ctx.dir.fd, "symlink");
|
|
|
|
|
|
|
|
var result = Dir{
|
|
|
|
.fd = undefined,
|
|
|
|
};
|
|
|
|
|
|
|
|
const path_len_bytes = @as(u16, @intCast(sub_path_w.span().len * 2));
|
|
|
|
var nt_name = w.UNICODE_STRING{
|
|
|
|
.Length = path_len_bytes,
|
|
|
|
.MaximumLength = path_len_bytes,
|
|
|
|
.Buffer = @constCast(&sub_path_w.data),
|
|
|
|
};
|
|
|
|
var attr = w.OBJECT_ATTRIBUTES{
|
|
|
|
.Length = @sizeOf(w.OBJECT_ATTRIBUTES),
|
|
|
|
.RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else ctx.dir.fd,
|
|
|
|
.Attributes = 0,
|
|
|
|
.ObjectName = &nt_name,
|
|
|
|
.SecurityDescriptor = null,
|
|
|
|
.SecurityQualityOfService = null,
|
|
|
|
};
|
|
|
|
var io: w.IO_STATUS_BLOCK = undefined;
|
|
|
|
const rc = w.ntdll.NtCreateFile(
|
|
|
|
&result.fd,
|
|
|
|
w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA | w.SYNCHRONIZE | w.FILE_TRAVERSE,
|
|
|
|
&attr,
|
|
|
|
&io,
|
|
|
|
null,
|
|
|
|
w.FILE_ATTRIBUTE_NORMAL,
|
|
|
|
w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,
|
|
|
|
w.FILE_OPEN,
|
|
|
|
// FILE_OPEN_REPARSE_POINT is the important thing here
|
|
|
|
w.FILE_OPEN_REPARSE_POINT | w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT,
|
|
|
|
null,
|
|
|
|
0,
|
|
|
|
);
|
|
|
|
|
|
|
|
switch (rc) {
|
|
|
|
.SUCCESS => break :windows_symlink result,
|
|
|
|
else => return w.unexpectedStatus(rc),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
.linux => linux_symlink: {
|
|
|
|
const sub_path_c = try os.toPosixPath("symlink");
|
|
|
|
// the O_NOFOLLOW | O_PATH combination can obtain a fd to a symlink
|
|
|
|
// note that if O_DIRECTORY is set, then this will error with ENOTDIR
|
|
|
|
const flags = os.O.NOFOLLOW | os.O.PATH | os.O.RDONLY | os.O.CLOEXEC;
|
|
|
|
const fd = try os.openatZ(ctx.dir.fd, &sub_path_c, flags, 0);
|
|
|
|
break :linux_symlink Dir{ .fd = fd };
|
|
|
|
},
|
|
|
|
else => unreachable,
|
|
|
|
};
|
2023-12-20 15:01:57 +08:00
|
|
|
defer symlink.close();
|
|
|
|
|
|
|
|
const stat = try symlink.stat();
|
|
|
|
try testing.expectEqual(File.Kind.sym_link, stat.kind);
|
|
|
|
}
|
|
|
|
}.impl);
|
|
|
|
}
|
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
test "openDir" {
|
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
2023-10-09 21:44:14 +08:00
|
|
|
const allocator = ctx.arena.allocator();
|
2023-08-17 15:58:44 +08:00
|
|
|
const subdir_path = try ctx.transformPath("subdir");
|
|
|
|
try ctx.dir.makeDir(subdir_path);
|
|
|
|
|
|
|
|
for ([_][]const u8{ "", ".", ".." }) |sub_path| {
|
2023-10-09 21:44:14 +08:00
|
|
|
const dir_path = try fs.path.join(allocator, &.{ subdir_path, sub_path });
|
2023-08-17 15:58:44 +08:00
|
|
|
var dir = try ctx.dir.openDir(dir_path, .{});
|
|
|
|
defer dir.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}.impl);
|
2020-07-22 02:40:34 +08:00
|
|
|
}
|
|
|
|
|
2020-11-18 15:42:35 +08:00
|
|
|
test "accessAbsolute" {
|
2022-12-06 06:52:44 +08:00
|
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
2020-11-18 15:42:35 +08:00
|
|
|
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
|
|
defer arena.deinit();
|
2021-10-29 09:08:41 +08:00
|
|
|
const allocator = arena.allocator();
|
2021-10-29 07:37:25 +08:00
|
|
|
|
2020-11-18 15:42:35 +08:00
|
|
|
const base_path = blk: {
|
2023-10-09 21:44:14 +08:00
|
|
|
const relative_path = try fs.path.join(allocator, &.{ "zig-cache", "tmp", tmp.sub_path[0..] });
|
2021-10-29 07:37:25 +08:00
|
|
|
break :blk try fs.realpathAlloc(allocator, relative_path);
|
2020-11-18 15:42:35 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
try fs.accessAbsolute(base_path, .{});
|
|
|
|
}
|
|
|
|
|
|
|
|
test "openDirAbsolute" {
|
2022-12-06 06:52:44 +08:00
|
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
2020-11-18 15:42:35 +08:00
|
|
|
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
try tmp.dir.makeDir("subdir");
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
|
|
defer arena.deinit();
|
2021-10-29 09:08:41 +08:00
|
|
|
const allocator = arena.allocator();
|
2021-10-29 07:37:25 +08:00
|
|
|
|
2020-11-18 15:42:35 +08:00
|
|
|
const base_path = blk: {
|
2023-10-09 21:44:14 +08:00
|
|
|
const relative_path = try fs.path.join(allocator, &.{ "zig-cache", "tmp", tmp.sub_path[0..], "subdir" });
|
2021-10-29 07:37:25 +08:00
|
|
|
break :blk try fs.realpathAlloc(allocator, relative_path);
|
2020-11-18 15:42:35 +08:00
|
|
|
};
|
|
|
|
|
2021-01-03 19:19:24 +08:00
|
|
|
{
|
|
|
|
var dir = try fs.openDirAbsolute(base_path, .{});
|
|
|
|
defer dir.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
for ([_][]const u8{ ".", ".." }) |sub_path| {
|
2023-10-09 21:44:14 +08:00
|
|
|
const dir_path = try fs.path.join(allocator, &.{ base_path, sub_path });
|
2021-01-03 19:19:24 +08:00
|
|
|
var dir = try fs.openDirAbsolute(dir_path, .{});
|
|
|
|
defer dir.close();
|
|
|
|
}
|
2020-11-18 15:42:35 +08:00
|
|
|
}
|
|
|
|
|
2024-01-07 05:22:14 +08:00
|
|
|
test "openDir cwd parent '..'" {
|
2021-04-03 03:17:06 +08:00
|
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
|
|
|
|
2021-05-21 13:23:10 +08:00
|
|
|
var dir = try fs.cwd().openDir("..", .{});
|
|
|
|
defer dir.close();
|
2021-04-03 03:17:06 +08:00
|
|
|
}
|
|
|
|
|
2024-01-07 05:22:14 +08:00
|
|
|
test "openDir non-cwd parent '..'" {
|
2023-08-16 05:20:05 +08:00
|
|
|
switch (builtin.os.tag) {
|
|
|
|
.wasi, .netbsd, .openbsd => return error.SkipZigTest,
|
|
|
|
else => {},
|
|
|
|
}
|
2023-08-12 08:48:25 +08:00
|
|
|
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
var subdir = try tmp.dir.makeOpenPath("subdir", .{});
|
|
|
|
defer subdir.close();
|
|
|
|
|
|
|
|
var dir = try subdir.openDir("..", .{});
|
|
|
|
defer dir.close();
|
|
|
|
|
|
|
|
const expected_path = try tmp.dir.realpathAlloc(testing.allocator, ".");
|
|
|
|
defer testing.allocator.free(expected_path);
|
|
|
|
|
|
|
|
const actual_path = try dir.realpathAlloc(testing.allocator, ".");
|
|
|
|
defer testing.allocator.free(actual_path);
|
|
|
|
|
|
|
|
try testing.expectEqualStrings(expected_path, actual_path);
|
|
|
|
}
|
|
|
|
|
2020-07-19 17:47:00 +08:00
|
|
|
test "readLinkAbsolute" {
|
2022-12-06 06:52:44 +08:00
|
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
2020-07-19 17:47:00 +08:00
|
|
|
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
// Create some targets
|
|
|
|
try tmp.dir.writeFile("file.txt", "nonsense");
|
|
|
|
try tmp.dir.makeDir("subdir");
|
|
|
|
|
|
|
|
// Get base abs path
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
|
|
defer arena.deinit();
|
2021-10-29 09:08:41 +08:00
|
|
|
const allocator = arena.allocator();
|
2020-07-19 17:47:00 +08:00
|
|
|
|
|
|
|
const base_path = blk: {
|
2023-10-09 21:44:14 +08:00
|
|
|
const relative_path = try fs.path.join(allocator, &.{ "zig-cache", "tmp", tmp.sub_path[0..] });
|
2021-10-29 07:37:25 +08:00
|
|
|
break :blk try fs.realpathAlloc(allocator, relative_path);
|
2020-07-19 17:47:00 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
{
|
2023-10-09 21:44:14 +08:00
|
|
|
const target_path = try fs.path.join(allocator, &.{ base_path, "file.txt" });
|
|
|
|
const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink1" });
|
2020-07-19 17:47:00 +08:00
|
|
|
|
|
|
|
// Create symbolic link by path
|
2023-10-24 06:00:17 +08:00
|
|
|
try setupSymlinkAbsolute(target_path, symlink_path, .{});
|
2020-07-20 04:25:00 +08:00
|
|
|
try testReadLinkAbsolute(target_path, symlink_path);
|
2020-07-19 17:47:00 +08:00
|
|
|
}
|
|
|
|
{
|
2023-10-09 21:44:14 +08:00
|
|
|
const target_path = try fs.path.join(allocator, &.{ base_path, "subdir" });
|
|
|
|
const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink2" });
|
2020-07-19 17:47:00 +08:00
|
|
|
|
2023-10-24 06:00:17 +08:00
|
|
|
// Create symbolic link to a directory by path
|
|
|
|
try setupSymlinkAbsolute(target_path, symlink_path, .{ .is_directory = true });
|
2020-07-20 04:25:00 +08:00
|
|
|
try testReadLinkAbsolute(target_path, symlink_path);
|
2020-07-19 17:47:00 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-30 23:43:00 +08:00
|
|
|
test "Dir.Iterator" {
|
2023-11-23 03:35:33 +08:00
|
|
|
var tmp_dir = tmpDir(.{ .iterate = true });
|
2020-06-30 23:43:00 +08:00
|
|
|
defer tmp_dir.cleanup();
|
|
|
|
|
|
|
|
// First, create a couple of entries to iterate over.
|
2023-11-23 03:35:33 +08:00
|
|
|
const file = try tmp_dir.dir.createFile("some_file", .{});
|
2020-06-30 23:43:00 +08:00
|
|
|
file.close();
|
|
|
|
|
2023-11-23 03:35:33 +08:00
|
|
|
try tmp_dir.dir.makeDir("some_dir");
|
2020-06-30 23:43:00 +08:00
|
|
|
|
2020-07-01 20:15:58 +08:00
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
|
|
defer arena.deinit();
|
2021-10-29 09:08:41 +08:00
|
|
|
const allocator = arena.allocator();
|
2020-07-01 20:15:58 +08:00
|
|
|
|
2023-11-23 03:35:33 +08:00
|
|
|
var entries = std.ArrayList(Dir.Entry).init(allocator);
|
2020-07-01 20:15:58 +08:00
|
|
|
|
2020-06-30 23:43:00 +08:00
|
|
|
// Create iterator.
|
2023-11-23 03:35:33 +08:00
|
|
|
var iter = tmp_dir.dir.iterate();
|
2020-06-30 23:43:00 +08:00
|
|
|
while (try iter.next()) |entry| {
|
2020-07-01 20:15:58 +08:00
|
|
|
// We cannot just store `entry` as on Windows, we're re-using the name buffer
|
|
|
|
// which means we'll actually share the `name` pointer between entries!
|
2021-10-29 07:37:25 +08:00
|
|
|
const name = try allocator.dupe(u8, entry.name);
|
2023-11-23 03:35:33 +08:00
|
|
|
try entries.append(Dir.Entry{ .name = name, .kind = entry.kind });
|
2020-06-30 23:43:00 +08:00
|
|
|
}
|
|
|
|
|
2023-08-16 07:08:10 +08:00
|
|
|
try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
|
2023-05-21 06:06:42 +08:00
|
|
|
try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file }));
|
|
|
|
try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory }));
|
2020-06-30 23:43:00 +08:00
|
|
|
}
|
|
|
|
|
2022-12-01 12:56:53 +08:00
|
|
|
test "Dir.Iterator many entries" {
|
2023-11-23 03:35:33 +08:00
|
|
|
var tmp_dir = tmpDir(.{ .iterate = true });
|
2022-12-01 12:56:53 +08:00
|
|
|
defer tmp_dir.cleanup();
|
|
|
|
|
|
|
|
const num = 1024;
|
|
|
|
var i: usize = 0;
|
|
|
|
var buf: [4]u8 = undefined; // Enough to store "1024".
|
|
|
|
while (i < num) : (i += 1) {
|
|
|
|
const name = try std.fmt.bufPrint(&buf, "{}", .{i});
|
2023-11-23 04:12:22 +08:00
|
|
|
const file = try tmp_dir.dir.createFile(name, .{});
|
2022-12-01 12:56:53 +08:00
|
|
|
file.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
|
|
defer arena.deinit();
|
|
|
|
const allocator = arena.allocator();
|
|
|
|
|
2023-11-23 03:35:33 +08:00
|
|
|
var entries = std.ArrayList(Dir.Entry).init(allocator);
|
2022-12-01 12:56:53 +08:00
|
|
|
|
|
|
|
// Create iterator.
|
2023-11-23 04:12:22 +08:00
|
|
|
var iter = tmp_dir.dir.iterate();
|
2022-12-01 12:56:53 +08:00
|
|
|
while (try iter.next()) |entry| {
|
|
|
|
// We cannot just store `entry` as on Windows, we're re-using the name buffer
|
|
|
|
// which means we'll actually share the `name` pointer between entries!
|
|
|
|
const name = try allocator.dupe(u8, entry.name);
|
|
|
|
try entries.append(.{ .name = name, .kind = entry.kind });
|
|
|
|
}
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
while (i < num) : (i += 1) {
|
|
|
|
const name = try std.fmt.bufPrint(&buf, "{}", .{i});
|
2023-05-21 06:06:42 +08:00
|
|
|
try testing.expect(contains(&entries, .{ .name = name, .kind = .file }));
|
2022-12-01 12:56:53 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-24 09:21:35 +08:00
|
|
|
test "Dir.Iterator twice" {
|
2023-11-23 03:35:33 +08:00
|
|
|
var tmp_dir = tmpDir(.{ .iterate = true });
|
2021-12-24 09:21:35 +08:00
|
|
|
defer tmp_dir.cleanup();
|
|
|
|
|
|
|
|
// First, create a couple of entries to iterate over.
|
2023-11-23 03:35:33 +08:00
|
|
|
const file = try tmp_dir.dir.createFile("some_file", .{});
|
2021-12-24 09:21:35 +08:00
|
|
|
file.close();
|
|
|
|
|
2023-11-23 03:35:33 +08:00
|
|
|
try tmp_dir.dir.makeDir("some_dir");
|
2021-12-24 09:21:35 +08:00
|
|
|
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
|
|
defer arena.deinit();
|
|
|
|
const allocator = arena.allocator();
|
|
|
|
|
|
|
|
var i: u8 = 0;
|
|
|
|
while (i < 2) : (i += 1) {
|
2023-11-23 03:35:33 +08:00
|
|
|
var entries = std.ArrayList(Dir.Entry).init(allocator);
|
2021-12-24 09:21:35 +08:00
|
|
|
|
|
|
|
// Create iterator.
|
2023-11-23 03:35:33 +08:00
|
|
|
var iter = tmp_dir.dir.iterate();
|
2021-12-24 09:21:35 +08:00
|
|
|
while (try iter.next()) |entry| {
|
|
|
|
// We cannot just store `entry` as on Windows, we're re-using the name buffer
|
|
|
|
// which means we'll actually share the `name` pointer between entries!
|
|
|
|
const name = try allocator.dupe(u8, entry.name);
|
2023-11-23 03:35:33 +08:00
|
|
|
try entries.append(Dir.Entry{ .name = name, .kind = entry.kind });
|
2021-12-24 09:21:35 +08:00
|
|
|
}
|
|
|
|
|
2023-08-16 07:08:10 +08:00
|
|
|
try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
|
2023-05-21 06:06:42 +08:00
|
|
|
try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file }));
|
|
|
|
try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory }));
|
2021-12-24 09:21:35 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-05 18:13:21 +08:00
|
|
|
test "Dir.Iterator reset" {
|
2023-11-23 03:35:33 +08:00
|
|
|
var tmp_dir = tmpDir(.{ .iterate = true });
|
2022-10-05 18:13:21 +08:00
|
|
|
defer tmp_dir.cleanup();
|
|
|
|
|
|
|
|
// First, create a couple of entries to iterate over.
|
2023-11-23 04:12:22 +08:00
|
|
|
const file = try tmp_dir.dir.createFile("some_file", .{});
|
2022-10-05 18:13:21 +08:00
|
|
|
file.close();
|
|
|
|
|
2023-11-23 04:12:22 +08:00
|
|
|
try tmp_dir.dir.makeDir("some_dir");
|
2022-10-05 18:13:21 +08:00
|
|
|
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
|
|
defer arena.deinit();
|
|
|
|
const allocator = arena.allocator();
|
|
|
|
|
|
|
|
// Create iterator.
|
2023-11-23 04:12:22 +08:00
|
|
|
var iter = tmp_dir.dir.iterate();
|
2022-10-05 18:13:21 +08:00
|
|
|
|
|
|
|
var i: u8 = 0;
|
|
|
|
while (i < 2) : (i += 1) {
|
2023-11-23 03:35:33 +08:00
|
|
|
var entries = std.ArrayList(Dir.Entry).init(allocator);
|
2022-10-05 18:13:21 +08:00
|
|
|
|
|
|
|
while (try iter.next()) |entry| {
|
|
|
|
// We cannot just store `entry` as on Windows, we're re-using the name buffer
|
|
|
|
// which means we'll actually share the `name` pointer between entries!
|
|
|
|
const name = try allocator.dupe(u8, entry.name);
|
|
|
|
try entries.append(.{ .name = name, .kind = entry.kind });
|
|
|
|
}
|
|
|
|
|
2023-08-16 07:08:10 +08:00
|
|
|
try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
|
2023-05-21 06:06:42 +08:00
|
|
|
try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file }));
|
|
|
|
try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory }));
|
2022-10-05 18:13:21 +08:00
|
|
|
|
|
|
|
iter.reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-25 21:14:25 +08:00
|
|
|
test "Dir.Iterator but dir is deleted during iteration" {
|
|
|
|
var tmp = std.testing.tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
// Create directory and setup an iterator for it
|
2023-11-23 04:12:22 +08:00
|
|
|
var subdir = try tmp.dir.makeOpenPath("subdir", .{ .iterate = true });
|
|
|
|
defer subdir.close();
|
2022-07-25 21:14:25 +08:00
|
|
|
|
2023-11-23 04:12:22 +08:00
|
|
|
var iterator = subdir.iterate();
|
2022-07-25 21:14:25 +08:00
|
|
|
|
|
|
|
// Create something to iterate over within the subdir
|
2023-10-01 13:17:07 +08:00
|
|
|
try tmp.dir.makePath("subdir" ++ fs.path.sep_str ++ "b");
|
2022-07-25 21:14:25 +08:00
|
|
|
|
|
|
|
// Then, before iterating, delete the directory that we're iterating.
|
|
|
|
// This is a contrived reproduction, but this could happen outside of the program, in another thread, etc.
|
|
|
|
// If we get an error while trying to delete, we can skip this test (this will happen on platforms
|
|
|
|
// like Windows which will give FileBusy if the directory is currently open for iteration).
|
|
|
|
tmp.dir.deleteTree("subdir") catch return error.SkipZigTest;
|
|
|
|
|
|
|
|
// Now, when we try to iterate, the next call should return null immediately.
|
|
|
|
const entry = try iterator.next();
|
|
|
|
try std.testing.expect(entry == null);
|
2022-07-31 14:41:24 +08:00
|
|
|
|
|
|
|
// On Linux, we can opt-in to receiving a more specific error by calling `nextLinux`
|
|
|
|
if (builtin.os.tag == .linux) {
|
|
|
|
try std.testing.expectError(error.DirNotFound, iterator.nextLinux());
|
|
|
|
}
|
2022-07-25 21:14:25 +08:00
|
|
|
}
|
|
|
|
|
2023-11-23 03:35:33 +08:00
|
|
|
fn entryEql(lhs: Dir.Entry, rhs: Dir.Entry) bool {
|
2020-06-30 23:43:00 +08:00
|
|
|
return mem.eql(u8, lhs.name, rhs.name) and lhs.kind == rhs.kind;
|
|
|
|
}
|
|
|
|
|
2023-11-23 03:35:33 +08:00
|
|
|
fn contains(entries: *const std.ArrayList(Dir.Entry), el: Dir.Entry) bool {
|
2020-06-30 23:43:00 +08:00
|
|
|
for (entries.items) |entry| {
|
2020-08-13 05:50:00 +08:00
|
|
|
if (entryEql(entry, el)) return true;
|
2020-06-30 23:43:00 +08:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-08-13 05:50:00 +08:00
|
|
|
test "Dir.realpath smoke test" {
|
2023-08-17 15:58:44 +08:00
|
|
|
if (!comptime std.os.isGetFdPathSupportedOnTarget(builtin.os)) return error.SkipZigTest;
|
|
|
|
|
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
2023-10-09 21:44:14 +08:00
|
|
|
const allocator = ctx.arena.allocator();
|
2023-08-17 15:58:44 +08:00
|
|
|
const test_file_path = try ctx.transformPath("test_file");
|
|
|
|
const test_dir_path = try ctx.transformPath("test_dir");
|
|
|
|
var buf: [fs.MAX_PATH_BYTES]u8 = undefined;
|
|
|
|
|
|
|
|
// FileNotFound if the path doesn't exist
|
2023-10-09 21:44:14 +08:00
|
|
|
try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_file_path));
|
2023-08-17 15:58:44 +08:00
|
|
|
try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_file_path, &buf));
|
2023-10-09 21:44:14 +08:00
|
|
|
try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_dir_path));
|
2023-08-17 15:58:44 +08:00
|
|
|
try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_dir_path, &buf));
|
|
|
|
|
|
|
|
// Now create the file and dir
|
|
|
|
try ctx.dir.writeFile(test_file_path, "");
|
|
|
|
try ctx.dir.makeDir(test_dir_path);
|
|
|
|
|
|
|
|
const base_path = try ctx.transformPath(".");
|
2023-10-09 21:44:14 +08:00
|
|
|
const base_realpath = try ctx.dir.realpathAlloc(allocator, base_path);
|
2023-08-17 15:58:44 +08:00
|
|
|
const expected_file_path = try fs.path.join(
|
2023-10-09 21:44:14 +08:00
|
|
|
allocator,
|
|
|
|
&.{ base_realpath, "test_file" },
|
2023-08-17 15:58:44 +08:00
|
|
|
);
|
|
|
|
const expected_dir_path = try fs.path.join(
|
2023-10-09 21:44:14 +08:00
|
|
|
allocator,
|
|
|
|
&.{ base_realpath, "test_dir" },
|
2023-08-17 15:58:44 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
// First, test non-alloc version
|
|
|
|
{
|
|
|
|
const file_path = try ctx.dir.realpath(test_file_path, &buf);
|
|
|
|
try testing.expectEqualStrings(expected_file_path, file_path);
|
|
|
|
|
|
|
|
const dir_path = try ctx.dir.realpath(test_dir_path, &buf);
|
|
|
|
try testing.expectEqualStrings(expected_dir_path, dir_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next, test alloc version
|
|
|
|
{
|
2023-10-09 21:44:14 +08:00
|
|
|
const file_path = try ctx.dir.realpathAlloc(allocator, test_file_path);
|
2023-08-17 15:58:44 +08:00
|
|
|
try testing.expectEqualStrings(expected_file_path, file_path);
|
|
|
|
|
2023-10-09 21:44:14 +08:00
|
|
|
const dir_path = try ctx.dir.realpathAlloc(allocator, test_dir_path);
|
2023-08-17 15:58:44 +08:00
|
|
|
try testing.expectEqualStrings(expected_dir_path, dir_path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}.impl);
|
2020-08-13 05:50:00 +08:00
|
|
|
}
|
|
|
|
|
2020-06-21 21:18:50 +08:00
|
|
|
test "readAllAlloc" {
|
|
|
|
var tmp_dir = tmpDir(.{});
|
|
|
|
defer tmp_dir.cleanup();
|
|
|
|
|
|
|
|
var file = try tmp_dir.dir.createFile("test_file", .{ .read = true });
|
|
|
|
defer file.close();
|
|
|
|
|
2020-09-04 15:28:43 +08:00
|
|
|
const buf1 = try file.readToEndAlloc(testing.allocator, 1024);
|
2020-06-21 21:18:50 +08:00
|
|
|
defer testing.allocator.free(buf1);
|
2023-08-16 07:08:10 +08:00
|
|
|
try testing.expectEqual(@as(usize, 0), buf1.len);
|
2020-06-21 21:18:50 +08:00
|
|
|
|
|
|
|
const write_buf: []const u8 = "this is a test.\nthis is a test.\nthis is a test.\nthis is a test.\n";
|
|
|
|
try file.writeAll(write_buf);
|
|
|
|
try file.seekTo(0);
|
|
|
|
|
|
|
|
// max_bytes > file_size
|
2020-09-04 15:28:43 +08:00
|
|
|
const buf2 = try file.readToEndAlloc(testing.allocator, 1024);
|
2020-06-21 21:18:50 +08:00
|
|
|
defer testing.allocator.free(buf2);
|
2021-05-05 01:47:26 +08:00
|
|
|
try testing.expectEqual(write_buf.len, buf2.len);
|
2023-08-16 07:08:10 +08:00
|
|
|
try testing.expectEqualStrings(write_buf, buf2);
|
2020-06-21 21:18:50 +08:00
|
|
|
try file.seekTo(0);
|
|
|
|
|
|
|
|
// max_bytes == file_size
|
2020-09-04 15:28:43 +08:00
|
|
|
const buf3 = try file.readToEndAlloc(testing.allocator, write_buf.len);
|
2020-06-21 21:18:50 +08:00
|
|
|
defer testing.allocator.free(buf3);
|
2021-05-05 01:47:26 +08:00
|
|
|
try testing.expectEqual(write_buf.len, buf3.len);
|
2023-08-16 07:08:10 +08:00
|
|
|
try testing.expectEqualStrings(write_buf, buf3);
|
2020-09-04 15:28:43 +08:00
|
|
|
try file.seekTo(0);
|
2020-06-21 21:18:50 +08:00
|
|
|
|
|
|
|
// max_bytes < file_size
|
2021-05-05 01:47:26 +08:00
|
|
|
try testing.expectError(error.FileTooBig, file.readToEndAlloc(testing.allocator, write_buf.len - 1));
|
2020-06-21 21:18:50 +08:00
|
|
|
}
|
2020-04-12 05:50:38 +08:00
|
|
|
|
2023-09-01 05:05:43 +08:00
|
|
|
test "Dir.statFile" {
|
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
|
|
|
const test_file_name = try ctx.transformPath("test_file");
|
|
|
|
|
|
|
|
try testing.expectError(error.FileNotFound, ctx.dir.statFile(test_file_name));
|
|
|
|
|
|
|
|
try ctx.dir.writeFile(test_file_name, "");
|
|
|
|
|
|
|
|
const stat = try ctx.dir.statFile(test_file_name);
|
|
|
|
try testing.expectEqual(File.Kind.file, stat.kind);
|
|
|
|
}
|
|
|
|
}.impl);
|
|
|
|
}
|
|
|
|
|
2024-01-12 13:03:36 +08:00
|
|
|
test "statFile on dangling symlink" {
|
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
|
|
|
const symlink_name = try ctx.transformPath("dangling-symlink");
|
|
|
|
const symlink_target = "." ++ fs.path.sep_str ++ "doesnotexist";
|
|
|
|
|
|
|
|
try setupSymlink(ctx.dir, symlink_target, symlink_name, .{});
|
|
|
|
|
|
|
|
try std.testing.expectError(error.FileNotFound, ctx.dir.statFile(symlink_name));
|
|
|
|
}
|
|
|
|
}.impl);
|
|
|
|
}
|
|
|
|
|
2020-06-24 08:41:41 +08:00
|
|
|
test "directory operations on files" {
|
2023-08-17 15:58:44 +08:00
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
|
|
|
const test_file_name = try ctx.transformPath("test_file");
|
|
|
|
|
|
|
|
var file = try ctx.dir.createFile(test_file_name, .{ .read = true });
|
|
|
|
file.close();
|
|
|
|
|
|
|
|
try testing.expectError(error.PathAlreadyExists, ctx.dir.makeDir(test_file_name));
|
|
|
|
try testing.expectError(error.NotDir, ctx.dir.openDir(test_file_name, .{}));
|
|
|
|
try testing.expectError(error.NotDir, ctx.dir.deleteDir(test_file_name));
|
|
|
|
|
|
|
|
if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) {
|
|
|
|
try testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(test_file_name));
|
|
|
|
try testing.expectError(error.NotDir, fs.deleteDirAbsolute(test_file_name));
|
|
|
|
}
|
|
|
|
|
|
|
|
// ensure the file still exists and is a file as a sanity check
|
|
|
|
file = try ctx.dir.openFile(test_file_name, .{});
|
|
|
|
const stat = try file.stat();
|
2023-08-16 07:08:10 +08:00
|
|
|
try testing.expectEqual(File.Kind.file, stat.kind);
|
2023-08-17 15:58:44 +08:00
|
|
|
file.close();
|
|
|
|
}
|
|
|
|
}.impl);
|
2020-06-24 08:41:41 +08:00
|
|
|
}
|
|
|
|
|
2020-06-26 15:06:23 +08:00
|
|
|
test "file operations on directories" {
|
2020-10-13 11:05:34 +08:00
|
|
|
// TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759
|
|
|
|
if (builtin.os.tag == .freebsd) return error.SkipZigTest;
|
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
|
|
|
const test_dir_name = try ctx.transformPath("test_dir");
|
|
|
|
|
|
|
|
try ctx.dir.makeDir(test_dir_name);
|
|
|
|
|
|
|
|
try testing.expectError(error.IsDir, ctx.dir.createFile(test_dir_name, .{}));
|
|
|
|
try testing.expectError(error.IsDir, ctx.dir.deleteFile(test_dir_name));
|
|
|
|
switch (builtin.os.tag) {
|
|
|
|
// no error when reading a directory.
|
|
|
|
.dragonfly, .netbsd => {},
|
|
|
|
// Currently, WASI will return error.Unexpected (via ENOTCAPABLE) when attempting fd_read on a directory handle.
|
|
|
|
// TODO: Re-enable on WASI once https://github.com/bytecodealliance/wasmtime/issues/1935 is resolved.
|
|
|
|
.wasi => {},
|
|
|
|
else => {
|
|
|
|
try testing.expectError(error.IsDir, ctx.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize)));
|
|
|
|
},
|
|
|
|
}
|
|
|
|
// Note: The `.mode = .read_write` is necessary to ensure the error occurs on all platforms.
|
|
|
|
// TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732
|
|
|
|
try testing.expectError(error.IsDir, ctx.dir.openFile(test_dir_name, .{ .mode = .read_write }));
|
|
|
|
|
|
|
|
if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) {
|
|
|
|
try testing.expectError(error.IsDir, fs.createFileAbsolute(test_dir_name, .{}));
|
|
|
|
try testing.expectError(error.IsDir, fs.deleteFileAbsolute(test_dir_name));
|
|
|
|
}
|
|
|
|
|
|
|
|
// ensure the directory still exists as a sanity check
|
|
|
|
var dir = try ctx.dir.openDir(test_dir_name, .{});
|
|
|
|
dir.close();
|
|
|
|
}
|
|
|
|
}.impl);
|
2020-06-26 15:06:23 +08:00
|
|
|
}
|
|
|
|
|
2023-03-04 19:10:33 +08:00
|
|
|
test "makeOpenPath parent dirs do not exist" {
|
|
|
|
var tmp_dir = tmpDir(.{});
|
|
|
|
defer tmp_dir.cleanup();
|
|
|
|
|
|
|
|
var dir = try tmp_dir.dir.makeOpenPath("root_dir/parent_dir/some_dir", .{});
|
|
|
|
dir.close();
|
|
|
|
|
|
|
|
// double check that the full directory structure was created
|
|
|
|
var dir_verification = try tmp_dir.dir.openDir("root_dir/parent_dir/some_dir", .{});
|
|
|
|
dir_verification.close();
|
|
|
|
}
|
|
|
|
|
2020-09-30 08:47:24 +08:00
|
|
|
test "deleteDir" {
|
2023-08-17 15:58:44 +08:00
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
|
|
|
const test_dir_path = try ctx.transformPath("test_dir");
|
2023-10-01 13:17:07 +08:00
|
|
|
const test_file_path = try ctx.transformPath("test_dir" ++ fs.path.sep_str ++ "test_file");
|
2023-08-17 15:58:44 +08:00
|
|
|
|
|
|
|
// deleting a non-existent directory
|
|
|
|
try testing.expectError(error.FileNotFound, ctx.dir.deleteDir(test_dir_path));
|
|
|
|
|
|
|
|
// deleting a non-empty directory
|
|
|
|
try ctx.dir.makeDir(test_dir_path);
|
|
|
|
try ctx.dir.writeFile(test_file_path, "");
|
|
|
|
try testing.expectError(error.DirNotEmpty, ctx.dir.deleteDir(test_dir_path));
|
|
|
|
|
|
|
|
// deleting an empty directory
|
|
|
|
try ctx.dir.deleteFile(test_file_path);
|
|
|
|
try ctx.dir.deleteDir(test_dir_path);
|
|
|
|
}
|
|
|
|
}.impl);
|
2020-09-30 08:47:24 +08:00
|
|
|
}
|
|
|
|
|
2020-09-17 11:59:45 +08:00
|
|
|
test "Dir.rename files" {
|
2023-08-17 15:58:44 +08:00
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
2023-09-14 10:11:35 +08:00
|
|
|
// Rename on Windows can hit intermittent AccessDenied errors
|
|
|
|
// when certain conditions are true about the host system.
|
|
|
|
// For now, skip this test when the path type is UNC to avoid them.
|
|
|
|
// See https://github.com/ziglang/zig/issues/17134
|
|
|
|
if (ctx.path_type == .unc) return;
|
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
const missing_file_path = try ctx.transformPath("missing_file_name");
|
|
|
|
const something_else_path = try ctx.transformPath("something_else");
|
|
|
|
|
|
|
|
try testing.expectError(error.FileNotFound, ctx.dir.rename(missing_file_path, something_else_path));
|
|
|
|
|
|
|
|
// Renaming files
|
|
|
|
const test_file_name = try ctx.transformPath("test_file");
|
|
|
|
const renamed_test_file_name = try ctx.transformPath("test_file_renamed");
|
|
|
|
var file = try ctx.dir.createFile(test_file_name, .{ .read = true });
|
|
|
|
file.close();
|
|
|
|
try ctx.dir.rename(test_file_name, renamed_test_file_name);
|
|
|
|
|
|
|
|
// Ensure the file was renamed
|
|
|
|
try testing.expectError(error.FileNotFound, ctx.dir.openFile(test_file_name, .{}));
|
|
|
|
file = try ctx.dir.openFile(renamed_test_file_name, .{});
|
|
|
|
file.close();
|
|
|
|
|
|
|
|
// Rename to self succeeds
|
|
|
|
try ctx.dir.rename(renamed_test_file_name, renamed_test_file_name);
|
|
|
|
|
|
|
|
// Rename to existing file succeeds
|
|
|
|
const existing_file_path = try ctx.transformPath("existing_file");
|
|
|
|
var existing_file = try ctx.dir.createFile(existing_file_path, .{ .read = true });
|
|
|
|
existing_file.close();
|
|
|
|
try ctx.dir.rename(renamed_test_file_name, existing_file_path);
|
|
|
|
|
|
|
|
try testing.expectError(error.FileNotFound, ctx.dir.openFile(renamed_test_file_name, .{}));
|
|
|
|
file = try ctx.dir.openFile(existing_file_path, .{});
|
|
|
|
file.close();
|
|
|
|
}
|
|
|
|
}.impl);
|
2020-09-17 11:59:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
test "Dir.rename directories" {
|
2023-08-17 15:58:44 +08:00
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
2023-09-14 10:11:35 +08:00
|
|
|
// Rename on Windows can hit intermittent AccessDenied errors
|
|
|
|
// when certain conditions are true about the host system.
|
|
|
|
// For now, skip this test when the path type is UNC to avoid them.
|
|
|
|
// See https://github.com/ziglang/zig/issues/17134
|
|
|
|
if (ctx.path_type == .unc) return;
|
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
const test_dir_path = try ctx.transformPath("test_dir");
|
|
|
|
const test_dir_renamed_path = try ctx.transformPath("test_dir_renamed");
|
|
|
|
|
|
|
|
// Renaming directories
|
|
|
|
try ctx.dir.makeDir(test_dir_path);
|
|
|
|
try ctx.dir.rename(test_dir_path, test_dir_renamed_path);
|
|
|
|
|
|
|
|
// Ensure the directory was renamed
|
|
|
|
try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{}));
|
|
|
|
var dir = try ctx.dir.openDir(test_dir_renamed_path, .{});
|
|
|
|
|
|
|
|
// Put a file in the directory
|
|
|
|
var file = try dir.createFile("test_file", .{ .read = true });
|
|
|
|
file.close();
|
|
|
|
dir.close();
|
|
|
|
|
|
|
|
const test_dir_renamed_again_path = try ctx.transformPath("test_dir_renamed_again");
|
|
|
|
try ctx.dir.rename(test_dir_renamed_path, test_dir_renamed_again_path);
|
|
|
|
|
|
|
|
// Ensure the directory was renamed and the file still exists in it
|
|
|
|
try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_renamed_path, .{}));
|
|
|
|
dir = try ctx.dir.openDir(test_dir_renamed_again_path, .{});
|
|
|
|
file = try dir.openFile("test_file", .{});
|
|
|
|
file.close();
|
|
|
|
dir.close();
|
|
|
|
}
|
|
|
|
}.impl);
|
2022-04-12 05:39:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
test "Dir.rename directory onto empty dir" {
|
|
|
|
// TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
|
|
|
|
if (builtin.os.tag == .windows) return error.SkipZigTest;
|
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
|
|
|
const test_dir_path = try ctx.transformPath("test_dir");
|
|
|
|
const target_dir_path = try ctx.transformPath("target_dir_path");
|
2020-09-17 11:59:45 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
try ctx.dir.makeDir(test_dir_path);
|
|
|
|
try ctx.dir.makeDir(target_dir_path);
|
|
|
|
try ctx.dir.rename(test_dir_path, target_dir_path);
|
2022-04-12 05:39:45 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
// Ensure the directory was renamed
|
|
|
|
try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{}));
|
|
|
|
var dir = try ctx.dir.openDir(target_dir_path, .{});
|
|
|
|
dir.close();
|
|
|
|
}
|
|
|
|
}.impl);
|
2022-04-12 05:39:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
test "Dir.rename directory onto non-empty dir" {
|
|
|
|
// TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
|
|
|
|
if (builtin.os.tag == .windows) return error.SkipZigTest;
|
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
|
|
|
const test_dir_path = try ctx.transformPath("test_dir");
|
|
|
|
const target_dir_path = try ctx.transformPath("target_dir_path");
|
2022-04-12 05:39:45 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
try ctx.dir.makeDir(test_dir_path);
|
2022-04-12 05:39:45 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
var target_dir = try ctx.dir.makeOpenPath(target_dir_path, .{});
|
|
|
|
var file = try target_dir.createFile("test_file", .{ .read = true });
|
|
|
|
file.close();
|
|
|
|
target_dir.close();
|
2020-09-17 11:59:45 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
// Rename should fail with PathAlreadyExists if target_dir is non-empty
|
|
|
|
try testing.expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, target_dir_path));
|
2020-09-17 11:59:45 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
// Ensure the directory was not renamed
|
|
|
|
var dir = try ctx.dir.openDir(test_dir_path, .{});
|
|
|
|
dir.close();
|
|
|
|
}
|
|
|
|
}.impl);
|
2020-09-17 11:59:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
test "Dir.rename file <-> dir" {
|
|
|
|
// TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
|
|
|
|
if (builtin.os.tag == .windows) return error.SkipZigTest;
|
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
|
|
|
const test_file_path = try ctx.transformPath("test_file");
|
|
|
|
const test_dir_path = try ctx.transformPath("test_dir");
|
2020-09-17 11:59:45 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
var file = try ctx.dir.createFile(test_file_path, .{ .read = true });
|
|
|
|
file.close();
|
|
|
|
try ctx.dir.makeDir(test_dir_path);
|
|
|
|
try testing.expectError(error.IsDir, ctx.dir.rename(test_file_path, test_dir_path));
|
|
|
|
try testing.expectError(error.NotDir, ctx.dir.rename(test_dir_path, test_file_path));
|
|
|
|
}
|
|
|
|
}.impl);
|
2020-09-17 11:59:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
test "rename" {
|
|
|
|
var tmp_dir1 = tmpDir(.{});
|
|
|
|
defer tmp_dir1.cleanup();
|
|
|
|
|
|
|
|
var tmp_dir2 = tmpDir(.{});
|
|
|
|
defer tmp_dir2.cleanup();
|
|
|
|
|
|
|
|
// Renaming files
|
|
|
|
const test_file_name = "test_file";
|
|
|
|
const renamed_test_file_name = "test_file_renamed";
|
|
|
|
var file = try tmp_dir1.dir.createFile(test_file_name, .{ .read = true });
|
|
|
|
file.close();
|
|
|
|
try fs.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name);
|
|
|
|
|
|
|
|
// ensure the file was renamed
|
2021-05-05 01:47:26 +08:00
|
|
|
try testing.expectError(error.FileNotFound, tmp_dir1.dir.openFile(test_file_name, .{}));
|
2020-09-17 11:59:45 +08:00
|
|
|
file = try tmp_dir2.dir.openFile(renamed_test_file_name, .{});
|
|
|
|
file.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
test "renameAbsolute" {
|
2022-12-06 06:52:44 +08:00
|
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
2020-09-17 11:59:45 +08:00
|
|
|
|
|
|
|
var tmp_dir = tmpDir(.{});
|
|
|
|
defer tmp_dir.cleanup();
|
|
|
|
|
|
|
|
// Get base abs path
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
|
|
defer arena.deinit();
|
2021-10-29 09:08:41 +08:00
|
|
|
const allocator = arena.allocator();
|
2020-09-17 11:59:45 +08:00
|
|
|
|
|
|
|
const base_path = blk: {
|
2023-10-09 21:44:14 +08:00
|
|
|
const relative_path = try fs.path.join(allocator, &.{ "zig-cache", "tmp", tmp_dir.sub_path[0..] });
|
2021-10-29 07:37:25 +08:00
|
|
|
break :blk try fs.realpathAlloc(allocator, relative_path);
|
2020-09-17 11:59:45 +08:00
|
|
|
};
|
|
|
|
|
2021-05-05 01:47:26 +08:00
|
|
|
try testing.expectError(error.FileNotFound, fs.renameAbsolute(
|
2023-10-09 21:44:14 +08:00
|
|
|
try fs.path.join(allocator, &.{ base_path, "missing_file_name" }),
|
|
|
|
try fs.path.join(allocator, &.{ base_path, "something_else" }),
|
2020-09-17 11:59:45 +08:00
|
|
|
));
|
|
|
|
|
|
|
|
// Renaming files
|
|
|
|
const test_file_name = "test_file";
|
|
|
|
const renamed_test_file_name = "test_file_renamed";
|
|
|
|
var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true });
|
|
|
|
file.close();
|
|
|
|
try fs.renameAbsolute(
|
2023-10-09 21:44:14 +08:00
|
|
|
try fs.path.join(allocator, &.{ base_path, test_file_name }),
|
|
|
|
try fs.path.join(allocator, &.{ base_path, renamed_test_file_name }),
|
2020-09-17 11:59:45 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
// ensure the file was renamed
|
2021-05-05 01:47:26 +08:00
|
|
|
try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{}));
|
2020-09-17 11:59:45 +08:00
|
|
|
file = try tmp_dir.dir.openFile(renamed_test_file_name, .{});
|
|
|
|
const stat = try file.stat();
|
2023-08-16 07:08:10 +08:00
|
|
|
try testing.expectEqual(File.Kind.file, stat.kind);
|
2020-09-17 11:59:45 +08:00
|
|
|
file.close();
|
|
|
|
|
|
|
|
// Renaming directories
|
|
|
|
const test_dir_name = "test_dir";
|
|
|
|
const renamed_test_dir_name = "test_dir_renamed";
|
|
|
|
try tmp_dir.dir.makeDir(test_dir_name);
|
|
|
|
try fs.renameAbsolute(
|
2023-10-09 21:44:14 +08:00
|
|
|
try fs.path.join(allocator, &.{ base_path, test_dir_name }),
|
|
|
|
try fs.path.join(allocator, &.{ base_path, renamed_test_dir_name }),
|
2020-09-17 11:59:45 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
// ensure the directory was renamed
|
2021-05-05 01:47:26 +08:00
|
|
|
try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir(test_dir_name, .{}));
|
2020-09-17 11:59:45 +08:00
|
|
|
var dir = try tmp_dir.dir.openDir(renamed_test_dir_name, .{});
|
|
|
|
dir.close();
|
|
|
|
}
|
|
|
|
|
2020-04-12 05:50:38 +08:00
|
|
|
test "openSelfExe" {
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 23:23:49 +08:00
|
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
|
|
|
|
2020-03-28 04:13:26 +08:00
|
|
|
const self_exe_file = try std.fs.openSelfExe(.{});
|
2020-04-12 05:50:38 +08:00
|
|
|
self_exe_file.close();
|
|
|
|
}
|
|
|
|
|
2023-12-22 15:15:12 +08:00
|
|
|
test "deleteTree does not follow symlinks" {
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
try tmp.dir.makePath("b");
|
|
|
|
{
|
|
|
|
var a = try tmp.dir.makeOpenPath("a", .{});
|
|
|
|
defer a.close();
|
|
|
|
|
2023-10-24 06:00:17 +08:00
|
|
|
try setupSymlink(a, "../b", "b", .{ .is_directory = true });
|
2023-12-22 15:15:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
try tmp.dir.deleteTree("a");
|
|
|
|
|
|
|
|
try testing.expectError(error.FileNotFound, tmp.dir.access("a", .{}));
|
|
|
|
try tmp.dir.access("b", .{});
|
|
|
|
}
|
|
|
|
|
|
|
|
test "deleteTree on a symlink" {
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
// Symlink to a file
|
|
|
|
try tmp.dir.writeFile("file", "");
|
2023-10-24 06:00:17 +08:00
|
|
|
try setupSymlink(tmp.dir, "file", "filelink", .{});
|
2023-12-22 15:15:12 +08:00
|
|
|
|
|
|
|
try tmp.dir.deleteTree("filelink");
|
|
|
|
try testing.expectError(error.FileNotFound, tmp.dir.access("filelink", .{}));
|
|
|
|
try tmp.dir.access("file", .{});
|
|
|
|
|
|
|
|
// Symlink to a directory
|
|
|
|
try tmp.dir.makePath("dir");
|
2023-10-24 06:00:17 +08:00
|
|
|
try setupSymlink(tmp.dir, "dir", "dirlink", .{ .is_directory = true });
|
2023-12-22 15:15:12 +08:00
|
|
|
|
|
|
|
try tmp.dir.deleteTree("dirlink");
|
|
|
|
try testing.expectError(error.FileNotFound, tmp.dir.access("dirlink", .{}));
|
|
|
|
try tmp.dir.access("dir", .{});
|
|
|
|
}
|
|
|
|
|
2020-06-27 14:17:40 +08:00
|
|
|
test "makePath, put some files in it, deleteTree" {
|
2023-08-17 15:58:44 +08:00
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
2023-10-09 21:44:14 +08:00
|
|
|
const allocator = ctx.arena.allocator();
|
2023-08-17 15:58:44 +08:00
|
|
|
const dir_path = try ctx.transformPath("os_test_tmp");
|
2020-06-27 14:17:40 +08:00
|
|
|
|
2023-10-09 21:44:14 +08:00
|
|
|
try ctx.dir.makePath(try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" }));
|
|
|
|
try ctx.dir.writeFile(try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }), "nonsense");
|
|
|
|
try ctx.dir.writeFile(try fs.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }), "blah");
|
2023-08-17 15:58:44 +08:00
|
|
|
|
|
|
|
try ctx.dir.deleteTree(dir_path);
|
|
|
|
try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{}));
|
|
|
|
}
|
|
|
|
}.impl);
|
2020-06-27 14:17:40 +08:00
|
|
|
}
|
|
|
|
|
2022-10-14 18:43:28 +08:00
|
|
|
test "makePath, put some files in it, deleteTreeMinStackSize" {
|
2023-08-17 15:58:44 +08:00
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
2023-10-09 21:44:14 +08:00
|
|
|
const allocator = ctx.arena.allocator();
|
2023-08-17 15:58:44 +08:00
|
|
|
const dir_path = try ctx.transformPath("os_test_tmp");
|
2022-10-14 18:43:28 +08:00
|
|
|
|
2023-10-09 21:44:14 +08:00
|
|
|
try ctx.dir.makePath(try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" }));
|
|
|
|
try ctx.dir.writeFile(try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }), "nonsense");
|
|
|
|
try ctx.dir.writeFile(try fs.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }), "blah");
|
2023-08-17 15:58:44 +08:00
|
|
|
|
|
|
|
try ctx.dir.deleteTreeMinStackSize(dir_path);
|
|
|
|
try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{}));
|
|
|
|
}
|
|
|
|
}.impl);
|
2022-10-14 18:43:28 +08:00
|
|
|
}
|
|
|
|
|
2022-04-07 14:48:30 +08:00
|
|
|
test "makePath in a directory that no longer exists" {
|
|
|
|
if (builtin.os.tag == .windows) return error.SkipZigTest; // Windows returns FileBusy if attempting to remove an open dir
|
|
|
|
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
try tmp.parent_dir.deleteTree(&tmp.sub_path);
|
|
|
|
|
|
|
|
try testing.expectError(error.FileNotFound, tmp.dir.makePath("sub-path"));
|
|
|
|
}
|
|
|
|
|
2024-01-09 04:58:14 +08:00
|
|
|
test "makePath but sub_path contains pre-existing file" {
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
try tmp.dir.makeDir("foo");
|
|
|
|
try tmp.dir.writeFile("foo/bar", "");
|
|
|
|
|
|
|
|
try testing.expectError(error.NotDir, tmp.dir.makePath("foo/bar/baz"));
|
|
|
|
}
|
|
|
|
|
2023-10-01 13:17:07 +08:00
|
|
|
fn expectDir(dir: Dir, path: []const u8) !void {
|
|
|
|
var d = try dir.openDir(path, .{});
|
|
|
|
d.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
test "makepath existing directories" {
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
try tmp.dir.makeDir("A");
|
|
|
|
const tmpA = try tmp.dir.openDir("A", .{});
|
|
|
|
try tmpA.makeDir("B");
|
|
|
|
|
|
|
|
const testPath = "A" ++ fs.path.sep_str ++ "B" ++ fs.path.sep_str ++ "C";
|
|
|
|
try tmp.dir.makePath(testPath);
|
|
|
|
|
|
|
|
try expectDir(tmp.dir, testPath);
|
|
|
|
}
|
|
|
|
|
|
|
|
test "makepath through existing valid symlink" {
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
try tmp.dir.makeDir("realfolder");
|
2023-10-24 06:00:17 +08:00
|
|
|
try setupSymlink(tmp.dir, "." ++ fs.path.sep_str ++ "realfolder", "working-symlink", .{});
|
2023-10-01 13:17:07 +08:00
|
|
|
|
|
|
|
try tmp.dir.makePath("working-symlink" ++ fs.path.sep_str ++ "in-realfolder");
|
|
|
|
|
|
|
|
try expectDir(tmp.dir, "realfolder" ++ fs.path.sep_str ++ "in-realfolder");
|
|
|
|
}
|
|
|
|
|
|
|
|
test "makepath relative walks" {
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
const relPath = try fs.path.join(testing.allocator, &.{
|
|
|
|
"first", "..", "second", "..", "third", "..", "first", "A", "..", "B", "..", "C",
|
|
|
|
});
|
|
|
|
defer testing.allocator.free(relPath);
|
|
|
|
|
|
|
|
try tmp.dir.makePath(relPath);
|
|
|
|
|
2024-01-05 15:03:46 +08:00
|
|
|
// How .. is handled is different on Windows than non-Windows
|
|
|
|
switch (builtin.os.tag) {
|
|
|
|
.windows => {
|
|
|
|
// On Windows, .. is resolved before passing the path to NtCreateFile,
|
|
|
|
// meaning everything except `first/C` drops out.
|
|
|
|
try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "C");
|
|
|
|
try testing.expectError(error.FileNotFound, tmp.dir.access("second", .{}));
|
|
|
|
try testing.expectError(error.FileNotFound, tmp.dir.access("third", .{}));
|
|
|
|
},
|
|
|
|
else => {
|
|
|
|
try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "A");
|
|
|
|
try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "B");
|
|
|
|
try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "C");
|
|
|
|
try expectDir(tmp.dir, "second");
|
|
|
|
try expectDir(tmp.dir, "third");
|
|
|
|
},
|
|
|
|
}
|
2023-10-01 13:17:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
test "makepath ignores '.'" {
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
// Path to create, with "." elements:
|
|
|
|
const dotPath = try fs.path.join(testing.allocator, &.{
|
|
|
|
"first", ".", "second", ".", "third",
|
|
|
|
});
|
|
|
|
defer testing.allocator.free(dotPath);
|
|
|
|
|
|
|
|
// Path to expect to find:
|
|
|
|
const expectedPath = try fs.path.join(testing.allocator, &.{
|
|
|
|
"first", "second", "third",
|
|
|
|
});
|
|
|
|
defer testing.allocator.free(expectedPath);
|
|
|
|
|
|
|
|
try tmp.dir.makePath(dotPath);
|
|
|
|
|
|
|
|
try expectDir(tmp.dir, expectedPath);
|
|
|
|
}
|
|
|
|
|
2023-11-23 03:35:33 +08:00
|
|
|
fn testFilenameLimits(iterable_dir: Dir, maxed_filename: []const u8) !void {
|
2022-10-14 12:21:16 +08:00
|
|
|
// setup, create a dir and a nested file both with maxed filenames, and walk the dir
|
|
|
|
{
|
2023-11-23 04:12:22 +08:00
|
|
|
var maxed_dir = try iterable_dir.makeOpenPath(maxed_filename, .{});
|
2022-10-14 12:21:16 +08:00
|
|
|
defer maxed_dir.close();
|
|
|
|
|
|
|
|
try maxed_dir.writeFile(maxed_filename, "");
|
|
|
|
|
|
|
|
var walker = try iterable_dir.walk(testing.allocator);
|
|
|
|
defer walker.deinit();
|
|
|
|
|
|
|
|
var count: usize = 0;
|
|
|
|
while (try walker.next()) |entry| {
|
|
|
|
try testing.expectEqualStrings(maxed_filename, entry.basename);
|
|
|
|
count += 1;
|
|
|
|
}
|
|
|
|
try testing.expectEqual(@as(usize, 2), count);
|
|
|
|
}
|
|
|
|
|
|
|
|
// ensure that we can delete the tree
|
2023-11-23 04:12:22 +08:00
|
|
|
try iterable_dir.deleteTree(maxed_filename);
|
2022-10-14 12:21:16 +08:00
|
|
|
}
|
|
|
|
|
2022-10-28 16:58:47 +08:00
|
|
|
test "max file name component lengths" {
|
2023-11-23 03:35:33 +08:00
|
|
|
var tmp = tmpDir(.{ .iterate = true });
|
2022-10-14 12:21:16 +08:00
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
if (builtin.os.tag == .windows) {
|
2023-08-17 15:58:44 +08:00
|
|
|
// U+FFFF is the character with the largest code point that is encoded as a single
|
|
|
|
// UTF-16 code unit, so Windows allows for NAME_MAX of them.
|
|
|
|
const maxed_windows_filename = ("\u{FFFF}".*) ** std.os.windows.NAME_MAX;
|
2023-11-23 04:12:22 +08:00
|
|
|
try testFilenameLimits(tmp.dir, &maxed_windows_filename);
|
2022-10-14 19:39:56 +08:00
|
|
|
} else if (builtin.os.tag == .wasi) {
|
|
|
|
// On WASI, the maxed filename depends on the host OS, so in order for this test to
|
2022-10-28 16:58:47 +08:00
|
|
|
// work on any host, we need to use a length that will work for all platforms
|
|
|
|
// (i.e. the minimum MAX_NAME_BYTES of all supported platforms).
|
2022-10-14 19:39:56 +08:00
|
|
|
const maxed_wasi_filename = [_]u8{'1'} ** 255;
|
2023-11-23 04:12:22 +08:00
|
|
|
try testFilenameLimits(tmp.dir, &maxed_wasi_filename);
|
2022-10-14 12:21:16 +08:00
|
|
|
} else {
|
|
|
|
const maxed_ascii_filename = [_]u8{'1'} ** std.fs.MAX_NAME_BYTES;
|
2023-11-23 04:12:22 +08:00
|
|
|
try testFilenameLimits(tmp.dir, &maxed_ascii_filename);
|
2022-10-14 12:21:16 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-07 21:24:37 +08:00
|
|
|
test "writev, readv" {
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
const line1 = "line1\n";
|
|
|
|
const line2 = "line2\n";
|
|
|
|
|
|
|
|
var buf1: [line1.len]u8 = undefined;
|
|
|
|
var buf2: [line2.len]u8 = undefined;
|
|
|
|
var write_vecs = [_]std.os.iovec_const{
|
|
|
|
.{
|
|
|
|
.iov_base = line1,
|
|
|
|
.iov_len = line1.len,
|
|
|
|
},
|
|
|
|
.{
|
|
|
|
.iov_base = line2,
|
|
|
|
.iov_len = line2.len,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
var read_vecs = [_]std.os.iovec{
|
|
|
|
.{
|
|
|
|
.iov_base = &buf2,
|
|
|
|
.iov_len = buf2.len,
|
|
|
|
},
|
|
|
|
.{
|
|
|
|
.iov_base = &buf1,
|
|
|
|
.iov_len = buf1.len,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
var src_file = try tmp.dir.createFile("test.txt", .{ .read = true });
|
|
|
|
defer src_file.close();
|
|
|
|
|
|
|
|
try src_file.writevAll(&write_vecs);
|
|
|
|
try testing.expectEqual(@as(u64, line1.len + line2.len), try src_file.getEndPos());
|
|
|
|
try src_file.seekTo(0);
|
|
|
|
const read = try src_file.readvAll(&read_vecs);
|
|
|
|
try testing.expectEqual(@as(usize, line1.len + line2.len), read);
|
|
|
|
try testing.expectEqualStrings(&buf1, "line2\n");
|
|
|
|
try testing.expectEqualStrings(&buf2, "line1\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
test "pwritev, preadv" {
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
const line1 = "line1\n";
|
|
|
|
const line2 = "line2\n";
|
|
|
|
|
|
|
|
var buf1: [line1.len]u8 = undefined;
|
|
|
|
var buf2: [line2.len]u8 = undefined;
|
|
|
|
var write_vecs = [_]std.os.iovec_const{
|
|
|
|
.{
|
|
|
|
.iov_base = line1,
|
|
|
|
.iov_len = line1.len,
|
|
|
|
},
|
|
|
|
.{
|
|
|
|
.iov_base = line2,
|
|
|
|
.iov_len = line2.len,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
var read_vecs = [_]std.os.iovec{
|
|
|
|
.{
|
|
|
|
.iov_base = &buf2,
|
|
|
|
.iov_len = buf2.len,
|
|
|
|
},
|
|
|
|
.{
|
|
|
|
.iov_base = &buf1,
|
|
|
|
.iov_len = buf1.len,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
var src_file = try tmp.dir.createFile("test.txt", .{ .read = true });
|
|
|
|
defer src_file.close();
|
|
|
|
|
|
|
|
try src_file.pwritevAll(&write_vecs, 16);
|
|
|
|
try testing.expectEqual(@as(u64, 16 + line1.len + line2.len), try src_file.getEndPos());
|
|
|
|
const read = try src_file.preadvAll(&read_vecs, 16);
|
|
|
|
try testing.expectEqual(@as(usize, line1.len + line2.len), read);
|
|
|
|
try testing.expectEqualStrings(&buf1, "line2\n");
|
|
|
|
try testing.expectEqualStrings(&buf2, "line1\n");
|
|
|
|
}
|
|
|
|
|
2020-06-27 14:17:40 +08:00
|
|
|
test "access file" {
|
2023-08-17 15:58:44 +08:00
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
|
|
|
const dir_path = try ctx.transformPath("os_test_tmp");
|
|
|
|
const file_path = try ctx.transformPath("os_test_tmp" ++ fs.path.sep_str ++ "file.txt");
|
2020-06-27 14:17:40 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
try ctx.dir.makePath(dir_path);
|
|
|
|
try testing.expectError(error.FileNotFound, ctx.dir.access(file_path, .{}));
|
2020-06-27 14:17:40 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
try ctx.dir.writeFile(file_path, "");
|
|
|
|
try ctx.dir.access(file_path, .{});
|
|
|
|
try ctx.dir.deleteTree(dir_path);
|
|
|
|
}
|
|
|
|
}.impl);
|
2020-06-27 14:17:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
test "sendfile" {
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
try tmp.dir.makePath("os_test_tmp");
|
|
|
|
defer tmp.dir.deleteTree("os_test_tmp") catch {};
|
|
|
|
|
|
|
|
var dir = try tmp.dir.openDir("os_test_tmp", .{});
|
|
|
|
defer dir.close();
|
|
|
|
|
|
|
|
const line1 = "line1\n";
|
|
|
|
const line2 = "second line\n";
|
|
|
|
var vecs = [_]std.os.iovec_const{
|
|
|
|
.{
|
|
|
|
.iov_base = line1,
|
|
|
|
.iov_len = line1.len,
|
|
|
|
},
|
|
|
|
.{
|
|
|
|
.iov_base = line2,
|
|
|
|
.iov_len = line2.len,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
var src_file = try dir.createFile("sendfile1.txt", .{ .read = true });
|
|
|
|
defer src_file.close();
|
|
|
|
|
|
|
|
try src_file.writevAll(&vecs);
|
|
|
|
|
|
|
|
var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true });
|
|
|
|
defer dest_file.close();
|
|
|
|
|
|
|
|
const header1 = "header1\n";
|
|
|
|
const header2 = "second header\n";
|
|
|
|
const trailer1 = "trailer1\n";
|
|
|
|
const trailer2 = "second trailer\n";
|
|
|
|
var hdtr = [_]std.os.iovec_const{
|
|
|
|
.{
|
|
|
|
.iov_base = header1,
|
|
|
|
.iov_len = header1.len,
|
|
|
|
},
|
|
|
|
.{
|
|
|
|
.iov_base = header2,
|
|
|
|
.iov_len = header2.len,
|
|
|
|
},
|
|
|
|
.{
|
|
|
|
.iov_base = trailer1,
|
|
|
|
.iov_len = trailer1.len,
|
|
|
|
},
|
|
|
|
.{
|
|
|
|
.iov_base = trailer2,
|
|
|
|
.iov_len = trailer2.len,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
var written_buf: [100]u8 = undefined;
|
|
|
|
try dest_file.writeFileAll(src_file, .{
|
|
|
|
.in_offset = 1,
|
|
|
|
.in_len = 10,
|
|
|
|
.headers_and_trailers = &hdtr,
|
|
|
|
.header_count = 2,
|
|
|
|
});
|
|
|
|
const amt = try dest_file.preadAll(&written_buf, 0);
|
2023-08-16 07:08:10 +08:00
|
|
|
try testing.expectEqualStrings("header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n", written_buf[0..amt]);
|
2020-06-27 14:17:40 +08:00
|
|
|
}
|
|
|
|
|
2020-08-12 03:49:43 +08:00
|
|
|
test "copyRangeAll" {
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
try tmp.dir.makePath("os_test_tmp");
|
|
|
|
defer tmp.dir.deleteTree("os_test_tmp") catch {};
|
|
|
|
|
|
|
|
var dir = try tmp.dir.openDir("os_test_tmp", .{});
|
|
|
|
defer dir.close();
|
|
|
|
|
|
|
|
var src_file = try dir.createFile("file1.txt", .{ .read = true });
|
|
|
|
defer src_file.close();
|
|
|
|
|
|
|
|
const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";
|
|
|
|
try src_file.writeAll(data);
|
|
|
|
|
|
|
|
var dest_file = try dir.createFile("file2.txt", .{ .read = true });
|
|
|
|
defer dest_file.close();
|
|
|
|
|
|
|
|
var written_buf: [100]u8 = undefined;
|
|
|
|
_ = try src_file.copyRangeAll(0, dest_file, 0, data.len);
|
|
|
|
|
|
|
|
const amt = try dest_file.preadAll(&written_buf, 0);
|
2023-08-16 07:08:10 +08:00
|
|
|
try testing.expectEqualStrings(data, written_buf[0..amt]);
|
2020-08-12 03:49:43 +08:00
|
|
|
}
|
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
test "copyFile" {
|
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
|
|
|
const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";
|
|
|
|
const src_file = try ctx.transformPath("tmp_test_copy_file.txt");
|
|
|
|
const dest_file = try ctx.transformPath("tmp_test_copy_file2.txt");
|
|
|
|
const dest_file2 = try ctx.transformPath("tmp_test_copy_file3.txt");
|
2020-06-27 14:17:40 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
try ctx.dir.writeFile(src_file, data);
|
|
|
|
defer ctx.dir.deleteFile(src_file) catch {};
|
2020-06-27 14:17:40 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
try ctx.dir.copyFile(src_file, ctx.dir, dest_file, .{});
|
|
|
|
defer ctx.dir.deleteFile(dest_file) catch {};
|
2020-06-27 14:17:40 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, .{ .override_mode = File.default_mode });
|
|
|
|
defer ctx.dir.deleteFile(dest_file2) catch {};
|
2020-06-27 14:17:40 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
try expectFileContents(ctx.dir, dest_file, data);
|
|
|
|
try expectFileContents(ctx.dir, dest_file2, data);
|
|
|
|
}
|
|
|
|
}.impl);
|
2020-06-27 14:17:40 +08:00
|
|
|
}
|
|
|
|
|
2020-06-30 23:43:00 +08:00
|
|
|
fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void {
|
2020-06-27 14:17:40 +08:00
|
|
|
const contents = try dir.readFileAlloc(testing.allocator, file_path, 1000);
|
|
|
|
defer testing.allocator.free(contents);
|
|
|
|
|
2021-05-05 01:47:26 +08:00
|
|
|
try testing.expectEqualSlices(u8, data, contents);
|
2020-06-27 14:17:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
test "AtomicFile" {
|
2023-08-17 15:58:44 +08:00
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
2023-10-09 21:44:14 +08:00
|
|
|
const allocator = ctx.arena.allocator();
|
2023-08-17 15:58:44 +08:00
|
|
|
const test_out_file = try ctx.transformPath("tmp_atomic_file_test_dest.txt");
|
|
|
|
const test_content =
|
|
|
|
\\ hello!
|
|
|
|
\\ this is a test file
|
|
|
|
;
|
|
|
|
|
|
|
|
{
|
|
|
|
var af = try ctx.dir.atomicFile(test_out_file, .{});
|
|
|
|
defer af.deinit();
|
|
|
|
try af.file.writeAll(test_content);
|
|
|
|
try af.finish();
|
|
|
|
}
|
2023-10-09 21:44:14 +08:00
|
|
|
const content = try ctx.dir.readFileAlloc(allocator, test_out_file, 9999);
|
2023-08-16 07:08:10 +08:00
|
|
|
try testing.expectEqualStrings(test_content, content);
|
2023-08-17 15:58:44 +08:00
|
|
|
|
|
|
|
try ctx.dir.deleteFile(test_out_file);
|
|
|
|
}
|
|
|
|
}.impl);
|
2020-06-27 14:17:40 +08:00
|
|
|
}
|
|
|
|
|
2020-04-12 05:50:38 +08:00
|
|
|
test "open file with exclusive nonblocking lock twice" {
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 23:23:49 +08:00
|
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
|
|
|
const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
|
2020-11-04 16:48:33 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
|
|
|
|
defer file1.close();
|
2020-04-12 05:50:38 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
|
|
|
|
try testing.expectError(error.WouldBlock, file2);
|
|
|
|
}
|
|
|
|
}.impl);
|
2020-11-04 16:48:33 +08:00
|
|
|
}
|
2020-04-12 05:50:38 +08:00
|
|
|
|
2020-11-04 16:48:33 +08:00
|
|
|
test "open file with shared and exclusive nonblocking lock" {
|
|
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
|
|
|
const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
|
2020-11-04 16:48:33 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
const file1 = try ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
|
|
|
|
defer file1.close();
|
2020-11-04 16:48:33 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
|
|
|
|
try testing.expectError(error.WouldBlock, file2);
|
|
|
|
}
|
|
|
|
}.impl);
|
2020-04-12 05:50:38 +08:00
|
|
|
}
|
|
|
|
|
2020-11-04 16:48:33 +08:00
|
|
|
test "open file with exclusive and shared nonblocking lock" {
|
|
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
2020-05-02 11:17:15 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
|
|
|
const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
|
2020-04-12 05:50:38 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
|
|
|
|
defer file1.close();
|
2020-11-04 16:48:33 +08:00
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
const file2 = ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
|
|
|
|
try testing.expectError(error.WouldBlock, file2);
|
|
|
|
}
|
|
|
|
}.impl);
|
2020-04-12 05:50:38 +08:00
|
|
|
}
|
|
|
|
|
2022-04-27 05:48:56 +08:00
|
|
|
test "open file with exclusive lock twice, make sure second lock waits" {
|
2020-04-12 05:50:38 +08:00
|
|
|
if (builtin.single_threaded) return error.SkipZigTest;
|
|
|
|
|
2020-05-02 11:17:15 +08:00
|
|
|
if (std.io.is_async) {
|
|
|
|
// This test starts its own threads and is not compatible with async I/O.
|
|
|
|
return error.SkipZigTest;
|
|
|
|
}
|
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
|
|
|
const filename = try ctx.transformPath("file_lock_test.txt");
|
|
|
|
|
|
|
|
const file = try ctx.dir.createFile(filename, .{ .lock = .exclusive });
|
|
|
|
errdefer file.close();
|
|
|
|
|
|
|
|
const S = struct {
|
|
|
|
fn checkFn(dir: *fs.Dir, path: []const u8, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void {
|
|
|
|
started.set();
|
|
|
|
const file1 = try dir.createFile(path, .{ .lock = .exclusive });
|
|
|
|
|
|
|
|
locked.set();
|
|
|
|
file1.close();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var started = std.Thread.ResetEvent{};
|
|
|
|
var locked = std.Thread.ResetEvent{};
|
|
|
|
|
|
|
|
const t = try std.Thread.spawn(.{}, S.checkFn, .{
|
|
|
|
&ctx.dir,
|
|
|
|
filename,
|
|
|
|
&started,
|
|
|
|
&locked,
|
|
|
|
});
|
|
|
|
defer t.join();
|
|
|
|
|
|
|
|
// Wait for the spawned thread to start trying to acquire the exclusive file lock.
|
|
|
|
// Then wait a bit to make sure that can't acquire it since we currently hold the file lock.
|
|
|
|
started.wait();
|
|
|
|
try testing.expectError(error.Timeout, locked.timedWait(10 * std.time.ns_per_ms));
|
|
|
|
|
|
|
|
// Release the file lock which should unlock the thread to lock it and set the locked event.
|
|
|
|
file.close();
|
|
|
|
locked.wait();
|
2020-11-04 16:48:33 +08:00
|
|
|
}
|
2023-08-17 15:58:44 +08:00
|
|
|
}.impl);
|
2020-04-12 05:50:38 +08:00
|
|
|
}
|
|
|
|
|
2020-05-02 16:19:07 +08:00
|
|
|
test "open file with exclusive nonblocking lock twice (absolute paths)" {
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 23:23:49 +08:00
|
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
|
|
|
|
2023-03-15 11:22:31 +08:00
|
|
|
var random_bytes: [12]u8 = undefined;
|
|
|
|
std.crypto.random.bytes(&random_bytes);
|
2020-05-02 16:19:07 +08:00
|
|
|
|
2023-03-15 11:22:31 +08:00
|
|
|
var random_b64: [fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined;
|
|
|
|
_ = fs.base64_encoder.encode(&random_b64, &random_bytes);
|
2020-05-02 16:19:07 +08:00
|
|
|
|
2023-03-15 11:22:31 +08:00
|
|
|
const sub_path = random_b64 ++ "-zig-test-absolute-paths.txt";
|
2020-05-02 16:19:07 +08:00
|
|
|
|
2023-03-15 11:22:31 +08:00
|
|
|
const gpa = testing.allocator;
|
|
|
|
|
|
|
|
const cwd = try std.process.getCwdAlloc(gpa);
|
|
|
|
defer gpa.free(cwd);
|
|
|
|
|
2023-10-09 21:44:14 +08:00
|
|
|
const filename = try fs.path.resolve(gpa, &.{ cwd, sub_path });
|
2023-03-15 11:22:31 +08:00
|
|
|
defer gpa.free(filename);
|
|
|
|
|
2024-01-07 05:27:59 +08:00
|
|
|
defer fs.deleteFileAbsolute(filename) catch {}; // createFileAbsolute can leave files on failures
|
2023-03-15 11:22:31 +08:00
|
|
|
const file1 = try fs.createFileAbsolute(filename, .{
|
2023-05-21 06:11:53 +08:00
|
|
|
.lock = .exclusive,
|
2023-03-15 11:22:31 +08:00
|
|
|
.lock_nonblocking = true,
|
|
|
|
});
|
|
|
|
|
|
|
|
const file2 = fs.createFileAbsolute(filename, .{
|
2023-05-21 06:11:53 +08:00
|
|
|
.lock = .exclusive,
|
2023-03-15 11:22:31 +08:00
|
|
|
.lock_nonblocking = true,
|
|
|
|
});
|
2020-05-02 16:19:07 +08:00
|
|
|
file1.close();
|
2021-05-05 01:47:26 +08:00
|
|
|
try testing.expectError(error.WouldBlock, file2);
|
2020-05-02 16:19:07 +08:00
|
|
|
}
|
2020-12-29 00:50:29 +08:00
|
|
|
|
|
|
|
test "walker" {
|
stdlib: Add emulated CWD to std.os for WASI targets
This adds a special CWD file descriptor, AT.FDCWD (-2), to refer to the
current working directory. The `*at(...)` functions look for this and
resolve relative paths against the stored CWD. Absolute paths are
dynamically matched against the stored Preopens.
"os.initPreopensWasi()" must be called before std.os functions will
resolve relative or absolute paths correctly. This is asserted at
runtime.
Support has been added for: `open`, `rename`, `mkdir`, `rmdir`, `chdir`,
`fchdir`, `link`, `symlink`, `unlink`, `readlink`, `fstatat`, `access`,
and `faccessat`.
This also includes limited support for `getcwd()` and `realpath()`.
These return an error if the CWD does not correspond to a Preopen with
an absolute path. They also do not currently expand symlinks.
2022-03-02 01:17:05 +08:00
|
|
|
if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
|
2020-12-29 00:50:29 +08:00
|
|
|
|
2023-11-23 03:35:33 +08:00
|
|
|
var tmp = tmpDir(.{ .iterate = true });
|
2020-12-29 00:50:29 +08:00
|
|
|
defer tmp.cleanup();
|
|
|
|
|
2021-08-14 07:22:51 +08:00
|
|
|
// iteration order of walker is undefined, so need lookup maps to check against
|
|
|
|
|
|
|
|
const expected_paths = std.ComptimeStringMap(void, .{
|
|
|
|
.{"dir1"},
|
|
|
|
.{"dir2"},
|
|
|
|
.{"dir3"},
|
|
|
|
.{"dir4"},
|
2023-10-01 13:17:07 +08:00
|
|
|
.{"dir3" ++ fs.path.sep_str ++ "sub1"},
|
|
|
|
.{"dir3" ++ fs.path.sep_str ++ "sub2"},
|
|
|
|
.{"dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1"},
|
2021-08-14 07:22:51 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
const expected_basenames = std.ComptimeStringMap(void, .{
|
|
|
|
.{"dir1"},
|
|
|
|
.{"dir2"},
|
|
|
|
.{"dir3"},
|
|
|
|
.{"dir4"},
|
|
|
|
.{"sub1"},
|
|
|
|
.{"sub2"},
|
|
|
|
.{"subsub1"},
|
|
|
|
});
|
|
|
|
|
|
|
|
for (expected_paths.kvs) |kv| {
|
2023-11-23 03:35:33 +08:00
|
|
|
try tmp.dir.makePath(kv.key);
|
2020-12-29 00:50:29 +08:00
|
|
|
}
|
|
|
|
|
2023-11-23 03:35:33 +08:00
|
|
|
var walker = try tmp.dir.walk(testing.allocator);
|
2020-12-29 00:50:29 +08:00
|
|
|
defer walker.deinit();
|
|
|
|
|
2021-08-14 07:22:51 +08:00
|
|
|
var num_walked: usize = 0;
|
|
|
|
while (try walker.next()) |entry| {
|
|
|
|
testing.expect(expected_basenames.has(entry.basename)) catch |err| {
|
|
|
|
std.debug.print("found unexpected basename: {s}\n", .{std.fmt.fmtSliceEscapeLower(entry.basename)});
|
|
|
|
return err;
|
|
|
|
};
|
|
|
|
testing.expect(expected_paths.has(entry.path)) catch |err| {
|
|
|
|
std.debug.print("found unexpected path: {s}\n", .{std.fmt.fmtSliceEscapeLower(entry.path)});
|
|
|
|
return err;
|
|
|
|
};
|
2022-08-14 13:08:40 +08:00
|
|
|
// make sure that the entry.dir is the containing dir
|
|
|
|
var entry_dir = try entry.dir.openDir(entry.basename, .{});
|
|
|
|
defer entry_dir.close();
|
2021-08-14 07:22:51 +08:00
|
|
|
num_walked += 1;
|
2020-12-29 00:50:29 +08:00
|
|
|
}
|
2021-08-14 07:22:51 +08:00
|
|
|
try testing.expectEqual(expected_paths.kvs.len, num_walked);
|
2020-12-29 00:50:29 +08:00
|
|
|
}
|
2021-05-24 05:31:52 +08:00
|
|
|
|
2022-07-24 11:14:34 +08:00
|
|
|
test "walker without fully iterating" {
|
|
|
|
if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
|
|
|
|
|
2023-11-23 03:35:33 +08:00
|
|
|
var tmp = tmpDir(.{ .iterate = true });
|
2022-07-24 11:14:34 +08:00
|
|
|
defer tmp.cleanup();
|
|
|
|
|
2023-11-23 04:12:22 +08:00
|
|
|
var walker = try tmp.dir.walk(testing.allocator);
|
2022-07-24 11:14:34 +08:00
|
|
|
defer walker.deinit();
|
|
|
|
|
|
|
|
// Create 2 directories inside the tmp directory, but then only iterate once before breaking.
|
|
|
|
// This ensures that walker doesn't try to close the initial directory when not fully iterating.
|
|
|
|
|
2023-11-23 04:12:22 +08:00
|
|
|
try tmp.dir.makePath("a");
|
|
|
|
try tmp.dir.makePath("b");
|
2022-07-24 11:14:34 +08:00
|
|
|
|
|
|
|
var num_walked: usize = 0;
|
|
|
|
while (try walker.next()) |_| {
|
|
|
|
num_walked += 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
try testing.expectEqual(@as(usize, 1), num_walked);
|
|
|
|
}
|
|
|
|
|
2024-01-07 05:22:14 +08:00
|
|
|
test "'.' and '..' in fs.Dir functions" {
|
stdlib: Add emulated CWD to std.os for WASI targets
This adds a special CWD file descriptor, AT.FDCWD (-2), to refer to the
current working directory. The `*at(...)` functions look for this and
resolve relative paths against the stored CWD. Absolute paths are
dynamically matched against the stored Preopens.
"os.initPreopensWasi()" must be called before std.os functions will
resolve relative or absolute paths correctly. This is asserted at
runtime.
Support has been added for: `open`, `rename`, `mkdir`, `rmdir`, `chdir`,
`fchdir`, `link`, `symlink`, `unlink`, `readlink`, `fstatat`, `access`,
and `faccessat`.
This also includes limited support for `getcwd()` and `realpath()`.
These return an error if the CWD does not correspond to a Preopen with
an absolute path. They also do not currently expand symlinks.
2022-03-02 01:17:05 +08:00
|
|
|
if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
|
2021-05-24 05:31:52 +08:00
|
|
|
|
2023-10-17 07:13:46 +08:00
|
|
|
if (builtin.os.tag == .windows and builtin.cpu.arch == .aarch64) {
|
|
|
|
// https://github.com/ziglang/zig/issues/17134
|
|
|
|
return error.SkipZigTest;
|
|
|
|
}
|
|
|
|
|
2023-08-17 15:58:44 +08:00
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
|
|
fn impl(ctx: *TestContext) !void {
|
|
|
|
const subdir_path = try ctx.transformPath("./subdir");
|
|
|
|
const file_path = try ctx.transformPath("./subdir/../file");
|
|
|
|
const copy_path = try ctx.transformPath("./subdir/../copy");
|
|
|
|
const rename_path = try ctx.transformPath("./subdir/../rename");
|
|
|
|
const update_path = try ctx.transformPath("./subdir/../update");
|
|
|
|
|
|
|
|
try ctx.dir.makeDir(subdir_path);
|
|
|
|
try ctx.dir.access(subdir_path, .{});
|
|
|
|
var created_subdir = try ctx.dir.openDir(subdir_path, .{});
|
|
|
|
created_subdir.close();
|
|
|
|
|
|
|
|
const created_file = try ctx.dir.createFile(file_path, .{});
|
|
|
|
created_file.close();
|
|
|
|
try ctx.dir.access(file_path, .{});
|
|
|
|
|
|
|
|
try ctx.dir.copyFile(file_path, ctx.dir, copy_path, .{});
|
|
|
|
try ctx.dir.rename(copy_path, rename_path);
|
|
|
|
const renamed_file = try ctx.dir.openFile(rename_path, .{});
|
|
|
|
renamed_file.close();
|
|
|
|
try ctx.dir.deleteFile(rename_path);
|
|
|
|
|
|
|
|
try ctx.dir.writeFile(update_path, "something");
|
|
|
|
const prev_status = try ctx.dir.updateFile(file_path, ctx.dir, update_path, .{});
|
2023-11-23 04:50:31 +08:00
|
|
|
try testing.expectEqual(fs.Dir.PrevStatus.stale, prev_status);
|
2023-08-17 15:58:44 +08:00
|
|
|
|
|
|
|
try ctx.dir.deleteDir(subdir_path);
|
|
|
|
}
|
|
|
|
}.impl);
|
2021-05-24 05:31:52 +08:00
|
|
|
}
|
|
|
|
|
2024-01-07 05:22:14 +08:00
|
|
|
test "'.' and '..' in absolute functions" {
|
2022-12-06 06:52:44 +08:00
|
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
2021-05-24 05:31:52 +08:00
|
|
|
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
|
|
defer arena.deinit();
|
2021-10-29 09:08:41 +08:00
|
|
|
const allocator = arena.allocator();
|
2021-05-24 05:31:52 +08:00
|
|
|
|
|
|
|
const base_path = blk: {
|
2023-10-09 21:44:14 +08:00
|
|
|
const relative_path = try fs.path.join(allocator, &.{ "zig-cache", "tmp", tmp.sub_path[0..] });
|
2021-10-29 07:37:25 +08:00
|
|
|
break :blk try fs.realpathAlloc(allocator, relative_path);
|
2021-05-24 05:31:52 +08:00
|
|
|
};
|
|
|
|
|
2023-10-09 21:44:14 +08:00
|
|
|
const subdir_path = try fs.path.join(allocator, &.{ base_path, "./subdir" });
|
2021-05-24 05:31:52 +08:00
|
|
|
try fs.makeDirAbsolute(subdir_path);
|
|
|
|
try fs.accessAbsolute(subdir_path, .{});
|
|
|
|
var created_subdir = try fs.openDirAbsolute(subdir_path, .{});
|
|
|
|
created_subdir.close();
|
|
|
|
|
2023-10-09 21:44:14 +08:00
|
|
|
const created_file_path = try fs.path.join(allocator, &.{ subdir_path, "../file" });
|
2021-05-24 05:31:52 +08:00
|
|
|
const created_file = try fs.createFileAbsolute(created_file_path, .{});
|
|
|
|
created_file.close();
|
|
|
|
try fs.accessAbsolute(created_file_path, .{});
|
|
|
|
|
2023-10-09 21:44:14 +08:00
|
|
|
const copied_file_path = try fs.path.join(allocator, &.{ subdir_path, "../copy" });
|
2021-05-24 05:31:52 +08:00
|
|
|
try fs.copyFileAbsolute(created_file_path, copied_file_path, .{});
|
2023-10-09 21:44:14 +08:00
|
|
|
const renamed_file_path = try fs.path.join(allocator, &.{ subdir_path, "../rename" });
|
2021-05-24 05:31:52 +08:00
|
|
|
try fs.renameAbsolute(copied_file_path, renamed_file_path);
|
|
|
|
const renamed_file = try fs.openFileAbsolute(renamed_file_path, .{});
|
|
|
|
renamed_file.close();
|
|
|
|
try fs.deleteFileAbsolute(renamed_file_path);
|
|
|
|
|
2023-10-09 21:44:14 +08:00
|
|
|
const update_file_path = try fs.path.join(allocator, &.{ subdir_path, "../update" });
|
2021-05-24 07:35:31 +08:00
|
|
|
const update_file = try fs.createFileAbsolute(update_file_path, .{});
|
|
|
|
try update_file.writeAll("something");
|
|
|
|
update_file.close();
|
|
|
|
const prev_status = try fs.updateFileAbsolute(created_file_path, update_file_path, .{});
|
2023-11-23 04:50:31 +08:00
|
|
|
try testing.expectEqual(fs.Dir.PrevStatus.stale, prev_status);
|
2021-05-24 07:35:31 +08:00
|
|
|
|
2021-05-24 05:31:52 +08:00
|
|
|
try fs.deleteDirAbsolute(subdir_path);
|
|
|
|
}
|
2021-11-05 23:19:49 +08:00
|
|
|
|
|
|
|
test "chmod" {
|
|
|
|
if (builtin.os.tag == .windows or builtin.os.tag == .wasi)
|
|
|
|
return error.SkipZigTest;
|
|
|
|
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
const file = try tmp.dir.createFile("test_file", .{ .mode = 0o600 });
|
|
|
|
defer file.close();
|
2023-08-16 07:08:10 +08:00
|
|
|
try testing.expectEqual(@as(File.Mode, 0o600), (try file.stat()).mode & 0o7777);
|
2021-11-05 23:19:49 +08:00
|
|
|
|
|
|
|
try file.chmod(0o644);
|
2023-08-16 07:08:10 +08:00
|
|
|
try testing.expectEqual(@as(File.Mode, 0o644), (try file.stat()).mode & 0o7777);
|
2021-11-05 23:19:49 +08:00
|
|
|
|
|
|
|
try tmp.dir.makeDir("test_dir");
|
2023-11-23 03:35:33 +08:00
|
|
|
var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true });
|
|
|
|
defer dir.close();
|
2021-11-05 23:19:49 +08:00
|
|
|
|
2023-11-23 03:35:33 +08:00
|
|
|
try dir.chmod(0o700);
|
|
|
|
try testing.expectEqual(@as(File.Mode, 0o700), (try dir.stat()).mode & 0o7777);
|
2021-11-05 23:19:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
test "chown" {
|
|
|
|
if (builtin.os.tag == .windows or builtin.os.tag == .wasi)
|
|
|
|
return error.SkipZigTest;
|
|
|
|
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
const file = try tmp.dir.createFile("test_file", .{});
|
|
|
|
defer file.close();
|
|
|
|
try file.chown(null, null);
|
|
|
|
|
|
|
|
try tmp.dir.makeDir("test_dir");
|
|
|
|
|
2023-11-23 03:35:33 +08:00
|
|
|
var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true });
|
|
|
|
defer dir.close();
|
|
|
|
try dir.chown(null, null);
|
2021-11-05 23:19:49 +08:00
|
|
|
}
|
2022-01-09 05:22:23 +08:00
|
|
|
|
|
|
|
test "File.Metadata" {
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
const file = try tmp.dir.createFile("test_file", .{ .read = true });
|
|
|
|
defer file.close();
|
|
|
|
|
|
|
|
const metadata = try file.metadata();
|
2023-08-16 07:08:10 +08:00
|
|
|
try testing.expectEqual(File.Kind.file, metadata.kind());
|
|
|
|
try testing.expectEqual(@as(u64, 0), metadata.size());
|
2022-01-09 05:22:23 +08:00
|
|
|
_ = metadata.accessed();
|
|
|
|
_ = metadata.modified();
|
|
|
|
_ = metadata.created();
|
|
|
|
}
|
|
|
|
|
|
|
|
test "File.Permissions" {
|
|
|
|
if (builtin.os.tag == .wasi)
|
|
|
|
return error.SkipZigTest;
|
|
|
|
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
const file = try tmp.dir.createFile("test_file", .{ .read = true });
|
|
|
|
defer file.close();
|
|
|
|
|
|
|
|
const metadata = try file.metadata();
|
|
|
|
var permissions = metadata.permissions();
|
|
|
|
|
|
|
|
try testing.expect(!permissions.readOnly());
|
|
|
|
permissions.setReadOnly(true);
|
|
|
|
try testing.expect(permissions.readOnly());
|
|
|
|
|
|
|
|
try file.setPermissions(permissions);
|
|
|
|
const new_permissions = (try file.metadata()).permissions();
|
|
|
|
try testing.expect(new_permissions.readOnly());
|
|
|
|
|
|
|
|
// Must be set to non-read-only to delete
|
|
|
|
permissions.setReadOnly(false);
|
|
|
|
try file.setPermissions(permissions);
|
|
|
|
}
|
|
|
|
|
|
|
|
test "File.PermissionsUnix" {
|
|
|
|
if (builtin.os.tag == .windows or builtin.os.tag == .wasi)
|
|
|
|
return error.SkipZigTest;
|
|
|
|
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
defer tmp.cleanup();
|
|
|
|
|
|
|
|
const file = try tmp.dir.createFile("test_file", .{ .mode = 0o666, .read = true });
|
|
|
|
defer file.close();
|
|
|
|
|
|
|
|
const metadata = try file.metadata();
|
|
|
|
var permissions = metadata.permissions();
|
|
|
|
|
|
|
|
permissions.setReadOnly(true);
|
|
|
|
try testing.expect(permissions.readOnly());
|
|
|
|
try testing.expect(!permissions.inner.unixHas(.user, .write));
|
|
|
|
permissions.inner.unixSet(.user, .{ .write = true });
|
|
|
|
try testing.expect(!permissions.readOnly());
|
|
|
|
try testing.expect(permissions.inner.unixHas(.user, .write));
|
|
|
|
try testing.expect(permissions.inner.mode & 0o400 != 0);
|
|
|
|
|
|
|
|
permissions.setReadOnly(true);
|
|
|
|
try file.setPermissions(permissions);
|
|
|
|
permissions = (try file.metadata()).permissions();
|
|
|
|
try testing.expect(permissions.readOnly());
|
|
|
|
|
|
|
|
// Must be set to non-read-only to delete
|
|
|
|
permissions.setReadOnly(false);
|
|
|
|
try file.setPermissions(permissions);
|
|
|
|
|
|
|
|
const permissions_unix = File.PermissionsUnix.unixNew(0o754);
|
|
|
|
try testing.expect(permissions_unix.unixHas(.user, .execute));
|
|
|
|
try testing.expect(!permissions_unix.unixHas(.other, .execute));
|
|
|
|
}
|
2023-04-18 17:13:38 +08:00
|
|
|
|
2023-07-23 15:21:07 +08:00
|
|
|
test "delete a read-only file on windows" {
|
|
|
|
if (builtin.os.tag != .windows)
|
2023-05-02 00:22:28 +08:00
|
|
|
return error.SkipZigTest;
|
|
|
|
|
2023-07-23 15:21:07 +08:00
|
|
|
var tmp = testing.tmpDir(.{});
|
2023-05-02 00:22:28 +08:00
|
|
|
defer tmp.cleanup();
|
2023-04-18 17:13:38 +08:00
|
|
|
|
|
|
|
const file = try tmp.dir.createFile("test_file", .{ .read = true });
|
2023-05-02 00:22:28 +08:00
|
|
|
defer file.close();
|
2023-04-18 17:13:38 +08:00
|
|
|
// Create a file and make it read-only
|
|
|
|
const metadata = try file.metadata();
|
|
|
|
var permissions = metadata.permissions();
|
|
|
|
permissions.setReadOnly(true);
|
|
|
|
try file.setPermissions(permissions);
|
2023-07-23 15:21:07 +08:00
|
|
|
|
|
|
|
// If the OS and filesystem support it, POSIX_SEMANTICS and IGNORE_READONLY_ATTRIBUTE
|
|
|
|
// is used meaning that the deletion of a read-only file will succeed.
|
|
|
|
// Otherwise, this delete will fail and the read-only flag must be unset before it's
|
|
|
|
// able to be deleted.
|
|
|
|
const delete_result = tmp.dir.deleteFile("test_file");
|
|
|
|
if (delete_result) {
|
|
|
|
try testing.expectError(error.FileNotFound, tmp.dir.deleteFile("test_file"));
|
|
|
|
} else |err| {
|
|
|
|
try testing.expectEqual(@as(anyerror, error.AccessDenied), err);
|
|
|
|
// Now make the file not read-only
|
|
|
|
permissions.setReadOnly(false);
|
|
|
|
try file.setPermissions(permissions);
|
|
|
|
try tmp.dir.deleteFile("test_file");
|
|
|
|
}
|
2023-04-18 17:13:38 +08:00
|
|
|
}
|
2023-04-26 13:53:19 +08:00
|
|
|
|
|
|
|
test "delete a setAsCwd directory on Windows" {
|
|
|
|
if (builtin.os.tag != .windows) return error.SkipZigTest;
|
|
|
|
|
|
|
|
var tmp = tmpDir(.{});
|
|
|
|
// Set tmp dir as current working directory.
|
|
|
|
try tmp.dir.setAsCwd();
|
|
|
|
tmp.dir.close();
|
|
|
|
try testing.expectError(error.FileBusy, tmp.parent_dir.deleteTree(&tmp.sub_path));
|
|
|
|
// Now set the parent dir as the current working dir for clean up.
|
|
|
|
try tmp.parent_dir.setAsCwd();
|
|
|
|
try tmp.parent_dir.deleteTree(&tmp.sub_path);
|
|
|
|
// Close the parent "tmp" so we don't leak the HANDLE.
|
|
|
|
tmp.parent_dir.close();
|
|
|
|
}
|