| # Tests covering properties of the block allocator |
| |
| # The ordering of these tests vs higher-level tests (files/dirs/etc) gets |
| # a bit weird because there is an inherent cyclic dependency |
| # |
| # It's counter-intuitive, but we run the alloc tests _after_ file/dir tests, |
| # since you can usually ignore allocator issues temporarily by making the test |
| # device really big |
| # |
| after = ['test_mtree', 'test_dirs', 'test_files'] |
| |
| # test that we can alloc |
| [cases.test_alloc_alloc] |
| defines.COUNT = [ |
| 'BLOCK_COUNT', |
| 'BLOCK_COUNT-1', |
| 'BLOCK_COUNT/2', |
| 'BLOCK_COUNT/4', |
| '5', |
| '2', |
| ] |
| defines.ERASE = [false, true] |
| in = 'lfs.c' |
| code = ''' |
| // test various block counts |
| struct lfs_config cfg = *CFG; |
| cfg.block_count = COUNT; |
| lfs_t lfs; |
| lfsr_format(&lfs, LFS_F_RDWR, &cfg) => 0; |
| lfsr_mount(&lfs, LFS_M_RDWR, &cfg) => 0; |
| |
| // start allocating |
| lfs_alloc_ckpoint(&lfs); |
| lfs_size_t alloced = 0; |
| while (true) { |
| lfs_sblock_t block = lfs_alloc(&lfs, ERASE); |
| assert(block >= 0 || block == LFS_ERR_NOSPC); |
| |
| if (block == LFS_ERR_NOSPC) { |
| break; |
| } |
| alloced += 1; |
| |
| // our allocator should stop at some point... |
| assert(alloced < 2*COUNT); |
| } |
| |
| // excluding our mroot, we should have allocated exactly |
| // block_count-2 blocks |
| printf("alloced %d/%d blocks\n", alloced, (lfs_block_t)COUNT); |
| assert(alloced == COUNT-2); |
| |
| lfsr_unmount(&lfs) => 0; |
| ''' |
| |
| # test that we can realloc after an ack |
| [cases.test_alloc_reuse] |
| defines.COUNT = [ |
| 'BLOCK_COUNT', |
| 'BLOCK_COUNT-1', |
| 'BLOCK_COUNT/2', |
| 'BLOCK_COUNT/4', |
| '5', |
| '2', |
| ] |
| defines.ERASE = [false, true] |
| in = 'lfs.c' |
| code = ''' |
| // test various block counts |
| struct lfs_config cfg = *CFG; |
| cfg.block_count = COUNT; |
| lfs_t lfs; |
| lfsr_format(&lfs, LFS_F_RDWR, &cfg) => 0; |
| lfsr_mount(&lfs, LFS_M_RDWR, &cfg) => 0; |
| |
| // start allocating |
| lfs_alloc_ckpoint(&lfs); |
| lfs_size_t alloced = 0; |
| while (true) { |
| lfs_sblock_t block = lfs_alloc(&lfs, ERASE); |
| assert(block >= 0 || block == LFS_ERR_NOSPC); |
| |
| if (block == LFS_ERR_NOSPC) { |
| break; |
| } |
| alloced += 1; |
| |
| // our allocator should stop at some point... |
| assert(alloced < 2*COUNT); |
| } |
| |
| // excluding our mroot, we should have allocated exactly |
| // block_count-2 blocks |
| printf("alloced %d/%d blocks\n", alloced, (lfs_block_t)COUNT); |
| assert(alloced == COUNT-2); |
| |
| // ack again, effectively releasing all the previously alloced blocks |
| lfs_alloc_ckpoint(&lfs); |
| alloced = 0; |
| while (true) { |
| lfs_sblock_t block = lfs_alloc(&lfs, ERASE); |
| assert(block >= 0 || block == LFS_ERR_NOSPC); |
| |
| if (block == LFS_ERR_NOSPC) { |
| break; |
| } |
| alloced += 1; |
| |
| // our allocator should stop at some point... |
| assert(alloced < 2*COUNT); |
| } |
| |
| // excluding our mroot, we should have allocated exactly |
| // block_count-2 blocks |
| printf("alloced %d/%d blocks\n", alloced, (lfs_block_t)COUNT); |
| assert(alloced == COUNT-2); |
| |
| lfsr_unmount(&lfs) => 0; |
| ''' |
| |
| |
| # clobber tests test that our traversal algorithm works |
| [cases.test_alloc_clobber_dirs] |
| defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] |
| defines.CKMETA = [false, true] |
| defines.REMOUNT = [false, true] |
| in = 'lfs.c' |
| code = ''' |
| lfs_t lfs; |
| lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; |
| lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; |
| |
| // create this many directories |
| for (lfs_size_t i = 0; i < N; i++) { |
| char name[256]; |
| sprintf(name, "dir%03x", i); |
| lfsr_mkdir(&lfs, name) => 0; |
| } |
| |
| // check that our mkdir worked |
| for (lfs_size_t i = 0; i < N; i++) { |
| char name[256]; |
| sprintf(name, "dir%03x", i); |
| struct lfs_info info; |
| lfsr_stat(&lfs, name, &info) => 0; |
| assert(strcmp(info.name, name) == 0); |
| assert(info.type == LFS_TYPE_DIR); |
| assert(info.size == 0); |
| } |
| |
| lfsr_dir_t dir; |
| lfsr_dir_open(&lfs, &dir, "/") => 0; |
| struct lfs_info info; |
| lfsr_dir_read(&lfs, &dir, &info) => 0; |
| assert(strcmp(info.name, ".") == 0); |
| assert(info.type == LFS_TYPE_DIR); |
| assert(info.size == 0); |
| lfsr_dir_read(&lfs, &dir, &info) => 0; |
| assert(strcmp(info.name, "..") == 0); |
| assert(info.type == LFS_TYPE_DIR); |
| assert(info.size == 0); |
| for (lfs_size_t i = 0; i < N; i++) { |
| char name[256]; |
| sprintf(name, "dir%03x", i); |
| lfsr_dir_read(&lfs, &dir, &info) => 0; |
| assert(strcmp(info.name, name) == 0); |
| assert(info.type == LFS_TYPE_DIR); |
| assert(info.size == 0); |
| } |
| lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; |
| lfsr_dir_close(&lfs, &dir) => 0; |
| |
| // remount? |
| if (REMOUNT) { |
| lfsr_unmount(&lfs) => 0; |
| lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; |
| } |
| |
| // first traverse the tree to find all blocks in use |
| uint8_t *seen = malloc((BLOCK_COUNT+7)/8); |
| memset(seen, 0, (BLOCK_COUNT+7)/8); |
| |
| lfsr_traversal_t t; |
| lfsr_traversal_init(&t, |
| (CKMETA) ? LFS_T_CKMETA : 0); |
| for (lfs_block_t i = 0;; i++) { |
| // a bit hacky, but this catches infinite loops |
| assert(i < 2*BLOCK_COUNT); |
| |
| lfsr_tag_t tag; |
| lfsr_bptr_t bptr; |
| int err = lfsr_mtree_traverse(&lfs, &t, |
| &tag, &bptr); |
| assert(!err || err == LFS_ERR_NOENT); |
| if (err == LFS_ERR_NOENT) { |
| break; |
| } |
| |
| if (tag == LFSR_TAG_MDIR) { |
| lfsr_mdir_t *mdir = (lfsr_mdir_t*)bptr.data.u.buffer; |
| printf("traversal: 0x%x mdir 0x{%x,%x}\n", |
| tag, |
| mdir->rbyd.blocks[0], |
| mdir->rbyd.blocks[1]); |
| |
| // keep track of seen blocks |
| seen[mdir->rbyd.blocks[1] / 8] |= 1 << (mdir->rbyd.blocks[1] % 8); |
| seen[mdir->rbyd.blocks[0] / 8] |= 1 << (mdir->rbyd.blocks[0] % 8); |
| |
| } else if (tag == LFSR_TAG_BRANCH) { |
| lfsr_rbyd_t *rbyd = (lfsr_rbyd_t*)bptr.data.u.buffer; |
| printf("traversal: 0x%x btree 0x%x.%x\n", |
| tag, |
| rbyd->blocks[0], rbyd->trunk); |
| |
| // keep track of seen blocks |
| seen[rbyd->blocks[0] / 8] |= 1 << (rbyd->blocks[0] % 8); |
| |
| } else { |
| // this shouldn't happen |
| printf("traversal: 0x%x\n", tag); |
| assert(false); |
| } |
| } |
| |
| // then clobber every other block |
| uint8_t clobber_buf[BLOCK_SIZE]; |
| memset(clobber_buf, 0xcc, BLOCK_SIZE); |
| for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) { |
| if (!(seen[block / 8] & (1 << (block % 8)))) { |
| CFG->erase(CFG, block) => 0; |
| CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0; |
| } |
| } |
| free(seen); |
| |
| // then check that we can read our directories after clobbering |
| for (int remount = 0; remount < 2; remount++) { |
| // remount? |
| if (remount) { |
| lfsr_unmount(&lfs) => 0; |
| lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; |
| } |
| |
| for (lfs_size_t i = 0; i < N; i++) { |
| char name[256]; |
| sprintf(name, "dir%03x", i); |
| struct lfs_info info; |
| lfsr_stat(&lfs, name, &info) => 0; |
| assert(strcmp(info.name, name) == 0); |
| assert(info.type == LFS_TYPE_DIR); |
| assert(info.size == 0); |
| } |
| |
| lfsr_dir_open(&lfs, &dir, "/") => 0; |
| lfsr_dir_read(&lfs, &dir, &info) => 0; |
| assert(strcmp(info.name, ".") == 0); |
| assert(info.type == LFS_TYPE_DIR); |
| assert(info.size == 0); |
| lfsr_dir_read(&lfs, &dir, &info) => 0; |
| assert(strcmp(info.name, "..") == 0); |
| assert(info.type == LFS_TYPE_DIR); |
| assert(info.size == 0); |
| for (lfs_size_t i = 0; i < N; i++) { |
| char name[256]; |
| sprintf(name, "dir%03x", i); |
| lfsr_dir_read(&lfs, &dir, &info) => 0; |
| assert(strcmp(info.name, name) == 0); |
| assert(info.type == LFS_TYPE_DIR); |
| assert(info.size == 0); |
| } |
| lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; |
| lfsr_dir_close(&lfs, &dir) => 0; |
| } |
| |
| lfsr_unmount(&lfs) => 0; |
| ''' |
| |
| [cases.test_alloc_clobber_files] |
| defines.N = [1, 2, 4, 8, 16, 32, 64] |
| defines.SIZE = [ |
| '0', |
| 'FILE_CACHE_SIZE/2', |
| '2*FILE_CACHE_SIZE', |
| 'BLOCK_SIZE/2', |
| 'BLOCK_SIZE', |
| '2*BLOCK_SIZE', |
| '8*BLOCK_SIZE', |
| ] |
| defines.CKMETA = [false, true] |
| defines.REMOUNT = [false, true] |
| in = 'lfs.c' |
| if = '(SIZE*N)/BLOCK_SIZE <= 32' |
| code = ''' |
| lfs_t lfs; |
| lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; |
| lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; |
| |
| // create this many files |
| uint32_t prng = 42; |
| for (lfs_size_t i = 0; i < N; i++) { |
| char name[256]; |
| sprintf(name, "file%03x", i); |
| |
| uint8_t wbuf[SIZE]; |
| for (lfs_size_t j = 0; j < SIZE; j++) { |
| wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); |
| } |
| |
| lfsr_file_t file; |
| lfsr_file_open(&lfs, &file, name, |
| LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; |
| lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; |
| lfsr_file_close(&lfs, &file) => 0; |
| } |
| |
| // check that our writes worked |
| prng = 42; |
| for (lfs_size_t i = 0; i < N; i++) { |
| // check with stat |
| char name[256]; |
| sprintf(name, "file%03x", i); |
| struct lfs_info info; |
| lfsr_stat(&lfs, name, &info) => 0; |
| assert(strcmp(info.name, name) == 0); |
| assert(info.type == LFS_TYPE_REG); |
| assert(info.size == SIZE); |
| |
| // try reading the file, note we reset prng above |
| uint8_t wbuf[SIZE]; |
| for (lfs_size_t j = 0; j < SIZE; j++) { |
| wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); |
| } |
| |
| lfsr_file_t file; |
| uint8_t rbuf[SIZE]; |
| lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; |
| lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; |
| assert(memcmp(rbuf, wbuf, SIZE) == 0); |
| lfsr_file_close(&lfs, &file) => 0; |
| } |
| |
| // remount? |
| if (REMOUNT) { |
| lfsr_unmount(&lfs) => 0; |
| lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; |
| } |
| |
| // first traverse the tree to find all blocks in use |
| uint8_t *seen = malloc((BLOCK_COUNT+7)/8); |
| memset(seen, 0, (BLOCK_COUNT+7)/8); |
| |
| lfsr_traversal_t t; |
| lfsr_traversal_init(&t, |
| ((CKMETA) ? LFS_T_CKMETA : 0)); |
| for (lfs_block_t i = 0;; i++) { |
| // a bit hacky, but this catches infinite loops |
| assert(i < 2*BLOCK_COUNT); |
| |
| lfsr_tag_t tag; |
| lfsr_bptr_t bptr; |
| int err = lfsr_mtree_traverse(&lfs, &t, |
| &tag, &bptr); |
| assert(!err || err == LFS_ERR_NOENT); |
| if (err == LFS_ERR_NOENT) { |
| break; |
| } |
| |
| if (tag == LFSR_TAG_MDIR) { |
| lfsr_mdir_t *mdir = (lfsr_mdir_t*)bptr.data.u.buffer; |
| printf("traversal: 0x%x mdir 0x{%x,%x}\n", |
| tag, |
| mdir->rbyd.blocks[0], |
| mdir->rbyd.blocks[1]); |
| |
| // keep track of seen blocks |
| seen[mdir->rbyd.blocks[1] / 8] |= 1 << (mdir->rbyd.blocks[1] % 8); |
| seen[mdir->rbyd.blocks[0] / 8] |= 1 << (mdir->rbyd.blocks[0] % 8); |
| |
| } else if (tag == LFSR_TAG_BRANCH) { |
| lfsr_rbyd_t *rbyd = (lfsr_rbyd_t*)bptr.data.u.buffer; |
| printf("traversal: 0x%x btree 0x%x.%x\n", |
| tag, |
| rbyd->blocks[0], rbyd->trunk); |
| |
| // keep track of seen blocks |
| seen[rbyd->blocks[0] / 8] |= 1 << (rbyd->blocks[0] % 8); |
| |
| } else if (tag == LFSR_TAG_BLOCK) { |
| printf("traversal: 0x%x block 0x%x\n", |
| tag, |
| bptr.data.u.disk.block); |
| |
| // keep track of seen blocks |
| seen[bptr.data.u.disk.block / 8] |
| |= 1 << (bptr.data.u.disk.block % 8); |
| |
| } else { |
| // this shouldn't happen |
| printf("traversal: 0x%x\n", tag); |
| assert(false); |
| } |
| } |
| |
| // then clobber every other block |
| uint8_t clobber_buf[BLOCK_SIZE]; |
| memset(clobber_buf, 0xcc, BLOCK_SIZE); |
| for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) { |
| if (!(seen[block / 8] & (1 << (block % 8)))) { |
| CFG->erase(CFG, block) => 0; |
| CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0; |
| } |
| } |
| free(seen); |
| |
| // then check that reading our files still works after clobbering |
| for (int remount = 0; remount < 2; remount++) { |
| // remount? |
| if (remount) { |
| lfsr_unmount(&lfs) => 0; |
| lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; |
| } |
| |
| prng = 42; |
| for (lfs_size_t i = 0; i < N; i++) { |
| // check with stat |
| char name[256]; |
| sprintf(name, "file%03x", i); |
| struct lfs_info info; |
| lfsr_stat(&lfs, name, &info) => 0; |
| assert(strcmp(info.name, name) == 0); |
| assert(info.type == LFS_TYPE_REG); |
| assert(info.size == SIZE); |
| |
| // try reading the file, note we reset prng above |
| uint8_t wbuf[SIZE]; |
| for (lfs_size_t j = 0; j < SIZE; j++) { |
| wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); |
| } |
| |
| lfsr_file_t file; |
| uint8_t rbuf[SIZE]; |
| lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; |
| lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; |
| assert(memcmp(rbuf, wbuf, SIZE) == 0); |
| lfsr_file_close(&lfs, &file) => 0; |
| } |
| } |
| |
| lfsr_unmount(&lfs) => 0; |
| ''' |
| |
| # open files need to be tracked internally to make sure this doesn't break |
| [cases.test_alloc_clobber_open_files] |
| defines.N = [1, 2, 4, 8, 16, 32, 64] |
| defines.SIZE = [ |
| '0', |
| 'FILE_CACHE_SIZE/2', |
| '2*FILE_CACHE_SIZE', |
| 'BLOCK_SIZE/2', |
| 'BLOCK_SIZE', |
| '2*BLOCK_SIZE', |
| '8*BLOCK_SIZE', |
| ] |
| defines.CKMETA = [false, true] |
| in = 'lfs.c' |
| if = '(SIZE*N)/BLOCK_SIZE <= 32' |
| code = ''' |
| lfs_t lfs; |
| lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; |
| lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; |
| |
| // create this many files |
| lfsr_file_t files[N]; |
| uint32_t prng = 42; |
| for (lfs_size_t i = 0; i < N; i++) { |
| char name[256]; |
| sprintf(name, "file%03x", i); |
| |
| uint8_t wbuf[SIZE]; |
| for (lfs_size_t j = 0; j < SIZE; j++) { |
| wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); |
| } |
| |
| lfsr_file_open(&lfs, &files[i], name, |
| LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; |
| lfsr_file_write(&lfs, &files[i], wbuf, SIZE) => SIZE; |
| } |
| |
| // check that our writes worked |
| prng = 42; |
| for (lfs_size_t i = 0; i < N; i++) { |
| // try reading the file, note we reset prng above |
| uint8_t wbuf[SIZE]; |
| for (lfs_size_t j = 0; j < SIZE; j++) { |
| wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); |
| } |
| |
| uint8_t rbuf[SIZE]; |
| lfsr_file_rewind(&lfs, &files[i]) => 0; |
| lfsr_file_read(&lfs, &files[i], rbuf, SIZE) => SIZE; |
| assert(memcmp(rbuf, wbuf, SIZE) == 0); |
| } |
| |
| // first traverse the tree to find all blocks in use |
| uint8_t *seen = malloc((BLOCK_COUNT+7)/8); |
| memset(seen, 0, (BLOCK_COUNT+7)/8); |
| |
| lfsr_traversal_t t; |
| lfsr_traversal_init(&t, |
| ((CKMETA) ? LFS_T_CKMETA : 0)); |
| for (lfs_block_t i = 0;; i++) { |
| // a bit hacky, but this catches infinite loops |
| assert(i < 2*BLOCK_COUNT); |
| |
| lfsr_tag_t tag; |
| lfsr_bptr_t bptr; |
| int err = lfsr_mtree_traverse(&lfs, &t, |
| &tag, &bptr); |
| assert(!err || err == LFS_ERR_NOENT); |
| if (err == LFS_ERR_NOENT) { |
| break; |
| } |
| |
| if (tag == LFSR_TAG_MDIR) { |
| lfsr_mdir_t *mdir = (lfsr_mdir_t*)bptr.data.u.buffer; |
| printf("traversal: 0x%x mdir 0x{%x,%x}\n", |
| tag, |
| mdir->rbyd.blocks[0], |
| mdir->rbyd.blocks[1]); |
| |
| // keep track of seen blocks |
| seen[mdir->rbyd.blocks[1] / 8] |
| |= 1 << (mdir->rbyd.blocks[1] % 8); |
| seen[mdir->rbyd.blocks[0] / 8] |
| |= 1 << (mdir->rbyd.blocks[0] % 8); |
| |
| } else if (tag == LFSR_TAG_BRANCH) { |
| lfsr_rbyd_t *rbyd = (lfsr_rbyd_t*)bptr.data.u.buffer; |
| printf("traversal: 0x%x btree 0x%x.%x\n", |
| tag, |
| rbyd->blocks[0], rbyd->trunk); |
| |
| // keep track of seen blocks |
| seen[rbyd->blocks[0] / 8] |
| |= 1 << (rbyd->blocks[0] % 8); |
| |
| } else if (tag == LFSR_TAG_BLOCK) { |
| printf("traversal: 0x%x block 0x%x\n", |
| tag, |
| bptr.data.u.disk.block); |
| |
| // keep track of seen blocks |
| seen[bptr.data.u.disk.block / 8] |
| |= 1 << (bptr.data.u.disk.block % 8); |
| |
| } else { |
| // this shouldn't happen |
| printf("traversal: 0x%x\n", tag); |
| assert(false); |
| } |
| } |
| |
| // then clobber every other block |
| uint8_t clobber_buf[BLOCK_SIZE]; |
| memset(clobber_buf, 0xcc, BLOCK_SIZE); |
| for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) { |
| if (!(seen[block / 8] & (1 << (block % 8)))) { |
| CFG->erase(CFG, block) => 0; |
| CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0; |
| } |
| } |
| free(seen); |
| |
| // then check that reading our files still works after clobbering |
| prng = 42; |
| for (lfs_size_t i = 0; i < N; i++) { |
| // try reading the file, note we reset prng above |
| uint8_t wbuf[SIZE]; |
| for (lfs_size_t j = 0; j < SIZE; j++) { |
| wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); |
| } |
| |
| uint8_t rbuf[SIZE]; |
| lfsr_file_rewind(&lfs, &files[i]) => 0; |
| lfsr_file_read(&lfs, &files[i], rbuf, SIZE) => SIZE; |
| assert(memcmp(rbuf, wbuf, SIZE) == 0); |
| } |
| |
| // and everything is fine after saving the files |
| for (lfs_size_t i = 0; i < N; i++) { |
| lfsr_file_close(&lfs, &files[i]) => 0; |
| } |
| |
| for (int remount = 0; remount < 2; remount++) { |
| // remount? |
| if (remount) { |
| lfsr_unmount(&lfs) => 0; |
| lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; |
| } |
| |
| prng = 42; |
| for (lfs_size_t i = 0; i < N; i++) { |
| // check with stat |
| char name[256]; |
| sprintf(name, "file%03x", i); |
| struct lfs_info info; |
| lfsr_stat(&lfs, name, &info) => 0; |
| assert(strcmp(info.name, name) == 0); |
| assert(info.type == LFS_TYPE_REG); |
| assert(info.size == SIZE); |
| |
| // try reading the file, note we reset prng above |
| uint8_t wbuf[SIZE]; |
| for (lfs_size_t j = 0; j < SIZE; j++) { |
| wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); |
| } |
| |
| lfsr_file_t file; |
| uint8_t rbuf[SIZE]; |
| lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; |
| lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; |
| assert(memcmp(rbuf, wbuf, SIZE) == 0); |
| lfsr_file_close(&lfs, &file) => 0; |
| } |
| } |
| |
| lfsr_unmount(&lfs) => 0; |
| ''' |
| |
| |
| # TODO more nospc tests (opened files? other?) |
| |
| # nospc tests mostly test that things still work when block allocation |
| # wraparound occurs |
| [cases.test_alloc_nospc_dirs] |
| defines.COUNT = [ |
| 'BLOCK_COUNT', |
| 'BLOCK_COUNT-1', |
| 'BLOCK_COUNT/2', |
| 'BLOCK_COUNT/4', |
| '5', |
| '2', |
| ] |
| code = ''' |
| // test various block counts |
| struct lfs_config cfg = *CFG; |
| cfg.block_count = COUNT; |
| lfs_t lfs; |
| lfsr_format(&lfs, LFS_F_RDWR, &cfg) => 0; |
| lfsr_mount(&lfs, LFS_M_RDWR, &cfg) => 0; |
| |
| // create directories until we run out of space |
| lfs_size_t n = 0; |
| for (;; n++) { |
| char name[256]; |
| sprintf(name, "dir%08d", n); |
| int err = lfsr_mkdir(&lfs, name); |
| assert(!err || err == LFS_ERR_NOSPC); |
| if (err == LFS_ERR_NOSPC) { |
| break; |
| } |
| } |
| |
| for (int remount = 0; remount < 2; remount++) { |
| // remount? |
| if (remount) { |
| lfsr_unmount(&lfs) => 0; |
| lfsr_mount(&lfs, LFS_M_RDWR, &cfg) => 0; |
| } |
| |
| // check that our mkdir worked until we ran out of space |
| for (lfs_size_t i = 0; i < n; i++) { |
| char name[256]; |
| sprintf(name, "dir%08d", i); |
| struct lfs_info info; |
| lfsr_stat(&lfs, name, &info) => 0; |
| assert(strcmp(info.name, name) == 0); |
| assert(info.type == LFS_TYPE_DIR); |
| assert(info.size == 0); |
| } |
| |
| lfsr_dir_t dir; |
| lfsr_dir_open(&lfs, &dir, "/") => 0; |
| struct lfs_info info; |
| lfsr_dir_read(&lfs, &dir, &info) => 0; |
| assert(strcmp(info.name, ".") == 0); |
| assert(info.type == LFS_TYPE_DIR); |
| assert(info.size == 0); |
| lfsr_dir_read(&lfs, &dir, &info) => 0; |
| assert(strcmp(info.name, "..") == 0); |
| assert(info.type == LFS_TYPE_DIR); |
| assert(info.size == 0); |
| for (lfs_size_t i = 0; i < n; i++) { |
| char name[256]; |
| sprintf(name, "dir%08d", i); |
| lfsr_dir_read(&lfs, &dir, &info) => 0; |
| assert(strcmp(info.name, name) == 0); |
| assert(info.type == LFS_TYPE_DIR); |
| assert(info.size == 0); |
| } |
| lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; |
| lfsr_dir_close(&lfs, &dir) => 0; |
| } |
| |
| lfsr_unmount(&lfs) => 0; |
| ''' |
| |
| [cases.test_alloc_nospc_files] |
| defines.COUNT = [ |
| 'BLOCK_COUNT', |
| 'BLOCK_COUNT-1', |
| 'BLOCK_COUNT/2', |
| 'BLOCK_COUNT/4', |
| '5', |
| '2', |
| ] |
| defines.SIZE = [ |
| '0', |
| 'FILE_CACHE_SIZE/2', |
| '2*FILE_CACHE_SIZE', |
| 'BLOCK_SIZE/2', |
| 'BLOCK_SIZE', |
| '2*BLOCK_SIZE', |
| '8*BLOCK_SIZE', |
| ] |
| code = ''' |
| // test various block counts |
| struct lfs_config cfg = *CFG; |
| cfg.block_count = COUNT; |
| lfs_t lfs; |
| lfsr_format(&lfs, LFS_F_RDWR, &cfg) => 0; |
| lfsr_mount(&lfs, LFS_M_RDWR, &cfg) => 0; |
| |
| // create files until we run out of space |
| uint32_t prng = 42; |
| lfs_size_t n = 0; |
| for (;; n++) { |
| char name[256]; |
| sprintf(name, "file%08d", n); |
| |
| uint8_t wbuf[SIZE]; |
| for (lfs_size_t j = 0; j < SIZE; j++) { |
| wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); |
| } |
| |
| lfsr_file_t file; |
| int err = lfsr_file_open(&lfs, &file, name, |
| LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL); |
| assert(!err || err == LFS_ERR_NOSPC); |
| if (err == LFS_ERR_NOSPC) { |
| break; |
| } |
| |
| lfs_ssize_t size = lfsr_file_write(&lfs, &file, wbuf, SIZE); |
| assert(size == SIZE || size == LFS_ERR_NOSPC); |
| if (size == LFS_ERR_NOSPC) { |
| lfsr_file_close(&lfs, &file) => 0; |
| break; |
| } |
| |
| err = lfsr_file_close(&lfs, &file); |
| assert(!err || err == LFS_ERR_NOSPC); |
| if (err == LFS_ERR_NOSPC) { |
| break; |
| } |
| } |
| |
| for (int remount = 0; remount < 2; remount++) { |
| // remount? |
| if (remount) { |
| lfsr_unmount(&lfs) => 0; |
| lfsr_mount(&lfs, LFS_M_RDWR, &cfg) => 0; |
| } |
| |
| // check that our file writes worked until we ran out of space |
| prng = 42; |
| for (lfs_size_t i = 0; i < n; i++) { |
| // check with stat |
| char name[256]; |
| sprintf(name, "file%08d", i); |
| struct lfs_info info; |
| lfsr_stat(&lfs, name, &info) => 0; |
| assert(strcmp(info.name, name) == 0); |
| assert(info.type == LFS_TYPE_REG); |
| assert(info.size == SIZE); |
| |
| // try reading the file, note we reset prng above |
| uint8_t wbuf[SIZE]; |
| for (lfs_size_t j = 0; j < SIZE; j++) { |
| wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); |
| } |
| |
| lfsr_file_t file; |
| uint8_t rbuf[SIZE]; |
| lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; |
| lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; |
| assert(memcmp(rbuf, wbuf, SIZE) == 0); |
| lfsr_file_close(&lfs, &file) => 0; |
| } |
| } |
| |
| lfsr_unmount(&lfs) => 0; |
| ''' |
| |
| |
| |
| ## allocator tests |
| ## note for these to work there are a number constraints on the device geometry |
| #if = 'BLOCK_CYCLES == -1' |
| # |
| ## parallel allocation test |
| #[cases.test_alloc_parallel] |
| #defines.FILES = 3 |
| #defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)' |
| #code = ''' |
| # const char *names[] = {"bacon", "eggs", "pancakes"}; |
| # lfs_file_t files[FILES]; |
| # |
| # lfs_t lfs; |
| # lfs_format(&lfs, cfg) => 0; |
| # lfs_mount(&lfs, cfg) => 0; |
| # lfs_mkdir(&lfs, "breakfast") => 0; |
| # lfs_unmount(&lfs) => 0; |
| # |
| # lfs_mount(&lfs, cfg) => 0; |
| # for (int n = 0; n < FILES; n++) { |
| # char path[1024]; |
| # sprintf(path, "breakfast/%s", names[n]); |
| # lfs_file_open(&lfs, &files[n], path, |
| # LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; |
| # } |
| # for (int n = 0; n < FILES; n++) { |
| # size_t size = strlen(names[n]); |
| # for (lfs_size_t i = 0; i < SIZE; i += size) { |
| # lfs_file_write(&lfs, &files[n], names[n], size) => size; |
| # } |
| # } |
| # for (int n = 0; n < FILES; n++) { |
| # lfs_file_close(&lfs, &files[n]) => 0; |
| # } |
| # lfs_unmount(&lfs) => 0; |
| # |
| # lfs_mount(&lfs, cfg) => 0; |
| # for (int n = 0; n < FILES; n++) { |
| # char path[1024]; |
| # sprintf(path, "breakfast/%s", names[n]); |
| # lfs_file_t file; |
| # lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; |
| # size_t size = strlen(names[n]); |
| # for (lfs_size_t i = 0; i < SIZE; i += size) { |
| # uint8_t buffer[1024]; |
| # lfs_file_read(&lfs, &file, buffer, size) => size; |
| # assert(memcmp(buffer, names[n], size) == 0); |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # } |
| # lfs_unmount(&lfs) => 0; |
| #''' |
| # |
| ## serial allocation test |
| #[cases.test_alloc_serial] |
| #defines.FILES = 3 |
| #defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)' |
| #code = ''' |
| # const char *names[] = {"bacon", "eggs", "pancakes"}; |
| # |
| # lfs_t lfs; |
| # lfs_format(&lfs, cfg) => 0; |
| # lfs_mount(&lfs, cfg) => 0; |
| # lfs_mkdir(&lfs, "breakfast") => 0; |
| # lfs_unmount(&lfs) => 0; |
| # |
| # for (int n = 0; n < FILES; n++) { |
| # lfs_mount(&lfs, cfg) => 0; |
| # char path[1024]; |
| # sprintf(path, "breakfast/%s", names[n]); |
| # lfs_file_t file; |
| # lfs_file_open(&lfs, &file, path, |
| # LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; |
| # size_t size = strlen(names[n]); |
| # uint8_t buffer[1024]; |
| # memcpy(buffer, names[n], size); |
| # for (int i = 0; i < SIZE; i += size) { |
| # lfs_file_write(&lfs, &file, buffer, size) => size; |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # lfs_unmount(&lfs) => 0; |
| # } |
| # |
| # lfs_mount(&lfs, cfg) => 0; |
| # for (int n = 0; n < FILES; n++) { |
| # char path[1024]; |
| # sprintf(path, "breakfast/%s", names[n]); |
| # lfs_file_t file; |
| # lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; |
| # size_t size = strlen(names[n]); |
| # for (int i = 0; i < SIZE; i += size) { |
| # uint8_t buffer[1024]; |
| # lfs_file_read(&lfs, &file, buffer, size) => size; |
| # assert(memcmp(buffer, names[n], size) == 0); |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # } |
| # lfs_unmount(&lfs) => 0; |
| #''' |
| # |
| ## parallel allocation reuse test |
| #[cases.test_alloc_parallel_reuse] |
| #defines.FILES = 3 |
| #defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)' |
| #defines.CYCLES = [1, 10] |
| #code = ''' |
| # const char *names[] = {"bacon", "eggs", "pancakes"}; |
| # lfs_file_t files[FILES]; |
| # |
| # lfs_t lfs; |
| # lfs_format(&lfs, cfg) => 0; |
| # |
| # for (int c = 0; c < CYCLES; c++) { |
| # lfs_mount(&lfs, cfg) => 0; |
| # lfs_mkdir(&lfs, "breakfast") => 0; |
| # lfs_unmount(&lfs) => 0; |
| # |
| # lfs_mount(&lfs, cfg) => 0; |
| # for (int n = 0; n < FILES; n++) { |
| # char path[1024]; |
| # sprintf(path, "breakfast/%s", names[n]); |
| # lfs_file_open(&lfs, &files[n], path, |
| # LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; |
| # } |
| # for (int n = 0; n < FILES; n++) { |
| # size_t size = strlen(names[n]); |
| # for (int i = 0; i < SIZE; i += size) { |
| # lfs_file_write(&lfs, &files[n], names[n], size) => size; |
| # } |
| # } |
| # for (int n = 0; n < FILES; n++) { |
| # lfs_file_close(&lfs, &files[n]) => 0; |
| # } |
| # lfs_unmount(&lfs) => 0; |
| # |
| # lfs_mount(&lfs, cfg) => 0; |
| # for (int n = 0; n < FILES; n++) { |
| # char path[1024]; |
| # sprintf(path, "breakfast/%s", names[n]); |
| # lfs_file_t file; |
| # lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; |
| # size_t size = strlen(names[n]); |
| # for (int i = 0; i < SIZE; i += size) { |
| # uint8_t buffer[1024]; |
| # lfs_file_read(&lfs, &file, buffer, size) => size; |
| # assert(memcmp(buffer, names[n], size) == 0); |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # } |
| # lfs_unmount(&lfs) => 0; |
| # |
| # lfs_mount(&lfs, cfg) => 0; |
| # for (int n = 0; n < FILES; n++) { |
| # char path[1024]; |
| # sprintf(path, "breakfast/%s", names[n]); |
| # lfs_remove(&lfs, path) => 0; |
| # } |
| # lfs_remove(&lfs, "breakfast") => 0; |
| # lfs_unmount(&lfs) => 0; |
| # } |
| #''' |
| # |
| ## serial allocation reuse test |
| #[cases.test_alloc_serial_reuse] |
| #defines.FILES = 3 |
| #defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)' |
| #defines.CYCLES = [1, 10] |
| #code = ''' |
| # const char *names[] = {"bacon", "eggs", "pancakes"}; |
| # |
| # lfs_t lfs; |
| # lfs_format(&lfs, cfg) => 0; |
| # |
| # for (int c = 0; c < CYCLES; c++) { |
| # lfs_mount(&lfs, cfg) => 0; |
| # lfs_mkdir(&lfs, "breakfast") => 0; |
| # lfs_unmount(&lfs) => 0; |
| # |
| # for (int n = 0; n < FILES; n++) { |
| # lfs_mount(&lfs, cfg) => 0; |
| # char path[1024]; |
| # sprintf(path, "breakfast/%s", names[n]); |
| # lfs_file_t file; |
| # lfs_file_open(&lfs, &file, path, |
| # LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; |
| # size_t size = strlen(names[n]); |
| # uint8_t buffer[1024]; |
| # memcpy(buffer, names[n], size); |
| # for (int i = 0; i < SIZE; i += size) { |
| # lfs_file_write(&lfs, &file, buffer, size) => size; |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # lfs_unmount(&lfs) => 0; |
| # } |
| # |
| # lfs_mount(&lfs, cfg) => 0; |
| # for (int n = 0; n < FILES; n++) { |
| # char path[1024]; |
| # sprintf(path, "breakfast/%s", names[n]); |
| # lfs_file_t file; |
| # lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; |
| # size_t size = strlen(names[n]); |
| # for (int i = 0; i < SIZE; i += size) { |
| # uint8_t buffer[1024]; |
| # lfs_file_read(&lfs, &file, buffer, size) => size; |
| # assert(memcmp(buffer, names[n], size) == 0); |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # } |
| # lfs_unmount(&lfs) => 0; |
| # |
| # lfs_mount(&lfs, cfg) => 0; |
| # for (int n = 0; n < FILES; n++) { |
| # char path[1024]; |
| # sprintf(path, "breakfast/%s", names[n]); |
| # lfs_remove(&lfs, path) => 0; |
| # } |
| # lfs_remove(&lfs, "breakfast") => 0; |
| # lfs_unmount(&lfs) => 0; |
| # } |
| #''' |
| # |
| ## exhaustion test |
| #[cases.test_alloc_exhaustion] |
| #code = ''' |
| # lfs_t lfs; |
| # lfs_format(&lfs, cfg) => 0; |
| # lfs_mount(&lfs, cfg) => 0; |
| # lfs_file_t file; |
| # lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); |
| # size_t size = strlen("exhaustion"); |
| # uint8_t buffer[1024]; |
| # memcpy(buffer, "exhaustion", size); |
| # lfs_file_write(&lfs, &file, buffer, size) => size; |
| # lfs_file_sync(&lfs, &file) => 0; |
| # |
| # size = strlen("blahblahblahblah"); |
| # memcpy(buffer, "blahblahblahblah", size); |
| # lfs_ssize_t res; |
| # while (true) { |
| # res = lfs_file_write(&lfs, &file, buffer, size); |
| # if (res < 0) { |
| # break; |
| # } |
| # |
| # res => size; |
| # } |
| # res => LFS_ERR_NOSPC; |
| # |
| # lfs_file_close(&lfs, &file) => 0; |
| # lfs_unmount(&lfs) => 0; |
| # |
| # lfs_mount(&lfs, cfg) => 0; |
| # lfs_file_open(&lfs, &file, "exhaustion", LFS_O_RDONLY); |
| # size = strlen("exhaustion"); |
| # lfs_file_size(&lfs, &file) => size; |
| # lfs_file_read(&lfs, &file, buffer, size) => size; |
| # memcmp(buffer, "exhaustion", size) => 0; |
| # lfs_file_close(&lfs, &file) => 0; |
| # lfs_unmount(&lfs) => 0; |
| #''' |
| # |
| ## exhaustion wraparound test |
| #[cases.test_alloc_exhaustion_wraparound] |
| #defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-4)) / 3)' |
| #code = ''' |
| # lfs_t lfs; |
| # lfs_format(&lfs, cfg) => 0; |
| # lfs_mount(&lfs, cfg) => 0; |
| # |
| # lfs_file_t file; |
| # lfs_file_open(&lfs, &file, "padding", LFS_O_WRONLY | LFS_O_CREAT); |
| # size_t size = strlen("buffering"); |
| # uint8_t buffer[1024]; |
| # memcpy(buffer, "buffering", size); |
| # for (int i = 0; i < SIZE; i += size) { |
| # lfs_file_write(&lfs, &file, buffer, size) => size; |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # lfs_remove(&lfs, "padding") => 0; |
| # |
| # lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); |
| # size = strlen("exhaustion"); |
| # memcpy(buffer, "exhaustion", size); |
| # lfs_file_write(&lfs, &file, buffer, size) => size; |
| # lfs_file_sync(&lfs, &file) => 0; |
| # |
| # size = strlen("blahblahblahblah"); |
| # memcpy(buffer, "blahblahblahblah", size); |
| # lfs_ssize_t res; |
| # while (true) { |
| # res = lfs_file_write(&lfs, &file, buffer, size); |
| # if (res < 0) { |
| # break; |
| # } |
| # |
| # res => size; |
| # } |
| # res => LFS_ERR_NOSPC; |
| # |
| # lfs_file_close(&lfs, &file) => 0; |
| # lfs_unmount(&lfs) => 0; |
| # |
| # lfs_mount(&lfs, cfg) => 0; |
| # lfs_file_open(&lfs, &file, "exhaustion", LFS_O_RDONLY); |
| # size = strlen("exhaustion"); |
| # lfs_file_size(&lfs, &file) => size; |
| # lfs_file_read(&lfs, &file, buffer, size) => size; |
| # memcmp(buffer, "exhaustion", size) => 0; |
| # lfs_file_close(&lfs, &file) => 0; |
| # lfs_remove(&lfs, "exhaustion") => 0; |
| # lfs_unmount(&lfs) => 0; |
| #''' |
| # |
| ## dir exhaustion test |
| #[cases.test_alloc_dir_exhaustion] |
| #code = ''' |
| # lfs_t lfs; |
| # lfs_format(&lfs, cfg) => 0; |
| # lfs_mount(&lfs, cfg) => 0; |
| # |
| # // find out max file size |
| # lfs_mkdir(&lfs, "exhaustiondir") => 0; |
| # size_t size = strlen("blahblahblahblah"); |
| # uint8_t buffer[1024]; |
| # memcpy(buffer, "blahblahblahblah", size); |
| # lfs_file_t file; |
| # lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); |
| # int count = 0; |
| # int err; |
| # while (true) { |
| # err = lfs_file_write(&lfs, &file, buffer, size); |
| # if (err < 0) { |
| # break; |
| # } |
| # |
| # count += 1; |
| # } |
| # err => LFS_ERR_NOSPC; |
| # lfs_file_close(&lfs, &file) => 0; |
| # |
| # lfs_remove(&lfs, "exhaustion") => 0; |
| # lfs_remove(&lfs, "exhaustiondir") => 0; |
| # |
| # // see if dir fits with max file size |
| # lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); |
| # for (int i = 0; i < count; i++) { |
| # lfs_file_write(&lfs, &file, buffer, size) => size; |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # |
| # lfs_mkdir(&lfs, "exhaustiondir") => 0; |
| # lfs_remove(&lfs, "exhaustiondir") => 0; |
| # lfs_remove(&lfs, "exhaustion") => 0; |
| # |
| # // see if dir fits with > max file size |
| # lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); |
| # for (int i = 0; i < count+1; i++) { |
| # lfs_file_write(&lfs, &file, buffer, size) => size; |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # |
| # lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC; |
| # |
| # lfs_remove(&lfs, "exhaustion") => 0; |
| # lfs_unmount(&lfs) => 0; |
| #''' |
| # |
| ## what if we have a bad block during an allocation scan? |
| #[cases.test_alloc_bad_blocks] |
| #in = "lfs.c" |
| #defines.ERASE_CYCLES = 0xffffffff |
| #defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_READERROR' |
| #code = ''' |
| # lfs_t lfs; |
| # lfs_format(&lfs, cfg) => 0; |
| # lfs_mount(&lfs, cfg) => 0; |
| # // first fill to exhaustion to find available space |
| # lfs_file_t file; |
| # lfs_file_open(&lfs, &file, "pacman", LFS_O_WRONLY | LFS_O_CREAT) => 0; |
| # uint8_t buffer[1024]; |
| # strcpy((char*)buffer, "waka"); |
| # size_t size = strlen("waka"); |
| # lfs_size_t filesize = 0; |
| # while (true) { |
| # lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size); |
| # assert(res == (lfs_ssize_t)size || res == LFS_ERR_NOSPC); |
| # if (res == LFS_ERR_NOSPC) { |
| # break; |
| # } |
| # filesize += size; |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # // now fill all but a couple of blocks of the filesystem with data |
| # filesize -= 3*BLOCK_SIZE; |
| # lfs_file_open(&lfs, &file, "pacman", LFS_O_WRONLY | LFS_O_CREAT) => 0; |
| # strcpy((char*)buffer, "waka"); |
| # size = strlen("waka"); |
| # for (lfs_size_t i = 0; i < filesize/size; i++) { |
| # lfs_file_write(&lfs, &file, buffer, size) => size; |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # // also save head of file so we can error during lookahead scan |
| # lfs_block_t fileblock = file.ctz.head; |
| # lfs_unmount(&lfs) => 0; |
| # |
| # // remount to force an alloc scan |
| # lfs_mount(&lfs, cfg) => 0; |
| # |
| # // but mark the head of our file as a "bad block", this is force our |
| # // scan to bail early |
| # lfs_emubd_setwear(cfg, fileblock, 0xffffffff) => 0; |
| # lfs_file_open(&lfs, &file, "ghost", LFS_O_WRONLY | LFS_O_CREAT) => 0; |
| # strcpy((char*)buffer, "chomp"); |
| # size = strlen("chomp"); |
| # while (true) { |
| # lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size); |
| # assert(res == (lfs_ssize_t)size || res == LFS_ERR_CORRUPT); |
| # if (res == LFS_ERR_CORRUPT) { |
| # break; |
| # } |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # |
| # // now reverse the "bad block" and try to write the file again until we |
| # // run out of space |
| # lfs_emubd_setwear(cfg, fileblock, 0) => 0; |
| # lfs_file_open(&lfs, &file, "ghost", LFS_O_WRONLY | LFS_O_CREAT) => 0; |
| # strcpy((char*)buffer, "chomp"); |
| # size = strlen("chomp"); |
| # while (true) { |
| # lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size); |
| # assert(res == (lfs_ssize_t)size || res == LFS_ERR_NOSPC); |
| # if (res == LFS_ERR_NOSPC) { |
| # break; |
| # } |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # |
| # lfs_unmount(&lfs) => 0; |
| # |
| # // check that the disk isn't hurt |
| # lfs_mount(&lfs, cfg) => 0; |
| # lfs_file_open(&lfs, &file, "pacman", LFS_O_RDONLY) => 0; |
| # strcpy((char*)buffer, "waka"); |
| # size = strlen("waka"); |
| # for (lfs_size_t i = 0; i < filesize/size; i++) { |
| # uint8_t rbuffer[4]; |
| # lfs_file_read(&lfs, &file, rbuffer, size) => size; |
| # assert(memcmp(rbuffer, buffer, size) == 0); |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # lfs_unmount(&lfs) => 0; |
| #''' |
| # |
| # |
| ## Below, I don't like these tests. They're fragile and depend _heavily_ |
| ## on the geometry of the block device. But they are valuable. Eventually they |
| ## should be removed and replaced with generalized tests. |
| # |
| ## chained dir exhaustion test |
| #[cases.test_alloc_chained_dir_exhaustion] |
| #if = 'BLOCK_SIZE == 512' |
| #defines.BLOCK_COUNT = 1024 |
| #code = ''' |
| # lfs_t lfs; |
| # lfs_format(&lfs, cfg) => 0; |
| # lfs_mount(&lfs, cfg) => 0; |
| # |
| # // find out max file size |
| # lfs_mkdir(&lfs, "exhaustiondir") => 0; |
| # for (int i = 0; i < 10; i++) { |
| # char path[1024]; |
| # sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i); |
| # lfs_mkdir(&lfs, path) => 0; |
| # } |
| # size_t size = strlen("blahblahblahblah"); |
| # uint8_t buffer[1024]; |
| # memcpy(buffer, "blahblahblahblah", size); |
| # lfs_file_t file; |
| # lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); |
| # int count = 0; |
| # int err; |
| # while (true) { |
| # err = lfs_file_write(&lfs, &file, buffer, size); |
| # if (err < 0) { |
| # break; |
| # } |
| # |
| # count += 1; |
| # } |
| # err => LFS_ERR_NOSPC; |
| # lfs_file_close(&lfs, &file) => 0; |
| # |
| # lfs_remove(&lfs, "exhaustion") => 0; |
| # lfs_remove(&lfs, "exhaustiondir") => 0; |
| # for (int i = 0; i < 10; i++) { |
| # char path[1024]; |
| # sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i); |
| # lfs_remove(&lfs, path) => 0; |
| # } |
| # |
| # // see that chained dir fails |
| # lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); |
| # for (int i = 0; i < count+1; i++) { |
| # lfs_file_write(&lfs, &file, buffer, size) => size; |
| # } |
| # lfs_file_sync(&lfs, &file) => 0; |
| # |
| # for (int i = 0; i < 10; i++) { |
| # char path[1024]; |
| # sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i); |
| # lfs_mkdir(&lfs, path) => 0; |
| # } |
| # |
| # lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC; |
| # |
| # // shorten file to try a second chained dir |
| # while (true) { |
| # err = lfs_mkdir(&lfs, "exhaustiondir"); |
| # if (err != LFS_ERR_NOSPC) { |
| # break; |
| # } |
| # |
| # lfs_ssize_t filesize = lfs_file_size(&lfs, &file); |
| # filesize > 0 => true; |
| # |
| # lfs_file_truncate(&lfs, &file, filesize - size) => 0; |
| # lfs_file_sync(&lfs, &file) => 0; |
| # } |
| # err => 0; |
| # |
| # lfs_mkdir(&lfs, "exhaustiondir2") => LFS_ERR_NOSPC; |
| # |
| # lfs_file_close(&lfs, &file) => 0; |
| # lfs_unmount(&lfs) => 0; |
| #''' |
| # |
| ## split dir test |
| #[cases.test_alloc_split_dir] |
| #if = 'BLOCK_SIZE == 512' |
| #defines.BLOCK_COUNT = 1024 |
| #code = ''' |
| # lfs_t lfs; |
| # lfs_format(&lfs, cfg) => 0; |
| # lfs_mount(&lfs, cfg) => 0; |
| # |
| # // create one block hole for half a directory |
| # lfs_file_t file; |
| # lfs_file_open(&lfs, &file, "bump", LFS_O_WRONLY | LFS_O_CREAT) => 0; |
| # for (lfs_size_t i = 0; i < cfg->block_size; i += 2) { |
| # uint8_t buffer[1024]; |
| # memcpy(&buffer[i], "hi", 2); |
| # } |
| # uint8_t buffer[1024]; |
| # lfs_file_write(&lfs, &file, buffer, cfg->block_size) => cfg->block_size; |
| # lfs_file_close(&lfs, &file) => 0; |
| # |
| # lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); |
| # size_t size = strlen("blahblahblahblah"); |
| # memcpy(buffer, "blahblahblahblah", size); |
| # for (lfs_size_t i = 0; |
| # i < (cfg->block_count-4)*(cfg->block_size-8); |
| # i += size) { |
| # lfs_file_write(&lfs, &file, buffer, size) => size; |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # |
| # // remount to force reset of lookahead |
| # lfs_unmount(&lfs) => 0; |
| # lfs_mount(&lfs, cfg) => 0; |
| # |
| # // open hole |
| # lfs_remove(&lfs, "bump") => 0; |
| # |
| # lfs_mkdir(&lfs, "splitdir") => 0; |
| # lfs_file_open(&lfs, &file, "splitdir/bump", |
| # LFS_O_WRONLY | LFS_O_CREAT) => 0; |
| # for (lfs_size_t i = 0; i < cfg->block_size; i += 2) { |
| # memcpy(&buffer[i], "hi", 2); |
| # } |
| # lfs_file_write(&lfs, &file, buffer, 2*cfg->block_size) => LFS_ERR_NOSPC; |
| # lfs_file_close(&lfs, &file) => 0; |
| # |
| # lfs_unmount(&lfs) => 0; |
| #''' |
| # |
| ## outdated lookahead test |
| #[cases.test_alloc_outdated_lookahead] |
| #if = 'BLOCK_SIZE == 512' |
| #defines.BLOCK_COUNT = 1024 |
| #code = ''' |
| # lfs_t lfs; |
| # lfs_format(&lfs, cfg) => 0; |
| # lfs_mount(&lfs, cfg) => 0; |
| # |
| # // fill completely with two files |
| # lfs_file_t file; |
| # lfs_file_open(&lfs, &file, "exhaustion1", |
| # LFS_O_WRONLY | LFS_O_CREAT) => 0; |
| # size_t size = strlen("blahblahblahblah"); |
| # uint8_t buffer[1024]; |
| # memcpy(buffer, "blahblahblahblah", size); |
| # for (lfs_size_t i = 0; |
| # i < ((cfg->block_count-2)/2)*(cfg->block_size-8); |
| # i += size) { |
| # lfs_file_write(&lfs, &file, buffer, size) => size; |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # |
| # lfs_file_open(&lfs, &file, "exhaustion2", |
| # LFS_O_WRONLY | LFS_O_CREAT) => 0; |
| # size = strlen("blahblahblahblah"); |
| # memcpy(buffer, "blahblahblahblah", size); |
| # for (lfs_size_t i = 0; |
| # i < ((cfg->block_count-2+1)/2)*(cfg->block_size-8); |
| # i += size) { |
| # lfs_file_write(&lfs, &file, buffer, size) => size; |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # |
| # // remount to force reset of lookahead |
| # lfs_unmount(&lfs) => 0; |
| # lfs_mount(&lfs, cfg) => 0; |
| # |
| # // rewrite one file |
| # lfs_file_open(&lfs, &file, "exhaustion1", |
| # LFS_O_WRONLY | LFS_O_TRUNC) => 0; |
| # lfs_file_sync(&lfs, &file) => 0; |
| # size = strlen("blahblahblahblah"); |
| # memcpy(buffer, "blahblahblahblah", size); |
| # for (lfs_size_t i = 0; |
| # i < ((cfg->block_count-2)/2)*(cfg->block_size-8); |
| # i += size) { |
| # lfs_file_write(&lfs, &file, buffer, size) => size; |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # |
| # // rewrite second file, this requires lookahead does not |
| # // use old population |
| # lfs_file_open(&lfs, &file, "exhaustion2", |
| # LFS_O_WRONLY | LFS_O_TRUNC) => 0; |
| # lfs_file_sync(&lfs, &file) => 0; |
| # size = strlen("blahblahblahblah"); |
| # memcpy(buffer, "blahblahblahblah", size); |
| # for (lfs_size_t i = 0; |
| # i < ((cfg->block_count-2+1)/2)*(cfg->block_size-8); |
| # i += size) { |
| # lfs_file_write(&lfs, &file, buffer, size) => size; |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # |
| # lfs_unmount(&lfs) => 0; |
| #''' |
| # |
| ## outdated lookahead and split dir test |
| #[cases.test_alloc_outdated_lookahead_split_dir] |
| #if = 'BLOCK_SIZE == 512' |
| #defines.BLOCK_COUNT = 1024 |
| #code = ''' |
| # lfs_t lfs; |
| # lfs_format(&lfs, cfg) => 0; |
| # lfs_mount(&lfs, cfg) => 0; |
| # |
| # // fill completely with two files |
| # lfs_file_t file; |
| # lfs_file_open(&lfs, &file, "exhaustion1", |
| # LFS_O_WRONLY | LFS_O_CREAT) => 0; |
| # size_t size = strlen("blahblahblahblah"); |
| # uint8_t buffer[1024]; |
| # memcpy(buffer, "blahblahblahblah", size); |
| # for (lfs_size_t i = 0; |
| # i < ((cfg->block_count-2)/2)*(cfg->block_size-8); |
| # i += size) { |
| # lfs_file_write(&lfs, &file, buffer, size) => size; |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # |
| # lfs_file_open(&lfs, &file, "exhaustion2", |
| # LFS_O_WRONLY | LFS_O_CREAT) => 0; |
| # size = strlen("blahblahblahblah"); |
| # memcpy(buffer, "blahblahblahblah", size); |
| # for (lfs_size_t i = 0; |
| # i < ((cfg->block_count-2+1)/2)*(cfg->block_size-8); |
| # i += size) { |
| # lfs_file_write(&lfs, &file, buffer, size) => size; |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # |
| # // remount to force reset of lookahead |
| # lfs_unmount(&lfs) => 0; |
| # lfs_mount(&lfs, cfg) => 0; |
| # |
| # // rewrite one file with a hole of one block |
| # lfs_file_open(&lfs, &file, "exhaustion1", |
| # LFS_O_WRONLY | LFS_O_TRUNC) => 0; |
| # lfs_file_sync(&lfs, &file) => 0; |
| # size = strlen("blahblahblahblah"); |
| # memcpy(buffer, "blahblahblahblah", size); |
| # for (lfs_size_t i = 0; |
| # i < ((cfg->block_count-2)/2 - 1)*(cfg->block_size-8); |
| # i += size) { |
| # lfs_file_write(&lfs, &file, buffer, size) => size; |
| # } |
| # lfs_file_close(&lfs, &file) => 0; |
| # |
| # // try to allocate a directory, should fail! |
| # lfs_mkdir(&lfs, "split") => LFS_ERR_NOSPC; |
| # |
| # // file should not fail |
| # lfs_file_open(&lfs, &file, "notasplit", |
| # LFS_O_WRONLY | LFS_O_CREAT) => 0; |
| # lfs_file_write(&lfs, &file, "hi", 2) => 2; |
| # lfs_file_close(&lfs, &file) => 0; |
| # |
| # lfs_unmount(&lfs) => 0; |
| #''' |