mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
This is a breaking change to the API. Instead of the first path
implicitly being the current working directory, it now asserts that the
number of paths passed is greater than zero.
Importantly, it never calls getcwd(); instead, it can possibly return
".", or a series of "../". This changes the error set to only be
`error{OutOfMemory}`.
closes #13613
1380 lines
48 KiB
Zig
1380 lines
48 KiB
Zig
const std = @import("../std.zig");
|
|
const builtin = @import("builtin");
|
|
const testing = std.testing;
|
|
const os = std.os;
|
|
const fs = std.fs;
|
|
const mem = std.mem;
|
|
const wasi = std.os.wasi;
|
|
|
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
|
const Dir = std.fs.Dir;
|
|
const IterableDir = std.fs.IterableDir;
|
|
const File = std.fs.File;
|
|
const tmpDir = testing.tmpDir;
|
|
const tmpIterableDir = testing.tmpIterableDir;
|
|
|
|
test "Dir.readLink" {
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
// Create some targets
|
|
try tmp.dir.writeFile("file.txt", "nonsense");
|
|
try tmp.dir.makeDir("subdir");
|
|
|
|
{
|
|
// Create symbolic link by path
|
|
tmp.dir.symLink("file.txt", "symlink1", .{}) catch |err| switch (err) {
|
|
// Symlink requires admin privileges on windows, so this test can legitimately fail.
|
|
error.AccessDenied => return error.SkipZigTest,
|
|
else => return err,
|
|
};
|
|
try testReadLink(tmp.dir, "file.txt", "symlink1");
|
|
}
|
|
{
|
|
// Create symbolic link by path
|
|
tmp.dir.symLink("subdir", "symlink2", .{ .is_directory = true }) catch |err| switch (err) {
|
|
// Symlink requires admin privileges on windows, so this test can legitimately fail.
|
|
error.AccessDenied => return error.SkipZigTest,
|
|
else => return err,
|
|
};
|
|
try testReadLink(tmp.dir, "subdir", "symlink2");
|
|
}
|
|
}
|
|
|
|
fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {
|
|
var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
|
|
const given = try dir.readLink(symlink_path, buffer[0..]);
|
|
try testing.expect(mem.eql(u8, target_path, given));
|
|
}
|
|
|
|
test "accessAbsolute" {
|
|
if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
|
|
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
const base_path = blk: {
|
|
const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
|
|
break :blk try fs.realpathAlloc(allocator, relative_path);
|
|
};
|
|
|
|
try fs.accessAbsolute(base_path, .{});
|
|
}
|
|
|
|
test "openDirAbsolute" {
|
|
if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
|
|
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
try tmp.dir.makeDir("subdir");
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
const base_path = blk: {
|
|
const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..], "subdir" });
|
|
break :blk try fs.realpathAlloc(allocator, relative_path);
|
|
};
|
|
|
|
{
|
|
var dir = try fs.openDirAbsolute(base_path, .{});
|
|
defer dir.close();
|
|
}
|
|
|
|
for ([_][]const u8{ ".", ".." }) |sub_path| {
|
|
const dir_path = try fs.path.join(allocator, &[_][]const u8{ base_path, sub_path });
|
|
defer allocator.free(dir_path);
|
|
var dir = try fs.openDirAbsolute(dir_path, .{});
|
|
defer dir.close();
|
|
}
|
|
}
|
|
|
|
test "openDir cwd parent .." {
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
|
|
|
var dir = try fs.cwd().openDir("..", .{});
|
|
defer dir.close();
|
|
}
|
|
|
|
test "readLinkAbsolute" {
|
|
if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
|
|
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
|
|
|
|
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();
|
|
const allocator = arena.allocator();
|
|
|
|
const base_path = blk: {
|
|
const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
|
|
break :blk try fs.realpathAlloc(allocator, relative_path);
|
|
};
|
|
|
|
{
|
|
const target_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "file.txt" });
|
|
const symlink_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "symlink1" });
|
|
|
|
// Create symbolic link by path
|
|
fs.symLinkAbsolute(target_path, symlink_path, .{}) catch |err| switch (err) {
|
|
// Symlink requires admin privileges on windows, so this test can legitimately fail.
|
|
error.AccessDenied => return error.SkipZigTest,
|
|
else => return err,
|
|
};
|
|
try testReadLinkAbsolute(target_path, symlink_path);
|
|
}
|
|
{
|
|
const target_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "subdir" });
|
|
const symlink_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "symlink2" });
|
|
|
|
// Create symbolic link by path
|
|
fs.symLinkAbsolute(target_path, symlink_path, .{ .is_directory = true }) catch |err| switch (err) {
|
|
// Symlink requires admin privileges on windows, so this test can legitimately fail.
|
|
error.AccessDenied => return error.SkipZigTest,
|
|
else => return err,
|
|
};
|
|
try testReadLinkAbsolute(target_path, symlink_path);
|
|
}
|
|
}
|
|
|
|
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.expect(mem.eql(u8, target_path, given));
|
|
}
|
|
|
|
test "Dir.Iterator" {
|
|
var tmp_dir = tmpIterableDir(.{});
|
|
defer tmp_dir.cleanup();
|
|
|
|
// First, create a couple of entries to iterate over.
|
|
const file = try tmp_dir.iterable_dir.dir.createFile("some_file", .{});
|
|
file.close();
|
|
|
|
try tmp_dir.iterable_dir.dir.makeDir("some_dir");
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
var entries = std.ArrayList(IterableDir.Entry).init(allocator);
|
|
|
|
// Create iterator.
|
|
var iter = tmp_dir.iterable_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 });
|
|
}
|
|
|
|
try testing.expect(entries.items.len == 2); // 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 twice" {
|
|
var tmp_dir = tmpIterableDir(.{});
|
|
defer tmp_dir.cleanup();
|
|
|
|
// First, create a couple of entries to iterate over.
|
|
const file = try tmp_dir.iterable_dir.dir.createFile("some_file", .{});
|
|
file.close();
|
|
|
|
try tmp_dir.iterable_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.ArrayList(IterableDir.Entry).init(allocator);
|
|
|
|
// Create iterator.
|
|
var iter = tmp_dir.iterable_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 });
|
|
}
|
|
|
|
try testing.expect(entries.items.len == 2); // 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 = tmpIterableDir(.{});
|
|
defer tmp_dir.cleanup();
|
|
|
|
// First, create a couple of entries to iterate over.
|
|
const file = try tmp_dir.iterable_dir.dir.createFile("some_file", .{});
|
|
file.close();
|
|
|
|
try tmp_dir.iterable_dir.dir.makeDir("some_dir");
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
// Create iterator.
|
|
var iter = tmp_dir.iterable_dir.iterate();
|
|
|
|
var i: u8 = 0;
|
|
while (i < 2) : (i += 1) {
|
|
var entries = std.ArrayList(IterableDir.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.expect(entries.items.len == 2); // 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 iterable_subdir = try tmp.dir.makeOpenPathIterable("subdir", .{});
|
|
defer iterable_subdir.close();
|
|
|
|
var iterator = iterable_subdir.iterate();
|
|
|
|
// Create something to iterate over within the subdir
|
|
try tmp.dir.makePath("subdir/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 (builtin.os.tag == .linux) {
|
|
try std.testing.expectError(error.DirNotFound, iterator.nextLinux());
|
|
}
|
|
}
|
|
|
|
fn entryEql(lhs: IterableDir.Entry, rhs: IterableDir.Entry) bool {
|
|
return mem.eql(u8, lhs.name, rhs.name) and lhs.kind == rhs.kind;
|
|
}
|
|
|
|
fn contains(entries: *const std.ArrayList(IterableDir.Entry), el: IterableDir.Entry) bool {
|
|
for (entries.items) |entry| {
|
|
if (entryEql(entry, el)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
test "Dir.realpath smoke test" {
|
|
switch (builtin.os.tag) {
|
|
.linux, .windows, .macos, .ios, .watchos, .tvos, .solaris => {},
|
|
else => return error.SkipZigTest,
|
|
}
|
|
|
|
var tmp_dir = tmpDir(.{});
|
|
defer tmp_dir.cleanup();
|
|
|
|
var file = try tmp_dir.dir.createFile("test_file", .{ .lock = File.Lock.Shared });
|
|
// We need to close the file immediately as otherwise on Windows we'll end up
|
|
// with a sharing violation.
|
|
file.close();
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
const base_path = blk: {
|
|
const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp_dir.sub_path[0..] });
|
|
break :blk try fs.realpathAlloc(allocator, relative_path);
|
|
};
|
|
|
|
// First, test non-alloc version
|
|
{
|
|
var buf1: [fs.MAX_PATH_BYTES]u8 = undefined;
|
|
const file_path = try tmp_dir.dir.realpath("test_file", buf1[0..]);
|
|
const expected_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "test_file" });
|
|
|
|
try testing.expect(mem.eql(u8, file_path, expected_path));
|
|
}
|
|
|
|
// Next, test alloc version
|
|
{
|
|
const file_path = try tmp_dir.dir.realpathAlloc(allocator, "test_file");
|
|
const expected_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "test_file" });
|
|
|
|
try testing.expect(mem.eql(u8, file_path, expected_path));
|
|
}
|
|
}
|
|
|
|
test "readAllAlloc" {
|
|
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 file.readToEndAlloc(testing.allocator, 1024);
|
|
defer testing.allocator.free(buf1);
|
|
try testing.expect(buf1.len == 0);
|
|
|
|
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
|
|
const buf2 = try file.readToEndAlloc(testing.allocator, 1024);
|
|
defer testing.allocator.free(buf2);
|
|
try testing.expectEqual(write_buf.len, buf2.len);
|
|
try testing.expect(std.mem.eql(u8, write_buf, buf2));
|
|
try file.seekTo(0);
|
|
|
|
// max_bytes == file_size
|
|
const buf3 = try file.readToEndAlloc(testing.allocator, write_buf.len);
|
|
defer testing.allocator.free(buf3);
|
|
try testing.expectEqual(write_buf.len, buf3.len);
|
|
try testing.expect(std.mem.eql(u8, write_buf, buf3));
|
|
try file.seekTo(0);
|
|
|
|
// max_bytes < file_size
|
|
try testing.expectError(error.FileTooBig, file.readToEndAlloc(testing.allocator, write_buf.len - 1));
|
|
}
|
|
|
|
test "directory operations on files" {
|
|
var tmp_dir = tmpDir(.{});
|
|
defer tmp_dir.cleanup();
|
|
|
|
const test_file_name = "test_file";
|
|
|
|
var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true });
|
|
file.close();
|
|
|
|
try testing.expectError(error.PathAlreadyExists, tmp_dir.dir.makeDir(test_file_name));
|
|
try testing.expectError(error.NotDir, tmp_dir.dir.openDir(test_file_name, .{}));
|
|
try testing.expectError(error.NotDir, tmp_dir.dir.deleteDir(test_file_name));
|
|
|
|
switch (builtin.os.tag) {
|
|
.wasi, .freebsd, .netbsd, .openbsd, .dragonfly => {},
|
|
else => {
|
|
const absolute_path = try tmp_dir.dir.realpathAlloc(testing.allocator, test_file_name);
|
|
defer testing.allocator.free(absolute_path);
|
|
|
|
try testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(absolute_path));
|
|
try testing.expectError(error.NotDir, fs.deleteDirAbsolute(absolute_path));
|
|
},
|
|
}
|
|
|
|
// ensure the file still exists and is a file as a sanity check
|
|
file = try tmp_dir.dir.openFile(test_file_name, .{});
|
|
const stat = try file.stat();
|
|
try testing.expect(stat.kind == .File);
|
|
file.close();
|
|
}
|
|
|
|
test "file operations on directories" {
|
|
// TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759
|
|
if (builtin.os.tag == .freebsd) return error.SkipZigTest;
|
|
|
|
var tmp_dir = tmpDir(.{});
|
|
defer tmp_dir.cleanup();
|
|
|
|
const test_dir_name = "test_dir";
|
|
|
|
try tmp_dir.dir.makeDir(test_dir_name);
|
|
|
|
try testing.expectError(error.IsDir, tmp_dir.dir.createFile(test_dir_name, .{}));
|
|
try testing.expectError(error.IsDir, tmp_dir.dir.deleteFile(test_dir_name));
|
|
switch (builtin.os.tag) {
|
|
// NetBSD does not error when reading a directory.
|
|
.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, tmp_dir.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, tmp_dir.dir.openFile(test_dir_name, .{ .mode = .read_write }));
|
|
|
|
switch (builtin.os.tag) {
|
|
.wasi, .freebsd, .netbsd, .openbsd, .dragonfly => {},
|
|
else => {
|
|
const absolute_path = try tmp_dir.dir.realpathAlloc(testing.allocator, test_dir_name);
|
|
defer testing.allocator.free(absolute_path);
|
|
|
|
try testing.expectError(error.IsDir, fs.createFileAbsolute(absolute_path, .{}));
|
|
try testing.expectError(error.IsDir, fs.deleteFileAbsolute(absolute_path));
|
|
},
|
|
}
|
|
|
|
// ensure the directory still exists as a sanity check
|
|
var dir = try tmp_dir.dir.openDir(test_dir_name, .{});
|
|
dir.close();
|
|
}
|
|
|
|
test "deleteDir" {
|
|
var tmp_dir = tmpDir(.{});
|
|
defer tmp_dir.cleanup();
|
|
|
|
// deleting a non-existent directory
|
|
try testing.expectError(error.FileNotFound, tmp_dir.dir.deleteDir("test_dir"));
|
|
|
|
var dir = try tmp_dir.dir.makeOpenPath("test_dir", .{});
|
|
var file = try dir.createFile("test_file", .{});
|
|
file.close();
|
|
dir.close();
|
|
|
|
// deleting a non-empty directory
|
|
// TODO: Re-enable this check on Windows, see https://github.com/ziglang/zig/issues/5537
|
|
if (builtin.os.tag != .windows) {
|
|
try testing.expectError(error.DirNotEmpty, tmp_dir.dir.deleteDir("test_dir"));
|
|
}
|
|
|
|
dir = try tmp_dir.dir.openDir("test_dir", .{});
|
|
try dir.deleteFile("test_file");
|
|
dir.close();
|
|
|
|
// deleting an empty directory
|
|
try tmp_dir.dir.deleteDir("test_dir");
|
|
}
|
|
|
|
test "Dir.rename files" {
|
|
var tmp_dir = tmpDir(.{});
|
|
defer tmp_dir.cleanup();
|
|
|
|
try testing.expectError(error.FileNotFound, tmp_dir.dir.rename("missing_file_name", "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 tmp_dir.dir.rename(test_file_name, 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, .{});
|
|
file.close();
|
|
|
|
// Rename to self succeeds
|
|
try tmp_dir.dir.rename(renamed_test_file_name, renamed_test_file_name);
|
|
|
|
// Rename to existing file succeeds
|
|
var existing_file = try tmp_dir.dir.createFile("existing_file", .{ .read = true });
|
|
existing_file.close();
|
|
try tmp_dir.dir.rename(renamed_test_file_name, "existing_file");
|
|
|
|
try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(renamed_test_file_name, .{}));
|
|
file = try tmp_dir.dir.openFile("existing_file", .{});
|
|
file.close();
|
|
}
|
|
|
|
test "Dir.rename directories" {
|
|
var tmp_dir = tmpDir(.{});
|
|
defer tmp_dir.cleanup();
|
|
|
|
// Renaming directories
|
|
try tmp_dir.dir.makeDir("test_dir");
|
|
try tmp_dir.dir.rename("test_dir", "test_dir_renamed");
|
|
|
|
// Ensure the directory was renamed
|
|
try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir", .{}));
|
|
var dir = try tmp_dir.dir.openDir("test_dir_renamed", .{});
|
|
|
|
// Put a file in the directory
|
|
var file = try dir.createFile("test_file", .{ .read = true });
|
|
file.close();
|
|
dir.close();
|
|
|
|
try tmp_dir.dir.rename("test_dir_renamed", "test_dir_renamed_again");
|
|
|
|
// Ensure the directory was renamed and the file still exists in it
|
|
try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir_renamed", .{}));
|
|
dir = try tmp_dir.dir.openDir("test_dir_renamed_again", .{});
|
|
file = try dir.openFile("test_file", .{});
|
|
file.close();
|
|
dir.close();
|
|
}
|
|
|
|
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;
|
|
|
|
var tmp_dir = testing.tmpDir(.{});
|
|
defer tmp_dir.cleanup();
|
|
|
|
try tmp_dir.dir.makeDir("test_dir");
|
|
try tmp_dir.dir.makeDir("target_dir");
|
|
try tmp_dir.dir.rename("test_dir", "target_dir");
|
|
|
|
// Ensure the directory was renamed
|
|
try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir", .{}));
|
|
var dir = try tmp_dir.dir.openDir("target_dir", .{});
|
|
dir.close();
|
|
}
|
|
|
|
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;
|
|
|
|
var tmp_dir = testing.tmpDir(.{});
|
|
defer tmp_dir.cleanup();
|
|
|
|
try tmp_dir.dir.makeDir("test_dir");
|
|
|
|
var target_dir = try tmp_dir.dir.makeOpenPath("target_dir", .{});
|
|
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, tmp_dir.dir.rename("test_dir", "target_dir"));
|
|
|
|
// Ensure the directory was not renamed
|
|
var dir = try tmp_dir.dir.openDir("test_dir", .{});
|
|
dir.close();
|
|
}
|
|
|
|
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;
|
|
|
|
var tmp_dir = tmpDir(.{});
|
|
defer tmp_dir.cleanup();
|
|
|
|
var file = try tmp_dir.dir.createFile("test_file", .{ .read = true });
|
|
file.close();
|
|
try tmp_dir.dir.makeDir("test_dir");
|
|
try testing.expectError(error.IsDir, tmp_dir.dir.rename("test_file", "test_dir"));
|
|
try testing.expectError(error.NotDir, tmp_dir.dir.rename("test_dir", "test_file"));
|
|
}
|
|
|
|
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 (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
|
|
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
|
|
|
|
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 = blk: {
|
|
const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp_dir.sub_path[0..] });
|
|
break :blk try fs.realpathAlloc(allocator, relative_path);
|
|
};
|
|
|
|
try testing.expectError(error.FileNotFound, fs.renameAbsolute(
|
|
try fs.path.join(allocator, &[_][]const u8{ base_path, "missing_file_name" }),
|
|
try fs.path.join(allocator, &[_][]const u8{ 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, &[_][]const u8{ base_path, test_file_name }),
|
|
try fs.path.join(allocator, &[_][]const u8{ 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.expect(stat.kind == .File);
|
|
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, &[_][]const u8{ base_path, test_dir_name }),
|
|
try fs.path.join(allocator, &[_][]const u8{ 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 (builtin.os.tag == .wasi) return error.SkipZigTest;
|
|
|
|
const self_exe_file = try std.fs.openSelfExe(.{});
|
|
self_exe_file.close();
|
|
}
|
|
|
|
test "makePath, put some files in it, deleteTree" {
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
try tmp.dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");
|
|
try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");
|
|
try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");
|
|
try tmp.dir.deleteTree("os_test_tmp");
|
|
if (tmp.dir.openDir("os_test_tmp", .{})) |dir| {
|
|
_ = dir;
|
|
@panic("expected error");
|
|
} else |err| {
|
|
try testing.expect(err == error.FileNotFound);
|
|
}
|
|
}
|
|
|
|
test "makePath, put some files in it, deleteTreeMinStackSize" {
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
try tmp.dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");
|
|
try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");
|
|
try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");
|
|
try tmp.dir.deleteTreeMinStackSize("os_test_tmp");
|
|
if (tmp.dir.openDir("os_test_tmp", .{})) |dir| {
|
|
_ = dir;
|
|
@panic("expected error");
|
|
} else |err| {
|
|
try testing.expect(err == error.FileNotFound);
|
|
}
|
|
}
|
|
|
|
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"));
|
|
}
|
|
|
|
fn testFilenameLimits(iterable_dir: IterableDir, 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.dir.makeOpenPath(maxed_filename, .{});
|
|
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
|
|
try iterable_dir.dir.deleteTree(maxed_filename);
|
|
}
|
|
|
|
test "max file name component lengths" {
|
|
var tmp = tmpIterableDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
if (builtin.os.tag == .windows) {
|
|
// € is the character with the largest codepoint that is encoded as a single u16 in UTF-16,
|
|
// so Windows allows for NAME_MAX of them
|
|
const maxed_windows_filename = ("€".*) ** std.os.windows.NAME_MAX;
|
|
try testFilenameLimits(tmp.iterable_dir, &maxed_windows_filename);
|
|
} else if (builtin.os.tag == .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.iterable_dir, &maxed_wasi_filename);
|
|
} else {
|
|
const maxed_ascii_filename = [_]u8{'1'} ** std.fs.MAX_NAME_BYTES;
|
|
try testFilenameLimits(tmp.iterable_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 = [_]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");
|
|
}
|
|
|
|
test "access file" {
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
try tmp.dir.makePath("os_test_tmp");
|
|
if (tmp.dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{})) |ok| {
|
|
_ = ok;
|
|
@panic("expected error");
|
|
} else |err| {
|
|
try testing.expect(err == error.FileNotFound);
|
|
}
|
|
|
|
try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", "");
|
|
try tmp.dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{});
|
|
try tmp.dir.deleteTree("os_test_tmp");
|
|
}
|
|
|
|
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);
|
|
try testing.expect(mem.eql(u8, written_buf[0..amt], "header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n"));
|
|
}
|
|
|
|
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);
|
|
try testing.expect(mem.eql(u8, written_buf[0..amt], data));
|
|
}
|
|
|
|
test "fs.copyFile" {
|
|
const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";
|
|
const src_file = "tmp_test_copy_file.txt";
|
|
const dest_file = "tmp_test_copy_file2.txt";
|
|
const dest_file2 = "tmp_test_copy_file3.txt";
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
try tmp.dir.writeFile(src_file, data);
|
|
defer tmp.dir.deleteFile(src_file) catch {};
|
|
|
|
try tmp.dir.copyFile(src_file, tmp.dir, dest_file, .{});
|
|
defer tmp.dir.deleteFile(dest_file) catch {};
|
|
|
|
try tmp.dir.copyFile(src_file, tmp.dir, dest_file2, .{ .override_mode = File.default_mode });
|
|
defer tmp.dir.deleteFile(dest_file2) catch {};
|
|
|
|
try expectFileContents(tmp.dir, dest_file, data);
|
|
try expectFileContents(tmp.dir, dest_file2, data);
|
|
}
|
|
|
|
fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void {
|
|
const contents = try dir.readFileAlloc(testing.allocator, file_path, 1000);
|
|
defer testing.allocator.free(contents);
|
|
|
|
try testing.expectEqualSlices(u8, data, contents);
|
|
}
|
|
|
|
test "AtomicFile" {
|
|
const test_out_file = "tmp_atomic_file_test_dest.txt";
|
|
const test_content =
|
|
\\ hello!
|
|
\\ this is a test file
|
|
;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
{
|
|
var af = try tmp.dir.atomicFile(test_out_file, .{});
|
|
defer af.deinit();
|
|
try af.file.writeAll(test_content);
|
|
try af.finish();
|
|
}
|
|
const content = try tmp.dir.readFileAlloc(testing.allocator, test_out_file, 9999);
|
|
defer testing.allocator.free(content);
|
|
try testing.expect(mem.eql(u8, content, test_content));
|
|
|
|
try tmp.dir.deleteFile(test_out_file);
|
|
}
|
|
|
|
test "realpath" {
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
|
|
|
var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
|
try testing.expectError(error.FileNotFound, fs.realpath("definitely_bogus_does_not_exist1234", &buf));
|
|
}
|
|
|
|
test "open file with exclusive nonblocking lock twice" {
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
|
|
|
const filename = "file_nonblocking_lock_test.txt";
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
const file1 = try tmp.dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
|
|
defer file1.close();
|
|
|
|
const file2 = tmp.dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
|
|
try testing.expectError(error.WouldBlock, file2);
|
|
}
|
|
|
|
test "open file with shared and exclusive nonblocking lock" {
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
|
|
|
const filename = "file_nonblocking_lock_test.txt";
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
const file1 = try tmp.dir.createFile(filename, .{ .lock = .Shared, .lock_nonblocking = true });
|
|
defer file1.close();
|
|
|
|
const file2 = tmp.dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
|
|
try testing.expectError(error.WouldBlock, file2);
|
|
}
|
|
|
|
test "open file with exclusive and shared nonblocking lock" {
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
|
|
|
const filename = "file_nonblocking_lock_test.txt";
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
const file1 = try tmp.dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
|
|
defer file1.close();
|
|
|
|
const file2 = tmp.dir.createFile(filename, .{ .lock = .Shared, .lock_nonblocking = true });
|
|
try testing.expectError(error.WouldBlock, file2);
|
|
}
|
|
|
|
test "open file with exclusive lock twice, make sure second lock waits" {
|
|
if (builtin.single_threaded) return error.SkipZigTest;
|
|
|
|
if (std.io.is_async) {
|
|
// This test starts its own threads and is not compatible with async I/O.
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
const filename = "file_lock_test.txt";
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
const file = try tmp.dir.createFile(filename, .{ .lock = .Exclusive });
|
|
errdefer file.close();
|
|
|
|
const S = struct {
|
|
fn checkFn(dir: *fs.Dir, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void {
|
|
started.set();
|
|
const file1 = try dir.createFile(filename, .{ .lock = .Exclusive });
|
|
|
|
locked.set();
|
|
file1.close();
|
|
}
|
|
};
|
|
|
|
var started = std.Thread.ResetEvent{};
|
|
var locked = std.Thread.ResetEvent{};
|
|
|
|
const t = try std.Thread.spawn(.{}, S.checkFn, .{
|
|
&tmp.dir,
|
|
&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();
|
|
}
|
|
|
|
test "open file with exclusive nonblocking lock twice (absolute paths)" {
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
|
|
|
const allocator = testing.allocator;
|
|
|
|
const cwd = try std.process.getCwdAlloc(allocator);
|
|
defer allocator.free(cwd);
|
|
const file_paths: [2][]const u8 = .{ cwd, "zig-test-absolute-paths.txt" };
|
|
const filename = try fs.path.resolve(allocator, &file_paths);
|
|
defer allocator.free(filename);
|
|
|
|
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);
|
|
|
|
try fs.deleteFileAbsolute(filename);
|
|
}
|
|
|
|
test "walker" {
|
|
if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
|
|
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
|
|
|
|
var tmp = tmpIterableDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
// iteration order of walker is undefined, so need lookup maps to check against
|
|
|
|
const expected_paths = std.ComptimeStringMap(void, .{
|
|
.{"dir1"},
|
|
.{"dir2"},
|
|
.{"dir3"},
|
|
.{"dir4"},
|
|
.{"dir3" ++ std.fs.path.sep_str ++ "sub1"},
|
|
.{"dir3" ++ std.fs.path.sep_str ++ "sub2"},
|
|
.{"dir3" ++ std.fs.path.sep_str ++ "sub2" ++ std.fs.path.sep_str ++ "subsub1"},
|
|
});
|
|
|
|
const expected_basenames = std.ComptimeStringMap(void, .{
|
|
.{"dir1"},
|
|
.{"dir2"},
|
|
.{"dir3"},
|
|
.{"dir4"},
|
|
.{"sub1"},
|
|
.{"sub2"},
|
|
.{"subsub1"},
|
|
});
|
|
|
|
for (expected_paths.kvs) |kv| {
|
|
try tmp.iterable_dir.dir.makePath(kv.key);
|
|
}
|
|
|
|
var walker = try tmp.iterable_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: {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;
|
|
};
|
|
// 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" {
|
|
if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
|
|
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
|
|
|
|
var tmp = tmpIterableDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
var walker = try tmp.iterable_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.iterable_dir.dir.makePath("a");
|
|
try tmp.iterable_dir.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 (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
|
|
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
try tmp.dir.makeDir("./subdir");
|
|
try tmp.dir.access("./subdir", .{});
|
|
var created_subdir = try tmp.dir.openDir("./subdir", .{});
|
|
created_subdir.close();
|
|
|
|
const created_file = try tmp.dir.createFile("./subdir/../file", .{});
|
|
created_file.close();
|
|
try tmp.dir.access("./subdir/../file", .{});
|
|
|
|
try tmp.dir.copyFile("./subdir/../file", tmp.dir, "./subdir/../copy", .{});
|
|
try tmp.dir.rename("./subdir/../copy", "./subdir/../rename");
|
|
const renamed_file = try tmp.dir.openFile("./subdir/../rename", .{});
|
|
renamed_file.close();
|
|
try tmp.dir.deleteFile("./subdir/../rename");
|
|
|
|
try tmp.dir.writeFile("./subdir/../update", "something");
|
|
const prev_status = try tmp.dir.updateFile("./subdir/../file", tmp.dir, "./subdir/../update", .{});
|
|
try testing.expectEqual(fs.PrevStatus.stale, prev_status);
|
|
|
|
try tmp.dir.deleteDir("./subdir");
|
|
}
|
|
|
|
test ". and .. in absolute functions" {
|
|
if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
|
|
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
const base_path = blk: {
|
|
const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
|
|
break :blk try fs.realpathAlloc(allocator, relative_path);
|
|
};
|
|
|
|
const subdir_path = try fs.path.join(allocator, &[_][]const u8{ 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, &[_][]const u8{ 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, &[_][]const u8{ subdir_path, "../copy" });
|
|
try fs.copyFileAbsolute(created_file_path, copied_file_path, .{});
|
|
const renamed_file_path = try fs.path.join(allocator, &[_][]const u8{ 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, &[_][]const u8{ 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.PrevStatus.stale, prev_status);
|
|
|
|
try fs.deleteDirAbsolute(subdir_path);
|
|
}
|
|
|
|
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();
|
|
try testing.expect((try file.stat()).mode & 0o7777 == 0o600);
|
|
|
|
try file.chmod(0o644);
|
|
try testing.expect((try file.stat()).mode & 0o7777 == 0o644);
|
|
|
|
try tmp.dir.makeDir("test_dir");
|
|
var iterable_dir = try tmp.dir.openIterableDir("test_dir", .{});
|
|
defer iterable_dir.close();
|
|
|
|
try iterable_dir.chmod(0o700);
|
|
try testing.expect((try iterable_dir.dir.stat()).mode & 0o7777 == 0o700);
|
|
}
|
|
|
|
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");
|
|
|
|
var iterable_dir = try tmp.dir.openIterableDir("test_dir", .{});
|
|
defer iterable_dir.close();
|
|
try iterable_dir.chown(null, null);
|
|
}
|
|
|
|
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();
|
|
try testing.expect(metadata.kind() == .File);
|
|
try testing.expect(metadata.size() == 0);
|
|
_ = 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));
|
|
}
|