zig/lib/std/fs/test.zig
Andrew Kelley 53d011fa1a (breaking) std.time fixups and API changes
Remove the constants that assume a base unit in favor of explicit
x_per_y constants.

nanosecond calendar timestamps now use i128 for the type. This affects
fs.File.Stat, std.time.nanoTimestamp, and fs.File.updateTimes.

calendar timestamps are now signed, because the value can be less than
the epoch (the user can set their computer time to whatever they wish).

implement std.os.clock_gettime for Windows when clock id is
CLOCK_CALENDAR.
2020-05-24 21:40:08 -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));
}
}