Skip to content

Commit de579b9

Browse files
committed
Add a test for IDBFS auto persistence
1 parent f9ce9e7 commit de579b9

File tree

2 files changed

+250
-0
lines changed

2 files changed

+250
-0
lines changed

test/fs/test_idbfs_autopersist.c

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

test/test_browser.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,6 +1353,19 @@ 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+
@parameterized({
1357+
'open': ('open', 2),
1358+
'close': ('close', 3),
1359+
'symlink': ('symlink', 3),
1360+
'unlink': ('unlink', 3),
1361+
'rename': ('rename', 3),
1362+
'mkdir': ('mkdir', 2),
1363+
})
1364+
def test_fs_idbfs_autopersist(self, test_case, phase_count):
1365+
self.cflags += ['-lidbfs.js', '-sEXPORTED_FUNCTIONS=_main,_test,_finish', f'-DTEST_CASE=TEST_CASE_{test_case}']
1366+
for phase in range(phase_count):
1367+
self.btest_exit('fs/test_idbfs_autopersist.c', cflags=[f'-DTEST_PHASE={phase+1}'])
1368+
13561369
def test_fs_idbfs_fsync(self):
13571370
# sync from persisted state into memory before main()
13581371
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', '$ccall')

0 commit comments

Comments
 (0)