# simple formatting test [cases.test_superblocks_format] code = ''' lfs_t lfs; lfs_format(&lfs, cfg) => 0; ''' # mount/unmount [cases.test_superblocks_mount] code = ''' lfs_t lfs; lfs_format(&lfs, cfg) => 0; lfs_mount(&lfs, cfg) => 0; lfs_unmount(&lfs) => 0; ''' # mount/unmount from interpretting a previous superblock block_count [cases.test_superblocks_mount_unknown_block_count] code = ''' lfs_t lfs; lfs_format(&lfs, cfg) => 0; memset(&lfs, 0, sizeof(lfs)); struct lfs_config tweaked_cfg = *cfg; tweaked_cfg.block_count = 0; lfs_mount(&lfs, &tweaked_cfg) => 0; assert(lfs.block_count == cfg->block_count); lfs_unmount(&lfs) => 0; ''' # reentrant format [cases.test_superblocks_reentrant_format] reentrant = true code = ''' lfs_t lfs; int err = lfs_mount(&lfs, cfg); if (err) { lfs_format(&lfs, cfg) => 0; lfs_mount(&lfs, cfg) => 0; } lfs_unmount(&lfs) => 0; ''' # invalid mount [cases.test_superblocks_invalid_mount] code = ''' lfs_t lfs; lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT; ''' # test we can read superblock info through lfs_fs_stat [cases.test_superblocks_stat] if = 'DISK_VERSION == 0' code = ''' lfs_t lfs; lfs_format(&lfs, cfg) => 0; // test we can mount and read fsinfo lfs_mount(&lfs, cfg) => 0; struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.disk_version == LFS_DISK_VERSION); assert(fsinfo.name_max == LFS_NAME_MAX); assert(fsinfo.file_max == LFS_FILE_MAX); assert(fsinfo.attr_max == LFS_ATTR_MAX); lfs_unmount(&lfs) => 0; ''' [cases.test_superblocks_stat_tweaked] if = 'DISK_VERSION == 0' defines.TWEAKED_NAME_MAX = 63 defines.TWEAKED_FILE_MAX = '(1 << 16)-1' defines.TWEAKED_ATTR_MAX = 512 code = ''' // create filesystem with tweaked params struct lfs_config tweaked_cfg = *cfg; tweaked_cfg.name_max = TWEAKED_NAME_MAX; tweaked_cfg.file_max = TWEAKED_FILE_MAX; tweaked_cfg.attr_max = TWEAKED_ATTR_MAX; lfs_t lfs; lfs_format(&lfs, &tweaked_cfg) => 0; // test we can mount and read these params with the original config lfs_mount(&lfs, cfg) => 0; struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.disk_version == LFS_DISK_VERSION); assert(fsinfo.name_max == TWEAKED_NAME_MAX); assert(fsinfo.file_max == TWEAKED_FILE_MAX); assert(fsinfo.attr_max == TWEAKED_ATTR_MAX); lfs_unmount(&lfs) => 0; ''' # expanding superblock [cases.test_superblocks_expand] defines.BLOCK_CYCLES = [32, 33, 1] defines.N = [10, 100, 1000] code = ''' lfs_t lfs; lfs_format(&lfs, cfg) => 0; lfs_mount(&lfs, cfg) => 0; for (int i = 0; i < N; i++) { lfs_file_t file; lfs_file_open(&lfs, &file, "dummy", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfs_file_close(&lfs, &file) => 0; struct lfs_info info; lfs_stat(&lfs, "dummy", &info) => 0; assert(strcmp(info.name, "dummy") == 0); assert(info.type == LFS_TYPE_REG); lfs_remove(&lfs, "dummy") => 0; } lfs_unmount(&lfs) => 0; // one last check after power-cycle lfs_mount(&lfs, cfg) => 0; lfs_file_t file; lfs_file_open(&lfs, &file, "dummy", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfs_file_close(&lfs, &file) => 0; struct lfs_info info; lfs_stat(&lfs, "dummy", &info) => 0; assert(strcmp(info.name, "dummy") == 0); assert(info.type == LFS_TYPE_REG); lfs_unmount(&lfs) => 0; ''' # expanding superblock with power cycle [cases.test_superblocks_expand_power_cycle] defines.BLOCK_CYCLES = [32, 33, 1] defines.N = [10, 100, 1000] code = ''' lfs_t lfs; lfs_format(&lfs, cfg) => 0; for (int i = 0; i < N; i++) { lfs_mount(&lfs, cfg) => 0; // remove lingering dummy? struct lfs_info info; int err = lfs_stat(&lfs, "dummy", &info); assert(err == 0 || (err == LFS_ERR_NOENT && i == 0)); if (!err) { assert(strcmp(info.name, "dummy") == 0); assert(info.type == LFS_TYPE_REG); lfs_remove(&lfs, "dummy") => 0; } lfs_file_t file; lfs_file_open(&lfs, &file, "dummy", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfs_file_close(&lfs, &file) => 0; lfs_stat(&lfs, "dummy", &info) => 0; assert(strcmp(info.name, "dummy") == 0); assert(info.type == LFS_TYPE_REG); lfs_unmount(&lfs) => 0; } // one last check after power-cycle lfs_mount(&lfs, cfg) => 0; struct lfs_info info; lfs_stat(&lfs, "dummy", &info) => 0; assert(strcmp(info.name, "dummy") == 0); assert(info.type == LFS_TYPE_REG); lfs_unmount(&lfs) => 0; ''' # reentrant expanding superblock [cases.test_superblocks_reentrant_expand] defines.BLOCK_CYCLES = [2, 1] defines.N = 24 reentrant = true code = ''' lfs_t lfs; int err = lfs_mount(&lfs, cfg); if (err) { lfs_format(&lfs, cfg) => 0; lfs_mount(&lfs, cfg) => 0; } for (int i = 0; i < N; i++) { // remove lingering dummy? struct lfs_info info; err = lfs_stat(&lfs, "dummy", &info); assert(err == 0 || (err == LFS_ERR_NOENT && i == 0)); if (!err) { assert(strcmp(info.name, "dummy") == 0); assert(info.type == LFS_TYPE_REG); lfs_remove(&lfs, "dummy") => 0; } lfs_file_t file; lfs_file_open(&lfs, &file, "dummy", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfs_file_close(&lfs, &file) => 0; lfs_stat(&lfs, "dummy", &info) => 0; assert(strcmp(info.name, "dummy") == 0); assert(info.type == LFS_TYPE_REG); } lfs_unmount(&lfs) => 0; // one last check after power-cycle lfs_mount(&lfs, cfg) => 0; struct lfs_info info; lfs_stat(&lfs, "dummy", &info) => 0; assert(strcmp(info.name, "dummy") == 0); assert(info.type == LFS_TYPE_REG); lfs_unmount(&lfs) => 0; ''' # mount with unknown block_count [cases.test_superblocks_unknown_blocks] code = ''' lfs_t lfs; lfs_format(&lfs, cfg) => 0; // known block_size/block_count cfg->block_size = BLOCK_SIZE; cfg->block_count = BLOCK_COUNT; lfs_mount(&lfs, cfg) => 0; struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BLOCK_COUNT); lfs_unmount(&lfs) => 0; // unknown block_count cfg->block_size = BLOCK_SIZE; cfg->block_count = 0; lfs_mount(&lfs, cfg) => 0; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BLOCK_COUNT); lfs_unmount(&lfs) => 0; // do some work lfs_mount(&lfs, cfg) => 0; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BLOCK_COUNT); lfs_file_t file; lfs_file_open(&lfs, &file, "test", LFS_O_CREAT | LFS_O_EXCL | LFS_O_WRONLY) => 0; lfs_file_write(&lfs, &file, "hello!", 6) => 6; lfs_file_close(&lfs, &file) => 0; lfs_unmount(&lfs) => 0; lfs_mount(&lfs, cfg) => 0; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BLOCK_COUNT); lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0; uint8_t buffer[256]; lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => 6; lfs_file_close(&lfs, &file) => 0; assert(memcmp(buffer, "hello!", 6) == 0); lfs_unmount(&lfs) => 0; ''' # mount with blocks fewer than the erase_count [cases.test_superblocks_fewer_blocks] defines.BLOCK_COUNT = ['ERASE_COUNT/2', 'ERASE_COUNT/4', '2'] code = ''' lfs_t lfs; lfs_format(&lfs, cfg) => 0; // known block_size/block_count cfg->block_size = BLOCK_SIZE; cfg->block_count = BLOCK_COUNT; lfs_mount(&lfs, cfg) => 0; struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BLOCK_COUNT); lfs_unmount(&lfs) => 0; // incorrect block_count cfg->block_size = BLOCK_SIZE; cfg->block_count = ERASE_COUNT; lfs_mount(&lfs, cfg) => LFS_ERR_INVAL; // unknown block_count cfg->block_size = BLOCK_SIZE; cfg->block_count = 0; lfs_mount(&lfs, cfg) => 0; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BLOCK_COUNT); lfs_unmount(&lfs) => 0; // do some work lfs_mount(&lfs, cfg) => 0; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BLOCK_COUNT); lfs_file_t file; lfs_file_open(&lfs, &file, "test", LFS_O_CREAT | LFS_O_EXCL | LFS_O_WRONLY) => 0; lfs_file_write(&lfs, &file, "hello!", 6) => 6; lfs_file_close(&lfs, &file) => 0; lfs_unmount(&lfs) => 0; lfs_mount(&lfs, cfg) => 0; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BLOCK_COUNT); lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0; uint8_t buffer[256]; lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => 6; lfs_file_close(&lfs, &file) => 0; assert(memcmp(buffer, "hello!", 6) == 0); lfs_unmount(&lfs) => 0; ''' # mount with more blocks than the erase_count [cases.test_superblocks_more_blocks] defines.FORMAT_BLOCK_COUNT = '2*ERASE_COUNT' in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfs.block_count = BLOCK_COUNT; lfs_mdir_t root = { .pair = {0, 0}, // make sure this goes into block 0 .rev = 0, .off = sizeof(uint32_t), .etag = 0xffffffff, .count = 0, .tail = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}, .erased = false, .split = false, }; lfs_superblock_t superblock = { .version = LFS_DISK_VERSION, .block_size = BLOCK_SIZE, .block_count = FORMAT_BLOCK_COUNT, .name_max = LFS_NAME_MAX, .file_max = LFS_FILE_MAX, .attr_max = LFS_ATTR_MAX, }; lfs_superblock_tole32(&superblock); lfs_dir_commit(&lfs, &root, LFS_MKATTRS( {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL}, {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"}, {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), &superblock})) => 0; lfs_deinit(&lfs) => 0; // known block_size/block_count cfg->block_size = BLOCK_SIZE; cfg->block_count = BLOCK_COUNT; lfs_mount(&lfs, cfg) => LFS_ERR_INVAL; ''' # mount and grow the filesystem [cases.test_superblocks_grow] defines.BLOCK_COUNT = ['ERASE_COUNT/2', 'ERASE_COUNT/4', '2'] defines.BLOCK_COUNT_2 = 'ERASE_COUNT' defines.KNOWN_BLOCK_COUNT = [true, false] code = ''' lfs_t lfs; lfs_format(&lfs, cfg) => 0; if (KNOWN_BLOCK_COUNT) { cfg->block_count = BLOCK_COUNT; } else { cfg->block_count = 0; } // mount with block_size < erase_size lfs_mount(&lfs, cfg) => 0; struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BLOCK_COUNT); lfs_unmount(&lfs) => 0; // same size is a noop lfs_mount(&lfs, cfg) => 0; lfs_fs_grow(&lfs, BLOCK_COUNT) => 0; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BLOCK_COUNT); lfs_unmount(&lfs) => 0; lfs_mount(&lfs, cfg) => 0; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BLOCK_COUNT); lfs_unmount(&lfs) => 0; // grow to new size lfs_mount(&lfs, cfg) => 0; lfs_fs_grow(&lfs, BLOCK_COUNT_2) => 0; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BLOCK_COUNT_2); lfs_unmount(&lfs) => 0; if (KNOWN_BLOCK_COUNT) { cfg->block_count = BLOCK_COUNT_2; } else { cfg->block_count = 0; } lfs_mount(&lfs, cfg) => 0; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BLOCK_COUNT_2); lfs_unmount(&lfs) => 0; // mounting with the previous size should fail cfg->block_count = BLOCK_COUNT; lfs_mount(&lfs, cfg) => LFS_ERR_INVAL; if (KNOWN_BLOCK_COUNT) { cfg->block_count = BLOCK_COUNT_2; } else { cfg->block_count = 0; } // same size is a noop lfs_mount(&lfs, cfg) => 0; lfs_fs_grow(&lfs, BLOCK_COUNT_2) => 0; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BLOCK_COUNT_2); lfs_unmount(&lfs) => 0; lfs_mount(&lfs, cfg) => 0; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BLOCK_COUNT_2); lfs_unmount(&lfs) => 0; // do some work lfs_mount(&lfs, cfg) => 0; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BLOCK_COUNT_2); lfs_file_t file; lfs_file_open(&lfs, &file, "test", LFS_O_CREAT | LFS_O_EXCL | LFS_O_WRONLY) => 0; lfs_file_write(&lfs, &file, "hello!", 6) => 6; lfs_file_close(&lfs, &file) => 0; lfs_unmount(&lfs) => 0; lfs_mount(&lfs, cfg) => 0; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BLOCK_COUNT_2); lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0; uint8_t buffer[256]; lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => 6; lfs_file_close(&lfs, &file) => 0; assert(memcmp(buffer, "hello!", 6) == 0); lfs_unmount(&lfs) => 0; '''