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