authorgravatar for thatlemon@gmail.comLemonBoy <thatlemon@gmail.com> 2020-11-04 09:48:33+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-11-06 14:02:11-05:00
logb9391c9564dd56a46fbf83d8c536af1a3ad14678
tree44e836124cd09e88c196c8ca5c02244b81e76517
parent9f1639a6bbedf44dc920f1c53f1eda11d22f4e34

std: Make file locking tests less fragile


1 files changed, 59 insertions(+), 138 deletions(-)

lib/std/fs/test.zig+59-138
...@@ -654,63 +654,61 @@ test "realpath" {...@@ -654,63 +654,61 @@ test "realpath" {
654 testing.expectError(error.FileNotFound, fs.realpath("definitely_bogus_does_not_exist1234", &buf));654 testing.expectError(error.FileNotFound, fs.realpath("definitely_bogus_does_not_exist1234", &buf));
655}655}
656656
657const FILE_LOCK_TEST_SLEEP_TIME = 5 * std.time.ns_per_ms;
658
659test "open file with exclusive nonblocking lock twice" {657test "open file with exclusive nonblocking lock twice" {
660 if (builtin.os.tag == .wasi) return error.SkipZigTest;658 if (builtin.os.tag == .wasi) return error.SkipZigTest;
661659
662 // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759660 // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759
663 if (builtin.os.tag == .freebsd) return error.SkipZigTest;661 if (builtin.os.tag == .freebsd) return error.SkipZigTest;
664662
665 const dir = fs.cwd();
666 const filename = "file_nonblocking_lock_test.txt";663 const filename = "file_nonblocking_lock_test.txt";
667664
668 const file1 = try dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });665 var tmp = tmpDir(.{});
666 defer tmp.cleanup();
667
668 const file1 = try tmp.dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
669 defer file1.close();669 defer file1.close();
670670
671 const file2 = dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });671 const file2 = tmp.dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
672 std.debug.assert(std.meta.eql(file2, error.WouldBlock));672 testing.expectError(error.WouldBlock, file2);
673}
673674
674 dir.deleteFile(filename) catch |err| switch (err) {675test "open file with shared and exclusive nonblocking lock" {
675 error.FileNotFound => {},676 if (builtin.os.tag == .wasi) return error.SkipZigTest;
676 else => return err,677
677 };678 // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759
679 if (builtin.os.tag == .freebsd) return error.SkipZigTest;
680
681 const filename = "file_nonblocking_lock_test.txt";
682
683 var tmp = tmpDir(.{});
684 defer tmp.cleanup();
685
686 const file1 = try tmp.dir.createFile(filename, .{ .lock = .Shared, .lock_nonblocking = true });
687 defer file1.close();
688
689 const file2 = tmp.dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
690 testing.expectError(error.WouldBlock, file2);
678}691}
679692
680test "open file with lock twice, make sure it wasn't open at the same time" {693test "open file with exclusive and shared nonblocking lock" {
681 if (builtin.single_threaded) return error.SkipZigTest;694 if (builtin.os.tag == .wasi) return error.SkipZigTest;
682695
683 if (std.io.is_async) {696 // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759
684 // This test starts its own threads and is not compatible with async I/O.697 if (builtin.os.tag == .freebsd) return error.SkipZigTest;
685 return error.SkipZigTest;
686 }
687698
688 const filename = "file_lock_test.txt";699 const filename = "file_nonblocking_lock_test.txt";
689 var contexts = [_]FileLockTestContext{
690 .{ .filename = filename, .create = true, .lock = .Exclusive },
691 .{ .filename = filename, .create = true, .lock = .Exclusive },
692 };
693 try run_lock_file_test(&contexts);
694
695 // Check for an error
696 var was_error = false;
697 for (contexts) |context, idx| {
698 if (context.err) |err| {
699 was_error = true;
700 std.debug.warn("\nError in context {}: {}\n", .{ idx, err });
701 }
702 }
703 if (was_error) builtin.panic("There was an error in contexts", null);
704700
705 std.debug.assert(!contexts[0].overlaps(&contexts[1]));701 var tmp = tmpDir(.{});
702 defer tmp.cleanup();
706703
707 fs.cwd().deleteFile(filename) catch |err| switch (err) {704 const file1 = try tmp.dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
708 error.FileNotFound => {},705 defer file1.close();
709 else => return err,706
710 };707 const file2 = tmp.dir.createFile(filename, .{ .lock = .Shared, .lock_nonblocking = true });
708 testing.expectError(error.WouldBlock, file2);
711}709}
712710
713test "create file, lock and read from multiple process at once" {711test "open file with exclusive lock twice, make sure it waits" {
714 if (builtin.single_threaded) return error.SkipZigTest;712 if (builtin.single_threaded) return error.SkipZigTest;
715713
716 if (std.io.is_async) {714 if (std.io.is_async) {
...@@ -718,46 +716,37 @@ test "create file, lock and read from multiple process at once" {...@@ -718,46 +716,37 @@ test "create file, lock and read from multiple process at once" {
718 return error.SkipZigTest;716 return error.SkipZigTest;
719 }717 }
720718
721 if (true) {719 const filename = "file_lock_test.txt";
722 // https://github.com/ziglang/zig/issues/5006
723 return error.SkipZigTest;
724 }
725720
726 const filename = "file_read_lock_test.txt";721 var tmp = tmpDir(.{});
727 const filedata = "Hello, world!\n";722 defer tmp.cleanup();
728723
729 try fs.cwd().writeFile(filename, filedata);724 const file = try tmp.dir.createFile(filename, .{ .lock = .Exclusive });
725 errdefer file.close();
730726
731 var contexts = [_]FileLockTestContext{727 const S = struct {
732 .{ .filename = filename, .create = false, .lock = .Shared },728 const C = struct { dir: *fs.Dir, evt: *std.ResetEvent };
733 .{ .filename = filename, .create = false, .lock = .Shared },729 fn checkFn(ctx: C) !void {
734 .{ .filename = filename, .create = false, .lock = .Exclusive },730 const file1 = try ctx.dir.createFile(filename, .{ .lock = .Exclusive });
731 defer file1.close();
732 ctx.evt.set();
733 }
735 };734 };
736735
737 try run_lock_file_test(&contexts);736 var evt = std.ResetEvent.init();
737 defer evt.deinit();
738738
739 var was_error = false;739 const t = try std.Thread.spawn(S.C{ .dir = &tmp.dir, .evt = &evt }, S.checkFn);
740 for (contexts) |context, idx| {740 defer t.wait();
741 if (context.err) |err| {
742 was_error = true;
743 std.debug.warn("\nError in context {}: {}\n", .{ idx, err });
744 }
745 }
746 if (was_error) builtin.panic("There was an error in contexts", null);
747741
748 std.debug.assert(contexts[0].overlaps(&contexts[1]));742 const SLEEP_TIMEOUT_NS = 10 * std.time.ns_per_ms;
749 std.debug.assert(!contexts[2].overlaps(&contexts[0]));
750 std.debug.assert(!contexts[2].overlaps(&contexts[1]));
751 if (contexts[0].bytes_read.? != filedata.len) {
752 std.debug.warn("\n bytes_read: {}, expected: {} \n", .{ contexts[0].bytes_read, filedata.len });
753 }
754 std.debug.assert(contexts[0].bytes_read.? == filedata.len);
755 std.debug.assert(contexts[1].bytes_read.? == filedata.len);
756743
757 fs.cwd().deleteFile(filename) catch |err| switch (err) {744 std.time.sleep(SLEEP_TIMEOUT_NS);
758 error.FileNotFound => {},745 // Check that createFile is still waiting for the lock to be released.
759 else => return err,746 testing.expect(!evt.isSet());
760 };747 file.close();
748 // Generous timeout to avoid failures on heavily loaded systems.
749 try evt.timedWait(SLEEP_TIMEOUT_NS);
761}750}
762751
763test "open file with exclusive nonblocking lock twice (absolute paths)" {752test "open file with exclusive nonblocking lock twice (absolute paths)" {
...@@ -780,71 +769,3 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {...@@ -780,71 +769,3 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
780769
781 try fs.deleteFileAbsolute(filename);770 try fs.deleteFileAbsolute(filename);
782}771}
783
784const FileLockTestContext = struct {
785 filename: []const u8,
786 pid: if (builtin.os.tag == .windows) ?void else ?std.os.pid_t = null,
787
788 // use file.createFile
789 create: bool,
790 // the type of lock to use
791 lock: File.Lock,
792
793 // Output variables
794 err: ?(File.OpenError || std.os.ReadError) = null,
795 start_time: i64 = 0,
796 end_time: i64 = 0,
797 bytes_read: ?usize = null,
798
799 fn overlaps(self: *const @This(), other: *const @This()) bool {
800 return (self.start_time < other.end_time) and (self.end_time > other.start_time);
801 }
802
803 fn run(ctx: *@This()) void {
804 var file: File = undefined;
805 if (ctx.create) {
806 file = fs.cwd().createFile(ctx.filename, .{ .lock = ctx.lock }) catch |err| {
807 ctx.err = err;
808 return;
809 };
810 } else {
811 file = fs.cwd().openFile(ctx.filename, .{ .lock = ctx.lock }) catch |err| {
812 ctx.err = err;
813 return;
814 };
815 }
816 defer file.close();
817
818 ctx.start_time = std.time.milliTimestamp();
819
820 if (!ctx.create) {
821 var buffer: [100]u8 = undefined;
822 ctx.bytes_read = 0;
823 while (true) {
824 const amt = file.read(buffer[0..]) catch |err| {
825 ctx.err = err;
826 return;
827 };
828 if (amt == 0) break;
829 ctx.bytes_read.? += amt;
830 }
831 }
832
833 std.time.sleep(FILE_LOCK_TEST_SLEEP_TIME);
834
835 ctx.end_time = std.time.milliTimestamp();
836 }
837};
838
839fn run_lock_file_test(contexts: []FileLockTestContext) !void {
840 var threads = std.ArrayList(*std.Thread).init(testing.allocator);
841 defer {
842 for (threads.items) |thread| {
843 thread.wait();
844 }
845 threads.deinit();
846 }
847 for (contexts) |*ctx, idx| {
848 try threads.append(try std.Thread.spawn(ctx, FileLockTestContext.run));
849 }
850}