Skip to content

Commit 85c3250

Browse files
committed
Add a comprehensive IDBFS autopersist test
1 parent 1fd381c commit 85c3250

File tree

2 files changed

+234
-0
lines changed

2 files changed

+234
-0
lines changed

test/fs/test_idbfs_autopersist.c

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
/*
2+
* Copyright 2025 The Emscripten Authors. All rights reserved.
3+
* Emscripten is available under two separate licenses, the MIT license and the
4+
* University of Illinois/NCSA Open Source License. Both these licenses can be
5+
* found in the LICENSE file.
6+
*/
7+
8+
#include <assert.h>
9+
#include <stdlib.h>
10+
#include <stdio.h>
11+
#include <emscripten.h>
12+
#include <fcntl.h>
13+
#include <unistd.h>
14+
#include <sys/stat.h>
15+
#include <errno.h>
16+
17+
EM_JS_DEPS(deps, "$callUserCallback");
18+
19+
void finish() {
20+
emscripten_force_exit(0);
21+
}
22+
23+
static void prepare() {
24+
printf("cleaning up test files\n");
25+
unlink("/working1/file");
26+
unlink("/working1/file_renamed");
27+
unlink("/working1/symlink");
28+
rmdir("/working1/dir");
29+
}
30+
31+
void test() {
32+
#ifdef TEST_PREPARE
33+
prepare();
34+
goto end;
35+
#endif
36+
37+
int res;
38+
struct stat st;
39+
40+
enum {
41+
TEST_CASE_open = 0,
42+
TEST_CASE_close,
43+
TEST_CASE_symlink,
44+
TEST_CASE_unlink,
45+
TEST_CASE_rename,
46+
TEST_CASE_mkdir,
47+
};
48+
49+
switch (TEST_CASE) {
50+
case TEST_CASE_open:
51+
switch (TEST_PHASE) {
52+
case 1: {
53+
int fd = open("/working1/file", O_RDWR | O_CREAT | O_EXCL, 0777);
54+
assert(fd != -1);
55+
break;
56+
}
57+
case 2: {
58+
res = lstat("/working1/file", &st);
59+
assert(res == 0);
60+
assert(st.st_size == 0);
61+
break;
62+
}
63+
default:
64+
assert(false);
65+
}
66+
break;
67+
68+
case TEST_CASE_close:
69+
switch (TEST_PHASE) {
70+
case 1: {
71+
int fd = open("/working1/file", O_RDWR, 0777);
72+
assert(fd != -1);
73+
ssize_t bytes_written = write(fd, "foo", 3);
74+
assert(bytes_written == 3);
75+
res = close(fd);
76+
assert(res == 0);
77+
break;
78+
}
79+
case 2: {
80+
res = lstat("/working1/file", &st);
81+
assert(res == 0);
82+
assert(st.st_size == 3);
83+
break;
84+
}
85+
default:
86+
assert(false);
87+
}
88+
break;
89+
90+
case TEST_CASE_symlink:
91+
switch (TEST_PHASE) {
92+
case 1: {
93+
res = symlink("/working1/file", "/working1/symlink");
94+
assert(res == 0);
95+
break;
96+
}
97+
case 2: {
98+
res = lstat("/working1/symlink", &st);
99+
assert(res == 0);
100+
break;
101+
}
102+
default:
103+
assert(false);
104+
}
105+
break;
106+
107+
case TEST_CASE_unlink:
108+
switch (TEST_PHASE) {
109+
case 1: {
110+
res = unlink("/working1/symlink");
111+
assert(res == 0);
112+
break;
113+
}
114+
case 2: {
115+
res = lstat("/working1/symlink", &st);
116+
assert(res == -1);
117+
assert(errno == ENOENT);
118+
break;
119+
}
120+
default:
121+
assert(false);
122+
}
123+
break;
124+
125+
case TEST_CASE_rename:
126+
switch (TEST_PHASE) {
127+
case 1: {
128+
res = rename("/working1/file", "/working1/file_renamed");
129+
assert(res == 0);
130+
break;
131+
}
132+
case 2: {
133+
res = lstat("/working1/file_renamed", &st);
134+
assert(res == 0);
135+
res = lstat("/working1/file", &st);
136+
assert(res == -1);
137+
assert(errno == ENOENT);
138+
break;
139+
}
140+
default:
141+
assert(false);
142+
}
143+
break;
144+
145+
case TEST_CASE_mkdir:
146+
switch (TEST_PHASE) {
147+
case 1: {
148+
res = mkdir("/working1/dir", 0777);
149+
assert(res == 0);
150+
break;
151+
}
152+
case 2: {
153+
res = lstat("/working1/dir", &st);
154+
assert(res == 0);
155+
break;
156+
}
157+
default:
158+
assert(false);
159+
}
160+
break;
161+
162+
default:
163+
assert(false);
164+
}
165+
166+
end:
167+
EM_ASM({
168+
// Wait until IDBFS has persisted before exiting
169+
runOnceIDBFSIdle(() => {
170+
callUserCallback(_finish);
171+
});
172+
});
173+
}
174+
175+
int main() {
176+
EM_ASM({
177+
globalThis.runOnceIDBFSIdle = (callback) => {
178+
const { mount } = FS.lookupPath('/working1').node;
179+
assert('idbPersistState' in mount, 'mount object must have idbPersistState');
180+
if (mount.idbPersistState !== 0) {
181+
// IDBFS hasn't finished persisting. Check again after all pending tasks have executed
182+
setTimeout(() => runOnceIDBFSIdle(callback), 0);
183+
return;
184+
}
185+
callback();
186+
};
187+
188+
FS.mkdir('/working1');
189+
FS.mount(IDBFS, {
190+
autoPersist: true
191+
}, '/working1');
192+
193+
// Sync from persisted state into memory
194+
FS.syncfs(true, (err) => {
195+
assert(!err);
196+
197+
// The resulting filesystem modifications might trigger IDBFS.queuePersist()
198+
// calls which will interfere with the shutdown code. Wait until all such
199+
// calls have completed.
200+
runOnceIDBFSIdle(() => {
201+
callUserCallback(_test);
202+
});
203+
});
204+
});
205+
206+
emscripten_exit_with_live_runtime();
207+
return 0;
208+
}

test/test_browser.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,6 +1353,32 @@ def test_fs_idbfs_sync(self, args):
13531353
print('done first half')
13541354
self.btest_exit('fs/test_idbfs_sync.c', cflags=['-lidbfs.js', f'-DSECRET="{secret}"', '-sEXPORTED_FUNCTIONS=_main,_test,_finish', '-lidbfs.js'] + args)
13551355

1356+
def test_fs_idbfs_autopersist(self):
1357+
common_cflags = ['-lidbfs.js', '-sEXPORTED_FUNCTIONS=_main,_test,_finish']
1358+
test_cases = [
1359+
'open',
1360+
'close',
1361+
'symlink',
1362+
'unlink',
1363+
'rename',
1364+
'mkdir',
1365+
]
1366+
1367+
def get_defines(current_case_name, phase):
1368+
return [
1369+
f'-DTEST_CASE={test_cases.index(current_case_name) if current_case_name in test_cases else -1}',
1370+
f'-DTEST_PHASE={phase}',
1371+
]
1372+
1373+
print('preparing')
1374+
self.btest_exit('fs/test_idbfs_autopersist.c', cflags=common_cflags + get_defines(None, 0) + ['-DTEST_PREPARE'])
1375+
1376+
for current_case_name in test_cases:
1377+
print(f'testing {current_case_name}: executing operation')
1378+
self.btest_exit('fs/test_idbfs_autopersist.c', cflags=common_cflags + get_defines(current_case_name, phase=1))
1379+
print(f'testing {current_case_name}: checking if the operation has persisted')
1380+
self.btest_exit('fs/test_idbfs_autopersist.c', cflags=common_cflags + get_defines(current_case_name, phase=2))
1381+
13561382
def test_fs_idbfs_fsync(self):
13571383
# sync from persisted state into memory before main()
13581384
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', '$ccall')

0 commit comments

Comments
 (0)