1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
|
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));
}
}
|