zig/lib/std/fs/test.zig
Jonathan S ab3931fa95 Prefer Files to paths in std.debug. Additionally [breaking] add a flags parameter to openSelfExe and stop exporting openElfDebugInfo.
This should save a call to readlink in openSelfDebugInfo and support executables in overlong paths on Linux.
2020-05-29 18:27:39 -04:00

201 lines
6.2 KiB
Zig

const std = @import("../std.zig");
const builtin = std.builtin;
const fs = std.fs;
const File = std.fs.File;
test "openSelfExe" {
if (builtin.os.tag == .wasi) return error.SkipZigTest;
const self_exe_file = try std.fs.openSelfExe(.{});
self_exe_file.close();
}
const FILE_LOCK_TEST_SLEEP_TIME = 5 * std.time.ns_per_ms;
test "open file with exclusive nonblocking lock twice" {
if (builtin.os.tag == .wasi) return error.SkipZigTest;
const dir = fs.cwd();
const filename = "file_nonblocking_lock_test.txt";
const file1 = try dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
defer file1.close();
const file2 = dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
std.debug.assert(std.meta.eql(file2, error.WouldBlock));
dir.deleteFile(filename) catch |err| switch (err) {
error.FileNotFound => {},
else => return err,
};
}
test "open file with lock twice, make sure it wasn't open at the same time" {
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 contexts = [_]FileLockTestContext{
.{ .filename = filename, .create = true, .lock = .Exclusive },
.{ .filename = filename, .create = true, .lock = .Exclusive },
};
try run_lock_file_test(&contexts);
// Check for an error
var was_error = false;
for (contexts) |context, idx| {
if (context.err) |err| {
was_error = true;
std.debug.warn("\nError in context {}: {}\n", .{ idx, err });
}
}
if (was_error) builtin.panic("There was an error in contexts", null);
std.debug.assert(!contexts[0].overlaps(&contexts[1]));
fs.cwd().deleteFile(filename) catch |err| switch (err) {
error.FileNotFound => {},
else => return err,
};
}
test "create file, lock and read from multiple process at once" {
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;
}
if (true) {
// https://github.com/ziglang/zig/issues/5006
return error.SkipZigTest;
}
const filename = "file_read_lock_test.txt";
const filedata = "Hello, world!\n";
try fs.cwd().writeFile(filename, filedata);
var contexts = [_]FileLockTestContext{
.{ .filename = filename, .create = false, .lock = .Shared },
.{ .filename = filename, .create = false, .lock = .Shared },
.{ .filename = filename, .create = false, .lock = .Exclusive },
};
try run_lock_file_test(&contexts);
var was_error = false;
for (contexts) |context, idx| {
if (context.err) |err| {
was_error = true;
std.debug.warn("\nError in context {}: {}\n", .{ idx, err });
}
}
if (was_error) builtin.panic("There was an error in contexts", null);
std.debug.assert(contexts[0].overlaps(&contexts[1]));
std.debug.assert(!contexts[2].overlaps(&contexts[0]));
std.debug.assert(!contexts[2].overlaps(&contexts[1]));
if (contexts[0].bytes_read.? != filedata.len) {
std.debug.warn("\n bytes_read: {}, expected: {} \n", .{ contexts[0].bytes_read, filedata.len });
}
std.debug.assert(contexts[0].bytes_read.? == filedata.len);
std.debug.assert(contexts[1].bytes_read.? == filedata.len);
fs.cwd().deleteFile(filename) catch |err| switch (err) {
error.FileNotFound => {},
else => return err,
};
}
test "open file with exclusive nonblocking lock twice (absolute paths)" {
if (builtin.os.tag == .wasi) return error.SkipZigTest;
const allocator = std.testing.allocator;
const file_paths: [1][]const u8 = .{"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();
std.testing.expectError(error.WouldBlock, file2);
try fs.deleteFileAbsolute(filename);
}
const FileLockTestContext = struct {
filename: []const u8,
pid: if (builtin.os.tag == .windows) ?void else ?std.os.pid_t = null,
// use file.createFile
create: bool,
// the type of lock to use
lock: File.Lock,
// Output variables
err: ?(File.OpenError || std.os.ReadError) = null,
start_time: i64 = 0,
end_time: i64 = 0,
bytes_read: ?usize = null,
fn overlaps(self: *const @This(), other: *const @This()) bool {
return (self.start_time < other.end_time) and (self.end_time > other.start_time);
}
fn run(ctx: *@This()) void {
var file: File = undefined;
if (ctx.create) {
file = fs.cwd().createFile(ctx.filename, .{ .lock = ctx.lock }) catch |err| {
ctx.err = err;
return;
};
} else {
file = fs.cwd().openFile(ctx.filename, .{ .lock = ctx.lock }) catch |err| {
ctx.err = err;
return;
};
}
defer file.close();
ctx.start_time = std.time.milliTimestamp();
if (!ctx.create) {
var buffer: [100]u8 = undefined;
ctx.bytes_read = 0;
while (true) {
const amt = file.read(buffer[0..]) catch |err| {
ctx.err = err;
return;
};
if (amt == 0) break;
ctx.bytes_read.? += amt;
}
}
std.time.sleep(FILE_LOCK_TEST_SLEEP_TIME);
ctx.end_time = std.time.milliTimestamp();
}
};
fn run_lock_file_test(contexts: []FileLockTestContext) !void {
var threads = std.ArrayList(*std.Thread).init(std.testing.allocator);
defer {
for (threads.items) |thread| {
thread.wait();
}
threads.deinit();
}
for (contexts) |*ctx, idx| {
try threads.append(try std.Thread.spawn(ctx, FileLockTestContext.run));
}
}