1[[case]] # test running a filesystem to exhaustion
2define.LFS_ERASE_CYCLES = 10
3define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
4define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2'
5define.LFS_BADBLOCK_BEHAVIOR = [
6    'LFS_TESTBD_BADBLOCK_PROGERROR',
7    'LFS_TESTBD_BADBLOCK_ERASEERROR',
8    'LFS_TESTBD_BADBLOCK_READERROR',
9    'LFS_TESTBD_BADBLOCK_PROGNOOP',
10    'LFS_TESTBD_BADBLOCK_ERASENOOP',
11]
12define.FILES = 10
13code = '''
14    lfs_format(&lfs, &cfg) => 0;
15    lfs_mount(&lfs, &cfg) => 0;
16    lfs_mkdir(&lfs, "roadrunner") => 0;
17    lfs_unmount(&lfs) => 0;
18
19    uint32_t cycle = 0;
20    while (true) {
21        lfs_mount(&lfs, &cfg) => 0;
22        for (uint32_t i = 0; i < FILES; i++) {
23            // chose name, roughly random seed, and random 2^n size
24            sprintf(path, "roadrunner/test%d", i);
25            srand(cycle * i);
26            size = 1 << ((rand() % 10)+2);
27
28            lfs_file_open(&lfs, &file, path,
29                    LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
30
31            for (lfs_size_t j = 0; j < size; j++) {
32                char c = 'a' + (rand() % 26);
33                lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
34                assert(res == 1 || res == LFS_ERR_NOSPC);
35                if (res == LFS_ERR_NOSPC) {
36                    err = lfs_file_close(&lfs, &file);
37                    assert(err == 0 || err == LFS_ERR_NOSPC);
38                    lfs_unmount(&lfs) => 0;
39                    goto exhausted;
40                }
41            }
42
43            err = lfs_file_close(&lfs, &file);
44            assert(err == 0 || err == LFS_ERR_NOSPC);
45            if (err == LFS_ERR_NOSPC) {
46                lfs_unmount(&lfs) => 0;
47                goto exhausted;
48            }
49        }
50
51        for (uint32_t i = 0; i < FILES; i++) {
52            // check for errors
53            sprintf(path, "roadrunner/test%d", i);
54            srand(cycle * i);
55            size = 1 << ((rand() % 10)+2);
56
57            lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
58            for (lfs_size_t j = 0; j < size; j++) {
59                char c = 'a' + (rand() % 26);
60                char r;
61                lfs_file_read(&lfs, &file, &r, 1) => 1;
62                assert(r == c);
63            }
64
65            lfs_file_close(&lfs, &file) => 0;
66        }
67        lfs_unmount(&lfs) => 0;
68
69        cycle += 1;
70    }
71
72exhausted:
73    // should still be readable
74    lfs_mount(&lfs, &cfg) => 0;
75    for (uint32_t i = 0; i < FILES; i++) {
76        // check for errors
77        sprintf(path, "roadrunner/test%d", i);
78        lfs_stat(&lfs, path, &info) => 0;
79    }
80    lfs_unmount(&lfs) => 0;
81
82    LFS_WARN("completed %d cycles", cycle);
83'''
84
85[[case]] # test running a filesystem to exhaustion
86         # which also requires expanding superblocks
87define.LFS_ERASE_CYCLES = 10
88define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
89define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2'
90define.LFS_BADBLOCK_BEHAVIOR = [
91    'LFS_TESTBD_BADBLOCK_PROGERROR',
92    'LFS_TESTBD_BADBLOCK_ERASEERROR',
93    'LFS_TESTBD_BADBLOCK_READERROR',
94    'LFS_TESTBD_BADBLOCK_PROGNOOP',
95    'LFS_TESTBD_BADBLOCK_ERASENOOP',
96]
97define.FILES = 10
98code = '''
99    lfs_format(&lfs, &cfg) => 0;
100
101    uint32_t cycle = 0;
102    while (true) {
103        lfs_mount(&lfs, &cfg) => 0;
104        for (uint32_t i = 0; i < FILES; i++) {
105            // chose name, roughly random seed, and random 2^n size
106            sprintf(path, "test%d", i);
107            srand(cycle * i);
108            size = 1 << ((rand() % 10)+2);
109
110            lfs_file_open(&lfs, &file, path,
111                    LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
112
113            for (lfs_size_t j = 0; j < size; j++) {
114                char c = 'a' + (rand() % 26);
115                lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
116                assert(res == 1 || res == LFS_ERR_NOSPC);
117                if (res == LFS_ERR_NOSPC) {
118                    err = lfs_file_close(&lfs, &file);
119                    assert(err == 0 || err == LFS_ERR_NOSPC);
120                    lfs_unmount(&lfs) => 0;
121                    goto exhausted;
122                }
123            }
124
125            err = lfs_file_close(&lfs, &file);
126            assert(err == 0 || err == LFS_ERR_NOSPC);
127            if (err == LFS_ERR_NOSPC) {
128                lfs_unmount(&lfs) => 0;
129                goto exhausted;
130            }
131        }
132
133        for (uint32_t i = 0; i < FILES; i++) {
134            // check for errors
135            sprintf(path, "test%d", i);
136            srand(cycle * i);
137            size = 1 << ((rand() % 10)+2);
138
139            lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
140            for (lfs_size_t j = 0; j < size; j++) {
141                char c = 'a' + (rand() % 26);
142                char r;
143                lfs_file_read(&lfs, &file, &r, 1) => 1;
144                assert(r == c);
145            }
146
147            lfs_file_close(&lfs, &file) => 0;
148        }
149        lfs_unmount(&lfs) => 0;
150
151        cycle += 1;
152    }
153
154exhausted:
155    // should still be readable
156    lfs_mount(&lfs, &cfg) => 0;
157    for (uint32_t i = 0; i < FILES; i++) {
158        // check for errors
159        sprintf(path, "test%d", i);
160        lfs_stat(&lfs, path, &info) => 0;
161    }
162    lfs_unmount(&lfs) => 0;
163
164    LFS_WARN("completed %d cycles", cycle);
165'''
166
167# These are a sort of high-level litmus test for wear-leveling. One definition
168# of wear-leveling is that increasing a block device's space translates directly
169# into increasing the block devices lifetime. This is something we can actually
170# check for.
171
172[[case]] # wear-level test running a filesystem to exhaustion
173define.LFS_ERASE_CYCLES = 20
174define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
175define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2'
176define.FILES = 10
177code = '''
178    uint32_t run_cycles[2];
179    const uint32_t run_block_count[2] = {LFS_BLOCK_COUNT/2, LFS_BLOCK_COUNT};
180
181    for (int run = 0; run < 2; run++) {
182        for (lfs_block_t b = 0; b < LFS_BLOCK_COUNT; b++) {
183            lfs_testbd_setwear(&cfg, b,
184                    (b < run_block_count[run]) ? 0 : LFS_ERASE_CYCLES) => 0;
185        }
186
187        lfs_format(&lfs, &cfg) => 0;
188        lfs_mount(&lfs, &cfg) => 0;
189        lfs_mkdir(&lfs, "roadrunner") => 0;
190        lfs_unmount(&lfs) => 0;
191
192        uint32_t cycle = 0;
193        while (true) {
194            lfs_mount(&lfs, &cfg) => 0;
195            for (uint32_t i = 0; i < FILES; i++) {
196                // chose name, roughly random seed, and random 2^n size
197                sprintf(path, "roadrunner/test%d", i);
198                srand(cycle * i);
199                size = 1 << ((rand() % 10)+2);
200
201                lfs_file_open(&lfs, &file, path,
202                        LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
203
204                for (lfs_size_t j = 0; j < size; j++) {
205                    char c = 'a' + (rand() % 26);
206                    lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
207                    assert(res == 1 || res == LFS_ERR_NOSPC);
208                    if (res == LFS_ERR_NOSPC) {
209                        err = lfs_file_close(&lfs, &file);
210                        assert(err == 0 || err == LFS_ERR_NOSPC);
211                        lfs_unmount(&lfs) => 0;
212                        goto exhausted;
213                    }
214                }
215
216                err = lfs_file_close(&lfs, &file);
217                assert(err == 0 || err == LFS_ERR_NOSPC);
218                if (err == LFS_ERR_NOSPC) {
219                    lfs_unmount(&lfs) => 0;
220                    goto exhausted;
221                }
222            }
223
224            for (uint32_t i = 0; i < FILES; i++) {
225                // check for errors
226                sprintf(path, "roadrunner/test%d", i);
227                srand(cycle * i);
228                size = 1 << ((rand() % 10)+2);
229
230                lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
231                for (lfs_size_t j = 0; j < size; j++) {
232                    char c = 'a' + (rand() % 26);
233                    char r;
234                    lfs_file_read(&lfs, &file, &r, 1) => 1;
235                    assert(r == c);
236                }
237
238                lfs_file_close(&lfs, &file) => 0;
239            }
240            lfs_unmount(&lfs) => 0;
241
242            cycle += 1;
243        }
244
245exhausted:
246        // should still be readable
247        lfs_mount(&lfs, &cfg) => 0;
248        for (uint32_t i = 0; i < FILES; i++) {
249            // check for errors
250            sprintf(path, "roadrunner/test%d", i);
251            lfs_stat(&lfs, path, &info) => 0;
252        }
253        lfs_unmount(&lfs) => 0;
254
255        run_cycles[run] = cycle;
256        LFS_WARN("completed %d blocks %d cycles",
257                run_block_count[run], run_cycles[run]);
258    }
259
260    // check we increased the lifetime by 2x with ~10% error
261    LFS_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]);
262'''
263
264[[case]] # wear-level test + expanding superblock
265define.LFS_ERASE_CYCLES = 20
266define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
267define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2'
268define.FILES = 10
269code = '''
270    uint32_t run_cycles[2];
271    const uint32_t run_block_count[2] = {LFS_BLOCK_COUNT/2, LFS_BLOCK_COUNT};
272
273    for (int run = 0; run < 2; run++) {
274        for (lfs_block_t b = 0; b < LFS_BLOCK_COUNT; b++) {
275            lfs_testbd_setwear(&cfg, b,
276                    (b < run_block_count[run]) ? 0 : LFS_ERASE_CYCLES) => 0;
277        }
278
279        lfs_format(&lfs, &cfg) => 0;
280
281        uint32_t cycle = 0;
282        while (true) {
283            lfs_mount(&lfs, &cfg) => 0;
284            for (uint32_t i = 0; i < FILES; i++) {
285                // chose name, roughly random seed, and random 2^n size
286                sprintf(path, "test%d", i);
287                srand(cycle * i);
288                size = 1 << ((rand() % 10)+2);
289
290                lfs_file_open(&lfs, &file, path,
291                        LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
292
293                for (lfs_size_t j = 0; j < size; j++) {
294                    char c = 'a' + (rand() % 26);
295                    lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
296                    assert(res == 1 || res == LFS_ERR_NOSPC);
297                    if (res == LFS_ERR_NOSPC) {
298                        err = lfs_file_close(&lfs, &file);
299                        assert(err == 0 || err == LFS_ERR_NOSPC);
300                        lfs_unmount(&lfs) => 0;
301                        goto exhausted;
302                    }
303                }
304
305                err = lfs_file_close(&lfs, &file);
306                assert(err == 0 || err == LFS_ERR_NOSPC);
307                if (err == LFS_ERR_NOSPC) {
308                    lfs_unmount(&lfs) => 0;
309                    goto exhausted;
310                }
311            }
312
313            for (uint32_t i = 0; i < FILES; i++) {
314                // check for errors
315                sprintf(path, "test%d", i);
316                srand(cycle * i);
317                size = 1 << ((rand() % 10)+2);
318
319                lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
320                for (lfs_size_t j = 0; j < size; j++) {
321                    char c = 'a' + (rand() % 26);
322                    char r;
323                    lfs_file_read(&lfs, &file, &r, 1) => 1;
324                    assert(r == c);
325                }
326
327                lfs_file_close(&lfs, &file) => 0;
328            }
329            lfs_unmount(&lfs) => 0;
330
331            cycle += 1;
332        }
333
334exhausted:
335        // should still be readable
336        lfs_mount(&lfs, &cfg) => 0;
337        for (uint32_t i = 0; i < FILES; i++) {
338            // check for errors
339            sprintf(path, "test%d", i);
340            lfs_stat(&lfs, path, &info) => 0;
341        }
342        lfs_unmount(&lfs) => 0;
343
344        run_cycles[run] = cycle;
345        LFS_WARN("completed %d blocks %d cycles",
346                run_block_count[run], run_cycles[run]);
347    }
348
349    // check we increased the lifetime by 2x with ~10% error
350    LFS_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]);
351'''
352
353[[case]] # test that we wear blocks roughly evenly
354define.LFS_ERASE_CYCLES = 0xffffffff
355define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
356define.LFS_BLOCK_CYCLES = [5, 4, 3, 2, 1]
357define.CYCLES = 100
358define.FILES = 10
359if = 'LFS_BLOCK_CYCLES < CYCLES/10'
360code = '''
361    lfs_format(&lfs, &cfg) => 0;
362    lfs_mount(&lfs, &cfg) => 0;
363    lfs_mkdir(&lfs, "roadrunner") => 0;
364    lfs_unmount(&lfs) => 0;
365
366    uint32_t cycle = 0;
367    while (cycle < CYCLES) {
368        lfs_mount(&lfs, &cfg) => 0;
369        for (uint32_t i = 0; i < FILES; i++) {
370            // chose name, roughly random seed, and random 2^n size
371            sprintf(path, "roadrunner/test%d", i);
372            srand(cycle * i);
373            size = 1 << 4; //((rand() % 10)+2);
374
375            lfs_file_open(&lfs, &file, path,
376                    LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
377
378            for (lfs_size_t j = 0; j < size; j++) {
379                char c = 'a' + (rand() % 26);
380                lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
381                assert(res == 1 || res == LFS_ERR_NOSPC);
382                if (res == LFS_ERR_NOSPC) {
383                    err = lfs_file_close(&lfs, &file);
384                    assert(err == 0 || err == LFS_ERR_NOSPC);
385                    lfs_unmount(&lfs) => 0;
386                    goto exhausted;
387                }
388            }
389
390            err = lfs_file_close(&lfs, &file);
391            assert(err == 0 || err == LFS_ERR_NOSPC);
392            if (err == LFS_ERR_NOSPC) {
393                lfs_unmount(&lfs) => 0;
394                goto exhausted;
395            }
396        }
397
398        for (uint32_t i = 0; i < FILES; i++) {
399            // check for errors
400            sprintf(path, "roadrunner/test%d", i);
401            srand(cycle * i);
402            size = 1 << 4; //((rand() % 10)+2);
403
404            lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
405            for (lfs_size_t j = 0; j < size; j++) {
406                char c = 'a' + (rand() % 26);
407                char r;
408                lfs_file_read(&lfs, &file, &r, 1) => 1;
409                assert(r == c);
410            }
411
412            lfs_file_close(&lfs, &file) => 0;
413        }
414        lfs_unmount(&lfs) => 0;
415
416        cycle += 1;
417    }
418
419exhausted:
420    // should still be readable
421    lfs_mount(&lfs, &cfg) => 0;
422    for (uint32_t i = 0; i < FILES; i++) {
423        // check for errors
424        sprintf(path, "roadrunner/test%d", i);
425        lfs_stat(&lfs, path, &info) => 0;
426    }
427    lfs_unmount(&lfs) => 0;
428
429    LFS_WARN("completed %d cycles", cycle);
430
431    // check the wear on our block device
432    lfs_testbd_wear_t minwear = -1;
433    lfs_testbd_wear_t totalwear = 0;
434    lfs_testbd_wear_t maxwear = 0;
435    // skip 0 and 1 as superblock movement is intentionally avoided
436    for (lfs_block_t b = 2; b < LFS_BLOCK_COUNT; b++) {
437        lfs_testbd_wear_t wear = lfs_testbd_getwear(&cfg, b);
438        printf("%08x: wear %d\n", b, wear);
439        assert(wear >= 0);
440        if (wear < minwear) {
441            minwear = wear;
442        }
443        if (wear > maxwear) {
444            maxwear = wear;
445        }
446        totalwear += wear;
447    }
448    lfs_testbd_wear_t avgwear = totalwear / LFS_BLOCK_COUNT;
449    LFS_WARN("max wear: %d cycles", maxwear);
450    LFS_WARN("avg wear: %d cycles", totalwear / LFS_BLOCK_COUNT);
451    LFS_WARN("min wear: %d cycles", minwear);
452
453    // find standard deviation^2
454    lfs_testbd_wear_t dev2 = 0;
455    for (lfs_block_t b = 2; b < LFS_BLOCK_COUNT; b++) {
456        lfs_testbd_wear_t wear = lfs_testbd_getwear(&cfg, b);
457        assert(wear >= 0);
458        lfs_testbd_swear_t diff = wear - avgwear;
459        dev2 += diff*diff;
460    }
461    dev2 /= totalwear;
462    LFS_WARN("std dev^2: %d", dev2);
463    assert(dev2 < 8);
464'''
465
466