mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 14:23:09 +00:00
std: Make file locking tests less fragile
This commit is contained in:
parent
9f1639a6bb
commit
b9391c9564
@ -654,30 +654,61 @@ test "realpath" {
|
|||||||
testing.expectError(error.FileNotFound, fs.realpath("definitely_bogus_does_not_exist1234", &buf));
|
testing.expectError(error.FileNotFound, fs.realpath("definitely_bogus_does_not_exist1234", &buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
const FILE_LOCK_TEST_SLEEP_TIME = 5 * std.time.ns_per_ms;
|
|
||||||
|
|
||||||
test "open file with exclusive nonblocking lock twice" {
|
test "open file with exclusive nonblocking lock twice" {
|
||||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||||
|
|
||||||
// TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759
|
// TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759
|
||||||
if (builtin.os.tag == .freebsd) return error.SkipZigTest;
|
if (builtin.os.tag == .freebsd) return error.SkipZigTest;
|
||||||
|
|
||||||
const dir = fs.cwd();
|
|
||||||
const filename = "file_nonblocking_lock_test.txt";
|
const filename = "file_nonblocking_lock_test.txt";
|
||||||
|
|
||||||
const file1 = try dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
|
var tmp = tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
|
|
||||||
|
const file1 = try tmp.dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
|
||||||
defer file1.close();
|
defer file1.close();
|
||||||
|
|
||||||
const file2 = dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
|
const file2 = tmp.dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
|
||||||
std.debug.assert(std.meta.eql(file2, error.WouldBlock));
|
testing.expectError(error.WouldBlock, file2);
|
||||||
|
|
||||||
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" {
|
test "open file with shared and exclusive nonblocking lock" {
|
||||||
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||||
|
|
||||||
|
// TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759
|
||||||
|
if (builtin.os.tag == .freebsd) 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 });
|
||||||
|
testing.expectError(error.WouldBlock, file2);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "open file with exclusive and shared nonblocking lock" {
|
||||||
|
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||||
|
|
||||||
|
// TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759
|
||||||
|
if (builtin.os.tag == .freebsd) 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 });
|
||||||
|
testing.expectError(error.WouldBlock, file2);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "open file with exclusive lock twice, make sure it waits" {
|
||||||
if (builtin.single_threaded) return error.SkipZigTest;
|
if (builtin.single_threaded) return error.SkipZigTest;
|
||||||
|
|
||||||
if (std.io.is_async) {
|
if (std.io.is_async) {
|
||||||
@ -686,78 +717,36 @@ test "open file with lock twice, make sure it wasn't open at the same time" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const filename = "file_lock_test.txt";
|
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 tmp = tmpDir(.{});
|
||||||
var was_error = false;
|
defer tmp.cleanup();
|
||||||
for (contexts) |context, idx| {
|
|
||||||
if (context.err) |err| {
|
const file = try tmp.dir.createFile(filename, .{ .lock = .Exclusive });
|
||||||
was_error = true;
|
errdefer file.close();
|
||||||
std.debug.warn("\nError in context {}: {}\n", .{ idx, err });
|
|
||||||
|
const S = struct {
|
||||||
|
const C = struct { dir: *fs.Dir, evt: *std.ResetEvent };
|
||||||
|
fn checkFn(ctx: C) !void {
|
||||||
|
const file1 = try ctx.dir.createFile(filename, .{ .lock = .Exclusive });
|
||||||
|
defer file1.close();
|
||||||
|
ctx.evt.set();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
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 evt = std.ResetEvent.init();
|
||||||
|
defer evt.deinit();
|
||||||
|
|
||||||
var was_error = false;
|
const t = try std.Thread.spawn(S.C{ .dir = &tmp.dir, .evt = &evt }, S.checkFn);
|
||||||
for (contexts) |context, idx| {
|
defer t.wait();
|
||||||
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]));
|
const SLEEP_TIMEOUT_NS = 10 * std.time.ns_per_ms;
|
||||||
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) {
|
std.time.sleep(SLEEP_TIMEOUT_NS);
|
||||||
error.FileNotFound => {},
|
// Check that createFile is still waiting for the lock to be released.
|
||||||
else => return err,
|
testing.expect(!evt.isSet());
|
||||||
};
|
file.close();
|
||||||
|
// Generous timeout to avoid failures on heavily loaded systems.
|
||||||
|
try evt.timedWait(SLEEP_TIMEOUT_NS);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "open file with exclusive nonblocking lock twice (absolute paths)" {
|
test "open file with exclusive nonblocking lock twice (absolute paths)" {
|
||||||
@ -780,71 +769,3 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
|
|||||||
|
|
||||||
try fs.deleteFileAbsolute(filename);
|
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(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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user