1# simple formatting test
2[cases.test_superblocks_format]
3code = '''
4    lfs_t lfs;
5    lfs_format(&lfs, cfg) => 0;
6'''
7
8# mount/unmount
9[cases.test_superblocks_mount]
10code = '''
11    lfs_t lfs;
12    lfs_format(&lfs, cfg) => 0;
13    lfs_mount(&lfs, cfg) => 0;
14    lfs_unmount(&lfs) => 0;
15'''
16
17# mount/unmount from interpretting a previous superblock block_count
18[cases.test_superblocks_mount_unknown_block_count]
19code = '''
20    lfs_t lfs;
21    lfs_format(&lfs, cfg) => 0;
22
23    memset(&lfs, 0, sizeof(lfs));
24    struct lfs_config tweaked_cfg = *cfg;
25    tweaked_cfg.block_count = 0;
26    lfs_mount(&lfs, &tweaked_cfg) => 0;
27    assert(lfs.block_count == cfg->block_count);
28    lfs_unmount(&lfs) => 0;
29'''
30
31
32# reentrant format
33[cases.test_superblocks_reentrant_format]
34reentrant = true
35code = '''
36    lfs_t lfs;
37    int err = lfs_mount(&lfs, cfg);
38    if (err) {
39        lfs_format(&lfs, cfg) => 0;
40        lfs_mount(&lfs, cfg) => 0;
41    }
42    lfs_unmount(&lfs) => 0;
43'''
44
45# invalid mount
46[cases.test_superblocks_invalid_mount]
47code = '''
48    lfs_t lfs;
49    lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
50'''
51
52# test we can read superblock info through lfs_fs_stat
53[cases.test_superblocks_stat]
54if = 'DISK_VERSION == 0'
55code = '''
56    lfs_t lfs;
57    lfs_format(&lfs, cfg) => 0;
58
59    // test we can mount and read fsinfo
60    lfs_mount(&lfs, cfg) => 0;
61
62    struct lfs_fsinfo fsinfo;
63    lfs_fs_stat(&lfs, &fsinfo) => 0;
64    assert(fsinfo.disk_version == LFS_DISK_VERSION);
65    assert(fsinfo.name_max == LFS_NAME_MAX);
66    assert(fsinfo.file_max == LFS_FILE_MAX);
67    assert(fsinfo.attr_max == LFS_ATTR_MAX);
68
69    lfs_unmount(&lfs) => 0;
70'''
71
72[cases.test_superblocks_stat_tweaked]
73if = 'DISK_VERSION == 0'
74defines.TWEAKED_NAME_MAX = 63
75defines.TWEAKED_FILE_MAX = '(1 << 16)-1'
76defines.TWEAKED_ATTR_MAX = 512
77code = '''
78    // create filesystem with tweaked params
79    struct lfs_config tweaked_cfg = *cfg;
80    tweaked_cfg.name_max = TWEAKED_NAME_MAX;
81    tweaked_cfg.file_max = TWEAKED_FILE_MAX;
82    tweaked_cfg.attr_max = TWEAKED_ATTR_MAX;
83
84    lfs_t lfs;
85    lfs_format(&lfs, &tweaked_cfg) => 0;
86
87    // test we can mount and read these params with the original config
88    lfs_mount(&lfs, cfg) => 0;
89
90    struct lfs_fsinfo fsinfo;
91    lfs_fs_stat(&lfs, &fsinfo) => 0;
92    assert(fsinfo.disk_version == LFS_DISK_VERSION);
93    assert(fsinfo.name_max == TWEAKED_NAME_MAX);
94    assert(fsinfo.file_max == TWEAKED_FILE_MAX);
95    assert(fsinfo.attr_max == TWEAKED_ATTR_MAX);
96
97    lfs_unmount(&lfs) => 0;
98'''
99
100# expanding superblock
101[cases.test_superblocks_expand]
102defines.BLOCK_CYCLES = [32, 33, 1]
103defines.N = [10, 100, 1000]
104code = '''
105    lfs_t lfs;
106    lfs_format(&lfs, cfg) => 0;
107    lfs_mount(&lfs, cfg) => 0;
108    for (int i = 0; i < N; i++) {
109        lfs_file_t file;
110        lfs_file_open(&lfs, &file, "dummy",
111                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
112        lfs_file_close(&lfs, &file) => 0;
113        struct lfs_info info;
114        lfs_stat(&lfs, "dummy", &info) => 0;
115        assert(strcmp(info.name, "dummy") == 0);
116        assert(info.type == LFS_TYPE_REG);
117        lfs_remove(&lfs, "dummy") => 0;
118    }
119    lfs_unmount(&lfs) => 0;
120
121    // one last check after power-cycle
122    lfs_mount(&lfs, cfg) => 0;
123    lfs_file_t file;
124    lfs_file_open(&lfs, &file, "dummy",
125            LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
126    lfs_file_close(&lfs, &file) => 0;
127    struct lfs_info info;
128    lfs_stat(&lfs, "dummy", &info) => 0;
129    assert(strcmp(info.name, "dummy") == 0);
130    assert(info.type == LFS_TYPE_REG);
131    lfs_unmount(&lfs) => 0;
132'''
133
134# expanding superblock with power cycle
135[cases.test_superblocks_expand_power_cycle]
136defines.BLOCK_CYCLES = [32, 33, 1]
137defines.N = [10, 100, 1000]
138code = '''
139    lfs_t lfs;
140    lfs_format(&lfs, cfg) => 0;
141    for (int i = 0; i < N; i++) {
142        lfs_mount(&lfs, cfg) => 0;
143        // remove lingering dummy?
144        struct lfs_info info;
145        int err = lfs_stat(&lfs, "dummy", &info);
146        assert(err == 0 || (err == LFS_ERR_NOENT && i == 0));
147        if (!err) {
148            assert(strcmp(info.name, "dummy") == 0);
149            assert(info.type == LFS_TYPE_REG);
150            lfs_remove(&lfs, "dummy") => 0;
151        }
152
153        lfs_file_t file;
154        lfs_file_open(&lfs, &file, "dummy",
155                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
156        lfs_file_close(&lfs, &file) => 0;
157        lfs_stat(&lfs, "dummy", &info) => 0;
158        assert(strcmp(info.name, "dummy") == 0);
159        assert(info.type == LFS_TYPE_REG);
160        lfs_unmount(&lfs) => 0;
161    }
162
163    // one last check after power-cycle
164    lfs_mount(&lfs, cfg) => 0;
165    struct lfs_info info;
166    lfs_stat(&lfs, "dummy", &info) => 0;
167    assert(strcmp(info.name, "dummy") == 0);
168    assert(info.type == LFS_TYPE_REG);
169    lfs_unmount(&lfs) => 0;
170'''
171
172# reentrant expanding superblock
173[cases.test_superblocks_reentrant_expand]
174defines.BLOCK_CYCLES = [2, 1]
175defines.N = 24
176reentrant = true
177code = '''
178    lfs_t lfs;
179    int err = lfs_mount(&lfs, cfg);
180    if (err) {
181        lfs_format(&lfs, cfg) => 0;
182        lfs_mount(&lfs, cfg) => 0;
183    }
184
185    for (int i = 0; i < N; i++) {
186        // remove lingering dummy?
187        struct lfs_info info;
188        err = lfs_stat(&lfs, "dummy", &info);
189        assert(err == 0 || (err == LFS_ERR_NOENT && i == 0));
190        if (!err) {
191            assert(strcmp(info.name, "dummy") == 0);
192            assert(info.type == LFS_TYPE_REG);
193            lfs_remove(&lfs, "dummy") => 0;
194        }
195
196        lfs_file_t file;
197        lfs_file_open(&lfs, &file, "dummy",
198                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
199        lfs_file_close(&lfs, &file) => 0;
200        lfs_stat(&lfs, "dummy", &info) => 0;
201        assert(strcmp(info.name, "dummy") == 0);
202        assert(info.type == LFS_TYPE_REG);
203    }
204
205    lfs_unmount(&lfs) => 0;
206
207    // one last check after power-cycle
208    lfs_mount(&lfs, cfg) => 0;
209    struct lfs_info info;
210    lfs_stat(&lfs, "dummy", &info) => 0;
211    assert(strcmp(info.name, "dummy") == 0);
212    assert(info.type == LFS_TYPE_REG);
213    lfs_unmount(&lfs) => 0;
214'''
215
216# mount with unknown block_count
217[cases.test_superblocks_unknown_blocks]
218code = '''
219    lfs_t lfs;
220    lfs_format(&lfs, cfg) => 0;
221
222    // known block_size/block_count
223    cfg->block_size = BLOCK_SIZE;
224    cfg->block_count = BLOCK_COUNT;
225    lfs_mount(&lfs, cfg) => 0;
226    struct lfs_fsinfo fsinfo;
227    lfs_fs_stat(&lfs, &fsinfo) => 0;
228    assert(fsinfo.block_size == BLOCK_SIZE);
229    assert(fsinfo.block_count == BLOCK_COUNT);
230    lfs_unmount(&lfs) => 0;
231
232    // unknown block_count
233    cfg->block_size = BLOCK_SIZE;
234    cfg->block_count = 0;
235    lfs_mount(&lfs, cfg) => 0;
236    lfs_fs_stat(&lfs, &fsinfo) => 0;
237    assert(fsinfo.block_size == BLOCK_SIZE);
238    assert(fsinfo.block_count == BLOCK_COUNT);
239    lfs_unmount(&lfs) => 0;
240
241    // do some work
242    lfs_mount(&lfs, cfg) => 0;
243    lfs_fs_stat(&lfs, &fsinfo) => 0;
244    assert(fsinfo.block_size == BLOCK_SIZE);
245    assert(fsinfo.block_count == BLOCK_COUNT);
246    lfs_file_t file;
247    lfs_file_open(&lfs, &file, "test",
248            LFS_O_CREAT | LFS_O_EXCL | LFS_O_WRONLY) => 0;
249    lfs_file_write(&lfs, &file, "hello!", 6) => 6;
250    lfs_file_close(&lfs, &file) => 0;
251    lfs_unmount(&lfs) => 0;
252
253    lfs_mount(&lfs, cfg) => 0;
254    lfs_fs_stat(&lfs, &fsinfo) => 0;
255    assert(fsinfo.block_size == BLOCK_SIZE);
256    assert(fsinfo.block_count == BLOCK_COUNT);
257    lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
258    uint8_t buffer[256];
259    lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => 6;
260    lfs_file_close(&lfs, &file) => 0;
261    assert(memcmp(buffer, "hello!", 6) == 0);
262    lfs_unmount(&lfs) => 0;
263'''
264
265# mount with blocks fewer than the erase_count
266[cases.test_superblocks_fewer_blocks]
267defines.BLOCK_COUNT = ['ERASE_COUNT/2', 'ERASE_COUNT/4', '2']
268code = '''
269    lfs_t lfs;
270    lfs_format(&lfs, cfg) => 0;
271
272    // known block_size/block_count
273    cfg->block_size = BLOCK_SIZE;
274    cfg->block_count = BLOCK_COUNT;
275    lfs_mount(&lfs, cfg) => 0;
276    struct lfs_fsinfo fsinfo;
277    lfs_fs_stat(&lfs, &fsinfo) => 0;
278    assert(fsinfo.block_size == BLOCK_SIZE);
279    assert(fsinfo.block_count == BLOCK_COUNT);
280    lfs_unmount(&lfs) => 0;
281
282    // incorrect block_count
283    cfg->block_size = BLOCK_SIZE;
284    cfg->block_count = ERASE_COUNT;
285    lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
286
287    // unknown block_count
288    cfg->block_size = BLOCK_SIZE;
289    cfg->block_count = 0;
290    lfs_mount(&lfs, cfg) => 0;
291    lfs_fs_stat(&lfs, &fsinfo) => 0;
292    assert(fsinfo.block_size == BLOCK_SIZE);
293    assert(fsinfo.block_count == BLOCK_COUNT);
294    lfs_unmount(&lfs) => 0;
295
296    // do some work
297    lfs_mount(&lfs, cfg) => 0;
298    lfs_fs_stat(&lfs, &fsinfo) => 0;
299    assert(fsinfo.block_size == BLOCK_SIZE);
300    assert(fsinfo.block_count == BLOCK_COUNT);
301    lfs_file_t file;
302    lfs_file_open(&lfs, &file, "test",
303            LFS_O_CREAT | LFS_O_EXCL | LFS_O_WRONLY) => 0;
304    lfs_file_write(&lfs, &file, "hello!", 6) => 6;
305    lfs_file_close(&lfs, &file) => 0;
306    lfs_unmount(&lfs) => 0;
307
308    lfs_mount(&lfs, cfg) => 0;
309    lfs_fs_stat(&lfs, &fsinfo) => 0;
310    assert(fsinfo.block_size == BLOCK_SIZE);
311    assert(fsinfo.block_count == BLOCK_COUNT);
312    lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
313    uint8_t buffer[256];
314    lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => 6;
315    lfs_file_close(&lfs, &file) => 0;
316    assert(memcmp(buffer, "hello!", 6) == 0);
317    lfs_unmount(&lfs) => 0;
318'''
319
320# mount with more blocks than the erase_count
321[cases.test_superblocks_more_blocks]
322defines.FORMAT_BLOCK_COUNT = '2*ERASE_COUNT'
323in = 'lfs.c'
324code = '''
325    lfs_t lfs;
326    lfs_init(&lfs, cfg) => 0;
327    lfs.block_count = BLOCK_COUNT;
328
329    lfs_mdir_t root = {
330        .pair = {0, 0}, // make sure this goes into block 0
331        .rev = 0,
332        .off = sizeof(uint32_t),
333        .etag = 0xffffffff,
334        .count = 0,
335        .tail = {LFS_BLOCK_NULL, LFS_BLOCK_NULL},
336        .erased = false,
337        .split = false,
338    };
339
340    lfs_superblock_t superblock = {
341        .version     = LFS_DISK_VERSION,
342        .block_size  = BLOCK_SIZE,
343        .block_count = FORMAT_BLOCK_COUNT,
344        .name_max    = LFS_NAME_MAX,
345        .file_max    = LFS_FILE_MAX,
346        .attr_max    = LFS_ATTR_MAX,
347    };
348
349    lfs_superblock_tole32(&superblock);
350    lfs_dir_commit(&lfs, &root, LFS_MKATTRS(
351            {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL},
352            {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"},
353            {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
354                &superblock})) => 0;
355    lfs_deinit(&lfs) => 0;
356
357    // known block_size/block_count
358    cfg->block_size = BLOCK_SIZE;
359    cfg->block_count = BLOCK_COUNT;
360    lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
361'''
362
363# mount and grow the filesystem
364[cases.test_superblocks_grow]
365defines.BLOCK_COUNT = ['ERASE_COUNT/2', 'ERASE_COUNT/4', '2']
366defines.BLOCK_COUNT_2 = 'ERASE_COUNT'
367defines.KNOWN_BLOCK_COUNT = [true, false]
368code = '''
369    lfs_t lfs;
370    lfs_format(&lfs, cfg) => 0;
371
372    if (KNOWN_BLOCK_COUNT) {
373        cfg->block_count = BLOCK_COUNT;
374    } else {
375        cfg->block_count = 0;
376    }
377
378    // mount with block_size < erase_size
379    lfs_mount(&lfs, cfg) => 0;
380    struct lfs_fsinfo fsinfo;
381    lfs_fs_stat(&lfs, &fsinfo) => 0;
382    assert(fsinfo.block_size == BLOCK_SIZE);
383    assert(fsinfo.block_count == BLOCK_COUNT);
384    lfs_unmount(&lfs) => 0;
385
386    // same size is a noop
387    lfs_mount(&lfs, cfg) => 0;
388    lfs_fs_grow(&lfs, BLOCK_COUNT) => 0;
389    lfs_fs_stat(&lfs, &fsinfo) => 0;
390    assert(fsinfo.block_size == BLOCK_SIZE);
391    assert(fsinfo.block_count == BLOCK_COUNT);
392    lfs_unmount(&lfs) => 0;
393
394    lfs_mount(&lfs, cfg) => 0;
395    lfs_fs_stat(&lfs, &fsinfo) => 0;
396    assert(fsinfo.block_size == BLOCK_SIZE);
397    assert(fsinfo.block_count == BLOCK_COUNT);
398    lfs_unmount(&lfs) => 0;
399
400    // grow to new size
401    lfs_mount(&lfs, cfg) => 0;
402    lfs_fs_grow(&lfs, BLOCK_COUNT_2) => 0;
403    lfs_fs_stat(&lfs, &fsinfo) => 0;
404    assert(fsinfo.block_size == BLOCK_SIZE);
405    assert(fsinfo.block_count == BLOCK_COUNT_2);
406    lfs_unmount(&lfs) => 0;
407
408    if (KNOWN_BLOCK_COUNT) {
409        cfg->block_count = BLOCK_COUNT_2;
410    } else {
411        cfg->block_count = 0;
412    }
413
414    lfs_mount(&lfs, cfg) => 0;
415    lfs_fs_stat(&lfs, &fsinfo) => 0;
416    assert(fsinfo.block_size == BLOCK_SIZE);
417    assert(fsinfo.block_count == BLOCK_COUNT_2);
418    lfs_unmount(&lfs) => 0;
419
420    // mounting with the previous size should fail
421    cfg->block_count = BLOCK_COUNT;
422    lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
423
424    if (KNOWN_BLOCK_COUNT) {
425        cfg->block_count = BLOCK_COUNT_2;
426    } else {
427        cfg->block_count = 0;
428    }
429
430    // same size is a noop
431    lfs_mount(&lfs, cfg) => 0;
432    lfs_fs_grow(&lfs, BLOCK_COUNT_2) => 0;
433    lfs_fs_stat(&lfs, &fsinfo) => 0;
434    assert(fsinfo.block_size == BLOCK_SIZE);
435    assert(fsinfo.block_count == BLOCK_COUNT_2);
436    lfs_unmount(&lfs) => 0;
437
438    lfs_mount(&lfs, cfg) => 0;
439    lfs_fs_stat(&lfs, &fsinfo) => 0;
440    assert(fsinfo.block_size == BLOCK_SIZE);
441    assert(fsinfo.block_count == BLOCK_COUNT_2);
442    lfs_unmount(&lfs) => 0;
443
444    // do some work
445    lfs_mount(&lfs, cfg) => 0;
446    lfs_fs_stat(&lfs, &fsinfo) => 0;
447    assert(fsinfo.block_size == BLOCK_SIZE);
448    assert(fsinfo.block_count == BLOCK_COUNT_2);
449    lfs_file_t file;
450    lfs_file_open(&lfs, &file, "test",
451            LFS_O_CREAT | LFS_O_EXCL | LFS_O_WRONLY) => 0;
452    lfs_file_write(&lfs, &file, "hello!", 6) => 6;
453    lfs_file_close(&lfs, &file) => 0;
454    lfs_unmount(&lfs) => 0;
455
456    lfs_mount(&lfs, cfg) => 0;
457    lfs_fs_stat(&lfs, &fsinfo) => 0;
458    assert(fsinfo.block_size == BLOCK_SIZE);
459    assert(fsinfo.block_count == BLOCK_COUNT_2);
460    lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
461    uint8_t buffer[256];
462    lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => 6;
463    lfs_file_close(&lfs, &file) => 0;
464    assert(memcmp(buffer, "hello!", 6) == 0);
465    lfs_unmount(&lfs) => 0;
466'''
467