diff --git a/lib/std/fs.zig b/lib/std/fs.zig index b924a498b7..4a13d3e3e4 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -1584,13 +1584,6 @@ pub fn openSelfExe() OpenSelfExeError!File { return openFileAbsoluteZ(buf[0..self_exe_path.len :0].ptr, .{}); } -test "openSelfExe" { - switch (builtin.os.tag) { - .linux, .macosx, .ios, .windows, .freebsd, .dragonfly => (try openSelfExe()).close(), - else => return error.SkipZigTest, // Unsupported OS. - } -} - pub const SelfExePathError = os.ReadLinkError || os.SysCtlError; /// `selfExePath` except allocates the result on the heap. @@ -1678,163 +1671,9 @@ test "" { _ = copyFileAbsolute; _ = updateFileAbsolute; _ = Dir.copyFile; + _ = @import("fs/test.zig"); _ = @import("fs/path.zig"); _ = @import("fs/file.zig"); _ = @import("fs/get_app_data_dir.zig"); _ = @import("fs/watch.zig"); } - -const FILE_LOCK_TEST_SLEEP_TIME = 5 * std.time.millisecond; - -test "open file with exclusive nonblocking lock twice" { - const dir = 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; - - 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])); - - 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; - - const filename = "file_read_lock_test.txt"; - const filedata = "Hello, world!\n"; - - try std.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); - - cwd().deleteFile(filename) catch |err| switch (err) { - error.FileNotFound => {}, - else => return err, - }; -} - -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: u64 = 0, - end_time: u64 = 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 = cwd().createFile(ctx.filename, .{ .lock = ctx.lock }) catch |err| { - ctx.err = err; - return; - }; - } else { - file = 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.toSlice()) |thread| { - thread.wait(); - } - threads.deinit(); - } - for (contexts) |*ctx, idx| { - try threads.append(try std.Thread.spawn(ctx, FileLockTestContext.run)); - } -} diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig new file mode 100644 index 0000000000..e9706e2b83 --- /dev/null +++ b/lib/std/fs/test.zig @@ -0,0 +1,169 @@ +const std = @import("../std.zig"); +const builtin = std.builtin; +const fs = std.fs; +const File = std.fs.File; + +test "openSelfExe" { + const self_exe_file = try std.fs.openSelfExe(); + self_exe_file.close(); +} + +const FILE_LOCK_TEST_SLEEP_TIME = 5 * std.time.millisecond; + +test "open file with exclusive nonblocking lock twice" { + 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; + + 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 (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, + }; +} + +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: u64 = 0, + end_time: u64 = 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.toSlice()) |thread| { + thread.wait(); + } + threads.deinit(); + } + for (contexts) |*ctx, idx| { + try threads.append(try std.Thread.spawn(ctx, FileLockTestContext.run)); + } +}