mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
2249 lines
85 KiB
Zig
2249 lines
85 KiB
Zig
const std = @import("../std.zig");
|
|
const builtin = @import("builtin");
|
|
const testing = std.testing;
|
|
const fs = std.fs;
|
|
const mem = std.mem;
|
|
const wasi = std.os.wasi;
|
|
const native_os = builtin.os.tag;
|
|
const windows = std.os.windows;
|
|
const posix = std.posix;
|
|
|
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
|
const Dir = std.fs.Dir;
|
|
const File = std.fs.File;
|
|
const tmpDir = testing.tmpDir;
|
|
const SymLinkFlags = std.fs.Dir.SymLinkFlags;
|
|
|
|
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,
|
|
};
|
|
}
|
|
|
|
pub const TransformError = posix.RealPathError || error{OutOfMemory};
|
|
pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]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: [:0]const u8) TransformError![:0]const u8 {
|
|
_ = allocator;
|
|
_ = dir;
|
|
return relative_path;
|
|
}
|
|
}.transform,
|
|
.absolute => return struct {
|
|
fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]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 std.os.getFdPath(dir.fd, &fd_path_buf);
|
|
return fs.path.joinZ(allocator, &.{ dir_path, relative_path });
|
|
}
|
|
}.transform,
|
|
.unc => return struct {
|
|
fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
|
|
// Any drive absolute path (C:\foo) can be converted into a UNC path by
|
|
// using '127.0.0.1' as the server name and '<drive letter>$' as the share name.
|
|
var fd_path_buf: [fs.max_path_bytes]u8 = undefined;
|
|
const dir_path = try std.os.getFdPath(dir.fd, &fd_path_buf);
|
|
const windows_path_type = windows.getUnprefixedPathType(u8, dir_path);
|
|
switch (windows_path_type) {
|
|
.unc_absolute => return fs.path.joinZ(allocator, &.{ dir_path, relative_path }),
|
|
.drive_absolute => {
|
|
// `C:\<...>` -> `\\127.0.0.1\C$\<...>`
|
|
const prepended = "\\\\127.0.0.1\\";
|
|
var path = try fs.path.joinZ(allocator, &.{ prepended, dir_path, relative_path });
|
|
path[prepended.len + 1] = '$';
|
|
return path;
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
}.transform,
|
|
}
|
|
}
|
|
};
|
|
|
|
const TestContext = struct {
|
|
path_type: PathType,
|
|
path_sep: u8,
|
|
arena: ArenaAllocator,
|
|
tmp: testing.TmpDir,
|
|
dir: std.fs.Dir,
|
|
transform_fn: *const PathType.TransformFn,
|
|
|
|
pub fn init(path_type: PathType, path_sep: u8, allocator: mem.Allocator, transform_fn: *const PathType.TransformFn) TestContext {
|
|
const tmp = tmpDir(.{ .iterate = true });
|
|
return .{
|
|
.path_type = path_type,
|
|
.path_sep = path_sep,
|
|
.arena = ArenaAllocator.init(allocator),
|
|
.tmp = tmp,
|
|
.dir = tmp.dir,
|
|
.transform_fn = transform_fn,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *TestContext) void {
|
|
self.arena.deinit();
|
|
self.tmp.cleanup();
|
|
}
|
|
|
|
/// Returns the `relative_path` transformed into the TestContext's `path_type`,
|
|
/// with any supported path separators replaced by `path_sep`.
|
|
/// The result is allocated by the TestContext's arena and will be free'd during
|
|
/// `TestContext.deinit`.
|
|
pub fn transformPath(self: *TestContext, relative_path: [:0]const u8) ![:0]const u8 {
|
|
const allocator = self.arena.allocator();
|
|
const transformed_path = try self.transform_fn(allocator, self.dir, relative_path);
|
|
if (native_os == .windows) {
|
|
const transformed_sep_path = try allocator.dupeZ(u8, transformed_path);
|
|
std.mem.replaceScalar(u8, transformed_sep_path, switch (self.path_sep) {
|
|
'/' => '\\',
|
|
'\\' => '/',
|
|
else => unreachable,
|
|
}, self.path_sep);
|
|
return transformed_sep_path;
|
|
}
|
|
return transformed_path;
|
|
}
|
|
|
|
/// Replaces any path separators with the canonical path separator for the platform
|
|
/// (e.g. all path separators are converted to `\` on Windows).
|
|
/// If path separators are replaced, then the result is allocated by the
|
|
/// TestContext's arena and will be free'd during `TestContext.deinit`.
|
|
pub fn toCanonicalPathSep(self: *TestContext, path: [:0]const u8) ![:0]const u8 {
|
|
if (native_os == .windows) {
|
|
const allocator = self.arena.allocator();
|
|
const transformed_sep_path = try allocator.dupeZ(u8, path);
|
|
std.mem.replaceScalar(u8, transformed_sep_path, '/', '\\');
|
|
return transformed_sep_path;
|
|
}
|
|
return 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 {
|
|
try testWithPathTypeIfSupported(.relative, '/', test_func);
|
|
try testWithPathTypeIfSupported(.absolute, '/', test_func);
|
|
try testWithPathTypeIfSupported(.unc, '/', test_func);
|
|
try testWithPathTypeIfSupported(.relative, '\\', test_func);
|
|
try testWithPathTypeIfSupported(.absolute, '\\', test_func);
|
|
try testWithPathTypeIfSupported(.unc, '\\', test_func);
|
|
}
|
|
|
|
fn testWithPathTypeIfSupported(comptime path_type: PathType, comptime path_sep: u8, test_func: anytype) !void {
|
|
if (!(comptime path_type.isSupported(builtin.os))) return;
|
|
if (!(comptime fs.path.isSep(path_sep))) return;
|
|
|
|
var ctx = TestContext.init(path_type, path_sep, testing.allocator, path_type.getTransformFn());
|
|
defer ctx.deinit();
|
|
|
|
try test_func(&ctx);
|
|
}
|
|
|
|
// 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 (native_os == .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 (native_os == .windows) return error.SkipZigTest else return err,
|
|
else => return err,
|
|
};
|
|
}
|
|
|
|
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(.{ .sub_path = file_target_path, .data = "nonsense" });
|
|
const dir_target_path = try ctx.transformPath("subdir");
|
|
try ctx.dir.makeDir(dir_target_path);
|
|
|
|
// On Windows, symlink targets always use the canonical path separator
|
|
const canonical_file_target_path = try ctx.toCanonicalPathSep(file_target_path);
|
|
const canonical_dir_target_path = try ctx.toCanonicalPathSep(dir_target_path);
|
|
|
|
// test 1: symlink to a file
|
|
try setupSymlink(ctx.dir, file_target_path, "symlink1", .{});
|
|
try testReadLink(ctx.dir, canonical_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, canonical_dir_target_path, "symlink2");
|
|
|
|
// test 3: relative path symlink
|
|
const parent_file = ".." ++ fs.path.sep_str ++ "target.txt";
|
|
const canonical_parent_file = try ctx.toCanonicalPathSep(parent_file);
|
|
var subdir = try ctx.dir.makeOpenPath("subdir", .{});
|
|
defer subdir.close();
|
|
try setupSymlink(subdir, canonical_parent_file, "relative-link.txt", .{});
|
|
try testReadLink(subdir, canonical_parent_file, "relative-link.txt");
|
|
}
|
|
}.impl);
|
|
}
|
|
|
|
fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {
|
|
var buffer: [fs.max_path_bytes]u8 = undefined;
|
|
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..]);
|
|
try testing.expectEqualStrings(target_path, given);
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
fn impl(ctx: *TestContext) !void {
|
|
const dir_target_path = try ctx.transformPath("subdir");
|
|
try ctx.dir.makeDir(dir_target_path);
|
|
|
|
try setupSymlink(ctx.dir, dir_target_path, "symlink", .{ .is_directory = true });
|
|
|
|
var symlink = switch (builtin.target.os.tag) {
|
|
.windows => windows_symlink: {
|
|
const sub_path_w = try 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 = windows.UNICODE_STRING{
|
|
.Length = path_len_bytes,
|
|
.MaximumLength = path_len_bytes,
|
|
.Buffer = @constCast(&sub_path_w.data),
|
|
};
|
|
var attr = windows.OBJECT_ATTRIBUTES{
|
|
.Length = @sizeOf(windows.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: windows.IO_STATUS_BLOCK = undefined;
|
|
const rc = windows.ntdll.NtCreateFile(
|
|
&result.fd,
|
|
windows.STANDARD_RIGHTS_READ | windows.FILE_READ_ATTRIBUTES | windows.FILE_READ_EA | windows.SYNCHRONIZE | windows.FILE_TRAVERSE,
|
|
&attr,
|
|
&io,
|
|
null,
|
|
windows.FILE_ATTRIBUTE_NORMAL,
|
|
windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE | windows.FILE_SHARE_DELETE,
|
|
windows.FILE_OPEN,
|
|
// FILE_OPEN_REPARSE_POINT is the important thing here
|
|
windows.FILE_OPEN_REPARSE_POINT | windows.FILE_DIRECTORY_FILE | windows.FILE_SYNCHRONOUS_IO_NONALERT | windows.FILE_OPEN_FOR_BACKUP_INTENT,
|
|
null,
|
|
0,
|
|
);
|
|
|
|
switch (rc) {
|
|
.SUCCESS => break :windows_symlink result,
|
|
else => return windows.unexpectedStatus(rc),
|
|
}
|
|
},
|
|
.linux => linux_symlink: {
|
|
const sub_path_c = try posix.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: posix.O = .{
|
|
.NOFOLLOW = true,
|
|
.PATH = true,
|
|
.ACCMODE = .RDONLY,
|
|
.CLOEXEC = true,
|
|
};
|
|
const fd = try posix.openatZ(ctx.dir.fd, &sub_path_c, flags, 0);
|
|
break :linux_symlink Dir{ .fd = fd };
|
|
},
|
|
else => unreachable,
|
|
};
|
|
defer symlink.close();
|
|
|
|
const stat = try symlink.stat();
|
|
try testing.expectEqual(File.Kind.sym_link, stat.kind);
|
|
}
|
|
}.impl);
|
|
}
|
|
|
|
test "openDir" {
|
|
try testWithAllSupportedPathTypes(struct {
|
|
fn impl(ctx: *TestContext) !void {
|
|
const allocator = ctx.arena.allocator();
|
|
const subdir_path = try ctx.transformPath("subdir");
|
|
try ctx.dir.makeDir(subdir_path);
|
|
|
|
for ([_][]const u8{ "", ".", ".." }) |sub_path| {
|
|
const dir_path = try fs.path.join(allocator, &.{ subdir_path, sub_path });
|
|
var dir = try ctx.dir.openDir(dir_path, .{});
|
|
defer dir.close();
|
|
}
|
|
}
|
|
}.impl);
|
|
}
|
|
|
|
test "accessAbsolute" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
const base_path = try tmp.dir.realpathAlloc(testing.allocator, ".");
|
|
defer testing.allocator.free(base_path);
|
|
|
|
try fs.accessAbsolute(base_path, .{});
|
|
}
|
|
|
|
test "openDirAbsolute" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
const tmp_ino = (try tmp.dir.stat()).inode;
|
|
|
|
try tmp.dir.makeDir("subdir");
|
|
const sub_path = try tmp.dir.realpathAlloc(testing.allocator, "subdir");
|
|
defer testing.allocator.free(sub_path);
|
|
|
|
// Can open sub_path
|
|
var tmp_sub = try fs.openDirAbsolute(sub_path, .{});
|
|
defer tmp_sub.close();
|
|
|
|
const sub_ino = (try tmp_sub.stat()).inode;
|
|
|
|
{
|
|
// Can open sub_path + ".."
|
|
const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, ".." });
|
|
defer testing.allocator.free(dir_path);
|
|
|
|
var dir = try fs.openDirAbsolute(dir_path, .{});
|
|
defer dir.close();
|
|
|
|
const ino = (try dir.stat()).inode;
|
|
try testing.expectEqual(tmp_ino, ino);
|
|
}
|
|
|
|
{
|
|
// Can open sub_path + "."
|
|
const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, "." });
|
|
defer testing.allocator.free(dir_path);
|
|
|
|
var dir = try fs.openDirAbsolute(dir_path, .{});
|
|
defer dir.close();
|
|
|
|
const ino = (try dir.stat()).inode;
|
|
try testing.expectEqual(sub_ino, ino);
|
|
}
|
|
|
|
{
|
|
// Can open subdir + "..", with some extra "."
|
|
const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, ".", "..", "." });
|
|
defer testing.allocator.free(dir_path);
|
|
|
|
var dir = try fs.openDirAbsolute(dir_path, .{});
|
|
defer dir.close();
|
|
|
|
const ino = (try dir.stat()).inode;
|
|
try testing.expectEqual(tmp_ino, ino);
|
|
}
|
|
}
|
|
|
|
test "openDir cwd parent '..'" {
|
|
var dir = fs.cwd().openDir("..", .{}) catch |err| {
|
|
if (native_os == .wasi and err == error.PermissionDenied) {
|
|
return; // This is okay. WASI disallows escaping from the fs sandbox
|
|
}
|
|
return err;
|
|
};
|
|
defer dir.close();
|
|
}
|
|
|
|
test "openDir non-cwd parent '..'" {
|
|
switch (native_os) {
|
|
.wasi, .netbsd, .openbsd => return error.SkipZigTest,
|
|
else => {},
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
test "readLinkAbsolute" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
// Create some targets
|
|
try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = "nonsense" });
|
|
try tmp.dir.makeDir("subdir");
|
|
|
|
// Get base abs path
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
const base_path = try tmp.dir.realpathAlloc(allocator, ".");
|
|
|
|
{
|
|
const target_path = try fs.path.join(allocator, &.{ base_path, "file.txt" });
|
|
const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink1" });
|
|
|
|
// Create symbolic link by path
|
|
try setupSymlinkAbsolute(target_path, symlink_path, .{});
|
|
try testReadLinkAbsolute(target_path, symlink_path);
|
|
}
|
|
{
|
|
const target_path = try fs.path.join(allocator, &.{ base_path, "subdir" });
|
|
const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink2" });
|
|
|
|
// Create symbolic link to a directory by path
|
|
try setupSymlinkAbsolute(target_path, symlink_path, .{ .is_directory = true });
|
|
try testReadLinkAbsolute(target_path, symlink_path);
|
|
}
|
|
}
|
|
|
|
test "Dir.Iterator" {
|
|
var tmp_dir = tmpDir(.{ .iterate = true });
|
|
defer tmp_dir.cleanup();
|
|
|
|
// First, create a couple of entries to iterate over.
|
|
const file = try tmp_dir.dir.createFile("some_file", .{});
|
|
file.close();
|
|
|
|
try tmp_dir.dir.makeDir("some_dir");
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
var entries = std.array_list.Managed(Dir.Entry).init(allocator);
|
|
|
|
// Create iterator.
|
|
var iter = tmp_dir.dir.iterate();
|
|
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(Dir.Entry{ .name = name, .kind = entry.kind });
|
|
}
|
|
|
|
try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
|
|
try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file }));
|
|
try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory }));
|
|
}
|
|
|
|
test "Dir.Iterator many entries" {
|
|
var tmp_dir = tmpDir(.{ .iterate = true });
|
|
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});
|
|
const file = try tmp_dir.dir.createFile(name, .{});
|
|
file.close();
|
|
}
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
var entries = std.array_list.Managed(Dir.Entry).init(allocator);
|
|
|
|
// Create iterator.
|
|
var iter = tmp_dir.dir.iterate();
|
|
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});
|
|
try testing.expect(contains(&entries, .{ .name = name, .kind = .file }));
|
|
}
|
|
}
|
|
|
|
test "Dir.Iterator twice" {
|
|
var tmp_dir = tmpDir(.{ .iterate = true });
|
|
defer tmp_dir.cleanup();
|
|
|
|
// First, create a couple of entries to iterate over.
|
|
const file = try tmp_dir.dir.createFile("some_file", .{});
|
|
file.close();
|
|
|
|
try tmp_dir.dir.makeDir("some_dir");
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
var i: u8 = 0;
|
|
while (i < 2) : (i += 1) {
|
|
var entries = std.array_list.Managed(Dir.Entry).init(allocator);
|
|
|
|
// Create iterator.
|
|
var iter = tmp_dir.dir.iterate();
|
|
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(Dir.Entry{ .name = name, .kind = entry.kind });
|
|
}
|
|
|
|
try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
|
|
try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file }));
|
|
try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory }));
|
|
}
|
|
}
|
|
|
|
test "Dir.Iterator reset" {
|
|
var tmp_dir = tmpDir(.{ .iterate = true });
|
|
defer tmp_dir.cleanup();
|
|
|
|
// First, create a couple of entries to iterate over.
|
|
const file = try tmp_dir.dir.createFile("some_file", .{});
|
|
file.close();
|
|
|
|
try tmp_dir.dir.makeDir("some_dir");
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
// Create iterator.
|
|
var iter = tmp_dir.dir.iterate();
|
|
|
|
var i: u8 = 0;
|
|
while (i < 2) : (i += 1) {
|
|
var entries = std.array_list.Managed(Dir.Entry).init(allocator);
|
|
|
|
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 });
|
|
}
|
|
|
|
try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
|
|
try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file }));
|
|
try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory }));
|
|
|
|
iter.reset();
|
|
}
|
|
}
|
|
|
|
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
|
|
var subdir = try tmp.dir.makeOpenPath("subdir", .{ .iterate = true });
|
|
defer subdir.close();
|
|
|
|
var iterator = subdir.iterate();
|
|
|
|
// Create something to iterate over within the subdir
|
|
try tmp.dir.makePath("subdir" ++ fs.path.sep_str ++ "b");
|
|
|
|
// 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);
|
|
|
|
// On Linux, we can opt-in to receiving a more specific error by calling `nextLinux`
|
|
if (native_os == .linux) {
|
|
try std.testing.expectError(error.DirNotFound, iterator.nextLinux());
|
|
}
|
|
}
|
|
|
|
fn entryEql(lhs: Dir.Entry, rhs: Dir.Entry) bool {
|
|
return mem.eql(u8, lhs.name, rhs.name) and lhs.kind == rhs.kind;
|
|
}
|
|
|
|
fn contains(entries: *const std.array_list.Managed(Dir.Entry), el: Dir.Entry) bool {
|
|
for (entries.items) |entry| {
|
|
if (entryEql(entry, el)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
test "Dir.realpath smoke test" {
|
|
if (!comptime std.os.isGetFdPathSupportedOnTarget(builtin.os)) return error.SkipZigTest;
|
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
fn impl(ctx: *TestContext) !void {
|
|
const allocator = ctx.arena.allocator();
|
|
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
|
|
try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_file_path));
|
|
try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_file_path, &buf));
|
|
try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_dir_path));
|
|
try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_dir_path, &buf));
|
|
|
|
// Now create the file and dir
|
|
try ctx.dir.writeFile(.{ .sub_path = test_file_path, .data = "" });
|
|
try ctx.dir.makeDir(test_dir_path);
|
|
|
|
const base_path = try ctx.transformPath(".");
|
|
const base_realpath = try ctx.dir.realpathAlloc(allocator, base_path);
|
|
const expected_file_path = try fs.path.join(
|
|
allocator,
|
|
&.{ base_realpath, "test_file" },
|
|
);
|
|
const expected_dir_path = try fs.path.join(
|
|
allocator,
|
|
&.{ base_realpath, "test_dir" },
|
|
);
|
|
|
|
// 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
|
|
{
|
|
const file_path = try ctx.dir.realpathAlloc(allocator, test_file_path);
|
|
try testing.expectEqualStrings(expected_file_path, file_path);
|
|
|
|
const dir_path = try ctx.dir.realpathAlloc(allocator, test_dir_path);
|
|
try testing.expectEqualStrings(expected_dir_path, dir_path);
|
|
}
|
|
}
|
|
}.impl);
|
|
}
|
|
|
|
test "readFileAlloc" {
|
|
var tmp_dir = tmpDir(.{});
|
|
defer tmp_dir.cleanup();
|
|
|
|
var file = try tmp_dir.dir.createFile("test_file", .{ .read = true });
|
|
defer file.close();
|
|
|
|
const buf1 = try tmp_dir.dir.readFileAlloc("test_file", testing.allocator, .limited(1024));
|
|
defer testing.allocator.free(buf1);
|
|
try testing.expectEqualStrings("", buf1);
|
|
|
|
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);
|
|
|
|
{
|
|
// max_bytes > file_size
|
|
const buf2 = try tmp_dir.dir.readFileAlloc("test_file", testing.allocator, .limited(1024));
|
|
defer testing.allocator.free(buf2);
|
|
try testing.expectEqualStrings(write_buf, buf2);
|
|
}
|
|
|
|
{
|
|
// max_bytes == file_size
|
|
try testing.expectError(
|
|
error.StreamTooLong,
|
|
tmp_dir.dir.readFileAlloc("test_file", testing.allocator, .limited(write_buf.len)),
|
|
);
|
|
}
|
|
|
|
{
|
|
// max_bytes == file_size + 1
|
|
const buf2 = try tmp_dir.dir.readFileAlloc("test_file", testing.allocator, .limited(write_buf.len + 1));
|
|
defer testing.allocator.free(buf2);
|
|
try testing.expectEqualStrings(write_buf, buf2);
|
|
}
|
|
|
|
// max_bytes < file_size
|
|
try testing.expectError(
|
|
error.StreamTooLong,
|
|
tmp_dir.dir.readFileAlloc("test_file", testing.allocator, .limited(write_buf.len - 1)),
|
|
);
|
|
}
|
|
|
|
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(.{ .sub_path = test_file_name, .data = "" });
|
|
|
|
const stat = try ctx.dir.statFile(test_file_name);
|
|
try testing.expectEqual(File.Kind.file, stat.kind);
|
|
}
|
|
}.impl);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
test "directory operations on files" {
|
|
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();
|
|
try testing.expectEqual(File.Kind.file, stat.kind);
|
|
file.close();
|
|
}
|
|
}.impl);
|
|
}
|
|
|
|
test "file operations on directories" {
|
|
// TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759
|
|
if (native_os == .freebsd) return error.SkipZigTest;
|
|
|
|
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 (native_os) {
|
|
.dragonfly, .netbsd => {
|
|
// no error when reading a directory. See https://github.com/ziglang/zig/issues/5732
|
|
const buf = try ctx.dir.readFileAlloc(test_dir_name, testing.allocator, .unlimited);
|
|
testing.allocator.free(buf);
|
|
},
|
|
.wasi => {
|
|
// WASI return EBADF, which gets mapped to NotOpenForReading.
|
|
// See https://github.com/bytecodealliance/wasmtime/issues/1935
|
|
try testing.expectError(error.NotOpenForReading, ctx.dir.readFileAlloc(test_dir_name, testing.allocator, .unlimited));
|
|
},
|
|
else => {
|
|
try testing.expectError(error.IsDir, ctx.dir.readFileAlloc(test_dir_name, testing.allocator, .unlimited));
|
|
},
|
|
}
|
|
|
|
if (native_os == .wasi and builtin.link_libc) {
|
|
// wasmtime unexpectedly succeeds here, see https://github.com/ziglang/zig/issues/20747
|
|
const handle = try ctx.dir.openFile(test_dir_name, .{ .mode = .read_write });
|
|
handle.close();
|
|
} else {
|
|
// 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);
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
test "deleteDir" {
|
|
try testWithAllSupportedPathTypes(struct {
|
|
fn impl(ctx: *TestContext) !void {
|
|
const test_dir_path = try ctx.transformPath("test_dir");
|
|
const test_file_path = try ctx.transformPath("test_dir" ++ fs.path.sep_str ++ "test_file");
|
|
|
|
// 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(.{ .sub_path = test_file_path, .data = "" });
|
|
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);
|
|
}
|
|
|
|
test "Dir.rename files" {
|
|
try testWithAllSupportedPathTypes(struct {
|
|
fn impl(ctx: *TestContext) !void {
|
|
// 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;
|
|
|
|
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);
|
|
}
|
|
|
|
test "Dir.rename directories" {
|
|
try testWithAllSupportedPathTypes(struct {
|
|
fn impl(ctx: *TestContext) !void {
|
|
// 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;
|
|
|
|
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);
|
|
}
|
|
|
|
test "Dir.rename directory onto empty dir" {
|
|
// TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
|
|
if (native_os == .windows) return error.SkipZigTest;
|
|
|
|
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");
|
|
|
|
try ctx.dir.makeDir(test_dir_path);
|
|
try ctx.dir.makeDir(target_dir_path);
|
|
try ctx.dir.rename(test_dir_path, target_dir_path);
|
|
|
|
// 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);
|
|
}
|
|
|
|
test "Dir.rename directory onto non-empty dir" {
|
|
// TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
|
|
if (native_os == .windows) return error.SkipZigTest;
|
|
|
|
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");
|
|
|
|
try ctx.dir.makeDir(test_dir_path);
|
|
|
|
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();
|
|
|
|
// 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));
|
|
|
|
// Ensure the directory was not renamed
|
|
var dir = try ctx.dir.openDir(test_dir_path, .{});
|
|
dir.close();
|
|
}
|
|
}.impl);
|
|
}
|
|
|
|
test "Dir.rename file <-> dir" {
|
|
// TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
|
|
if (native_os == .windows) return error.SkipZigTest;
|
|
|
|
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");
|
|
|
|
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);
|
|
}
|
|
|
|
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
|
|
try testing.expectError(error.FileNotFound, tmp_dir1.dir.openFile(test_file_name, .{}));
|
|
file = try tmp_dir2.dir.openFile(renamed_test_file_name, .{});
|
|
file.close();
|
|
}
|
|
|
|
test "renameAbsolute" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
|
|
var tmp_dir = tmpDir(.{});
|
|
defer tmp_dir.cleanup();
|
|
|
|
// Get base abs path
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
const base_path = try tmp_dir.dir.realpathAlloc(allocator, ".");
|
|
|
|
try testing.expectError(error.FileNotFound, fs.renameAbsolute(
|
|
try fs.path.join(allocator, &.{ base_path, "missing_file_name" }),
|
|
try fs.path.join(allocator, &.{ base_path, "something_else" }),
|
|
));
|
|
|
|
// 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(
|
|
try fs.path.join(allocator, &.{ base_path, test_file_name }),
|
|
try fs.path.join(allocator, &.{ base_path, renamed_test_file_name }),
|
|
);
|
|
|
|
// ensure the file was renamed
|
|
try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{}));
|
|
file = try tmp_dir.dir.openFile(renamed_test_file_name, .{});
|
|
const stat = try file.stat();
|
|
try testing.expectEqual(File.Kind.file, stat.kind);
|
|
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(
|
|
try fs.path.join(allocator, &.{ base_path, test_dir_name }),
|
|
try fs.path.join(allocator, &.{ base_path, renamed_test_dir_name }),
|
|
);
|
|
|
|
// ensure the directory was renamed
|
|
try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir(test_dir_name, .{}));
|
|
var dir = try tmp_dir.dir.openDir(renamed_test_dir_name, .{});
|
|
dir.close();
|
|
}
|
|
|
|
test "openSelfExe" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
|
|
const self_exe_file = try std.fs.openSelfExe(.{});
|
|
self_exe_file.close();
|
|
}
|
|
|
|
test "selfExePath" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
|
|
var buf: [fs.max_path_bytes]u8 = undefined;
|
|
const buf_self_exe_path = try std.fs.selfExePath(&buf);
|
|
const alloc_self_exe_path = try std.fs.selfExePathAlloc(testing.allocator);
|
|
defer testing.allocator.free(alloc_self_exe_path);
|
|
try testing.expectEqualSlices(u8, buf_self_exe_path, alloc_self_exe_path);
|
|
}
|
|
|
|
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();
|
|
|
|
try setupSymlink(a, "../b", "b", .{ .is_directory = true });
|
|
}
|
|
|
|
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(.{ .sub_path = "file", .data = "" });
|
|
try setupSymlink(tmp.dir, "file", "filelink", .{});
|
|
|
|
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");
|
|
try setupSymlink(tmp.dir, "dir", "dirlink", .{ .is_directory = true });
|
|
|
|
try tmp.dir.deleteTree("dirlink");
|
|
try testing.expectError(error.FileNotFound, tmp.dir.access("dirlink", .{}));
|
|
try tmp.dir.access("dir", .{});
|
|
}
|
|
|
|
test "makePath, put some files in it, deleteTree" {
|
|
try testWithAllSupportedPathTypes(struct {
|
|
fn impl(ctx: *TestContext) !void {
|
|
const allocator = ctx.arena.allocator();
|
|
const dir_path = try ctx.transformPath("os_test_tmp");
|
|
|
|
try ctx.dir.makePath(try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" }));
|
|
try ctx.dir.writeFile(.{
|
|
.sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }),
|
|
.data = "nonsense",
|
|
});
|
|
try ctx.dir.writeFile(.{
|
|
.sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }),
|
|
.data = "blah",
|
|
});
|
|
|
|
try ctx.dir.deleteTree(dir_path);
|
|
try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{}));
|
|
}
|
|
}.impl);
|
|
}
|
|
|
|
test "makePath, put some files in it, deleteTreeMinStackSize" {
|
|
try testWithAllSupportedPathTypes(struct {
|
|
fn impl(ctx: *TestContext) !void {
|
|
const allocator = ctx.arena.allocator();
|
|
const dir_path = try ctx.transformPath("os_test_tmp");
|
|
|
|
try ctx.dir.makePath(try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" }));
|
|
try ctx.dir.writeFile(.{
|
|
.sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }),
|
|
.data = "nonsense",
|
|
});
|
|
try ctx.dir.writeFile(.{
|
|
.sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }),
|
|
.data = "blah",
|
|
});
|
|
|
|
try ctx.dir.deleteTreeMinStackSize(dir_path);
|
|
try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{}));
|
|
}
|
|
}.impl);
|
|
}
|
|
|
|
test "makePath in a directory that no longer exists" {
|
|
if (native_os == .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"));
|
|
}
|
|
|
|
test "makePath but sub_path contains pre-existing file" {
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
try tmp.dir.makeDir("foo");
|
|
try tmp.dir.writeFile(.{ .sub_path = "foo/bar", .data = "" });
|
|
|
|
try testing.expectError(error.NotDir, tmp.dir.makePath("foo/bar/baz"));
|
|
}
|
|
|
|
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");
|
|
var tmpA = try tmp.dir.openDir("A", .{});
|
|
defer tmpA.close();
|
|
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");
|
|
try setupSymlink(tmp.dir, "." ++ fs.path.sep_str ++ "realfolder", "working-symlink", .{});
|
|
|
|
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);
|
|
|
|
// How .. is handled is different on Windows than non-Windows
|
|
switch (native_os) {
|
|
.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");
|
|
},
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
fn testFilenameLimits(iterable_dir: Dir, maxed_filename: []const u8) !void {
|
|
// setup, create a dir and a nested file both with maxed filenames, and walk the dir
|
|
{
|
|
var maxed_dir = try iterable_dir.makeOpenPath(maxed_filename, .{});
|
|
defer maxed_dir.close();
|
|
|
|
try maxed_dir.writeFile(.{ .sub_path = maxed_filename, .data = "" });
|
|
|
|
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
|
|
try iterable_dir.deleteTree(maxed_filename);
|
|
}
|
|
|
|
test "max file name component lengths" {
|
|
var tmp = tmpDir(.{ .iterate = true });
|
|
defer tmp.cleanup();
|
|
|
|
if (native_os == .windows) {
|
|
// 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}".*) ** windows.NAME_MAX;
|
|
try testFilenameLimits(tmp.dir, &maxed_windows_filename);
|
|
} else if (native_os == .wasi) {
|
|
// On WASI, the maxed filename depends on the host OS, so in order for this test to
|
|
// 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).
|
|
const maxed_wasi_filename = [_]u8{'1'} ** 255;
|
|
try testFilenameLimits(tmp.dir, &maxed_wasi_filename);
|
|
} else {
|
|
const maxed_ascii_filename = [_]u8{'1'} ** std.fs.max_name_bytes;
|
|
try testFilenameLimits(tmp.dir, &maxed_ascii_filename);
|
|
}
|
|
}
|
|
|
|
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 = [_]posix.iovec_const{
|
|
.{
|
|
.base = line1,
|
|
.len = line1.len,
|
|
},
|
|
.{
|
|
.base = line2,
|
|
.len = line2.len,
|
|
},
|
|
};
|
|
var read_vecs = [_]posix.iovec{
|
|
.{
|
|
.base = &buf2,
|
|
.len = buf2.len,
|
|
},
|
|
.{
|
|
.base = &buf1,
|
|
.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 = [_]posix.iovec_const{
|
|
.{
|
|
.base = line1,
|
|
.len = line1.len,
|
|
},
|
|
.{
|
|
.base = line2,
|
|
.len = line2.len,
|
|
},
|
|
};
|
|
var read_vecs = [_]posix.iovec{
|
|
.{
|
|
.base = &buf2,
|
|
.len = buf2.len,
|
|
},
|
|
.{
|
|
.base = &buf1,
|
|
.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");
|
|
}
|
|
|
|
test "setEndPos" {
|
|
// https://github.com/ziglang/zig/issues/20747 (open fd does not have write permission)
|
|
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
|
|
if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23806
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
const file_name = "afile.txt";
|
|
try tmp.dir.writeFile(.{ .sub_path = file_name, .data = "ninebytes" });
|
|
const f = try tmp.dir.openFile(file_name, .{ .mode = .read_write });
|
|
defer f.close();
|
|
|
|
const initial_size = try f.getEndPos();
|
|
var buffer: [32]u8 = undefined;
|
|
|
|
{
|
|
try f.setEndPos(initial_size);
|
|
try testing.expectEqual(initial_size, try f.getEndPos());
|
|
try testing.expectEqual(initial_size, try f.preadAll(&buffer, 0));
|
|
try testing.expectEqualStrings("ninebytes", buffer[0..@intCast(initial_size)]);
|
|
}
|
|
|
|
{
|
|
const larger = initial_size + 4;
|
|
try f.setEndPos(larger);
|
|
try testing.expectEqual(larger, try f.getEndPos());
|
|
try testing.expectEqual(larger, try f.preadAll(&buffer, 0));
|
|
try testing.expectEqualStrings("ninebytes\x00\x00\x00\x00", buffer[0..@intCast(larger)]);
|
|
}
|
|
|
|
{
|
|
const smaller = initial_size - 5;
|
|
try f.setEndPos(smaller);
|
|
try testing.expectEqual(smaller, try f.getEndPos());
|
|
try testing.expectEqual(smaller, try f.preadAll(&buffer, 0));
|
|
try testing.expectEqualStrings("nine", buffer[0..@intCast(smaller)]);
|
|
}
|
|
|
|
try f.setEndPos(0);
|
|
try testing.expectEqual(0, try f.getEndPos());
|
|
try testing.expectEqual(0, try f.preadAll(&buffer, 0));
|
|
|
|
// Invalid file length should error gracefully. Actual limit is host
|
|
// and file-system dependent, but 1PB should fail on filesystems like
|
|
// EXT4 and NTFS. But XFS or Btrfs support up to 8EiB files.
|
|
f.setEndPos(0x4_0000_0000_0000) catch |err| if (err != error.FileTooBig) {
|
|
return err;
|
|
};
|
|
|
|
f.setEndPos(std.math.maxInt(u63)) catch |err| if (err != error.FileTooBig) {
|
|
return err;
|
|
};
|
|
|
|
try testing.expectError(error.FileTooBig, f.setEndPos(std.math.maxInt(u63) + 1));
|
|
try testing.expectError(error.FileTooBig, f.setEndPos(std.math.maxInt(u64)));
|
|
}
|
|
|
|
test "access file" {
|
|
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");
|
|
|
|
try ctx.dir.makePath(dir_path);
|
|
try testing.expectError(error.FileNotFound, ctx.dir.access(file_path, .{}));
|
|
|
|
try ctx.dir.writeFile(.{ .sub_path = file_path, .data = "" });
|
|
try ctx.dir.access(file_path, .{});
|
|
try ctx.dir.deleteTree(dir_path);
|
|
}
|
|
}.impl);
|
|
}
|
|
|
|
test "sendfile" {
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
try tmp.dir.makePath("os_test_tmp");
|
|
|
|
var dir = try tmp.dir.openDir("os_test_tmp", .{});
|
|
defer dir.close();
|
|
|
|
const line1 = "line1\n";
|
|
const line2 = "second line\n";
|
|
var vecs = [_]posix.iovec_const{
|
|
.{
|
|
.base = line1,
|
|
.len = line1.len,
|
|
},
|
|
.{
|
|
.base = line2,
|
|
.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 headers: [2][]const u8 = .{ header1, header2 };
|
|
var trailers: [2][]const u8 = .{ trailer1, trailer2 };
|
|
|
|
var written_buf: [100]u8 = undefined;
|
|
var file_reader = src_file.reader(&.{});
|
|
var fallback_buffer: [50]u8 = undefined;
|
|
var file_writer = dest_file.writer(&fallback_buffer);
|
|
try file_writer.interface.writeVecAll(&headers);
|
|
try file_reader.seekTo(1);
|
|
try testing.expectEqual(10, try file_writer.interface.sendFileAll(&file_reader, .limited(10)));
|
|
try file_writer.interface.writeVecAll(&trailers);
|
|
try file_writer.interface.flush();
|
|
const amt = try dest_file.preadAll(&written_buf, 0);
|
|
try testing.expectEqualStrings("header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n", written_buf[0..amt]);
|
|
}
|
|
|
|
test "copyRangeAll" {
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
try tmp.dir.makePath("os_test_tmp");
|
|
|
|
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);
|
|
try testing.expectEqualStrings(data, written_buf[0..amt]);
|
|
}
|
|
|
|
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");
|
|
|
|
try ctx.dir.writeFile(.{ .sub_path = src_file, .data = data });
|
|
defer ctx.dir.deleteFile(src_file) catch {};
|
|
|
|
try ctx.dir.copyFile(src_file, ctx.dir, dest_file, .{});
|
|
defer ctx.dir.deleteFile(dest_file) catch {};
|
|
|
|
try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, .{ .override_mode = File.default_mode });
|
|
defer ctx.dir.deleteFile(dest_file2) catch {};
|
|
|
|
try expectFileContents(ctx.dir, dest_file, data);
|
|
try expectFileContents(ctx.dir, dest_file2, data);
|
|
}
|
|
}.impl);
|
|
}
|
|
|
|
fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void {
|
|
const contents = try dir.readFileAlloc(file_path, testing.allocator, .limited(1000));
|
|
defer testing.allocator.free(contents);
|
|
|
|
try testing.expectEqualSlices(u8, data, contents);
|
|
}
|
|
|
|
test "AtomicFile" {
|
|
try testWithAllSupportedPathTypes(struct {
|
|
fn impl(ctx: *TestContext) !void {
|
|
const allocator = ctx.arena.allocator();
|
|
const test_out_file = try ctx.transformPath("tmp_atomic_file_test_dest.txt");
|
|
const test_content =
|
|
\\ hello!
|
|
\\ this is a test file
|
|
;
|
|
|
|
{
|
|
var buffer: [100]u8 = undefined;
|
|
var af = try ctx.dir.atomicFile(test_out_file, .{ .write_buffer = &buffer });
|
|
defer af.deinit();
|
|
try af.file_writer.interface.writeAll(test_content);
|
|
try af.finish();
|
|
}
|
|
const content = try ctx.dir.readFileAlloc(test_out_file, allocator, .limited(9999));
|
|
try testing.expectEqualStrings(test_content, content);
|
|
|
|
try ctx.dir.deleteFile(test_out_file);
|
|
}
|
|
}.impl);
|
|
}
|
|
|
|
test "open file with exclusive nonblocking lock twice" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
fn impl(ctx: *TestContext) !void {
|
|
const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
|
|
|
|
const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
|
|
defer file1.close();
|
|
|
|
const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
|
|
try testing.expectError(error.WouldBlock, file2);
|
|
}
|
|
}.impl);
|
|
}
|
|
|
|
test "open file with shared and exclusive nonblocking lock" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
fn impl(ctx: *TestContext) !void {
|
|
const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
|
|
|
|
const file1 = try ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
|
|
defer file1.close();
|
|
|
|
const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
|
|
try testing.expectError(error.WouldBlock, file2);
|
|
}
|
|
}.impl);
|
|
}
|
|
|
|
test "open file with exclusive and shared nonblocking lock" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
fn impl(ctx: *TestContext) !void {
|
|
const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
|
|
|
|
const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
|
|
defer file1.close();
|
|
|
|
const file2 = ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
|
|
try testing.expectError(error.WouldBlock, file2);
|
|
}
|
|
}.impl);
|
|
}
|
|
|
|
test "open file with exclusive lock twice, make sure second lock waits" {
|
|
if (builtin.single_threaded) return error.SkipZigTest;
|
|
|
|
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();
|
|
}
|
|
}.impl);
|
|
}
|
|
|
|
test "open file with exclusive nonblocking lock twice (absolute paths)" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
|
|
var random_bytes: [12]u8 = undefined;
|
|
std.crypto.random.bytes(&random_bytes);
|
|
|
|
var random_b64: [fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined;
|
|
_ = fs.base64_encoder.encode(&random_b64, &random_bytes);
|
|
|
|
const sub_path = random_b64 ++ "-zig-test-absolute-paths.txt";
|
|
|
|
const gpa = testing.allocator;
|
|
|
|
const cwd = try std.process.getCwdAlloc(gpa);
|
|
defer gpa.free(cwd);
|
|
|
|
const filename = try fs.path.resolve(gpa, &.{ cwd, sub_path });
|
|
defer gpa.free(filename);
|
|
|
|
defer fs.deleteFileAbsolute(filename) catch {}; // createFileAbsolute can leave files on failures
|
|
const file1 = try fs.createFileAbsolute(filename, .{
|
|
.lock = .exclusive,
|
|
.lock_nonblocking = true,
|
|
});
|
|
|
|
const file2 = fs.createFileAbsolute(filename, .{
|
|
.lock = .exclusive,
|
|
.lock_nonblocking = true,
|
|
});
|
|
file1.close();
|
|
try testing.expectError(error.WouldBlock, file2);
|
|
}
|
|
|
|
test "read from locked file" {
|
|
try testWithAllSupportedPathTypes(struct {
|
|
fn impl(ctx: *TestContext) !void {
|
|
const filename = try ctx.transformPath("read_lock_file_test.txt");
|
|
|
|
{
|
|
const f = try ctx.dir.createFile(filename, .{ .read = true });
|
|
defer f.close();
|
|
var buffer: [1]u8 = undefined;
|
|
_ = try f.readAll(&buffer);
|
|
}
|
|
{
|
|
const f = try ctx.dir.createFile(filename, .{
|
|
.read = true,
|
|
.lock = .exclusive,
|
|
});
|
|
defer f.close();
|
|
const f2 = try ctx.dir.openFile(filename, .{});
|
|
defer f2.close();
|
|
var buffer: [1]u8 = undefined;
|
|
if (builtin.os.tag == .windows) {
|
|
try std.testing.expectError(error.LockViolation, f2.readAll(&buffer));
|
|
} else {
|
|
try std.testing.expectEqual(0, f2.readAll(&buffer));
|
|
}
|
|
}
|
|
}
|
|
}.impl);
|
|
}
|
|
|
|
test "walker" {
|
|
var tmp = tmpDir(.{ .iterate = true });
|
|
defer tmp.cleanup();
|
|
|
|
// iteration order of walker is undefined, so need lookup maps to check against
|
|
|
|
const expected_paths = std.StaticStringMap(void).initComptime(.{
|
|
.{"dir1"},
|
|
.{"dir2"},
|
|
.{"dir3"},
|
|
.{"dir4"},
|
|
.{"dir3" ++ fs.path.sep_str ++ "sub1"},
|
|
.{"dir3" ++ fs.path.sep_str ++ "sub2"},
|
|
.{"dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1"},
|
|
});
|
|
|
|
const expected_basenames = std.StaticStringMap(void).initComptime(.{
|
|
.{"dir1"},
|
|
.{"dir2"},
|
|
.{"dir3"},
|
|
.{"dir4"},
|
|
.{"sub1"},
|
|
.{"sub2"},
|
|
.{"subsub1"},
|
|
});
|
|
|
|
for (expected_paths.keys()) |key| {
|
|
try tmp.dir.makePath(key);
|
|
}
|
|
|
|
var walker = try tmp.dir.walk(testing.allocator);
|
|
defer walker.deinit();
|
|
|
|
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: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)});
|
|
return err;
|
|
};
|
|
testing.expect(expected_paths.has(entry.path)) catch |err| {
|
|
std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)});
|
|
return err;
|
|
};
|
|
// make sure that the entry.dir is the containing dir
|
|
var entry_dir = try entry.dir.openDir(entry.basename, .{});
|
|
defer entry_dir.close();
|
|
num_walked += 1;
|
|
}
|
|
try testing.expectEqual(expected_paths.kvs.len, num_walked);
|
|
}
|
|
|
|
test "selective walker, skip entries that start with ." {
|
|
var tmp = tmpDir(.{ .iterate = true });
|
|
defer tmp.cleanup();
|
|
|
|
const paths_to_create: []const []const u8 = &.{
|
|
"dir1/foo/.git/ignored",
|
|
".hidden/bar",
|
|
"a/b/c",
|
|
"a/baz",
|
|
};
|
|
|
|
// iteration order of walker is undefined, so need lookup maps to check against
|
|
|
|
const expected_paths = std.StaticStringMap(usize).initComptime(.{
|
|
.{ "dir1", 1 },
|
|
.{ "dir1" ++ fs.path.sep_str ++ "foo", 2 },
|
|
.{ "a", 1 },
|
|
.{ "a" ++ fs.path.sep_str ++ "b", 2 },
|
|
.{ "a" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c", 3 },
|
|
.{ "a" ++ fs.path.sep_str ++ "baz", 2 },
|
|
});
|
|
|
|
const expected_basenames = std.StaticStringMap(void).initComptime(.{
|
|
.{"dir1"},
|
|
.{"foo"},
|
|
.{"a"},
|
|
.{"b"},
|
|
.{"c"},
|
|
.{"baz"},
|
|
});
|
|
|
|
for (paths_to_create) |path| {
|
|
try tmp.dir.makePath(path);
|
|
}
|
|
|
|
var walker = try tmp.dir.walkSelectively(testing.allocator);
|
|
defer walker.deinit();
|
|
|
|
var num_walked: usize = 0;
|
|
while (try walker.next()) |entry| {
|
|
if (entry.basename[0] == '.') continue;
|
|
testing.expect(expected_basenames.has(entry.basename)) catch |err| {
|
|
std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)});
|
|
return err;
|
|
};
|
|
testing.expect(expected_paths.has(entry.path)) catch |err| {
|
|
std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)});
|
|
return err;
|
|
};
|
|
|
|
testing.expectEqual(expected_paths.get(entry.path).?, walker.depth()) catch |err| {
|
|
std.debug.print("path reported unexpected depth: {f}, {d}, expected {d}\n", .{ std.ascii.hexEscape(entry.path, .lower), walker.depth(), expected_paths.get(entry.path).? });
|
|
return err;
|
|
};
|
|
|
|
if (entry.kind == .directory) {
|
|
try walker.enter(entry);
|
|
}
|
|
|
|
// make sure that the entry.dir is the containing dir
|
|
var entry_dir = try entry.dir.openDir(entry.basename, .{});
|
|
defer entry_dir.close();
|
|
num_walked += 1;
|
|
}
|
|
try testing.expectEqual(expected_paths.kvs.len, num_walked);
|
|
}
|
|
|
|
test "walker without fully iterating" {
|
|
var tmp = tmpDir(.{ .iterate = true });
|
|
defer tmp.cleanup();
|
|
|
|
var walker = try tmp.dir.walk(testing.allocator);
|
|
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.
|
|
|
|
try tmp.dir.makePath("a");
|
|
try tmp.dir.makePath("b");
|
|
|
|
var num_walked: usize = 0;
|
|
while (try walker.next()) |_| {
|
|
num_walked += 1;
|
|
break;
|
|
}
|
|
try testing.expectEqual(@as(usize, 1), num_walked);
|
|
}
|
|
|
|
test "'.' and '..' in fs.Dir functions" {
|
|
if (native_os == .windows and builtin.cpu.arch == .aarch64) {
|
|
// https://github.com/ziglang/zig/issues/17134
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
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(.{ .sub_path = update_path, .data = "something" });
|
|
const prev_status = try ctx.dir.updateFile(file_path, ctx.dir, update_path, .{});
|
|
try testing.expectEqual(fs.Dir.PrevStatus.stale, prev_status);
|
|
|
|
try ctx.dir.deleteDir(subdir_path);
|
|
}
|
|
}.impl);
|
|
}
|
|
|
|
test "'.' and '..' in absolute functions" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
const base_path = try tmp.dir.realpathAlloc(allocator, ".");
|
|
|
|
const subdir_path = try fs.path.join(allocator, &.{ base_path, "./subdir" });
|
|
try fs.makeDirAbsolute(subdir_path);
|
|
try fs.accessAbsolute(subdir_path, .{});
|
|
var created_subdir = try fs.openDirAbsolute(subdir_path, .{});
|
|
created_subdir.close();
|
|
|
|
const created_file_path = try fs.path.join(allocator, &.{ subdir_path, "../file" });
|
|
const created_file = try fs.createFileAbsolute(created_file_path, .{});
|
|
created_file.close();
|
|
try fs.accessAbsolute(created_file_path, .{});
|
|
|
|
const copied_file_path = try fs.path.join(allocator, &.{ subdir_path, "../copy" });
|
|
try fs.copyFileAbsolute(created_file_path, copied_file_path, .{});
|
|
const renamed_file_path = try fs.path.join(allocator, &.{ subdir_path, "../rename" });
|
|
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);
|
|
|
|
const update_file_path = try fs.path.join(allocator, &.{ subdir_path, "../update" });
|
|
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, .{});
|
|
try testing.expectEqual(fs.Dir.PrevStatus.stale, prev_status);
|
|
|
|
try fs.deleteDirAbsolute(subdir_path);
|
|
}
|
|
|
|
test "chmod" {
|
|
if (native_os == .windows or native_os == .wasi)
|
|
return error.SkipZigTest;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
const file = try tmp.dir.createFile("test_file", .{ .mode = 0o600 });
|
|
defer file.close();
|
|
try testing.expectEqual(@as(File.Mode, 0o600), (try file.stat()).mode & 0o7777);
|
|
|
|
try file.chmod(0o644);
|
|
try testing.expectEqual(@as(File.Mode, 0o644), (try file.stat()).mode & 0o7777);
|
|
|
|
try tmp.dir.makeDir("test_dir");
|
|
var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true });
|
|
defer dir.close();
|
|
|
|
try dir.chmod(0o700);
|
|
try testing.expectEqual(@as(File.Mode, 0o700), (try dir.stat()).mode & 0o7777);
|
|
}
|
|
|
|
test "chown" {
|
|
if (native_os == .windows or native_os == .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");
|
|
|
|
var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true });
|
|
defer dir.close();
|
|
try dir.chown(null, null);
|
|
}
|
|
|
|
test "delete a setAsCwd directory on Windows" {
|
|
if (native_os != .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();
|
|
}
|
|
|
|
test "invalid UTF-8/WTF-8 paths" {
|
|
const expected_err = switch (native_os) {
|
|
.wasi => error.InvalidUtf8,
|
|
.windows => error.InvalidWtf8,
|
|
else => return error.SkipZigTest,
|
|
};
|
|
|
|
try testWithAllSupportedPathTypes(struct {
|
|
fn impl(ctx: *TestContext) !void {
|
|
// This is both invalid UTF-8 and WTF-8, since \xFF is an invalid start byte
|
|
const invalid_path = try ctx.transformPath("\xFF");
|
|
|
|
try testing.expectError(expected_err, ctx.dir.openFile(invalid_path, .{}));
|
|
try testing.expectError(expected_err, ctx.dir.openFileZ(invalid_path, .{}));
|
|
|
|
try testing.expectError(expected_err, ctx.dir.createFile(invalid_path, .{}));
|
|
try testing.expectError(expected_err, ctx.dir.createFileZ(invalid_path, .{}));
|
|
|
|
try testing.expectError(expected_err, ctx.dir.makeDir(invalid_path));
|
|
try testing.expectError(expected_err, ctx.dir.makeDirZ(invalid_path));
|
|
|
|
try testing.expectError(expected_err, ctx.dir.makePath(invalid_path));
|
|
try testing.expectError(expected_err, ctx.dir.makeOpenPath(invalid_path, .{}));
|
|
|
|
try testing.expectError(expected_err, ctx.dir.openDir(invalid_path, .{}));
|
|
try testing.expectError(expected_err, ctx.dir.openDirZ(invalid_path, .{}));
|
|
|
|
try testing.expectError(expected_err, ctx.dir.deleteFile(invalid_path));
|
|
try testing.expectError(expected_err, ctx.dir.deleteFileZ(invalid_path));
|
|
|
|
try testing.expectError(expected_err, ctx.dir.deleteDir(invalid_path));
|
|
try testing.expectError(expected_err, ctx.dir.deleteDirZ(invalid_path));
|
|
|
|
try testing.expectError(expected_err, ctx.dir.rename(invalid_path, invalid_path));
|
|
try testing.expectError(expected_err, ctx.dir.renameZ(invalid_path, invalid_path));
|
|
|
|
try testing.expectError(expected_err, ctx.dir.symLink(invalid_path, invalid_path, .{}));
|
|
try testing.expectError(expected_err, ctx.dir.symLinkZ(invalid_path, invalid_path, .{}));
|
|
if (native_os == .wasi) {
|
|
try testing.expectError(expected_err, ctx.dir.symLinkWasi(invalid_path, invalid_path, .{}));
|
|
}
|
|
|
|
try testing.expectError(expected_err, ctx.dir.readLink(invalid_path, &[_]u8{}));
|
|
try testing.expectError(expected_err, ctx.dir.readLinkZ(invalid_path, &[_]u8{}));
|
|
if (native_os == .wasi) {
|
|
try testing.expectError(expected_err, ctx.dir.readLinkWasi(invalid_path, &[_]u8{}));
|
|
}
|
|
|
|
try testing.expectError(expected_err, ctx.dir.readFile(invalid_path, &[_]u8{}));
|
|
try testing.expectError(expected_err, ctx.dir.readFileAlloc(invalid_path, testing.allocator, .limited(0)));
|
|
|
|
try testing.expectError(expected_err, ctx.dir.deleteTree(invalid_path));
|
|
try testing.expectError(expected_err, ctx.dir.deleteTreeMinStackSize(invalid_path));
|
|
|
|
try testing.expectError(expected_err, ctx.dir.writeFile(.{ .sub_path = invalid_path, .data = "" }));
|
|
|
|
try testing.expectError(expected_err, ctx.dir.access(invalid_path, .{}));
|
|
try testing.expectError(expected_err, ctx.dir.accessZ(invalid_path, .{}));
|
|
|
|
try testing.expectError(expected_err, ctx.dir.updateFile(invalid_path, ctx.dir, invalid_path, .{}));
|
|
try testing.expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, .{}));
|
|
|
|
try testing.expectError(expected_err, ctx.dir.statFile(invalid_path));
|
|
|
|
if (native_os != .wasi) {
|
|
try testing.expectError(expected_err, ctx.dir.realpath(invalid_path, &[_]u8{}));
|
|
try testing.expectError(expected_err, ctx.dir.realpathZ(invalid_path, &[_]u8{}));
|
|
try testing.expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path));
|
|
}
|
|
|
|
try testing.expectError(expected_err, fs.rename(ctx.dir, invalid_path, ctx.dir, invalid_path));
|
|
try testing.expectError(expected_err, fs.renameZ(ctx.dir, invalid_path, ctx.dir, invalid_path));
|
|
|
|
if (native_os != .wasi and ctx.path_type != .relative) {
|
|
try testing.expectError(expected_err, fs.updateFileAbsolute(invalid_path, invalid_path, .{}));
|
|
try testing.expectError(expected_err, fs.copyFileAbsolute(invalid_path, invalid_path, .{}));
|
|
try testing.expectError(expected_err, fs.makeDirAbsolute(invalid_path));
|
|
try testing.expectError(expected_err, fs.makeDirAbsoluteZ(invalid_path));
|
|
try testing.expectError(expected_err, fs.deleteDirAbsolute(invalid_path));
|
|
try testing.expectError(expected_err, fs.deleteDirAbsoluteZ(invalid_path));
|
|
try testing.expectError(expected_err, fs.renameAbsolute(invalid_path, invalid_path));
|
|
try testing.expectError(expected_err, fs.renameAbsoluteZ(invalid_path, invalid_path));
|
|
try testing.expectError(expected_err, fs.openDirAbsolute(invalid_path, .{}));
|
|
try testing.expectError(expected_err, fs.openDirAbsoluteZ(invalid_path, .{}));
|
|
try testing.expectError(expected_err, fs.openFileAbsolute(invalid_path, .{}));
|
|
try testing.expectError(expected_err, fs.openFileAbsoluteZ(invalid_path, .{}));
|
|
try testing.expectError(expected_err, fs.accessAbsolute(invalid_path, .{}));
|
|
try testing.expectError(expected_err, fs.accessAbsoluteZ(invalid_path, .{}));
|
|
try testing.expectError(expected_err, fs.createFileAbsolute(invalid_path, .{}));
|
|
try testing.expectError(expected_err, fs.createFileAbsoluteZ(invalid_path, .{}));
|
|
try testing.expectError(expected_err, fs.deleteFileAbsolute(invalid_path));
|
|
try testing.expectError(expected_err, fs.deleteFileAbsoluteZ(invalid_path));
|
|
try testing.expectError(expected_err, fs.deleteTreeAbsolute(invalid_path));
|
|
var readlink_buf: [fs.max_path_bytes]u8 = undefined;
|
|
try testing.expectError(expected_err, fs.readLinkAbsolute(invalid_path, &readlink_buf));
|
|
try testing.expectError(expected_err, fs.readLinkAbsoluteZ(invalid_path, &readlink_buf));
|
|
try testing.expectError(expected_err, fs.symLinkAbsolute(invalid_path, invalid_path, .{}));
|
|
try testing.expectError(expected_err, fs.symLinkAbsoluteZ(invalid_path, invalid_path, .{}));
|
|
try testing.expectError(expected_err, fs.realpathAlloc(testing.allocator, invalid_path));
|
|
}
|
|
}
|
|
}.impl);
|
|
}
|
|
|
|
test "read file non vectored" {
|
|
var tmp_dir = testing.tmpDir(.{});
|
|
defer tmp_dir.cleanup();
|
|
|
|
const contents = "hello, world!\n";
|
|
|
|
const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });
|
|
defer file.close();
|
|
{
|
|
var file_writer: std.fs.File.Writer = .init(file, &.{});
|
|
try file_writer.interface.writeAll(contents);
|
|
try file_writer.interface.flush();
|
|
}
|
|
|
|
var file_reader: std.fs.File.Reader = .init(file, &.{});
|
|
|
|
var write_buffer: [100]u8 = undefined;
|
|
var w: std.Io.Writer = .fixed(&write_buffer);
|
|
|
|
var i: usize = 0;
|
|
while (true) {
|
|
i += file_reader.interface.stream(&w, .limited(3)) catch |err| switch (err) {
|
|
error.EndOfStream => break,
|
|
else => |e| return e,
|
|
};
|
|
}
|
|
try testing.expectEqualStrings(contents, w.buffered());
|
|
try testing.expectEqual(contents.len, i);
|
|
}
|
|
|
|
test "seek keeping partial buffer" {
|
|
var tmp_dir = testing.tmpDir(.{});
|
|
defer tmp_dir.cleanup();
|
|
|
|
const contents = "0123456789";
|
|
|
|
const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });
|
|
defer file.close();
|
|
{
|
|
var file_writer: std.fs.File.Writer = .init(file, &.{});
|
|
try file_writer.interface.writeAll(contents);
|
|
try file_writer.interface.flush();
|
|
}
|
|
|
|
var read_buffer: [3]u8 = undefined;
|
|
var file_reader: std.fs.File.Reader = .init(file, &read_buffer);
|
|
|
|
try testing.expectEqual(0, file_reader.logicalPos());
|
|
|
|
var buf: [4]u8 = undefined;
|
|
try file_reader.interface.readSliceAll(&buf);
|
|
|
|
if (file_reader.interface.bufferedLen() != 3) {
|
|
// Pass the test if the OS doesn't give us vectored reads.
|
|
return;
|
|
}
|
|
|
|
try testing.expectEqual(4, file_reader.logicalPos());
|
|
try testing.expectEqual(7, file_reader.pos);
|
|
try file_reader.seekTo(6);
|
|
try testing.expectEqual(6, file_reader.logicalPos());
|
|
try testing.expectEqual(7, file_reader.pos);
|
|
|
|
try testing.expectEqualStrings("0123", &buf);
|
|
|
|
const n = try file_reader.interface.readSliceShort(&buf);
|
|
try testing.expectEqual(4, n);
|
|
|
|
try testing.expectEqualStrings("6789", &buf);
|
|
}
|
|
|
|
test "seekBy" {
|
|
var tmp_dir = testing.tmpDir(.{});
|
|
defer tmp_dir.cleanup();
|
|
|
|
try tmp_dir.dir.writeFile(.{ .sub_path = "blah.txt", .data = "let's test seekBy" });
|
|
const f = try tmp_dir.dir.openFile("blah.txt", .{ .mode = .read_only });
|
|
defer f.close();
|
|
var reader = f.readerStreaming(&.{});
|
|
try reader.seekBy(2);
|
|
|
|
var buffer: [20]u8 = undefined;
|
|
const n = try reader.interface.readSliceShort(&buffer);
|
|
try testing.expectEqual(15, n);
|
|
try testing.expectEqualStrings("t's test seekBy", buffer[0..15]);
|
|
}
|
|
|
|
test "seekTo flushes buffered data" {
|
|
var tmp = std.testing.tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
const contents = "data";
|
|
|
|
const file = try tmp.dir.createFile("seek.bin", .{ .read = true });
|
|
defer file.close();
|
|
{
|
|
var buf: [16]u8 = undefined;
|
|
var file_writer = std.fs.File.writer(file, &buf);
|
|
|
|
try file_writer.interface.writeAll(contents);
|
|
try file_writer.seekTo(8);
|
|
try file_writer.interface.flush();
|
|
}
|
|
|
|
var read_buffer: [16]u8 = undefined;
|
|
var file_reader: std.fs.File.Reader = .init(file, &read_buffer);
|
|
|
|
var buf: [4]u8 = undefined;
|
|
try file_reader.interface.readSliceAll(&buf);
|
|
try std.testing.expectEqualStrings(contents, &buf);
|
|
}
|