Skip to content

Commit 9a22905

Browse files
committed
Add a comprehensive IDBFS autopersist test
1 parent 1fd381c commit 9a22905

File tree

2 files changed

+236
-0
lines changed

2 files changed

+236
-0
lines changed

test/fs/test_idbfs_autopersist.c

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
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+
// res = lstat("/working1/file", &st);
72+
// assert(res == 0);
73+
int fd = open("/working1/file", O_RDWR, 0777);
74+
assert(fd != -1);
75+
ssize_t bytes_written = write(fd, "foo", 3);
76+
assert(bytes_written == 3);
77+
res = close(fd);
78+
assert(res == 0);
79+
break;
80+
}
81+
case 2: {
82+
res = lstat("/working1/file", &st);
83+
assert(res == 0);
84+
assert(st.st_size == 3);
85+
break;
86+
}
87+
default:
88+
assert(false);
89+
}
90+
break;
91+
92+
case TEST_CASE_symlink:
93+
switch (TEST_PHASE) {
94+
case 1: {
95+
res = symlink("/working1/file", "/working1/symlink");
96+
assert(res == 0);
97+
break;
98+
}
99+
case 2: {
100+
res = lstat("/working1/symlink", &st);
101+
assert(res == 0);
102+
break;
103+
}
104+
default:
105+
assert(false);
106+
}
107+
break;
108+
109+
case TEST_CASE_unlink:
110+
switch (TEST_PHASE) {
111+
case 1: {
112+
res = unlink("/working1/symlink");
113+
assert(res == 0);
114+
break;
115+
}
116+
case 2: {
117+
res = lstat("/working1/symlink", &st);
118+
assert(res == -1);
119+
assert(errno == ENOENT);
120+
break;
121+
}
122+
default:
123+
assert(false);
124+
}
125+
break;
126+
127+
case TEST_CASE_rename:
128+
switch (TEST_PHASE) {
129+
case 1: {
130+
res = rename("/working1/file", "/working1/file_renamed");
131+
assert(res == 0);
132+
break;
133+
}
134+
case 2: {
135+
res = lstat("/working1/file_renamed", &st);
136+
assert(res == 0);
137+
res = lstat("/working1/file", &st);
138+
assert(res == -1);
139+
assert(errno == ENOENT);
140+
break;
141+
}
142+
default:
143+
assert(false);
144+
}
145+
break;
146+
147+
case TEST_CASE_mkdir:
148+
switch (TEST_PHASE) {
149+
case 1: {
150+
res = mkdir("/working1/dir", 0777);
151+
assert(res == 0);
152+
break;
153+
}
154+
case 2: {
155+
res = lstat("/working1/dir", &st);
156+
assert(res == 0);
157+
break;
158+
}
159+
default:
160+
assert(false);
161+
}
162+
break;
163+
164+
default:
165+
assert(false);
166+
}
167+
168+
end:
169+
EM_ASM({
170+
// Wait until IDBFS has persisted before exiting
171+
runOnceIDBFSIdle(() => {
172+
callUserCallback(_finish);
173+
});
174+
});
175+
}
176+
177+
int main() {
178+
EM_ASM({
179+
globalThis.runOnceIDBFSIdle = (callback) => {
180+
const { mount } = FS.lookupPath('/working1').node;
181+
assert('idbPersistState' in mount, 'mount object must have idbPersistState');
182+
if (mount.idbPersistState !== 0) {
183+
// IDBFS hasn't finished persisting. Check again after all pending tasks have executed
184+
setTimeout(() => runOnceIDBFSIdle(callback), 0);
185+
return;
186+
}
187+
callback();
188+
};
189+
190+
FS.mkdir('/working1');
191+
FS.mount(IDBFS, {
192+
autoPersist: true
193+
}, '/working1');
194+
195+
// Sync from persisted state into memory
196+
FS.syncfs(true, (err) => {
197+
assert(!err);
198+
199+
// The resulting filesystem modifications might trigger IDBFS.queuePersist()
200+
// calls which will interfere with the shutdown code. Wait until all such
201+
// calls have completed.
202+
runOnceIDBFSIdle(() => {
203+
callUserCallback(_test);
204+
});
205+
});
206+
});
207+
208+
emscripten_exit_with_live_runtime();
209+
return 0;
210+
}

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)