diff --git a/lfs.c b/lfs.c index bba39503..feb7d001 100644 --- a/lfs.c +++ b/lfs.c @@ -3461,8 +3461,8 @@ static int lfsr_rbyd_appendcksum(lfs_t *lfs, lfsr_rbyd_t *rbyd) { return err; } - // flush our caches, finalizing the commit on-disk - err = lfsr_bd_sync(lfs); + // flush any pending progs + err = lfsr_bd_flush(lfs, NULL, false); if (err) { return err; } @@ -7115,6 +7115,13 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, } } + // make sure mtree/mroot changes are on-disk before committing + // metadata + err = lfsr_bd_sync(lfs); + if (err) { + goto failed; + } + // commit new mtree into our mroot // // note end_rid=0 here will delete any files leftover from a split @@ -7157,6 +7164,13 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, mrootchild = mrootparent_; + // make sure mtree/mroot changes are on-disk before committing + // metadata + err = lfsr_bd_sync(lfs); + if (err) { + goto failed; + } + // commit mrootchild uint8_t mrootchild_buf[LFSR_MPTR_DSIZE]; err = lfsr_mdir_commit_(lfs, &mrootparent_, -1, -1, NULL, @@ -7186,6 +7200,13 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, mrootchild.rbyd.blocks[0], mrootchild.rbyd.blocks[1], mrootchild_.rbyd.blocks[0], mrootchild_.rbyd.blocks[1]); + // make sure mtree/mroot changes are on-disk before committing + // metadata + err = lfsr_bd_sync(lfs); + if (err) { + goto failed; + } + // commit the new mroot anchor lfsr_mdir_t mrootanchor_; err = lfsr_mdir_swap__(lfs, &mrootanchor_, &mrootchild, true); @@ -7215,6 +7236,12 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, // gstate must have been committed by a lower-level function at this point LFS_ASSERT(lfsr_gdelta_iszero(lfs->grm_d, LFSR_GRM_DSIZE)); + // sync on-disk state + err = lfsr_bd_sync(lfs); + if (err) { + return err; + } + // success? update in-device state, we must not error at this point // toss our cksum into the filesystem seed for pseudorandom numbers @@ -8672,8 +8699,14 @@ static int lfsr_formatinited(lfs_t *lfs) { } } + // sync on-disk state + int err = lfsr_bd_sync(lfs); + if (err) { + return err; + } + // test that mount works with our formatted disk - int err = lfsr_mountinited(lfs); + err = lfsr_mountinited(lfs); if (err) { return err; } @@ -11681,7 +11714,7 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { // checkpoint the allocator again lfs_alloc_ckpoint(lfs); - // commit our file's metadata + // commit any changes to our file's metadata lfsr_attr_t attrs[2]; lfs_size_t attr_count = 0; lfsr_data_t name_data; @@ -11728,6 +11761,13 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { LFS_UNREACHABLE(); } + // make sure data is on-disk before committing metadata + err = lfsr_bd_sync(lfs); + if (err) { + goto failed; + } + + // commit! LFS_ASSERT(attr_count <= sizeof(attrs)/sizeof(lfsr_attr_t)); err = lfsr_mdir_commit(lfs, &file->o.mdir, diff --git a/tests/test_badblocks.toml b/tests/test_badblocks.toml index cb112ca1..38ac33d6 100644 --- a/tests/test_badblocks.toml +++ b/tests/test_badblocks.toml @@ -1,10 +1,19 @@ # Bad-block related tests -after = ['test_dirs', 'test_files', 'test_fwrite', 'test_forphans'] +after = [ + 'test_dirs', + 'test_files', + 'test_fwrite', + 'test_forphans' +] + +## Single badblock tests +# +# first test with every possible single badblock # B-tree's ridiculous branching factor is great for performance, but it makes # them a bit of a pain to test, here we test them explicitly -[cases.test_badblocks_one_btree] +[cases.test_badblocks_single_btree_many] defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ @@ -111,220 +120,8 @@ code = ''' } ''' -# this should cause cascading failures -[cases.test_badblocks_region_btree] -defines.ERASE_CYCLES = 0xffffffff -defines.BADBLOCK_BEHAVIOR = [ - 'LFS_EMUBD_BADBLOCK_PROGERROR', - 'LFS_EMUBD_BADBLOCK_ERASEERROR', - 'LFS_EMUBD_BADBLOCK_READERROR', - 'LFS_EMUBD_BADBLOCK_PROGNOOP', - 'LFS_EMUBD_BADBLOCK_ERASENOOP', -] -# we need prog checking to detect read errors -defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' -defines.MIRROR = [false, true] -defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] -# maximize lookahead buffer to avoid alloc scans -defines.LOOKAHEAD_SIZE = 'lfs_alignup(BLOCK_COUNT / 8, 8)' -defines.SEED = 42 -fuzz = 'SEED' -in = 'lfs.c' -code = ''' - // test a large region of bad blocks - for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { - // mark our badblock as bad - if (!MIRROR) { - lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; - } else { - lfs_emubd_setwear(CFG, i + BLOCK_COUNT/2, 0xffffffff) => 0; - } - } - - // test creating a btree - lfs_t lfs; - lfs_init(&lfs, CFG) => 0; - // create free lookahead - memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; - lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; - lfs_alloc_ckpoint(&lfs); - - // create a btree - lfsr_btree_t btree = LFSR_BTREE_NULL(); - - // set up a simulation to compare against - char *sim = malloc(N); - lfs_size_t sim_size = 0; - memset(sim, 0, N); - - uint32_t prng = SEED; - for (lfs_size_t i = 0; i < N; i++) { - // choose a pseudo-random bid - lfs_size_t bid = TEST_PRNG(&prng) % (sim_size+1); - - // add to btree - lfsr_btree_commit(&lfs, &btree, bid, LFSR_ATTRS( - LFSR_ATTR( - LFSR_TAG_DATA, +1, - LFSR_DATA_BUF(&(uint8_t){'a'+(i % 26)}, 1)))) => 0; - - // add to sim - memmove(&sim[bid+1], &sim[bid], sim_size-bid); - sim[bid] = 'a'+(i % 26); - sim_size += 1; - } - - // check that btree matches sim - printf("expd: ["); - bool first = true; - for (lfs_size_t i = 0; i < sim_size; i++) { - if (!first) { - printf(", "); - } - first = false; - printf("%c", sim[i]); - } - printf("]\n"); - printf("btree: w%d 0x%x.%x\n", - btree.weight, - btree.blocks[0], - btree.trunk); - assert(btree.weight == sim_size); - - uint8_t buffer[4]; - lfsr_tag_t tag_; - lfs_size_t weight_; - lfsr_data_t data_; - for (lfs_size_t i = 0; i < sim_size; i++) { - lfsr_btree_lookup(&lfs, &btree, i, - &tag_, &weight_, &data_) => 0; - lfsr_data_read(&lfs, &data_, buffer, 4) => 1; - assert(tag_ == LFSR_TAG_DATA); - assert(weight_ == 1); - assert(memcmp(buffer, &sim[i], 1) == 0); - } - - // and no extra elements - lfsr_btree_lookup(&lfs, &btree, sim_size, - &tag_, &weight_, &data_) => LFS_ERR_NOENT; - - // clean up sim - free(sim); - lfs_deinit(&lfs) => 0; -''' - -# this should cause cascading failures -[cases.test_badblocks_alternating_btree] -defines.ERASE_CYCLES = 0xffffffff -defines.BADBLOCK_BEHAVIOR = [ - 'LFS_EMUBD_BADBLOCK_PROGERROR', - 'LFS_EMUBD_BADBLOCK_ERASEERROR', - 'LFS_EMUBD_BADBLOCK_READERROR', - 'LFS_EMUBD_BADBLOCK_PROGNOOP', - 'LFS_EMUBD_BADBLOCK_ERASENOOP', -] -# we need prog checking to detect read errors -defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' -defines.MIRROR = [false, true] -defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] -# maximize lookahead buffer to avoid alloc scans -defines.LOOKAHEAD_SIZE = 'lfs_alignup(BLOCK_COUNT / 8, 8)' -defines.SEED = 42 -fuzz = 'SEED' -in = 'lfs.c' -code = ''' - // test a large region of bad blocks - for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { - // mark our badblock as bad - if (!MIRROR) { - lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; - } else { - lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; - } - } - - // test creating a btree - lfs_t lfs; - lfs_init(&lfs, CFG) => 0; - // create free lookahead - memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; - lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; - lfs_alloc_ckpoint(&lfs); - - // create a btree - lfsr_btree_t btree = LFSR_BTREE_NULL(); - - // set up a simulation to compare against - char *sim = malloc(N); - lfs_size_t sim_size = 0; - memset(sim, 0, N); - - uint32_t prng = SEED; - for (lfs_size_t i = 0; i < N; i++) { - // choose a pseudo-random bid - lfs_size_t bid = TEST_PRNG(&prng) % (sim_size+1); - - // add to btree - lfsr_btree_commit(&lfs, &btree, bid, LFSR_ATTRS( - LFSR_ATTR( - LFSR_TAG_DATA, +1, - LFSR_DATA_BUF(&(uint8_t){'a'+(i % 26)}, 1)))) => 0; - - // add to sim - memmove(&sim[bid+1], &sim[bid], sim_size-bid); - sim[bid] = 'a'+(i % 26); - sim_size += 1; - } - - // check that btree matches sim - printf("expd: ["); - bool first = true; - for (lfs_size_t i = 0; i < sim_size; i++) { - if (!first) { - printf(", "); - } - first = false; - printf("%c", sim[i]); - } - printf("]\n"); - printf("btree: w%d 0x%x.%x\n", - btree.weight, - btree.blocks[0], - btree.trunk); - assert(btree.weight == sim_size); - - uint8_t buffer[4]; - lfsr_tag_t tag_; - lfs_size_t weight_; - lfsr_data_t data_; - for (lfs_size_t i = 0; i < sim_size; i++) { - lfsr_btree_lookup(&lfs, &btree, i, - &tag_, &weight_, &data_) => 0; - lfsr_data_read(&lfs, &data_, buffer, 4) => 1; - assert(tag_ == LFSR_TAG_DATA); - assert(weight_ == 1); - assert(memcmp(buffer, &sim[i], 1) == 0); - } - - // and no extra elements - lfsr_btree_lookup(&lfs, &btree, sim_size, - &tag_, &weight_, &data_) => LFS_ERR_NOENT; - - // clean up sim - free(sim); - lfs_deinit(&lfs) => 0; -''' - - - -# similar tests, but now over a real filesystem -[cases.test_badblocks_one_dirs] +# with dirs +[cases.test_badblocks_single_dir_many] defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ @@ -336,11 +133,7 @@ defines.BADBLOCK_BEHAVIOR = [ ] # we need prog checking to detect read errors defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' -defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] -# do more ops than dirs to encourage rename collisions -defines.OPS = '2*N' -defines.SEED = 42 -fuzz = 'SEED' +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] code = ''' // test all possible bad blocks for (lfs_size_t i = 2; @@ -356,37 +149,12 @@ code = ''' lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, CFG) => 0; - // set up a simulation to compare against - lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); - lfs_size_t sim_size = 0; - - uint32_t prng = SEED; - for (lfs_size_t i = 0; i < OPS; i++) { - // choose a pseudo-random number - lfs_size_t x = TEST_PRNG(&prng) % N; - - // insert into our sim - for (lfs_size_t j = 0;; j++) { - if (j >= sim_size || sim[j] >= x) { - // already seen? - if (j < sim_size && sim[j] == x) { - // do nothing - } else { - // insert - memmove(&sim[j+1], &sim[j], - (sim_size-j)*sizeof(lfs_size_t)); - sim_size += 1; - sim[j] = x; - } - break; - } - } - - // create a directory here + // make this many directories + for (lfs_size_t i = 0; i < N; i++) { char name[256]; - sprintf(name, "dir%03x", x); + sprintf(name, "dir%03x", i); int err = lfsr_mkdir(&lfs, name); - assert(!err || err == LFS_ERR_EXIST); + assert(!err || (TEST_PLS && err == LFS_ERR_EXIST)); } for (int remount = 0; remount < 2; remount++) { @@ -396,10 +164,13 @@ code = ''' lfsr_mount(&lfs, CFG) => 0; } - // test that our directories match our simulation - for (lfs_size_t j = 0; j < sim_size; j++) { + // grm should be zero here + assert(lfs.grm_p[0] == 0); + + // check that our mkdir worked + for (lfs_size_t i = 0; i < N; i++) { char name[256]; - sprintf(name, "dir%03x", sim[j]); + sprintf(name, "dir%03x", i); struct lfs_info info; lfsr_stat(&lfs, name, &info) => 0; assert(strcmp(info.name, name) == 0); @@ -418,9 +189,9 @@ code = ''' assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); - for (lfs_size_t j = 0; j < sim_size; j++) { + for (lfs_size_t i = 0; i < N; i++) { char name[256]; - sprintf(name, "dir%03x", sim[j]); + sprintf(name, "dir%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_DIR); @@ -429,9 +200,9 @@ code = ''' lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; - for (lfs_size_t j = 0; j < sim_size; j++) { + for (lfs_size_t i = 0; i < N; i++) { char name[256]; - sprintf(name, "dir%03x", sim[j]); + sprintf(name, "dir%03x", i); lfsr_dir_open(&lfs, &dir, name) => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); @@ -446,8 +217,6 @@ code = ''' } } - // clean up sim/lfs - free(sim); lfsr_unmount(&lfs) => 0; // reset badblock @@ -455,9 +224,10 @@ code = ''' } ''' -# this should cause cascading failures -[cases.test_badblocks_region_dirs] +# fuzz dirs +[cases.test_badblocks_single_dir_fuzz] defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', @@ -467,109 +237,139 @@ defines.BADBLOCK_BEHAVIOR = [ ] # we need prog checking to detect read errors defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' -defines.MIRROR = [false, true] defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] -# do more ops than dirs to encourage rename collisions defines.OPS = '2*N' defines.SEED = 42 fuzz = 'SEED' code = ''' - // test a large region of bad blocks - for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // test all possible bad blocks + for (lfs_size_t i = 2; + i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); + i++) { + lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; // mark our badblock as bad - if (!MIRROR) { - if (i >= 2) { - lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; - } - } else { - if (i+BLOCK_COUNT/2 >= 2) { - lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; - } - } - } + lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; + printf("--- badblock: 0x%x ---\n", badblock); - // test creating directories - lfs_t lfs; - lfsr_format(&lfs, CFG) => 0; - lfsr_mount(&lfs, CFG) => 0; + // test fuzz with dirs + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; - // set up a simulation to compare against - lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); - lfs_size_t sim_size = 0; + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + lfs_size_t sim_size = 0; - uint32_t prng = SEED; - for (lfs_size_t i = 0; i < OPS; i++) { - // choose a pseudo-random number - lfs_size_t x = TEST_PRNG(&prng) % N; + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + // choose a pseudo-random op, either mkdir, remove, or rename + uint8_t op = TEST_PRNG(&prng) % 3; - // insert into our sim - for (lfs_size_t j = 0;; j++) { - if (j >= sim_size || sim[j] >= x) { - // already seen? - if (j < sim_size && sim[j] == x) { - // do nothing - } else { - // insert - memmove(&sim[j+1], &sim[j], - (sim_size-j)*sizeof(lfs_size_t)); - sim_size += 1; - sim[j] = x; + if (op == 0 || sim_size == 0) { + // choose a pseudo-random number, truncate to 3 hexadecimals + lfs_size_t x = TEST_PRNG(&prng) % N; + // insert into our sim + for (lfs_size_t j = 0;; j++) { + if (j >= sim_size || sim[j] >= x) { + // already seen? + if (j < sim_size && sim[j] == x) { + // do nothing + } else { + // insert + memmove(&sim[j+1], &sim[j], + (sim_size-j)*sizeof(lfs_size_t)); + sim_size += 1; + sim[j] = x; + } + break; + } } - break; - } - } - // create a directory here - char name[256]; - sprintf(name, "dir%03x", x); - int err = lfsr_mkdir(&lfs, name); - assert(!err || err == LFS_ERR_EXIST); - } + // create a directory here + char name[256]; + sprintf(name, "dir%03x", x); + int err = lfsr_mkdir(&lfs, name); + assert(!err || err == LFS_ERR_EXIST); - for (int remount = 0; remount < 2; remount++) { - // remount? - if (remount) { - lfsr_unmount(&lfs) => 0; - lfsr_mount(&lfs, CFG) => 0; - } + } else if (op == 1) { + // choose a pseudo-random entry to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + sim_size -= 1; - // test that our directories match our simulation - for (lfs_size_t j = 0; j < sim_size; j++) { - char name[256]; - sprintf(name, "dir%03x", sim[j]); - 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); - } + // remove this directory + char name[256]; + sprintf(name, "dir%03x", x); + lfsr_remove(&lfs, name) => 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 j = 0; j < sim_size; j++) { - char name[256]; - sprintf(name, "dir%03x", sim[j]); - lfsr_dir_read(&lfs, &dir, &info) => 0; - assert(strcmp(info.name, name) == 0); - assert(info.type == LFS_TYPE_DIR); - assert(info.size == 0); + } else { + // choose a pseudo-random entry to rename, and a pseudo-random + // number to rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // already seen and not a noop? + if (k < sim_size && sim[k] == y && x != y) { + // just delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + sim_size -= 1; + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + sim[k] = y; + } + break; + } + } + + // rename this directory + char old_name[256]; + sprintf(old_name, "dir%03x", x); + char new_name[256]; + sprintf(new_name, "dir%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } } - lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; - lfsr_dir_close(&lfs, &dir) => 0; - for (lfs_size_t j = 0; j < sim_size; j++) { - char name[256]; - sprintf(name, "dir%03x", sim[j]); - lfsr_dir_open(&lfs, &dir, name) => 0; + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // grm should be zero here + assert(lfs.grm_p[0] == 0); + + // test that our directories match our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "dir%03x", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + char name2[256]; + sprintf(name2, "dir%03x", sim[j]); + assert(strcmp(info.name, name2) == 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); @@ -578,19 +378,31 @@ code = ''' assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "dir%03x", sim[j]); + 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; } - } - // clean up sim/lfs - free(sim); - lfsr_unmount(&lfs) => 0; + // clean up sim/lfs + free(sim); + lfsr_unmount(&lfs) => 0; + + // reset badblock + lfs_emubd_setwear(CFG, badblock, 0) => 0; + } ''' -# this should cause cascading failures -[cases.test_badblocks_alternating_dirs] +# with files +[cases.test_badblocks_single_file_many] defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', @@ -600,130 +412,93 @@ defines.BADBLOCK_BEHAVIOR = [ ] # we need prog checking to detect read errors defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' -defines.MIRROR = [false, true] -defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] -# do more ops than dirs to encourage rename collisions -defines.OPS = '2*N' -defines.SEED = 42 -fuzz = 'SEED' +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +if = '(SIZE*N)/BLOCK_SIZE <= 32' code = ''' - // test a large region of bad blocks - for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // test all possible bad blocks + for (lfs_size_t i = 2; + i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); + i++) { + lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; // mark our badblock as bad - if (!MIRROR) { - if (2*i+0 >= 2) { - lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; - } - } else { - if (2*i+1 >= 2) { - lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; + printf("--- badblock: 0x%x ---\n", badblock); + + // test creating files + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many files + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "amethyst%03x", i); + + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } - } - } - // test creating directories - lfs_t lfs; - lfsr_format(&lfs, CFG) => 0; - lfsr_mount(&lfs, CFG) => 0; + 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; + } - // set up a simulation to compare against - lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); - lfs_size_t sim_size = 0; + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } - uint32_t prng = SEED; - for (lfs_size_t i = 0; i < OPS; i++) { - // choose a pseudo-random number - lfs_size_t x = TEST_PRNG(&prng) % N; + // check that our writes worked + prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + // check with stat + char name[256]; + sprintf(name, "amethyst%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); - // insert into our sim - for (lfs_size_t j = 0;; j++) { - if (j >= sim_size || sim[j] >= x) { - // already seen? - if (j < sim_size && sim[j] == x) { - // do nothing - } else { - // insert - memmove(&sim[j+1], &sim[j], - (sim_size-j)*sizeof(lfs_size_t)); - sim_size += 1; - sim[j] = x; + // 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); } - break; + + 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; } } - // create a directory here - char name[256]; - sprintf(name, "dir%03x", x); - int err = lfsr_mkdir(&lfs, name); - assert(!err || err == LFS_ERR_EXIST); - } - - for (int remount = 0; remount < 2; remount++) { - // remount? - if (remount) { - lfsr_unmount(&lfs) => 0; - lfsr_mount(&lfs, CFG) => 0; - } - - // test that our directories match our simulation - for (lfs_size_t j = 0; j < sim_size; j++) { - char name[256]; - sprintf(name, "dir%03x", sim[j]); - 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 j = 0; j < sim_size; j++) { - char name[256]; - sprintf(name, "dir%03x", sim[j]); - 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; - for (lfs_size_t j = 0; j < sim_size; j++) { - char name[256]; - sprintf(name, "dir%03x", sim[j]); - lfsr_dir_open(&lfs, &dir, name) => 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); - lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; - lfsr_dir_close(&lfs, &dir) => 0; - } + // reset badblock + lfs_emubd_setwear(CFG, badblock, 0) => 0; } - - // clean up sim/lfs - free(sim); - lfsr_unmount(&lfs) => 0; ''' - -# with files -[cases.test_badblocks_one_files] +# fuzz files +[cases.test_badblocks_single_file_fuzz] defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ @@ -736,7 +511,6 @@ defines.BADBLOCK_BEHAVIOR = [ # we need prog checking to detect read errors defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' defines.N = [1, 2, 4, 8, 16, 32, 64] -# do more ops than dirs to encourage rename collisions defines.OPS = '2*N' defines.SIZE = [ '0', @@ -760,7 +534,7 @@ code = ''' lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; printf("--- badblock: 0x%x ---\n", badblock); - // test creating files + // test fuzz with files lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, CFG) => 0; @@ -772,45 +546,122 @@ code = ''' uint32_t prng = SEED; for (lfs_size_t i = 0; i < OPS; i++) { - // choose a pseudo-random number - lfs_size_t x = TEST_PRNG(&prng) % N; - // associate each file with a prng that generates its contents - uint32_t wprng = TEST_PRNG(&prng); + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 3; - // insert into our sim - for (lfs_size_t j = 0;; j++) { - if (j >= sim_size || sim[j] >= x) { - // already seen? - if (j < sim_size && sim[j] == x) { - // new prng - sim_prngs[j] = wprng; - } else { - // insert - memmove(&sim[j+1], &sim[j], - (sim_size-j)*sizeof(lfs_size_t)); - memmove(&sim_prngs[j+1], &sim_prngs[j], - (sim_size-j)*sizeof(uint32_t)); - sim_size += 1; - sim[j] = x; - sim_prngs[j] = wprng; + // creating a new file? + if (op == 0 || sim_size == 0) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + // associate each file with a prng that generates its contents + uint32_t wprng = TEST_PRNG(&prng); + + // insert into our sim + for (lfs_size_t j = 0;; j++) { + if (j >= sim_size || sim[j] >= x) { + // already seen? + if (j < sim_size && sim[j] == x) { + // new prng + sim_prngs[j] = wprng; + } else { + // insert + memmove(&sim[j+1], &sim[j], + (sim_size-j)*sizeof(lfs_size_t)); + memmove(&sim_prngs[j+1], &sim_prngs[j], + (sim_size-j)*sizeof(uint32_t)); + sim_size += 1; + sim[j] = x; + sim_prngs[j] = wprng; + } + break; } - break; } - } - // create a file here - char name[256]; - sprintf(name, "amethyst%03x", x); - uint8_t wbuf[SIZE]; - for (lfs_size_t j = 0; j < SIZE; j++) { - wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); - } + // create a file here + char name[256]; + sprintf(name, "amethyst%03x", x); + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } - lfsr_file_t file; - lfsr_file_open(&lfs, &file, name, - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; - lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; - lfsr_file_close(&lfs, &file) => 0; + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // deleting a file? + } else if (op == 1) { + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + + // delete this file + char name[256]; + sprintf(name, "amethyst%03x", x); + lfsr_remove(&lfs, name) => 0; + + // renaming a file? + } else { + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + uint32_t wprng = sim_prngs[j]; + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + if (k > j) { + k -= 1; + } + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim[k] = y; + sim_prngs[k] = wprng; + } + break; + } + } + + // rename this file + char old_name[256]; + sprintf(old_name, "amethyst%03x", x); + char new_name[256]; + sprintf(new_name, "amethyst%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } } for (int remount = 0; remount < 2; remount++) { @@ -883,9 +734,10 @@ code = ''' } ''' -# this should cause cascading failures -[cases.test_badblocks_region_files] +# with more complex file writes +[cases.test_badblocks_single_fwrite_fuzz] defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', @@ -895,12 +747,8 @@ defines.BADBLOCK_BEHAVIOR = [ ] # we need prog checking to detect read errors defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' -defines.MIRROR = [false, true] -defines.N = [1, 2, 4, 8, 16, 32, 64] -# do more ops than dirs to encourage rename collisions -defines.OPS = '2*N' +defines.OPS = 20 defines.SIZE = [ - '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', @@ -908,146 +756,147 @@ defines.SIZE = [ '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] +# chunk is more an upper limit here +defines.CHUNK = 64 +# INIT=0 => no init +# INIT=1 => fill with data +# INIT=2 => truncate to size +defines.INIT = [0, 1, 2] +defines.SYNC = [false, true] defines.SEED = 42 fuzz = 'SEED' -if = '(SIZE*N)/BLOCK_SIZE <= 16' +if = [ + 'CHUNK <= SIZE', + # this just saves testing time + 'SIZE <= 4*1024*FRAGMENT_SIZE', +] code = ''' - // test a large region of bad blocks - for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // test all possible bad blocks + for (lfs_size_t i = 2; + i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); + i++) { + lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; // mark our badblock as bad - if (!MIRROR) { - if (i >= 2) { - lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; + printf("--- badblock: 0x%x ---\n", badblock); + + // test with complex file writes + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + // simulate our file in ram + uint8_t sim[SIZE]; + lfs_off_t size; + uint32_t prng = SEED; + if (INIT == 0) { + memset(sim, 0, SIZE); + size = 0; + } else if (INIT == 1) { + for (lfs_size_t i = 0; i < SIZE; i++) { + sim[i] = 'a' + (TEST_PRNG(&prng) % 26); } + lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; + size = SIZE; } else { - if (i+BLOCK_COUNT/2 >= 2) { - lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; - } + memset(sim, 0, SIZE); + lfsr_file_truncate(&lfs, &file, SIZE) => 0; + size = SIZE; } - } - // test creating files - lfs_t lfs; - lfsr_format(&lfs, CFG) => 0; - lfsr_mount(&lfs, CFG) => 0; + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } - // set up a simulation to compare against - lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); - uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); - lfs_size_t sim_size = 0; + for (lfs_size_t i = 0; i < OPS; i++) { + // choose a random location + lfs_off_t off = TEST_PRNG(&prng) % SIZE; + // and a random size, up to the chunk size + lfs_size_t chunk = lfs_min32( + TEST_PRNG(&prng) % CHUNK, + SIZE - off); - uint32_t prng = SEED; - for (lfs_size_t i = 0; i < OPS; i++) { - // choose a pseudo-random number - lfs_size_t x = TEST_PRNG(&prng) % N; - // associate each file with a prng that generates its contents - uint32_t wprng = TEST_PRNG(&prng); - - // insert into our sim - for (lfs_size_t j = 0;; j++) { - if (j >= sim_size || sim[j] >= x) { - // already seen? - if (j < sim_size && sim[j] == x) { - // new prng - sim_prngs[j] = wprng; - } else { - // insert - memmove(&sim[j+1], &sim[j], - (sim_size-j)*sizeof(lfs_size_t)); - memmove(&sim_prngs[j+1], &sim_prngs[j], - (sim_size-j)*sizeof(uint32_t)); - sim_size += 1; - sim[j] = x; - sim_prngs[j] = wprng; - } - break; - } - } - - // create a file here - char name[256]; - sprintf(name, "amethyst%03x", x); - uint8_t wbuf[SIZE]; - for (lfs_size_t j = 0; j < SIZE; j++) { - wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + // update sim + for (lfs_size_t j = 0; j < chunk; j++) { + sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); + } + if (chunk != 0) { + size = lfs_max32(size, off+chunk); + } + + // update file + lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off; + lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk; + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } } - lfsr_file_t file; - lfsr_file_open(&lfs, &file, name, - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; - lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; - } - for (int remount = 0; remount < 2; remount++) { - // remount? - if (remount) { - lfsr_unmount(&lfs) => 0; - lfsr_mount(&lfs, CFG) => 0; - } + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } - // check that our files match our simulation - for (lfs_size_t j = 0; j < sim_size; j++) { - char name[256]; - sprintf(name, "amethyst%03x", sim[j]); + // check our file with stat struct lfs_info info; - lfsr_stat(&lfs, name, &info) => 0; - assert(strcmp(info.name, name) == 0); + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); assert(info.type == LFS_TYPE_REG); - assert(info.size == SIZE); - } + assert(info.size == size); - 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 j = 0; j < sim_size; j++) { - char name[256]; - sprintf(name, "amethyst%03x", sim[j]); + // and with dir read + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; - assert(strcmp(info.name, name) == 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); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "hello") == 0); assert(info.type == LFS_TYPE_REG); - assert(info.size == SIZE); - } - lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; - lfsr_dir_close(&lfs, &dir) => 0; - - // check the file contents - for (lfs_size_t j = 0; j < sim_size; j++) { - char name[256]; - sprintf(name, "amethyst%03x", sim[j]); - lfsr_file_t file; - lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; - - uint32_t wprng = sim_prngs[j]; - uint8_t wbuf[SIZE]; - for (lfs_size_t j = 0; j < SIZE; j++) { - wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); - } + assert(info.size == size); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; - uint8_t rbuf[SIZE]; - lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; - assert(memcmp(rbuf, wbuf, SIZE) == 0); + // try reading our file + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file) => size; + // try reading + uint8_t rbuf[2*SIZE]; + memset(rbuf, 0xaa, 2*SIZE); + lfsr_file_read(&lfs, &file, rbuf, 2*SIZE) => size; + // does our file match our simulation? + assert(memcmp(rbuf, sim, size) == 0); lfsr_file_close(&lfs, &file) => 0; } - } - // clean up sim/lfs - free(sim); - free(sim_prngs); - lfsr_unmount(&lfs) => 0; + lfsr_unmount(&lfs) => 0; + + // reset badblock + lfs_emubd_setwear(CFG, badblock, 0) => 0; + } ''' -# this should cause cascading failures -[cases.test_badblocks_alternating_files] +# with orphans, zombies, etc +[cases.test_badblocks_single_orphanzombie_fuzz] defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', @@ -1057,9 +906,7 @@ defines.BADBLOCK_BEHAVIOR = [ ] # we need prog checking to detect read errors defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' -defines.MIRROR = [false, true] defines.N = [1, 2, 4, 8, 16, 32, 64] -# do more ops than dirs to encourage rename collisions defines.OPS = '2*N' defines.SIZE = [ '0', @@ -1074,117 +921,298 @@ defines.SEED = 42 fuzz = 'SEED' if = '(SIZE*N)/BLOCK_SIZE <= 16' code = ''' - // test a large region of bad blocks - for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // test all possible bad blocks + for (lfs_size_t i = 2; + i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); + i++) { + lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; // mark our badblock as bad - if (!MIRROR) { - if (2*i+0 >= 2) { - lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; - } - } else { - if (2*i+1 >= 2) { - lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; - } - } - } + lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; + printf("--- badblock: 0x%x ---\n", badblock); - // test creating files - lfs_t lfs; - lfsr_format(&lfs, CFG) => 0; - lfsr_mount(&lfs, CFG) => 0; + // test with orphans, zombies, etc + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; - // set up a simulation to compare against - lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); - uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); - lfs_size_t sim_size = 0; + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + lfs_size_t sim_size = 0; - uint32_t prng = SEED; - for (lfs_size_t i = 0; i < OPS; i++) { - // choose a pseudo-random number - lfs_size_t x = TEST_PRNG(&prng) % N; - // associate each file with a prng that generates its contents - uint32_t wprng = TEST_PRNG(&prng); - - // insert into our sim - for (lfs_size_t j = 0;; j++) { - if (j >= sim_size || sim[j] >= x) { - // already seen? - if (j < sim_size && sim[j] == x) { - // new prng - sim_prngs[j] = wprng; - } else { - // insert - memmove(&sim[j+1], &sim[j], - (sim_size-j)*sizeof(lfs_size_t)); - memmove(&sim_prngs[j+1], &sim_prngs[j], - (sim_size-j)*sizeof(uint32_t)); - sim_size += 1; - sim[j] = x; - sim_prngs[j] = wprng; - } - break; - } - } - - // create a file here - char name[256]; - sprintf(name, "amethyst%03x", x); - uint8_t wbuf[SIZE]; - for (lfs_size_t j = 0; j < SIZE; j++) { - wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); - } + typedef struct sim_file { + lfs_size_t x; + bool orphan; + bool zombie; + uint32_t prng; + lfsr_file_t file; + } sim_file_t; + sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); + lfs_size_t sim_file_count = 0; - lfsr_file_t file; - lfsr_file_open(&lfs, &file, name, - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; - lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; - lfsr_file_close(&lfs, &file) => 0; - } + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + nonsense:; + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 5; - for (int remount = 0; remount < 2; remount++) { - // remount? - if (remount) { - lfsr_unmount(&lfs) => 0; - lfsr_mount(&lfs, CFG) => 0; - } + // open a new file? + if (op == 0) { + if (sim_file_count >= N) { + goto nonsense; + } + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; - // check that our files match our simulation - for (lfs_size_t j = 0; j < sim_size; j++) { - char name[256]; - sprintf(name, "amethyst%03x", sim[j]); - 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); - } + // already exists? + bool orphan = true; + uint32_t wprng = 0; + for (lfs_size_t j = 0; j < sim_size; j++) { + if (sim[j] == x) { + orphan = false; + wprng = sim_prngs[j]; + break; + } + } + // choose a random seed if we don't exist + if (orphan) { + wprng = TEST_PRNG(&prng); + } - 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 j = 0; j < sim_size; j++) { - char name[256]; - sprintf(name, "amethyst%03x", sim[j]); - lfsr_dir_read(&lfs, &dir, &info) => 0; - assert(strcmp(info.name, name) == 0); - assert(info.type == LFS_TYPE_REG); - assert(info.size == SIZE); - } - lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; - lfsr_dir_close(&lfs, &dir) => 0; + // open in our sim + lfs_size_t j = sim_file_count; + sim_files[j] = malloc(sizeof(sim_file_t)); + sim_files[j]->x = x; + sim_files[j]->orphan = orphan; + sim_files[j]->zombie = false; + sim_files[j]->prng = wprng; + sim_file_count++; - // check the file contents + // open the actual file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_file_open(&lfs, &sim_files[j]->file, name, + LFS_O_RDWR | LFS_O_CREAT) => 0; + + // write some initial data if we don't exist + if (orphan) { + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) + => SIZE; + } + + // write/rewrite a file? + } else if (op == 1) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + lfs_size_t x = sim_files[j]->x; + // choose a random seed + uint32_t wprng = TEST_PRNG(&prng); + + // update sim + sim_files[j]->prng = wprng; + if (!sim_files[j]->zombie) { + // insert into our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= x) { + // already seen? + if (k < sim_size && sim[k] == x) { + // new prng + sim_prngs[k] = wprng; + } else { + // insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim_size += 1; + sim[k] = x; + sim_prngs[k] = wprng; + } + break; + } + } + + // update related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x && !sim_files[k]->zombie) { + sim_files[k]->orphan = false; + sim_files[k]->prng = wprng; + } + } + } + + // write to the file + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; + lfsr_file_sync(&lfs, &sim_files[j]->file) + => (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT; + + // close a file? + } else if (op == 2) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + + // this doesn't really test anything, but if we don't close + // files eventually everything will end up zombies + + // close the file without affected disk + lfsr_file_desync(&lfs, &sim_files[j]->file) => 0; + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + + // remove from list + free(sim_files[j]); + sim_files[j] = sim_files[sim_file_count-1]; + sim_file_count -= 1; + + // remove a file? + } else if (op == 3) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + + // mark any related sim files as zombied + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x) { + sim_files[k]->zombie = true; + } + } + + // delete this file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_remove(&lfs, name) => 0; + + // rename a file? + } else if (op == 4) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + uint32_t wprng = sim_prngs[j]; + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + if (k > j) { + k -= 1; + } + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim[k] = y; + sim_prngs[k] = wprng; + } + break; + } + } + + // update any related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + // move source files + if (sim_files[k]->x == x) { + sim_files[k]->x = y; + + // mark target files as zombied + } else if (sim_files[k]->x == y) { + sim_files[k]->zombie = true; + } + } + + // rename this file + char old_name[256]; + sprintf(old_name, "batman%03x", x); + char new_name[256]; + sprintf(new_name, "batman%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + } + + // check that disk matches our simulation for (lfs_size_t j = 0; j < sim_size; j++) { char name[256]; - sprintf(name, "amethyst%03x", sim[j]); + sprintf(name, "batman%03x", sim[j]); + 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); + } + + 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 j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; @@ -1199,193 +1227,39 @@ code = ''' assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } - } - // clean up sim/lfs - free(sim); - free(sim_prngs); - lfsr_unmount(&lfs) => 0; -''' + // check that our file handles match our simulation + for (lfs_size_t j = 0; j < sim_file_count; j++) { + uint32_t wprng = sim_files[j]->prng; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + } -# with more complex file writes -[cases.test_badblocks_one_fwrite_fuzz] -defines.ERASE_CYCLES = 0xffffffff -defines.BADBLOCK = -1 -defines.BADBLOCK_BEHAVIOR = [ - 'LFS_EMUBD_BADBLOCK_PROGERROR', - 'LFS_EMUBD_BADBLOCK_ERASEERROR', - 'LFS_EMUBD_BADBLOCK_READERROR', - 'LFS_EMUBD_BADBLOCK_PROGNOOP', - 'LFS_EMUBD_BADBLOCK_ERASENOOP', -] -# we need prog checking to detect read errors -defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' -defines.N = 20 -defines.SIZE = [ - 'FILE_BUFFER_SIZE/2', - '2*FILE_BUFFER_SIZE', - 'BLOCK_SIZE/2', - 'BLOCK_SIZE', - '2*BLOCK_SIZE', - '4*BLOCK_SIZE', -] -# chunk is more an upper limit here -defines.CHUNK = [32, 8, 1] -# INIT=0 => no init -# INIT=1 => fill with data -# INIT=2 => truncate to size -defines.INIT = [0, 1, 2] -defines.SYNC = [false, true] -defines.REMOUNT = [false, true] -defines.SEED = 42 -fuzz = 'SEED' -if = [ - 'CHUNK <= SIZE', - # this just saves testing time - 'SIZE <= 4*1024*FRAGMENT_SIZE', -] -code = ''' - // test all possible bad blocks - for (lfs_size_t i = 2; - i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); - i++) { - lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; - // mark our badblock as bad - lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; - printf("--- badblock: 0x%x ---\n", badblock); - - // test with complex file writes - lfs_t lfs; - lfsr_format(&lfs, CFG) => 0; - lfsr_mount(&lfs, CFG) => 0; - - // create a file - lfsr_file_t file; - lfsr_file_open(&lfs, &file, "hello", - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; - // simulate our file in ram - uint8_t sim[SIZE]; - lfs_off_t size; - uint32_t prng = SEED; - if (INIT == 0) { - memset(sim, 0, SIZE); - size = 0; - } else if (INIT == 1) { - for (lfs_size_t i = 0; i < SIZE; i++) { - sim[i] = 'a' + (TEST_PRNG(&prng) % 26); - } - lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; - size = SIZE; - } else { - memset(sim, 0, SIZE); - lfsr_file_truncate(&lfs, &file, SIZE) => 0; - size = SIZE; - } - - // sync? - if (SYNC) { - lfsr_file_sync(&lfs, &file) => 0; - } - - // remount? - if (REMOUNT) { - lfsr_file_close(&lfs, &file) => 0; - lfsr_unmount(&lfs) => 0; - lfsr_mount(&lfs, CFG) => 0; - lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; - } - - for (lfs_size_t i = 0; i < N; i++) { - // choose a random location - lfs_off_t off = TEST_PRNG(&prng) % SIZE; - // and a random size, up to the chunk size - lfs_size_t chunk = lfs_min32( - TEST_PRNG(&prng) % CHUNK, - SIZE - off); - - // update sim - for (lfs_size_t j = 0; j < chunk; j++) { - sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); - } - if (chunk != 0) { - size = lfs_max32(size, off+chunk); - } - - // update file - lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off; - lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk; - - // sync? - if (SYNC) { - lfsr_file_sync(&lfs, &file) => 0; - } - - // remount? - if (REMOUNT) { - lfsr_file_close(&lfs, &file) => 0; - lfsr_unmount(&lfs) => 0; - lfsr_mount(&lfs, CFG) => 0; - lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; - } - } - lfsr_file_close(&lfs, &file) => 0; - - for (int remount = 0; remount < 2; remount++) { - // remount? - if (remount) { - lfsr_unmount(&lfs) => 0; - lfsr_mount(&lfs, CFG) => 0; - } - - // check our file with stat - struct lfs_info info; - lfsr_stat(&lfs, "hello", &info) => 0; - assert(strcmp(info.name, "hello") == 0); - assert(info.type == LFS_TYPE_REG); - assert(info.size == size); - - // and with dir read - lfsr_dir_t dir; - 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); - lfsr_dir_read(&lfs, &dir, &info) => 0; - assert(strcmp(info.name, "hello") == 0); - assert(info.type == LFS_TYPE_REG); - assert(info.size == size); - lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; - lfsr_dir_close(&lfs, &dir) => 0; - - // try reading our file - lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; - // is size correct? - lfsr_file_size(&lfs, &file) => size; - // try reading - uint8_t rbuf[2*SIZE]; - memset(rbuf, 0xaa, 2*SIZE); - lfsr_file_read(&lfs, &file, rbuf, 2*SIZE) => size; - // does our file match our simulation? - assert(memcmp(rbuf, sim, size) == 0); - lfsr_file_close(&lfs, &file) => 0; + // clean up sim/lfs + free(sim); + free(sim_prngs); + for (lfs_size_t j = 0; j < sim_file_count; j++) { + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + free(sim_files[j]); } - + free(sim_files); lfsr_unmount(&lfs) => 0; - // reset badblock lfs_emubd_setwear(CFG, badblock, 0) => 0; } ''' -# this should cause cascading failures -[cases.test_badblocks_region_fwrite_fuzz] +# with orphans, zombies, dirs, etc +[cases.test_badblocks_single_orphanzombiedir_fuzz] defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', @@ -1395,9 +1269,10 @@ defines.BADBLOCK_BEHAVIOR = [ ] # we need prog checking to detect read errors defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' -defines.MIRROR = [false, true] -defines.N = 20 +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = '2*N' defines.SIZE = [ + '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', @@ -1405,305 +1280,346 @@ defines.SIZE = [ '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] -# chunk is more an upper limit here -defines.CHUNK = [32, 8, 1] -# INIT=0 => no init -# INIT=1 => fill with data -# INIT=2 => truncate to size -defines.INIT = [0, 1, 2] -defines.SYNC = [false, true] -defines.REMOUNT = [false, true] defines.SEED = 42 fuzz = 'SEED' -if = [ - 'CHUNK <= SIZE', - # this just saves testing time - 'SIZE <= 4*1024*FRAGMENT_SIZE', -] +if = '(SIZE*N)/BLOCK_SIZE <= 16' code = ''' - // test a large region of bad blocks - for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // test all possible bad blocks + for (lfs_size_t i = 2; + i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); + i++) { + lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; // mark our badblock as bad - if (!MIRROR) { - if (i >= 2) { - lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; - } - } else { - if (i+BLOCK_COUNT/2 >= 2) { - lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; - } - } - } + lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; + printf("--- badblock: 0x%x ---\n", badblock); - // test with complex file writes - lfs_t lfs; - lfsr_format(&lfs, CFG) => 0; - lfsr_mount(&lfs, CFG) => 0; + // test with orphans, zombies, dirs, etc + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; - // create a file - lfsr_file_t file; - lfsr_file_open(&lfs, &file, "hello", - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; - // simulate our file in ram - uint8_t sim[SIZE]; - lfs_off_t size; - uint32_t prng = SEED; - if (INIT == 0) { - memset(sim, 0, SIZE); - size = 0; - } else if (INIT == 1) { - for (lfs_size_t i = 0; i < SIZE; i++) { - sim[i] = 'a' + (TEST_PRNG(&prng) % 26); - } - lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; - size = SIZE; - } else { - memset(sim, 0, SIZE); - lfsr_file_truncate(&lfs, &file, SIZE) => 0; - size = SIZE; - } + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + bool *sim_isdirs = malloc(N*sizeof(bool)); + lfs_size_t sim_size = 0; - // sync? - if (SYNC) { - lfsr_file_sync(&lfs, &file) => 0; - } + typedef struct sim_file { + lfs_size_t x; + bool orphan; + bool zombie; + uint32_t prng; + lfsr_file_t file; + } sim_file_t; + sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); + lfs_size_t sim_file_count = 0; - // remount? - if (REMOUNT) { - lfsr_file_close(&lfs, &file) => 0; - lfsr_unmount(&lfs) => 0; - lfsr_mount(&lfs, CFG) => 0; - lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; - } + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + nonsense:; + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 8; - for (lfs_size_t i = 0; i < N; i++) { - // choose a random location - lfs_off_t off = TEST_PRNG(&prng) % SIZE; - // and a random size, up to the chunk size - lfs_size_t chunk = lfs_min32( - TEST_PRNG(&prng) % CHUNK, - SIZE - off); + // open a new file? + if (op == 0) { + if (sim_file_count >= N) { + goto nonsense; + } + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; - // update sim - for (lfs_size_t j = 0; j < chunk; j++) { - sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); - } - if (chunk != 0) { - size = lfs_max32(size, off+chunk); - } + // already exists? + bool orphan = true; + uint32_t wprng = 0; + for (lfs_size_t j = 0; j < sim_size; j++) { + if (sim[j] == x) { + if (sim_isdirs[j]) { + goto nonsense; + } + orphan = false; + wprng = sim_prngs[j]; + break; + } + } + // choose a random seed if we don't exist + if (orphan) { + wprng = TEST_PRNG(&prng); + } - // update file - lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off; - lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk; + // open in our sim + lfs_size_t j = sim_file_count; + sim_files[j] = malloc(sizeof(sim_file_t)); + sim_files[j]->x = x; + sim_files[j]->orphan = orphan; + sim_files[j]->zombie = false; + sim_files[j]->prng = wprng; + sim_file_count++; - // sync? - if (SYNC) { - lfsr_file_sync(&lfs, &file) => 0; - } + // open the actual file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_file_open(&lfs, &sim_files[j]->file, name, + LFS_O_RDWR | LFS_O_CREAT) => 0; - // remount? - if (REMOUNT) { - lfsr_file_close(&lfs, &file) => 0; - lfsr_unmount(&lfs) => 0; - lfsr_mount(&lfs, CFG) => 0; - lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; - } - } - lfsr_file_close(&lfs, &file) => 0; + // write some initial data if we don't exist + if (orphan) { + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) + => SIZE; + } - for (int remount = 0; remount < 2; remount++) { - // remount? - if (remount) { - lfsr_unmount(&lfs) => 0; - lfsr_mount(&lfs, CFG) => 0; - } + // write/rewrite a file? + } else if (op == 1) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + lfs_size_t x = sim_files[j]->x; + // choose a random seed + uint32_t wprng = TEST_PRNG(&prng); - // check our file with stat - struct lfs_info info; - lfsr_stat(&lfs, "hello", &info) => 0; - assert(strcmp(info.name, "hello") == 0); - assert(info.type == LFS_TYPE_REG); - assert(info.size == size); + // update sim + sim_files[j]->prng = wprng; + if (!sim_files[j]->zombie) { + // insert into our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= x) { + // already seen? + if (k < sim_size && sim[k] == x) { + // new prng + sim_prngs[k] = wprng; + } else { + // insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + memmove(&sim_isdirs[k+1], &sim_isdirs[k], + (sim_size-k)*sizeof(bool)); + sim_size += 1; + sim[k] = x; + sim_prngs[k] = wprng; + sim_isdirs[k] = false; + } + break; + } + } - // and with dir read - lfsr_dir_t dir; - 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); - lfsr_dir_read(&lfs, &dir, &info) => 0; - assert(strcmp(info.name, "hello") == 0); - assert(info.type == LFS_TYPE_REG); - assert(info.size == size); - lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; - lfsr_dir_close(&lfs, &dir) => 0; + // update related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x && !sim_files[k]->zombie) { + sim_files[k]->orphan = false; + sim_files[k]->prng = wprng; + } + } + } - // try reading our file - lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; - // is size correct? - lfsr_file_size(&lfs, &file) => size; - // try reading - uint8_t rbuf[2*SIZE]; - memset(rbuf, 0xaa, 2*SIZE); - lfsr_file_read(&lfs, &file, rbuf, 2*SIZE) => size; - // does our file match our simulation? - assert(memcmp(rbuf, sim, size) == 0); - lfsr_file_close(&lfs, &file) => 0; - } + // write to the file + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; + lfsr_file_sync(&lfs, &sim_files[j]->file) + => (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT; - lfsr_unmount(&lfs) => 0; -''' + // close a file? + } else if (op == 2) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; -# this should cause cascading failures -[cases.test_badblocks_alternating_fwrite_fuzz] -defines.ERASE_CYCLES = 0xffffffff -defines.BADBLOCK_BEHAVIOR = [ - 'LFS_EMUBD_BADBLOCK_PROGERROR', - 'LFS_EMUBD_BADBLOCK_ERASEERROR', - 'LFS_EMUBD_BADBLOCK_READERROR', - 'LFS_EMUBD_BADBLOCK_PROGNOOP', - 'LFS_EMUBD_BADBLOCK_ERASENOOP', -] -# we need prog checking to detect read errors -defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' -defines.MIRROR = [false, true] -defines.N = 20 -defines.SIZE = [ - 'FILE_BUFFER_SIZE/2', - '2*FILE_BUFFER_SIZE', - 'BLOCK_SIZE/2', - 'BLOCK_SIZE', - '2*BLOCK_SIZE', - '4*BLOCK_SIZE', -] -# chunk is more an upper limit here -defines.CHUNK = [32, 8, 1] -# INIT=0 => no init -# INIT=1 => fill with data -# INIT=2 => truncate to size -defines.INIT = [0, 1, 2] -defines.SYNC = [false, true] -defines.REMOUNT = [false, true] -defines.SEED = 42 -fuzz = 'SEED' -if = [ - 'CHUNK <= SIZE', - # this just saves testing time - 'SIZE <= 4*1024*FRAGMENT_SIZE', -] -code = ''' - // test a large region of bad blocks - for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { - // mark our badblock as bad - if (!MIRROR) { - if (2*i+0 >= 2) { - lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; - } - } else { - if (2*i+1 >= 2) { - lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; - } - } - } + // this doesn't really test anything, but if we don't close + // files eventually everything will end up zombies - // test with complex file writes - lfs_t lfs; - lfsr_format(&lfs, CFG) => 0; - lfsr_mount(&lfs, CFG) => 0; + // close the file without affected disk + lfsr_file_desync(&lfs, &sim_files[j]->file) => 0; + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; - // create a file - lfsr_file_t file; - lfsr_file_open(&lfs, &file, "hello", - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; - // simulate our file in ram - uint8_t sim[SIZE]; - lfs_off_t size; - uint32_t prng = SEED; - if (INIT == 0) { - memset(sim, 0, SIZE); - size = 0; - } else if (INIT == 1) { - for (lfs_size_t i = 0; i < SIZE; i++) { - sim[i] = 'a' + (TEST_PRNG(&prng) % 26); - } - lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; - size = SIZE; - } else { - memset(sim, 0, SIZE); - lfsr_file_truncate(&lfs, &file, SIZE) => 0; - size = SIZE; - } + // remove from list + free(sim_files[j]); + sim_files[j] = sim_files[sim_file_count-1]; + sim_file_count -= 1; - // sync? - if (SYNC) { - lfsr_file_sync(&lfs, &file) => 0; - } + // remove a file? + } else if (op == 3) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; - // remount? - if (REMOUNT) { - lfsr_file_close(&lfs, &file) => 0; - lfsr_unmount(&lfs) => 0; - lfsr_mount(&lfs, CFG) => 0; - lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; - } + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + memmove(&sim_isdirs[j], &sim_isdirs[j+1], + (sim_size-(j+1))*sizeof(bool)); + sim_size -= 1; - for (lfs_size_t i = 0; i < N; i++) { - // choose a random location - lfs_off_t off = TEST_PRNG(&prng) % SIZE; - // and a random size, up to the chunk size - lfs_size_t chunk = lfs_min32( - TEST_PRNG(&prng) % CHUNK, - SIZE - off); + // mark any related sim files as zombied + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x) { + sim_files[k]->zombie = true; + } + } - // update sim - for (lfs_size_t j = 0; j < chunk; j++) { - sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); - } - if (chunk != 0) { - size = lfs_max32(size, off+chunk); - } + // delete this file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_remove(&lfs, name) => 0; - // update file - lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off; - lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk; + // rename a file? + } else if (op == 4) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + uint32_t wprng = sim_prngs[j]; + bool isdir = sim_isdirs[j]; - // sync? - if (SYNC) { - lfsr_file_sync(&lfs, &file) => 0; - } + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // type mismatch? + if (sim_isdirs[k] != isdir) { + goto nonsense; + } - // remount? - if (REMOUNT) { - lfsr_file_close(&lfs, &file) => 0; - lfsr_unmount(&lfs) => 0; - lfsr_mount(&lfs, CFG) => 0; - lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; - } - } - lfsr_file_close(&lfs, &file) => 0; + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + memmove(&sim_isdirs[j], &sim_isdirs[j+1], + (sim_size-(j+1))*sizeof(bool)); + sim_size -= 1; + if (k > j) { + k -= 1; + } + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + memmove(&sim_isdirs[j], &sim_isdirs[j+1], + (sim_size-(j+1))*sizeof(bool)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + memmove(&sim_isdirs[k+1], &sim_isdirs[k], + (sim_size-k)*sizeof(bool)); + sim[k] = y; + sim_prngs[k] = wprng; + sim_isdirs[k] = isdir; + } + break; + } + } - for (int remount = 0; remount < 2; remount++) { - // remount? - if (remount) { - lfsr_unmount(&lfs) => 0; - lfsr_mount(&lfs, CFG) => 0; + // update any related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + // move source files + if (sim_files[k]->x == x) { + sim_files[k]->x = y; + + // mark target files as zombied + } else if (sim_files[k]->x == y) { + sim_files[k]->zombie = true; + } + } + + // rename this file + char old_name[256]; + sprintf(old_name, "batman%03x", x); + char new_name[256]; + sprintf(new_name, "batman%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + + // toss a directory into the mix + } else if (op == 5) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + + // insert into our sim, use negative numbers for dirs + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= x) { + // already seen? + if (k < sim_size && sim[k] == x) { + goto nonsense; + } else { + // insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + memmove(&sim_isdirs[k+1], &sim_isdirs[k], + (sim_size-k)*sizeof(bool)); + sim_size += 1; + sim[k] = x; + sim_prngs[k] = 0; + sim_isdirs[k] = true; + } + break; + } + } + + // mark any related sim files as zombied + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x) { + sim_files[k]->zombie = true; + } + } + + // make the directory + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_mkdir(&lfs, name) => 0; + } } - // check our file with stat - struct lfs_info info; - lfsr_stat(&lfs, "hello", &info) => 0; - assert(strcmp(info.name, "hello") == 0); - assert(info.type == LFS_TYPE_REG); - assert(info.size == size); + // check that disk matches our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + if (sim_isdirs[j]) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + } - // and with dir read 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); @@ -1712,34 +1628,88 @@ code = ''' 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, "hello") == 0); - assert(info.type == LFS_TYPE_REG); - assert(info.size == size); + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + if (sim_isdirs[j]) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; - // try reading our file - lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; - // is size correct? - lfsr_file_size(&lfs, &file) => size; - // try reading - uint8_t rbuf[2*SIZE]; - memset(rbuf, 0xaa, 2*SIZE); - lfsr_file_read(&lfs, &file, rbuf, 2*SIZE) => size; - // does our file match our simulation? - assert(memcmp(rbuf, sim, size) == 0); - lfsr_file_close(&lfs, &file) => 0; - } + for (lfs_size_t j = 0; j < sim_size; j++) { + if (sim_isdirs[j]) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) + => LFS_ERR_ISDIR; - lfsr_unmount(&lfs) => 0; -''' + } else { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } -# with orphans, zombies, dirs, etc -[cases.test_badblocks_one_orphanzombiedir_fuzz] -defines.ERASE_CYCLES = 0xffffffff -defines.BADBLOCK = -1 + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + // check that our file handles match our simulation + for (lfs_size_t j = 0; j < sim_file_count; j++) { + uint32_t wprng = sim_files[j]->prng; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + for (lfs_size_t j = 0; j < sim_file_count; j++) { + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + free(sim_files[j]); + } + free(sim_files); + lfsr_unmount(&lfs) => 0; + + // reset badblock + lfs_emubd_setwear(CFG, badblock, 0) => 0; + } +''' + + + +## Badblock regions +# +# Test with a region of badblocks, this chould cause cascading failures, +# which can be tricky + +# B-tree's ridiculous branching factor is great for performance, but it makes +# them a bit of a pain to test, here we test them explicitly +[cases.test_badblocks_region_btree_many] +defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', @@ -1749,357 +1719,2540 @@ defines.BADBLOCK_BEHAVIOR = [ ] # we need prog checking to detect read errors defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' -defines.N = [1, 2, 4, 8, 16, 32, 64] -defines.OPS = '2*N' -defines.SIZE = [ - '0', - 'FILE_BUFFER_SIZE/2', - '2*FILE_BUFFER_SIZE', - 'BLOCK_SIZE/2', - 'BLOCK_SIZE', - '2*BLOCK_SIZE', - '4*BLOCK_SIZE', -] +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] +# maximize lookahead buffer to avoid alloc scans +defines.LOOKAHEAD_SIZE = 'lfs_alignup(BLOCK_COUNT / 8, 8)' defines.SEED = 42 fuzz = 'SEED' -if = '(SIZE*N)/BLOCK_SIZE <= 16' +in = 'lfs.c' code = ''' - // test all possible bad blocks - for (lfs_size_t i = 2; - i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); - i++) { - lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad - lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; - printf("--- badblock: 0x%x ---\n", badblock); - - // test with orphans, zombies, dirs, etc - lfs_t lfs; - lfsr_format(&lfs, CFG) => 0; - lfsr_mount(&lfs, CFG) => 0; - - // set up a simulation to compare against - lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); - uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); - bool *sim_isdirs = malloc(N*sizeof(bool)); - lfs_size_t sim_size = 0; + if (!MIRROR) { + lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + } else { + lfs_emubd_setwear(CFG, i + BLOCK_COUNT/2, 0xffffffff) => 0; + } + } - typedef struct sim_file { - lfs_size_t x; - bool orphan; - bool zombie; - uint32_t prng; - lfsr_file_t file; - } sim_file_t; - sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); - lfs_size_t sim_file_count = 0; + // test creating a btree + lfs_t lfs; + lfs_init(&lfs, CFG) => 0; + // create free lookahead + memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); + lfs.lookahead.start = 0; + lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, + CFG->block_count); + lfs.lookahead.next = 0; + lfs_alloc_ckpoint(&lfs); - uint32_t prng = SEED; - for (lfs_size_t i = 0; i < OPS; i++) { - nonsense:; - // choose which operation to do - uint8_t op = TEST_PRNG(&prng) % 8; + // create a btree + lfsr_btree_t btree = LFSR_BTREE_NULL(); - // open a new file? - if (op == 0) { - if (sim_file_count >= N) { - goto nonsense; - } - // choose a pseudo-random number - lfs_size_t x = TEST_PRNG(&prng) % N; + // set up a simulation to compare against + char *sim = malloc(N); + lfs_size_t sim_size = 0; + memset(sim, 0, N); - // already exists? - bool orphan = true; - uint32_t wprng = 0; - for (lfs_size_t j = 0; j < sim_size; j++) { - if (sim[j] == x) { - if (sim_isdirs[j]) { - goto nonsense; - } - orphan = false; - wprng = sim_prngs[j]; - break; - } - } - // choose a random seed if we don't exist - if (orphan) { - wprng = TEST_PRNG(&prng); - } + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < N; i++) { + // choose a pseudo-random bid + lfs_size_t bid = TEST_PRNG(&prng) % (sim_size+1); - // open in our sim - lfs_size_t j = sim_file_count; - sim_files[j] = malloc(sizeof(sim_file_t)); - sim_files[j]->x = x; - sim_files[j]->orphan = orphan; - sim_files[j]->zombie = false; - sim_files[j]->prng = wprng; - sim_file_count++; + // add to btree + lfsr_btree_commit(&lfs, &btree, bid, LFSR_ATTRS( + LFSR_ATTR( + LFSR_TAG_DATA, +1, + LFSR_DATA_BUF(&(uint8_t){'a'+(i % 26)}, 1)))) => 0; - // open the actual file - char name[256]; - sprintf(name, "batman%03x", x); - lfsr_file_open(&lfs, &sim_files[j]->file, name, - LFS_O_RDWR | LFS_O_CREAT) => 0; + // add to sim + memmove(&sim[bid+1], &sim[bid], sim_size-bid); + sim[bid] = 'a'+(i % 26); + sim_size += 1; + } - // write some initial data if we don't exist - if (orphan) { - uint8_t wbuf[SIZE]; - for (lfs_size_t k = 0; k < SIZE; k++) { - wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); - } - lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) - => SIZE; - } + // check that btree matches sim + printf("expd: ["); + bool first = true; + for (lfs_size_t i = 0; i < sim_size; i++) { + if (!first) { + printf(", "); + } + first = false; + printf("%c", sim[i]); + } + printf("]\n"); + printf("btree: w%d 0x%x.%x\n", + btree.weight, + btree.blocks[0], + btree.trunk); + assert(btree.weight == sim_size); - // write/rewrite a file? - } else if (op == 1) { - if (sim_file_count == 0) { - goto nonsense; - } - // choose a random file handle - lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; - lfs_size_t x = sim_files[j]->x; - // choose a random seed - uint32_t wprng = TEST_PRNG(&prng); + uint8_t buffer[4]; + lfsr_tag_t tag_; + lfs_size_t weight_; + lfsr_data_t data_; + for (lfs_size_t i = 0; i < sim_size; i++) { + lfsr_btree_lookup(&lfs, &btree, i, + &tag_, &weight_, &data_) => 0; + lfsr_data_read(&lfs, &data_, buffer, 4) => 1; + assert(tag_ == LFSR_TAG_DATA); + assert(weight_ == 1); + assert(memcmp(buffer, &sim[i], 1) == 0); + } - // update sim - sim_files[j]->prng = wprng; - if (!sim_files[j]->zombie) { - // insert into our sim - for (lfs_size_t k = 0;; k++) { - if (k >= sim_size || sim[k] >= x) { - // already seen? - if (k < sim_size && sim[k] == x) { - // new prng - sim_prngs[k] = wprng; - } else { - // insert - memmove(&sim[k+1], &sim[k], - (sim_size-k)*sizeof(lfs_size_t)); - memmove(&sim_prngs[k+1], &sim_prngs[k], - (sim_size-k)*sizeof(uint32_t)); - memmove(&sim_isdirs[k+1], &sim_isdirs[k], - (sim_size-k)*sizeof(bool)); - sim_size += 1; - sim[k] = x; - sim_prngs[k] = wprng; - sim_isdirs[k] = false; - } - break; - } - } + // and no extra elements + lfsr_btree_lookup(&lfs, &btree, sim_size, + &tag_, &weight_, &data_) => LFS_ERR_NOENT; - // update related sim files - for (lfs_size_t k = 0; k < sim_file_count; k++) { - if (sim_files[k]->x == x && !sim_files[k]->zombie) { - sim_files[k]->orphan = false; - sim_files[k]->prng = wprng; - } - } - } + // clean up sim + free(sim); + lfs_deinit(&lfs) => 0; +''' - // write to the file - lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; - uint8_t wbuf[SIZE]; - for (lfs_size_t k = 0; k < SIZE; k++) { - wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); - } - lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; - lfsr_file_sync(&lfs, &sim_files[j]->file) - => (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT; +# with dirs +[cases.test_badblocks_region_dir_many] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', + 'LFS_EMUBD_BADBLOCK_READERROR', + 'LFS_EMUBD_BADBLOCK_PROGNOOP', + 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +# we need prog checking to detect read errors +defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (i >= 2) { + lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + } + } else { + if (i+BLOCK_COUNT/2 >= 2) { + lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; + } + } + } - // close a file? - } else if (op == 2) { - if (sim_file_count == 0) { - goto nonsense; - } - // choose a random file handle - lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + // test creating directories + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; - // this doesn't really test anything, but if we don't close - // files eventually everything will end up zombies + // make this many directories + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + int err = lfsr_mkdir(&lfs, name); + assert(!err || (TEST_PLS && err == LFS_ERR_EXIST)); + } - // close the file without affected disk - lfsr_file_desync(&lfs, &sim_files[j]->file) => 0; - lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } - // remove from list - free(sim_files[j]); - sim_files[j] = sim_files[sim_file_count-1]; - sim_file_count -= 1; + // grm should be zero here + assert(lfs.grm_p[0] == 0); - // remove a file? - } else if (op == 3) { - if (sim_size == 0) { - goto nonsense; - } - // choose a random file to delete - lfs_size_t j = TEST_PRNG(&prng) % sim_size; - lfs_size_t x = sim[j]; + // 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); + } - // delete from our sim - memmove(&sim[j], &sim[j+1], - (sim_size-(j+1))*sizeof(lfs_size_t)); - memmove(&sim_prngs[j], &sim_prngs[j+1], - (sim_size-(j+1))*sizeof(uint32_t)); - memmove(&sim_isdirs[j], &sim_isdirs[j+1], - (sim_size-(j+1))*sizeof(bool)); - sim_size -= 1; + 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; - // mark any related sim files as zombied - for (lfs_size_t k = 0; k < sim_file_count; k++) { - if (sim_files[k]->x == x) { - sim_files[k]->zombie = true; - } - } + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + lfsr_dir_open(&lfs, &dir, name) => 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); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + } + } - // delete this file - char name[256]; - sprintf(name, "batman%03x", x); - lfsr_remove(&lfs, name) => 0; + lfsr_unmount(&lfs) => 0; +''' - // rename a file? - } else if (op == 4) { - if (sim_size == 0) { - goto nonsense; - } - // choose a random file to rename, and a random number to - // rename to - lfs_size_t j = TEST_PRNG(&prng) % sim_size; - lfs_size_t x = sim[j]; - lfs_size_t y = TEST_PRNG(&prng) % N; - uint32_t wprng = sim_prngs[j]; - bool isdir = sim_isdirs[j]; +# fuzz dirs +[cases.test_badblocks_region_dir_fuzz] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', + 'LFS_EMUBD_BADBLOCK_READERROR', + 'LFS_EMUBD_BADBLOCK_PROGNOOP', + 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +# we need prog checking to detect read errors +defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] +defines.OPS = '2*N' +defines.SEED = 42 +fuzz = 'SEED' +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (i >= 2) { + lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + } + } else { + if (i+BLOCK_COUNT/2 >= 2) { + lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; + } + } + } - // update our sim - for (lfs_size_t k = 0;; k++) { - if (k >= sim_size || sim[k] >= y) { - // renaming and replacing - if (k < sim_size && sim[k] == y && x != y) { - // type mismatch? - if (sim_isdirs[k] != isdir) { - goto nonsense; - } + // test fuzz with dirs + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; - // delete the original entry - memmove(&sim[j], &sim[j+1], - (sim_size-(j+1))*sizeof(lfs_size_t)); - memmove(&sim_prngs[j], &sim_prngs[j+1], - (sim_size-(j+1))*sizeof(uint32_t)); - memmove(&sim_isdirs[j], &sim_isdirs[j+1], - (sim_size-(j+1))*sizeof(bool)); - sim_size -= 1; - if (k > j) { - k -= 1; - } - // update the prng - sim_prngs[k] = wprng; - // just renaming - } else { - // first delete - memmove(&sim[j], &sim[j+1], - (sim_size-(j+1))*sizeof(lfs_size_t)); - memmove(&sim_prngs[j], &sim_prngs[j+1], - (sim_size-(j+1))*sizeof(uint32_t)); - memmove(&sim_isdirs[j], &sim_isdirs[j+1], - (sim_size-(j+1))*sizeof(bool)); - if (k > j) { - k -= 1; - } - // then insert - memmove(&sim[k+1], &sim[k], - (sim_size-k)*sizeof(lfs_size_t)); - memmove(&sim_prngs[k+1], &sim_prngs[k], - (sim_size-k)*sizeof(uint32_t)); - memmove(&sim_isdirs[k+1], &sim_isdirs[k], - (sim_size-k)*sizeof(bool)); - sim[k] = y; - sim_prngs[k] = wprng; - sim_isdirs[k] = isdir; - } - break; - } - } + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + lfs_size_t sim_size = 0; - // update any related sim files - for (lfs_size_t k = 0; k < sim_file_count; k++) { - // move source files - if (sim_files[k]->x == x) { - sim_files[k]->x = y; + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + // choose a pseudo-random op, either mkdir, remove, or rename + uint8_t op = TEST_PRNG(&prng) % 3; - // mark target files as zombied - } else if (sim_files[k]->x == y) { - sim_files[k]->zombie = true; + if (op == 0 || sim_size == 0) { + // choose a pseudo-random number, truncate to 3 hexadecimals + lfs_size_t x = TEST_PRNG(&prng) % N; + // insert into our sim + for (lfs_size_t j = 0;; j++) { + if (j >= sim_size || sim[j] >= x) { + // already seen? + if (j < sim_size && sim[j] == x) { + // do nothing + } else { + // insert + memmove(&sim[j+1], &sim[j], + (sim_size-j)*sizeof(lfs_size_t)); + sim_size += 1; + sim[j] = x; } + break; } + } - // rename this file - char old_name[256]; - sprintf(old_name, "batman%03x", x); - char new_name[256]; - sprintf(new_name, "batman%03x", y); - lfsr_rename(&lfs, old_name, new_name) => 0; + // create a directory here + char name[256]; + sprintf(name, "dir%03x", x); + int err = lfsr_mkdir(&lfs, name); + assert(!err || err == LFS_ERR_EXIST); + + } else if (op == 1) { + // choose a pseudo-random entry to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + sim_size -= 1; + + // remove this directory + char name[256]; + sprintf(name, "dir%03x", x); + lfsr_remove(&lfs, name) => 0; + + } else { + // choose a pseudo-random entry to rename, and a pseudo-random + // number to rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // already seen and not a noop? + if (k < sim_size && sim[k] == y && x != y) { + // just delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + sim_size -= 1; + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + sim[k] = y; + } + break; + } + } + + // rename this directory + char old_name[256]; + sprintf(old_name, "dir%03x", x); + char new_name[256]; + sprintf(new_name, "dir%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // grm should be zero here + assert(lfs.grm_p[0] == 0); + + // test that our directories match our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "dir%03x", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + char name2[256]; + sprintf(name2, "dir%03x", sim[j]); + assert(strcmp(info.name, name2) == 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 j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "dir%03x", sim[j]); + 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; + } + + // clean up sim/lfs + free(sim); + lfsr_unmount(&lfs) => 0; +''' + +# with files +[cases.test_badblocks_region_file_many] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', + 'LFS_EMUBD_BADBLOCK_READERROR', + 'LFS_EMUBD_BADBLOCK_PROGNOOP', + 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +# we need prog checking to detect read errors +defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (i >= 2) { + lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + } + } else { + if (i+BLOCK_COUNT/2 >= 2) { + lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; + } + } + } + + // test creating files + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many files + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "amethyst%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; + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 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, "amethyst%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; +''' + +# fuzz files +[cases.test_badblocks_region_file_fuzz] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', + 'LFS_EMUBD_BADBLOCK_READERROR', + 'LFS_EMUBD_BADBLOCK_PROGNOOP', + 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +# we need prog checking to detect read errors +defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = '2*N' +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 42 +fuzz = 'SEED' +if = '(SIZE*N)/BLOCK_SIZE <= 16' +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (i >= 2) { + lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + } + } else { + if (i+BLOCK_COUNT/2 >= 2) { + lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; + } + } + } + + // test fuzz with files + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + lfs_size_t sim_size = 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 3; + + // creating a new file? + if (op == 0 || sim_size == 0) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + // associate each file with a prng that generates its contents + uint32_t wprng = TEST_PRNG(&prng); + + // insert into our sim + for (lfs_size_t j = 0;; j++) { + if (j >= sim_size || sim[j] >= x) { + // already seen? + if (j < sim_size && sim[j] == x) { + // new prng + sim_prngs[j] = wprng; + } else { + // insert + memmove(&sim[j+1], &sim[j], + (sim_size-j)*sizeof(lfs_size_t)); + memmove(&sim_prngs[j+1], &sim_prngs[j], + (sim_size-j)*sizeof(uint32_t)); + sim_size += 1; + sim[j] = x; + sim_prngs[j] = wprng; + } + break; + } + } + + // create a file here + char name[256]; + sprintf(name, "amethyst%03x", x); + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // deleting a file? + } else if (op == 1) { + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + + // delete this file + char name[256]; + sprintf(name, "amethyst%03x", x); + lfsr_remove(&lfs, name) => 0; + + // renaming a file? + } else { + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + uint32_t wprng = sim_prngs[j]; + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + if (k > j) { + k -= 1; + } + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim[k] = y; + sim_prngs[k] = wprng; + } + break; + } + } + + // rename this file + char old_name[256]; + sprintf(old_name, "amethyst%03x", x); + char new_name[256]; + sprintf(new_name, "amethyst%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that our files match our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%03x", sim[j]); + 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); + } + + 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 j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // check the file contents + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + lfsr_unmount(&lfs) => 0; +''' + +# with more complex file writes +[cases.test_badblocks_region_fwrite_fuzz] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', + 'LFS_EMUBD_BADBLOCK_READERROR', + 'LFS_EMUBD_BADBLOCK_PROGNOOP', + 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +# we need prog checking to detect read errors +defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' +defines.MIRROR = [false, true] +defines.OPS = 20 +defines.SIZE = [ + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +# chunk is more an upper limit here +defines.CHUNK = [32, 8, 1] +# INIT=0 => no init +# INIT=1 => fill with data +# INIT=2 => truncate to size +defines.INIT = [0, 1, 2] +defines.SYNC = [false, true] +defines.SEED = 42 +fuzz = 'SEED' +if = [ + 'CHUNK <= SIZE', + # this just saves testing time + 'SIZE <= 4*1024*FRAGMENT_SIZE', +] +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (i >= 2) { + lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + } + } else { + if (i+BLOCK_COUNT/2 >= 2) { + lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; + } + } + } + + // test with complex file writes + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + // simulate our file in ram + uint8_t sim[SIZE]; + lfs_off_t size; + uint32_t prng = SEED; + if (INIT == 0) { + memset(sim, 0, SIZE); + size = 0; + } else if (INIT == 1) { + for (lfs_size_t i = 0; i < SIZE; i++) { + sim[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; + size = SIZE; + } else { + memset(sim, 0, SIZE); + lfsr_file_truncate(&lfs, &file, SIZE) => 0; + size = SIZE; + } + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + for (lfs_size_t i = 0; i < OPS; i++) { + // choose a random location + lfs_off_t off = TEST_PRNG(&prng) % SIZE; + // and a random size, up to the chunk size + lfs_size_t chunk = lfs_min32( + TEST_PRNG(&prng) % CHUNK, + SIZE - off); + + // update sim + for (lfs_size_t j = 0; j < chunk; j++) { + sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); + } + if (chunk != 0) { + size = lfs_max32(size, off+chunk); + } + + // update file + lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off; + lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk; + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + } + + lfsr_file_close(&lfs, &file) => 0; + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check our file with stat + struct lfs_info info; + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == size); + + // and with dir read + lfsr_dir_t dir; + 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); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == size); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // try reading our file + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file) => size; + // try reading + uint8_t rbuf[2*SIZE]; + memset(rbuf, 0xaa, 2*SIZE); + lfsr_file_read(&lfs, &file, rbuf, 2*SIZE) => size; + // does our file match our simulation? + assert(memcmp(rbuf, sim, size) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + lfsr_unmount(&lfs) => 0; +''' + +# with orphans, zombies, etc +[cases.test_badblocks_region_orphanzombie_fuzz] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', + 'LFS_EMUBD_BADBLOCK_READERROR', + 'LFS_EMUBD_BADBLOCK_PROGNOOP', + 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +# we need prog checking to detect read errors +defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = '2*N' +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 42 +fuzz = 'SEED' +if = '(SIZE*N)/BLOCK_SIZE <= 16' +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (i >= 2) { + lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + } + } else { + if (i+BLOCK_COUNT/2 >= 2) { + lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; + } + } + } + + // test with orphans, zombies, etc + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + lfs_size_t sim_size = 0; + + typedef struct sim_file { + lfs_size_t x; + bool orphan; + bool zombie; + uint32_t prng; + lfsr_file_t file; + } sim_file_t; + sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); + lfs_size_t sim_file_count = 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + nonsense:; + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 5; + + // open a new file? + if (op == 0) { + if (sim_file_count >= N) { + goto nonsense; + } + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + + // already exists? + bool orphan = true; + uint32_t wprng = 0; + for (lfs_size_t j = 0; j < sim_size; j++) { + if (sim[j] == x) { + orphan = false; + wprng = sim_prngs[j]; + break; + } + } + // choose a random seed if we don't exist + if (orphan) { + wprng = TEST_PRNG(&prng); + } + + // open in our sim + lfs_size_t j = sim_file_count; + sim_files[j] = malloc(sizeof(sim_file_t)); + sim_files[j]->x = x; + sim_files[j]->orphan = orphan; + sim_files[j]->zombie = false; + sim_files[j]->prng = wprng; + sim_file_count++; + + // open the actual file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_file_open(&lfs, &sim_files[j]->file, name, + LFS_O_RDWR | LFS_O_CREAT) => 0; + + // write some initial data if we don't exist + if (orphan) { + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; + } + + // write/rewrite a file? + } else if (op == 1) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + lfs_size_t x = sim_files[j]->x; + // choose a random seed + uint32_t wprng = TEST_PRNG(&prng); + + // update sim + sim_files[j]->prng = wprng; + if (!sim_files[j]->zombie) { + // insert into our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= x) { + // already seen? + if (k < sim_size && sim[k] == x) { + // new prng + sim_prngs[k] = wprng; + } else { + // insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim_size += 1; + sim[k] = x; + sim_prngs[k] = wprng; + } + break; + } + } + + // update related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x && !sim_files[k]->zombie) { + sim_files[k]->orphan = false; + sim_files[k]->prng = wprng; + } + } + } + + // write to the file + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; + lfsr_file_sync(&lfs, &sim_files[j]->file) + => (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT; + + // close a file? + } else if (op == 2) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + + // this doesn't really test anything, but if we don't close + // files eventually everything will end up zombies + + // close the file without affected disk + lfsr_file_desync(&lfs, &sim_files[j]->file) => 0; + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + + // remove from list + free(sim_files[j]); + sim_files[j] = sim_files[sim_file_count-1]; + sim_file_count -= 1; + + // remove a file? + } else if (op == 3) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + + // mark any related sim files as zombied + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x) { + sim_files[k]->zombie = true; + } + } + + // delete this file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_remove(&lfs, name) => 0; + + // rename a file? + } else if (op == 4) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + uint32_t wprng = sim_prngs[j]; + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + if (k > j) { + k -= 1; + } + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim[k] = y; + sim_prngs[k] = wprng; + } + break; + } + } + + // update any related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + // move source files + if (sim_files[k]->x == x) { + sim_files[k]->x = y; + + // mark target files as zombied + } else if (sim_files[k]->x == y) { + sim_files[k]->zombie = true; + } + } + + // rename this file + char old_name[256]; + sprintf(old_name, "batman%03x", x); + char new_name[256]; + sprintf(new_name, "batman%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + } + + // check that disk matches our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + 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); + } + + 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 j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + // check that our file handles match our simulation + for (lfs_size_t j = 0; j < sim_file_count; j++) { + uint32_t wprng = sim_files[j]->prng; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + for (lfs_size_t j = 0; j < sim_file_count; j++) { + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + free(sim_files[j]); + } + free(sim_files); + lfsr_unmount(&lfs) => 0; +''' + +# with orphans, zombies, dirs, etc +[cases.test_badblocks_region_orphanzombiedir_fuzz] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', + 'LFS_EMUBD_BADBLOCK_READERROR', + 'LFS_EMUBD_BADBLOCK_PROGNOOP', + 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +# we need prog checking to detect read errors +defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = '2*N' +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 42 +fuzz = 'SEED' +if = '(SIZE*N)/BLOCK_SIZE <= 16' +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (i >= 2) { + lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + } + } else { + if (i+BLOCK_COUNT/2 >= 2) { + lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; + } + } + } + + // test with orphans, zombies, dirs, etc + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + bool *sim_isdirs = malloc(N*sizeof(bool)); + lfs_size_t sim_size = 0; + + typedef struct sim_file { + lfs_size_t x; + bool orphan; + bool zombie; + uint32_t prng; + lfsr_file_t file; + } sim_file_t; + sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); + lfs_size_t sim_file_count = 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + nonsense:; + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 8; + + // open a new file? + if (op == 0) { + if (sim_file_count >= N) { + goto nonsense; + } + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + + // already exists? + bool orphan = true; + uint32_t wprng = 0; + for (lfs_size_t j = 0; j < sim_size; j++) { + if (sim[j] == x) { + if (sim_isdirs[j]) { + goto nonsense; + } + orphan = false; + wprng = sim_prngs[j]; + break; + } + } + // choose a random seed if we don't exist + if (orphan) { + wprng = TEST_PRNG(&prng); + } + + // open in our sim + lfs_size_t j = sim_file_count; + sim_files[j] = malloc(sizeof(sim_file_t)); + sim_files[j]->x = x; + sim_files[j]->orphan = orphan; + sim_files[j]->zombie = false; + sim_files[j]->prng = wprng; + sim_file_count++; + + // open the actual file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_file_open(&lfs, &sim_files[j]->file, name, + LFS_O_RDWR | LFS_O_CREAT) => 0; + + // write some initial data if we don't exist + if (orphan) { + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; + } + + // write/rewrite a file? + } else if (op == 1) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + lfs_size_t x = sim_files[j]->x; + // choose a random seed + uint32_t wprng = TEST_PRNG(&prng); + + // update sim + sim_files[j]->prng = wprng; + if (!sim_files[j]->zombie) { + // insert into our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= x) { + // already seen? + if (k < sim_size && sim[k] == x) { + // new prng + sim_prngs[k] = wprng; + } else { + // insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + memmove(&sim_isdirs[k+1], &sim_isdirs[k], + (sim_size-k)*sizeof(bool)); + sim_size += 1; + sim[k] = x; + sim_prngs[k] = wprng; + sim_isdirs[k] = false; + } + break; + } + } + + // update related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x && !sim_files[k]->zombie) { + sim_files[k]->orphan = false; + sim_files[k]->prng = wprng; + } + } + } + + // write to the file + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; + lfsr_file_sync(&lfs, &sim_files[j]->file) + => (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT; + + // close a file? + } else if (op == 2) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + + // this doesn't really test anything, but if we don't close + // files eventually everything will end up zombies + + // close the file without affected disk + lfsr_file_desync(&lfs, &sim_files[j]->file) => 0; + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + + // remove from list + free(sim_files[j]); + sim_files[j] = sim_files[sim_file_count-1]; + sim_file_count -= 1; + + // remove a file? + } else if (op == 3) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + memmove(&sim_isdirs[j], &sim_isdirs[j+1], + (sim_size-(j+1))*sizeof(bool)); + sim_size -= 1; + + // mark any related sim files as zombied + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x) { + sim_files[k]->zombie = true; + } + } + + // delete this file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_remove(&lfs, name) => 0; + + // rename a file? + } else if (op == 4) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + uint32_t wprng = sim_prngs[j]; + bool isdir = sim_isdirs[j]; + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // type mismatch? + if (sim_isdirs[k] != isdir) { + goto nonsense; + } + + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + memmove(&sim_isdirs[j], &sim_isdirs[j+1], + (sim_size-(j+1))*sizeof(bool)); + sim_size -= 1; + if (k > j) { + k -= 1; + } + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + memmove(&sim_isdirs[j], &sim_isdirs[j+1], + (sim_size-(j+1))*sizeof(bool)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + memmove(&sim_isdirs[k+1], &sim_isdirs[k], + (sim_size-k)*sizeof(bool)); + sim[k] = y; + sim_prngs[k] = wprng; + sim_isdirs[k] = isdir; + } + break; + } + } + + // update any related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + // move source files + if (sim_files[k]->x == x) { + sim_files[k]->x = y; + + // mark target files as zombied + } else if (sim_files[k]->x == y) { + sim_files[k]->zombie = true; + } + } + + // rename this file + char old_name[256]; + sprintf(old_name, "batman%03x", x); + char new_name[256]; + sprintf(new_name, "batman%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + + // toss a directory into the mix + } else if (op == 5) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + + // insert into our sim, use negative numbers for dirs + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= x) { + // already seen? + if (k < sim_size && sim[k] == x) { + goto nonsense; + } else { + // insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + memmove(&sim_isdirs[k+1], &sim_isdirs[k], + (sim_size-k)*sizeof(bool)); + sim_size += 1; + sim[k] = x; + sim_prngs[k] = 0; + sim_isdirs[k] = true; + } + break; + } + } + + // mark any related sim files as zombied + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x) { + sim_files[k]->zombie = true; + } + } + + // make the directory + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_mkdir(&lfs, name) => 0; + } + } + + // check that disk matches our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + if (sim_isdirs[j]) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + } + + 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 j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + if (sim_isdirs[j]) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + for (lfs_size_t j = 0; j < sim_size; j++) { + if (sim_isdirs[j]) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => LFS_ERR_ISDIR; + + } else { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + // check that our file handles match our simulation + for (lfs_size_t j = 0; j < sim_file_count; j++) { + uint32_t wprng = sim_files[j]->prng; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + for (lfs_size_t j = 0; j < sim_file_count; j++) { + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + free(sim_files[j]); + } + free(sim_files); + lfsr_unmount(&lfs) => 0; +''' + + + +## Alternating badblocks +# +# Test alternating badblocks, this can be difficult for pair allocations + +# B-tree's ridiculous branching factor is great for performance, but it makes +# them a bit of a pain to test, here we test them explicitly +[cases.test_badblocks_alternating_btree_many] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', + 'LFS_EMUBD_BADBLOCK_READERROR', + 'LFS_EMUBD_BADBLOCK_PROGNOOP', + 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +# we need prog checking to detect read errors +defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] +# maximize lookahead buffer to avoid alloc scans +defines.LOOKAHEAD_SIZE = 'lfs_alignup(BLOCK_COUNT / 8, 8)' +defines.SEED = 42 +fuzz = 'SEED' +in = 'lfs.c' +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; + } else { + lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + } + } + + // test creating a btree + lfs_t lfs; + lfs_init(&lfs, CFG) => 0; + // create free lookahead + memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); + lfs.lookahead.start = 0; + lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, + CFG->block_count); + lfs.lookahead.next = 0; + lfs_alloc_ckpoint(&lfs); + + // create a btree + lfsr_btree_t btree = LFSR_BTREE_NULL(); + + // set up a simulation to compare against + char *sim = malloc(N); + lfs_size_t sim_size = 0; + memset(sim, 0, N); + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < N; i++) { + // choose a pseudo-random bid + lfs_size_t bid = TEST_PRNG(&prng) % (sim_size+1); + + // add to btree + lfsr_btree_commit(&lfs, &btree, bid, LFSR_ATTRS( + LFSR_ATTR( + LFSR_TAG_DATA, +1, + LFSR_DATA_BUF(&(uint8_t){'a'+(i % 26)}, 1)))) => 0; + + // add to sim + memmove(&sim[bid+1], &sim[bid], sim_size-bid); + sim[bid] = 'a'+(i % 26); + sim_size += 1; + } + + // check that btree matches sim + printf("expd: ["); + bool first = true; + for (lfs_size_t i = 0; i < sim_size; i++) { + if (!first) { + printf(", "); + } + first = false; + printf("%c", sim[i]); + } + printf("]\n"); + printf("btree: w%d 0x%x.%x\n", + btree.weight, + btree.blocks[0], + btree.trunk); + assert(btree.weight == sim_size); + + uint8_t buffer[4]; + lfsr_tag_t tag_; + lfs_size_t weight_; + lfsr_data_t data_; + for (lfs_size_t i = 0; i < sim_size; i++) { + lfsr_btree_lookup(&lfs, &btree, i, + &tag_, &weight_, &data_) => 0; + lfsr_data_read(&lfs, &data_, buffer, 4) => 1; + assert(tag_ == LFSR_TAG_DATA); + assert(weight_ == 1); + assert(memcmp(buffer, &sim[i], 1) == 0); + } + + // and no extra elements + lfsr_btree_lookup(&lfs, &btree, sim_size, + &tag_, &weight_, &data_) => LFS_ERR_NOENT; + + // clean up sim + free(sim); + lfs_deinit(&lfs) => 0; +''' + +# with dirs +[cases.test_badblocks_alternating_dir_many] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', + 'LFS_EMUBD_BADBLOCK_READERROR', + 'LFS_EMUBD_BADBLOCK_PROGNOOP', + 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +# we need prog checking to detect read errors +defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (2*i+0 >= 2) { + lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; + } + } else { + if (2*i+1 >= 2) { + lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + } + } + } + + // test creating directories + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // make this many directories + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + int err = lfsr_mkdir(&lfs, name); + assert(!err || (TEST_PLS && err == LFS_ERR_EXIST)); + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // grm should be zero here + assert(lfs.grm_p[0] == 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; + + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + lfsr_dir_open(&lfs, &dir, name) => 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); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + } + } + + lfsr_unmount(&lfs) => 0; +''' + +# fuzz dirs +[cases.test_badblocks_alternating_dir_fuzz] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', + 'LFS_EMUBD_BADBLOCK_READERROR', + 'LFS_EMUBD_BADBLOCK_PROGNOOP', + 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +# we need prog checking to detect read errors +defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] +defines.OPS = '2*N' +defines.SEED = 42 +fuzz = 'SEED' +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (2*i+0 >= 2) { + lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; + } + } else { + if (2*i+1 >= 2) { + lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + } + } + } + + // test fuzz with dirs + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + lfs_size_t sim_size = 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + // choose a pseudo-random op, either mkdir, remove, or rename + uint8_t op = TEST_PRNG(&prng) % 3; + + if (op == 0 || sim_size == 0) { + // choose a pseudo-random number, truncate to 3 hexadecimals + lfs_size_t x = TEST_PRNG(&prng) % N; + // insert into our sim + for (lfs_size_t j = 0;; j++) { + if (j >= sim_size || sim[j] >= x) { + // already seen? + if (j < sim_size && sim[j] == x) { + // do nothing + } else { + // insert + memmove(&sim[j+1], &sim[j], + (sim_size-j)*sizeof(lfs_size_t)); + sim_size += 1; + sim[j] = x; + } + break; + } + } + + // create a directory here + char name[256]; + sprintf(name, "dir%03x", x); + int err = lfsr_mkdir(&lfs, name); + assert(!err || err == LFS_ERR_EXIST); + + } else if (op == 1) { + // choose a pseudo-random entry to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + sim_size -= 1; + + // remove this directory + char name[256]; + sprintf(name, "dir%03x", x); + lfsr_remove(&lfs, name) => 0; + + } else { + // choose a pseudo-random entry to rename, and a pseudo-random + // number to rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // already seen and not a noop? + if (k < sim_size && sim[k] == y && x != y) { + // just delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + sim_size -= 1; + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + sim[k] = y; + } + break; + } + } + + // rename this directory + char old_name[256]; + sprintf(old_name, "dir%03x", x); + char new_name[256]; + sprintf(new_name, "dir%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // grm should be zero here + assert(lfs.grm_p[0] == 0); + + // test that our directories match our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "dir%03x", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + char name2[256]; + sprintf(name2, "dir%03x", sim[j]); + assert(strcmp(info.name, name2) == 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 j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "dir%03x", sim[j]); + 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; + } + + // clean up sim/lfs + free(sim); + lfsr_unmount(&lfs) => 0; +''' + +# with files +[cases.test_badblocks_alternating_file_many] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', + 'LFS_EMUBD_BADBLOCK_READERROR', + 'LFS_EMUBD_BADBLOCK_PROGNOOP', + 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +# we need prog checking to detect read errors +defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (2*i+0 >= 2) { + lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; + } + } else { + if (2*i+1 >= 2) { + lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + } + } + } + + // test creating files + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many files + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "amethyst%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; + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 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, "amethyst%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; +''' + +# fuzz files +[cases.test_badblocks_alternating_file_fuzz] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', + 'LFS_EMUBD_BADBLOCK_READERROR', + 'LFS_EMUBD_BADBLOCK_PROGNOOP', + 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +# we need prog checking to detect read errors +defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = '2*N' +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 42 +fuzz = 'SEED' +if = '(SIZE*N)/BLOCK_SIZE <= 16' +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (2*i+0 >= 2) { + lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; + } + } else { + if (2*i+1 >= 2) { + lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + } + } + } + + // test fuzz with files + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + lfs_size_t sim_size = 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 3; + + // creating a new file? + if (op == 0 || sim_size == 0) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + // associate each file with a prng that generates its contents + uint32_t wprng = TEST_PRNG(&prng); + + // insert into our sim + for (lfs_size_t j = 0;; j++) { + if (j >= sim_size || sim[j] >= x) { + // already seen? + if (j < sim_size && sim[j] == x) { + // new prng + sim_prngs[j] = wprng; + } else { + // insert + memmove(&sim[j+1], &sim[j], + (sim_size-j)*sizeof(lfs_size_t)); + memmove(&sim_prngs[j+1], &sim_prngs[j], + (sim_size-j)*sizeof(uint32_t)); + sim_size += 1; + sim[j] = x; + sim_prngs[j] = wprng; + } + break; + } + } - // toss a directory into the mix - } else if (op == 5) { - // choose a pseudo-random number - lfs_size_t x = TEST_PRNG(&prng) % N; + // create a file here + char name[256]; + sprintf(name, "amethyst%03x", x); + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } - // insert into our sim, use negative numbers for dirs - for (lfs_size_t k = 0;; k++) { - if (k >= sim_size || sim[k] >= x) { - // already seen? - if (k < sim_size && sim[k] == x) { - goto nonsense; - } else { - // insert - memmove(&sim[k+1], &sim[k], - (sim_size-k)*sizeof(lfs_size_t)); - memmove(&sim_prngs[k+1], &sim_prngs[k], - (sim_size-k)*sizeof(uint32_t)); - memmove(&sim_isdirs[k+1], &sim_isdirs[k], - (sim_size-k)*sizeof(bool)); - sim_size += 1; - sim[k] = x; - sim_prngs[k] = 0; - sim_isdirs[k] = true; + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // deleting a file? + } else if (op == 1) { + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + + // delete this file + char name[256]; + sprintf(name, "amethyst%03x", x); + lfsr_remove(&lfs, name) => 0; + + // renaming a file? + } else { + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + uint32_t wprng = sim_prngs[j]; + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + if (k > j) { + k -= 1; } - break; + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim[k] = y; + sim_prngs[k] = wprng; } + break; } + } + + // rename this file + char old_name[256]; + sprintf(old_name, "amethyst%03x", x); + char new_name[256]; + sprintf(new_name, "amethyst%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that our files match our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%03x", sim[j]); + 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); + } + + 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 j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // check the file contents + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + lfsr_unmount(&lfs) => 0; +''' + +# with more complex file writes +[cases.test_badblocks_alternating_fwrite_fuzz] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', + 'LFS_EMUBD_BADBLOCK_READERROR', + 'LFS_EMUBD_BADBLOCK_PROGNOOP', + 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +# we need prog checking to detect read errors +defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' +defines.MIRROR = [false, true] +defines.OPS = 20 +defines.SIZE = [ + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +# chunk is more an upper limit here +defines.CHUNK = [32, 8, 1] +# INIT=0 => no init +# INIT=1 => fill with data +# INIT=2 => truncate to size +defines.INIT = [0, 1, 2] +defines.SYNC = [false, true] +defines.SEED = 42 +fuzz = 'SEED' +if = [ + 'CHUNK <= SIZE', + # this just saves testing time + 'SIZE <= 4*1024*FRAGMENT_SIZE', +] +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (2*i+0 >= 2) { + lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; + } + } else { + if (2*i+1 >= 2) { + lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + } + } + } + + // test with complex file writes + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + // simulate our file in ram + uint8_t sim[SIZE]; + lfs_off_t size; + uint32_t prng = SEED; + if (INIT == 0) { + memset(sim, 0, SIZE); + size = 0; + } else if (INIT == 1) { + for (lfs_size_t i = 0; i < SIZE; i++) { + sim[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; + size = SIZE; + } else { + memset(sim, 0, SIZE); + lfsr_file_truncate(&lfs, &file, SIZE) => 0; + size = SIZE; + } + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + for (lfs_size_t i = 0; i < OPS; i++) { + // choose a random location + lfs_off_t off = TEST_PRNG(&prng) % SIZE; + // and a random size, up to the chunk size + lfs_size_t chunk = lfs_min32( + (TEST_PRNG(&prng) % (CHUNK+1-1)) + 1, + SIZE - off); - // mark any related sim files as zombied - for (lfs_size_t k = 0; k < sim_file_count; k++) { - if (sim_files[k]->x == x) { - sim_files[k]->zombie = true; - } - } + // update sim + for (lfs_size_t j = 0; j < chunk; j++) { + sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); + } + size = lfs_max32(size, off+chunk); - // make the directory - char name[256]; - sprintf(name, "batman%03x", x); - lfsr_mkdir(&lfs, name) => 0; - } + // update file + lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off; + lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk; + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; } + } - // check that disk matches our simulation - for (lfs_size_t j = 0; j < sim_size; j++) { - char name[256]; - sprintf(name, "batman%03x", sim[j]); - struct lfs_info info; - lfsr_stat(&lfs, name, &info) => 0; - assert(strcmp(info.name, name) == 0); - if (sim_isdirs[j]) { - assert(info.type == LFS_TYPE_DIR); - } else { - assert(info.type == LFS_TYPE_REG); - assert(info.size == SIZE); - } + lfsr_file_close(&lfs, &file) => 0; + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; } + // check our file with stat + struct lfs_info info; + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == size); + + // and with dir read 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); @@ -2108,79 +4261,31 @@ code = ''' assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); - for (lfs_size_t j = 0; j < sim_size; j++) { - char name[256]; - sprintf(name, "batman%03x", sim[j]); - lfsr_dir_read(&lfs, &dir, &info) => 0; - assert(strcmp(info.name, name) == 0); - if (sim_isdirs[j]) { - assert(info.type == LFS_TYPE_DIR); - } else { - assert(info.type == LFS_TYPE_REG); - assert(info.size == SIZE); - } - } + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == size); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; - for (lfs_size_t j = 0; j < sim_size; j++) { - if (sim_isdirs[j]) { - char name[256]; - sprintf(name, "batman%03x", sim[j]); - lfsr_file_t file; - lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) - => LFS_ERR_ISDIR; - - } else { - char name[256]; - sprintf(name, "batman%03x", sim[j]); - lfsr_file_t file; - lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; - - uint32_t wprng = sim_prngs[j]; - uint8_t wbuf[SIZE]; - for (lfs_size_t j = 0; j < SIZE; j++) { - wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); - } - - uint8_t rbuf[SIZE]; - lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; - assert(memcmp(rbuf, wbuf, SIZE) == 0); - lfsr_file_close(&lfs, &file) => 0; - } - } - - // check that our file handles match our simulation - for (lfs_size_t j = 0; j < sim_file_count; j++) { - uint32_t wprng = sim_files[j]->prng; - uint8_t wbuf[SIZE]; - for (lfs_size_t j = 0; j < SIZE; j++) { - wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); - } - - lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; - uint8_t rbuf[SIZE]; - lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE; - assert(memcmp(rbuf, wbuf, SIZE) == 0); - } - - // clean up sim/lfs - free(sim); - free(sim_prngs); - for (lfs_size_t j = 0; j < sim_file_count; j++) { - lfsr_file_close(&lfs, &sim_files[j]->file) => 0; - free(sim_files[j]); - } - free(sim_files); - lfsr_unmount(&lfs) => 0; - - // reset badblock - lfs_emubd_setwear(CFG, badblock, 0) => 0; + // try reading our file + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file) => size; + // try reading + uint8_t rbuf[2*SIZE]; + memset(rbuf, 0xaa, 2*SIZE); + lfsr_file_read(&lfs, &file, rbuf, 2*SIZE) => size; + // does our file match our simulation? + assert(memcmp(rbuf, sim, size) == 0); + lfsr_file_close(&lfs, &file) => 0; } + + lfsr_unmount(&lfs) => 0; ''' -# this should cause cascading failures -[cases.test_badblocks_region_orphanzombiedir_fuzz] +# with orphans, zombies, etc +[cases.test_badblocks_alternating_orphanzombie_fuzz] defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -2211,17 +4316,17 @@ code = ''' for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad if (!MIRROR) { - if (i >= 2) { - lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + if (2*i+0 >= 2) { + lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; } } else { - if (i+BLOCK_COUNT/2 >= 2) { - lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; + if (2*i+1 >= 2) { + lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; } } } - // test with orphans, zombies, dirs, etc + // test with orphans, zombies, etc lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, CFG) => 0; @@ -2229,7 +4334,6 @@ code = ''' // set up a simulation to compare against lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); - bool *sim_isdirs = malloc(N*sizeof(bool)); lfs_size_t sim_size = 0; typedef struct sim_file { @@ -2246,7 +4350,7 @@ code = ''' for (lfs_size_t i = 0; i < OPS; i++) { nonsense:; // choose which operation to do - uint8_t op = TEST_PRNG(&prng) % 8; + uint8_t op = TEST_PRNG(&prng) % 5; // open a new file? if (op == 0) { @@ -2261,9 +4365,6 @@ code = ''' uint32_t wprng = 0; for (lfs_size_t j = 0; j < sim_size; j++) { if (sim[j] == x) { - if (sim_isdirs[j]) { - goto nonsense; - } orphan = false; wprng = sim_prngs[j]; break; @@ -2325,12 +4426,9 @@ code = ''' (sim_size-k)*sizeof(lfs_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); - memmove(&sim_isdirs[k+1], &sim_isdirs[k], - (sim_size-k)*sizeof(bool)); sim_size += 1; sim[k] = x; sim_prngs[k] = wprng; - sim_isdirs[k] = false; } break; } @@ -2389,8 +4487,6 @@ code = ''' (sim_size-(j+1))*sizeof(lfs_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); - memmove(&sim_isdirs[j], &sim_isdirs[j+1], - (sim_size-(j+1))*sizeof(bool)); sim_size -= 1; // mark any related sim files as zombied @@ -2416,25 +4512,17 @@ code = ''' lfs_size_t x = sim[j]; lfs_size_t y = TEST_PRNG(&prng) % N; uint32_t wprng = sim_prngs[j]; - bool isdir = sim_isdirs[j]; // update our sim for (lfs_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= y) { // renaming and replacing if (k < sim_size && sim[k] == y && x != y) { - // type mismatch? - if (sim_isdirs[k] != isdir) { - goto nonsense; - } - // delete the original entry memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); - memmove(&sim_isdirs[j], &sim_isdirs[j+1], - (sim_size-(j+1))*sizeof(bool)); sim_size -= 1; if (k > j) { k -= 1; @@ -2448,8 +4536,6 @@ code = ''' (sim_size-(j+1))*sizeof(lfs_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); - memmove(&sim_isdirs[j], &sim_isdirs[j+1], - (sim_size-(j+1))*sizeof(bool)); if (k > j) { k -= 1; } @@ -2458,11 +4544,8 @@ code = ''' (sim_size-k)*sizeof(lfs_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); - memmove(&sim_isdirs[k+1], &sim_isdirs[k], - (sim_size-k)*sizeof(bool)); sim[k] = y; sim_prngs[k] = wprng; - sim_isdirs[k] = isdir; } break; } @@ -2486,46 +4569,6 @@ code = ''' char new_name[256]; sprintf(new_name, "batman%03x", y); lfsr_rename(&lfs, old_name, new_name) => 0; - - // toss a directory into the mix - } else if (op == 5) { - // choose a pseudo-random number - lfs_size_t x = TEST_PRNG(&prng) % N; - - // insert into our sim, use negative numbers for dirs - for (lfs_size_t k = 0;; k++) { - if (k >= sim_size || sim[k] >= x) { - // already seen? - if (k < sim_size && sim[k] == x) { - goto nonsense; - } else { - // insert - memmove(&sim[k+1], &sim[k], - (sim_size-k)*sizeof(lfs_size_t)); - memmove(&sim_prngs[k+1], &sim_prngs[k], - (sim_size-k)*sizeof(uint32_t)); - memmove(&sim_isdirs[k+1], &sim_isdirs[k], - (sim_size-k)*sizeof(bool)); - sim_size += 1; - sim[k] = x; - sim_prngs[k] = 0; - sim_isdirs[k] = true; - } - break; - } - } - - // mark any related sim files as zombied - for (lfs_size_t k = 0; k < sim_file_count; k++) { - if (sim_files[k]->x == x) { - sim_files[k]->zombie = true; - } - } - - // make the directory - char name[256]; - sprintf(name, "batman%03x", x); - lfsr_mkdir(&lfs, name) => 0; } } @@ -2536,12 +4579,8 @@ code = ''' struct lfs_info info; lfsr_stat(&lfs, name, &info) => 0; assert(strcmp(info.name, name) == 0); - if (sim_isdirs[j]) { - assert(info.type == LFS_TYPE_DIR); - } else { - assert(info.type == LFS_TYPE_REG); - assert(info.size == SIZE); - } + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); } lfsr_dir_t dir; @@ -2560,40 +4599,28 @@ code = ''' sprintf(name, "batman%03x", sim[j]); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); - if (sim_isdirs[j]) { - assert(info.type == LFS_TYPE_DIR); - } else { - assert(info.type == LFS_TYPE_REG); - assert(info.size == SIZE); - } + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; for (lfs_size_t j = 0; j < sim_size; j++) { - if (sim_isdirs[j]) { - char name[256]; - sprintf(name, "batman%03x", sim[j]); - lfsr_file_t file; - lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => LFS_ERR_ISDIR; - - } else { - char name[256]; - sprintf(name, "batman%03x", sim[j]); - lfsr_file_t file; - lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; - - uint32_t wprng = sim_prngs[j]; - uint8_t wbuf[SIZE]; - for (lfs_size_t j = 0; j < SIZE; j++) { - wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); - } + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; - uint8_t rbuf[SIZE]; - lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; - assert(memcmp(rbuf, wbuf, SIZE) == 0); - lfsr_file_close(&lfs, &file) => 0; + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; } // check that our file handles match our simulation @@ -2621,7 +4648,7 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -# this should cause cascading failures +# with orphans, zombies, dirs, etc [cases.test_badblocks_alternating_orphanzombiedir_fuzz] defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK_BEHAVIOR = [ @@ -3064,7 +5091,8 @@ code = ''' ''' -# other corner cases + +## other corner cases # test formatting with 0 or 1 bad, this should just error [cases.test_badblocks_mrootanchor] diff --git a/tests/test_exhaustion.toml b/tests/test_exhaustion.toml index b5efb221..b41d8512 100644 --- a/tests/test_exhaustion.toml +++ b/tests/test_exhaustion.toml @@ -2,6 +2,7 @@ after = [ 'test_dirs', 'test_files', + 'test_fwrite', 'test_forphans', 'test_alloc', 'test_badblocks', @@ -489,7 +490,187 @@ code = ''' assert(run_ops[1]*110/100 > 2*run_ops[0]); ''' -# just more things that could go wrong +# with more complex file writes +[cases.test_exhaustion_fwrite_fuzz] +defines.ERASE_CYCLES = 10 +defines.BLOCK_RECYCLES = 4 +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', + 'LFS_EMUBD_BADBLOCK_READERROR', + 'LFS_EMUBD_BADBLOCK_PROGNOOP', + 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +# we need prog checking to detect read errors +defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' +defines.SIZE = [ + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +# chunk is more an upper limit here +defines.CHUNK = 64 +# INIT=0 => no init +# INIT=1 => fill with data +# INIT=2 => truncate to size +defines.INIT = [0, 1, 2] +defines.SYNC = [false, true] +defines.SEED = 42 +fuzz = 'SEED' +if = [ + 'CHUNK <= SIZE', + # this just saves testing time + 'SIZE <= 4*1024*FRAGMENT_SIZE', +] +code = ''' + // run our test twice, once with 1/2 the storage, once with 2/2 the + // storage, and compare how many operations we were able to perform + // before filesystem death + uint32_t run_bc[2] = {BLOCK_COUNT/2, BLOCK_COUNT}; + uint32_t run_ops[2] = {0, 0}; + + for (int run = 0; run < 2; run++) { + // clear any wear from the previous run + for (lfs_block_t i = 0; i < BLOCK_COUNT; i++) { + lfs_emubd_setwear(CFG, i, 0) => 0; + } + + // configure the filesystem size + struct lfs_config cfg = *CFG; + cfg.block_count = run_bc[run]; + + // run the test + lfs_t lfs; + lfsr_format(&lfs, &cfg) => 0; + lfsr_mount(&lfs, &cfg) => 0; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + // simulate our file in ram + uint8_t sim[SIZE]; + lfs_off_t size; + uint32_t prng = SEED; + if (INIT == 0) { + memset(sim, 0, SIZE); + size = 0; + } else if (INIT == 1) { + for (lfs_size_t i = 0; i < SIZE; i++) { + sim[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; + size = SIZE; + } else { + memset(sim, 0, SIZE); + lfsr_file_truncate(&lfs, &file, SIZE) => 0; + size = SIZE; + } + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + for (;; run_ops[run]++) { + // choose a random location + lfs_off_t off = TEST_PRNG(&prng) % SIZE; + // and a random size, up to the chunk size + lfs_size_t chunk = lfs_min32( + (TEST_PRNG(&prng) % (CHUNK+1-1)) + 1, + SIZE - off); + + // update sim + for (lfs_size_t j = 0; j < chunk; j++) { + sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); + } + size = lfs_max32(size, off+chunk); + + // update file + lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off; + lfs_ssize_t d = lfsr_file_write(&lfs, &file, &sim[off], chunk); + assert(d == (lfs_ssize_t)chunk || d == LFS_ERR_NOSPC); + if (d == LFS_ERR_NOSPC) { + goto dead; + } + + // sync? + if (SYNC) { + int err = lfsr_file_sync(&lfs, &file); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto dead; + } + } + + // check our simulation every power-of-2 ops + if (lfs_popc(run_ops[run]) == 1 && SYNC) { + // check our file with stat + struct lfs_info info; + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == size); + + // and with dir read + lfsr_dir_t dir; + 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); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == size); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // try reading our file + lfsr_file_t file_; + lfsr_file_open(&lfs, &file_, "hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file_) => size; + // try reading + uint8_t rbuf[2*SIZE]; + memset(rbuf, 0xaa, 2*SIZE); + lfsr_file_read(&lfs, &file_, rbuf, 2*SIZE) => size; + // does our file match our simulation? + assert(memcmp(rbuf, sim, size) == 0); + lfsr_file_close(&lfs, &file_) => 0; + } + } + + dead:; + // clean up sim/lfs + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + + // print how many ops + printf("run %d, %dx%d, %d ec: %d ops\n", + run, + (int)BLOCK_SIZE, + run_bc[run], + (int)ERASE_CYCLES, + run_ops[run]); + } + + // check that we increased the liftime by ~2x, with ~10% error + printf("lifetime: %d -> %d (x%.2f)\n", + run_ops[0], + run_ops[1], + (double)run_ops[1] / (double)run_ops[0]); + assert(run_ops[1]*110/100 > 2*run_ops[0]); +''' + +# with orphans, zombies, etc [cases.test_exhaustion_orphanzombie_fuzz] defines.ERASE_CYCLES = 10 defines.BLOCK_RECYCLES = 4 @@ -902,7 +1083,7 @@ code = ''' assert(run_ops[1]*110/100 > 2*run_ops[0]); ''' -# just more things that could go wrong +# with orphans, zombies, dirs, etc [cases.test_exhaustion_orphanzombiedir_fuzz] defines.ERASE_CYCLES = 10 defines.BLOCK_RECYCLES = 4 diff --git a/tests/test_files.toml b/tests/test_files.toml index 815fbc07..ca584678 100644 --- a/tests/test_files.toml +++ b/tests/test_files.toml @@ -2869,6 +2869,10 @@ code = ''' uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + // all data should be lowercase ascii + for (lfs_size_t j = 0; j < SIZE; j++) { + assert(rbuf[j] >= 'a' && rbuf[j] <= 'z'); + } // sum should be equal to 'a' mod 26 uint8_t ck = 0; for (lfs_size_t j = 0; j < SIZE; j++) { diff --git a/tests/test_fwrite.toml b/tests/test_fwrite.toml index 2c1486d6..feb225f2 100644 --- a/tests/test_fwrite.toml +++ b/tests/test_fwrite.toml @@ -1676,7 +1676,7 @@ defines.SIZE = [ '4*BLOCK_SIZE', ] # chunk is more an upper limit here -defines.CHUNK = [32, 8] +defines.CHUNK = [64, 16] # INIT=0 => no init # INIT=1 => fill with data # INIT=2 => truncate to size @@ -1736,16 +1736,14 @@ code = ''' lfs_off_t off = TEST_PRNG(&prng) % SIZE; // and a random size, up to the chunk size lfs_size_t chunk = lfs_min32( - TEST_PRNG(&prng) % CHUNK, + (TEST_PRNG(&prng) % (CHUNK+1-1)) + 1, SIZE - off); // update sim for (lfs_size_t j = 0; j < chunk; j++) { sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); } - if (chunk != 0) { - size = lfs_max32(size, off+chunk); - } + size = lfs_max32(size, off+chunk); // update file lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off; @@ -1828,7 +1826,7 @@ defines.SIZE = [ '4*BLOCK_SIZE', ] # chunk is more an upper limit here -defines.CHUNK = [32, 8] +defines.CHUNK = [64, 16] defines.SEED = 'range(10)' fuzz = 'SEED' if = [ @@ -1861,7 +1859,7 @@ code = ''' lfs_soff_t off = TEST_PRNG(&prng) % SIZE; // and a random size, up to the chunk size lfs_size_t chunk = lfs_min32( - TEST_PRNG(&prng) % CHUNK, + (TEST_PRNG(&prng) % (CHUNK+1-1)) + 1, SIZE - off); // test different seek methods @@ -1907,7 +1905,7 @@ defines.SIZE = [ '4*BLOCK_SIZE', ] # chunk is more an upper limit here -defines.CHUNK = [32, 8] +defines.CHUNK = [64, 16] # INIT=0 => no init # INIT=1 => fill with data # INIT=2 => truncate to size @@ -1956,7 +1954,7 @@ code = ''' lfs_off_t off = TEST_PRNG(&prng) % SIZE; // and a random size, up to the chunk size lfs_size_t chunk = lfs_min32( - TEST_PRNG(&prng) % CHUNK, + (TEST_PRNG(&prng) % (CHUNK+1-1)) + 1, SIZE - off); // test different seek methods @@ -1975,9 +1973,7 @@ code = ''' for (lfs_size_t j = 0; j < chunk; j++) { sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); } - if (chunk != 0) { - size = lfs_max32(size, off+chunk); - } + size = lfs_max32(size, off+chunk); // update the file lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk; @@ -2056,7 +2052,7 @@ defines.SIZE = [ '4*BLOCK_SIZE', ] # chunk is more an upper limit here -defines.CHUNK = [32, 8] +defines.CHUNK = [64, 16] # INIT=0 => no init # INIT=1 => fill with data # INIT=2 => truncate to size @@ -2105,7 +2101,7 @@ code = ''' lfs_off_t off = TEST_PRNG(&prng) % SIZE; // and a random size, up to the chunk size lfs_size_t chunk = lfs_min32( - TEST_PRNG(&prng) % CHUNK, + (TEST_PRNG(&prng) % (CHUNK+1-1)) + 1, SIZE - off); // and if we are reading or writing uint8_t op = TEST_PRNG(&prng) % 2; @@ -2128,9 +2124,7 @@ code = ''' for (lfs_size_t j = 0; j < chunk; j++) { sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); } - if (chunk != 0) { - size = lfs_max32(size, off+chunk); - } + size = lfs_max32(size, off+chunk); // update the file lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk; @@ -2336,7 +2330,7 @@ defines.SIZE = [ '4*BLOCK_SIZE', ] # chunk is more an upper limit here -defines.CHUNK = [32, 8] +defines.CHUNK = [64, 16] # INIT=0 => no init # INIT=1 => fill with data # INIT=2 => truncate to size @@ -2401,7 +2395,7 @@ code = ''' lfs_off_t off = TEST_PRNG(&prng) % SIZE; // and a random size, up to the chunk size lfs_size_t chunk = lfs_min32( - TEST_PRNG(&prng) % CHUNK, + (TEST_PRNG(&prng) % (CHUNK+1-1)) + 1, SIZE - off); // seek @@ -2411,9 +2405,7 @@ code = ''' for (lfs_size_t j = 0; j < chunk; j++) { sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); } - if (chunk != 0) { - size = lfs_max32(size, off+chunk); - } + size = lfs_max32(size, off+chunk); // update the file lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk; @@ -2437,7 +2429,7 @@ code = ''' lfs_off_t off = TEST_PRNG(&prng) % SIZE; // and a random size, up to the chunk size lfs_size_t chunk = lfs_min32( - TEST_PRNG(&prng) % CHUNK, + (TEST_PRNG(&prng) % (CHUNK+1-1)) + 1, SIZE - off); // seek diff --git a/tests/test_powerloss.toml b/tests/test_powerloss.toml index be07538b..b788a62b 100644 --- a/tests/test_powerloss.toml +++ b/tests/test_powerloss.toml @@ -1,3 +1,797 @@ +# Many tests already test the internal machinery under powerloss, these +# tests are more interested in running the filesystem under +# difficult/weird powerloss environments +# +# Try to keep these below O(n^2), which can be tricky +after = [ + 'test_mtree', + 'test_dirs', + 'test_files', + 'test_forphans', +] + + +# Create many dirs under powerloss +# +# We always make progress so this should be O(n) progs, (but maybe +# O(n^2) reads) +# +[cases.test_powerloss_dir_many] +defines.POWERLOSS_BEHAVIOR = [ + 'LFS_EMUBD_POWERLOSS_NOOP', + 'LFS_EMUBD_POWERLOSS_OOO', +] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] +reentrant = true +code = ''' + // format once per test + lfs_t lfs; + int err = lfsr_mount(&lfs, CFG); + if (err) { + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // make this many directories + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + err = lfsr_mkdir(&lfs, name); + assert(!err || (TEST_PLS && err == LFS_ERR_EXIST)); + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // grm should be zero here + assert(lfs.grm_p[0] == 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; + + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + lfsr_dir_open(&lfs, &dir, name) => 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); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + } + } + + lfsr_unmount(&lfs) => 0; +''' + +# Create many files under powerloss +# +# We always make progress so this should be O(n) progs, (but maybe +# O(n^2) reads) +# +[cases.test_powerloss_file_many] +defines.POWERLOSS_BEHAVIOR = [ + 'LFS_EMUBD_POWERLOSS_NOOP', + 'LFS_EMUBD_POWERLOSS_OOO', +] +# inlining has a tendency to hide sync issues, so try without +defines.INLINE_SIZE = ['BLOCK_SIZE/4', '0'] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +if = '(SIZE*N)/BLOCK_SIZE <= 32' +reentrant = true +code = ''' + // format once per test + lfs_t lfs; + int err = lfsr_mount(&lfs, CFG); + if (err) { + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // create this many files + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "amethyst%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; + err = lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL); + assert(!err || (TEST_PLS && err == LFS_ERR_EXIST)); + if (!err) { + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + } + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 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, "amethyst%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; +''' + +# A general purpose powerloss fuzz test +# +# Under powerloss, we can't really keep track of a sim reliably/ +# efficiently, instead just do random operations, store a counter in a +# special file so we know how much progress has been made, and hope for +# the best. Most likely an internal assert will trigger if anything goes +# wrong. +# +[cases.test_powerloss_file_pl_fuzz] +defines.POWERLOSS_BEHAVIOR = [ + 'LFS_EMUBD_POWERLOSS_NOOP', + 'LFS_EMUBD_POWERLOSS_OOO', +] +# inlining has a tendency to hide sync issues, so try without +defines.INLINE_SIZE = ['BLOCK_SIZE/4', '0'] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = 1024 +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 'range(10)' +fuzz = 'SEED' +if = '(SIZE*N)/BLOCK_SIZE <= 16' +reentrant = true +code = ''' + // format once per test + lfs_t lfs; + int err = lfsr_mount(&lfs, CFG); + if (err) { + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // keep some test state on disk to survive powerloss + typedef struct fuzz_state { + lfs_size_t i; + uint32_t prng; + } fuzz_state_t; + fuzz_state_t state = {.i = 0, .prng = SEED}; + + lfsr_file_t state_file; + lfsr_file_open(&lfs, &state_file, "state", LFS_O_RDWR | LFS_O_CREAT) => 0; + lfs_ssize_t d = lfsr_file_read(&lfs, &state_file, &state, sizeof(state)); + assert(d == 0 || d == sizeof(state)); + + // keep test files in a separate directory + err = lfsr_mkdir(&lfs, "test"); + assert(!err || err == LFS_ERR_EXIST); + + uint32_t prng = state.prng; + for (lfs_size_t i = state.i; i < OPS; i++) { + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 3; + + // how many files do we have? + lfs_size_t count = 0; + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "test") => 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); + while (true) { + int err = lfsr_dir_read(&lfs, &dir, &info); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + assert(strlen(info.name) == strlen("amethyst...")); + assert(memcmp(info.name, "amethyst", strlen("amethyst")) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + count++; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // creating a new file? + if (op == 0 || count == 0) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + uint32_t wprng = TEST_PRNG(&prng); + + // create a file here + char name[256]; + sprintf(name, "test/amethyst%03x", x); + uint8_t wbuf[SIZE]; + uint8_t ck = 0; + for (lfs_size_t j = 0; j < SIZE-1; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + ck = (ck + (wbuf[j] - 'a')) % 26; + } + // make the sum equal to 'a' mod 26 + if (SIZE > 0) { + wbuf[SIZE-1] = 'a' + ((26 - ck) % 26); + } + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // deleting a file? + } else if (op == 1) { + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % count; + // find the file + lfsr_dir_open(&lfs, &dir, "test") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + for (lfs_size_t k = 0; k <= j; k++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // delete this file + char name[256]; + assert(strlen(info.name) == strlen("amethyst...")); + sprintf(name, "test/%s", info.name); + lfsr_remove(&lfs, name) => 0; + + // renaming a file? + } else { + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % count; + lfs_size_t y = TEST_PRNG(&prng) % N; + // find the file + lfsr_dir_open(&lfs, &dir, "test") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + for (lfs_size_t k = 0; k <= j; k++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // rename this file + char old_name[256]; + assert(strlen(info.name) == strlen("amethyst...")); + sprintf(old_name, "test/%s", info.name); + char new_name[256]; + sprintf(new_name, "test/amethyst%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + + // update our state file + state.i = i; + state.prng = prng; + lfsr_file_rewind(&lfs, &state_file) => 0; + lfsr_file_write(&lfs, &state_file, &state, sizeof(state)) + => sizeof(state); + lfsr_file_sync(&lfs, &state_file) => 0; + } + + // go ahead and close our state file in case we remount + lfsr_file_close(&lfs, &state_file) => 0; + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that things look more-or-less ok + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "test") => 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); + while (true) { + int err = lfsr_dir_read(&lfs, &dir, &info); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + assert(strlen(info.name) == strlen("amethyst...")); + assert(memcmp(info.name, "amethyst", strlen("amethyst")) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + // at least try to read the files + char name[256]; + sprintf(name, "test/%s", info.name); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + // all data should be lowercase ascii + for (lfs_size_t j = 0; j < SIZE; j++) { + assert(rbuf[j] >= 'a' && rbuf[j] <= 'z'); + } + // sum should be equal to 'a' mod 26 + uint8_t ck = 0; + for (lfs_size_t j = 0; j < SIZE; j++) { + ck = (ck + (rbuf[j] - 'a')) % 26; + } + assert(ck == 0); + lfsr_file_close(&lfs, &file) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + } + + lfsr_unmount(&lfs) => 0; +''' + +# A general purpose powerloss fuzz test, with directories! +# +# Under powerloss, we can't really keep track of a sim reliably/ +# efficiently, instead just do random operations, store a counter in a +# special file so we know how much progress has been made, and hope for +# the best. Most likely an internal assert will trigger if anything goes +# wrong. +# +[cases.test_powerloss_filedir_pl_fuzz] +defines.POWERLOSS_BEHAVIOR = [ + 'LFS_EMUBD_POWERLOSS_NOOP', + 'LFS_EMUBD_POWERLOSS_OOO', +] +# inlining has a tendency to hide sync issues, so try without +defines.INLINE_SIZE = ['BLOCK_SIZE/4', '0'] +# note dirs x files grows O(n^2) +defines.N = [1, 2, 4, 8] +defines.M = 'N' +defines.OPS = 1024 +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 'range(10)' +fuzz = 'SEED' +if = '(SIZE*N)/BLOCK_SIZE <= 16' +reentrant = true +code = ''' + // format once per test + lfs_t lfs; + int err = lfsr_mount(&lfs, CFG); + if (err) { + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // keep some test state on disk to survive powerloss + typedef struct fuzz_state { + lfs_size_t i; + uint32_t prng; + } fuzz_state_t; + fuzz_state_t state = {.i = 0, .prng = SEED}; + + lfsr_file_t state_file; + lfsr_file_open(&lfs, &state_file, "state", LFS_O_RDWR | LFS_O_CREAT) => 0; + lfs_ssize_t d = lfsr_file_read(&lfs, &state_file, &state, sizeof(state)); + assert(d == 0 || d == sizeof(state)); + + // keep test files in a separate directory + err = lfsr_mkdir(&lfs, "test"); + assert(!err || err == LFS_ERR_EXIST); + + uint32_t prng = state.prng; + for (lfs_size_t i = state.i; i < OPS; i++) { + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 6; + + // how many dirs do we have? + lfs_size_t dir_count = 0; + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "test") => 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); + while (true) { + int err = lfsr_dir_read(&lfs, &dir, &info); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + assert(strlen(info.name) == strlen("quartz...")); + assert(memcmp(info.name, "quartz", strlen("quartz")) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + dir_count++; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // dir op? + if (op < 3 || dir_count == 0) { + // creating a new dir? + if (op == 0 || dir_count == 0) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + + // create a dir here + char name[256]; + sprintf(name, "test/quartz%03x", x); + int err = lfsr_mkdir(&lfs, name); + assert(!err || err == LFS_ERR_EXIST); + + // deleting a dir? + } else if (op == 1) { + // choose a random dir to delete + lfs_size_t j = TEST_PRNG(&prng) % dir_count; + // find the dir + lfsr_dir_open(&lfs, &dir, "test") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + for (lfs_size_t k = 0; k <= j; k++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // try to delete this dir, ignore non-empty dirs! + char name[256]; + assert(strlen(info.name) == strlen("quartz...")); + sprintf(name, "test/%s", info.name); + int err = lfsr_remove(&lfs, name); + assert(!err || err == LFS_ERR_NOTEMPTY); + + // renaming a dir? + } else { + // choose a random dir to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % dir_count; + lfs_size_t y = TEST_PRNG(&prng) % N; + // find the dir + lfsr_dir_open(&lfs, &dir, "test") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + for (lfs_size_t k = 0; k <= j; k++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // rename this dir, ignore conflicts! + char old_name[256]; + assert(strlen(info.name) == strlen("quartz...")); + sprintf(old_name, "test/%s", info.name); + char new_name[256]; + sprintf(new_name, "test/quartz%03x", y); + int err = lfsr_rename(&lfs, old_name, new_name); + assert(!err || err == LFS_ERR_NOTEMPTY); + } + + // file op? + } else { + // choose a pseudo-random dir + lfs_size_t dir_i = TEST_PRNG(&prng) % dir_count; + // find the dir + lfsr_dir_open(&lfs, &dir, "test") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + for (lfs_size_t k = 0; k <= dir_i; k++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + char dir_path[256]; + sprintf(dir_path, "test/%s", info.name); + + // how many files do we have? + lfs_size_t count = 0; + lfsr_dir_open(&lfs, &dir, dir_path) => 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); + while (true) { + int err = lfsr_dir_read(&lfs, &dir, &info); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + assert(strlen(info.name) == strlen("amethyst...")); + assert(memcmp( + info.name, + "amethyst", strlen("amethyst")) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + count++; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // creating a new file? + if (op == 3 || count == 0) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % M; + uint32_t wprng = TEST_PRNG(&prng); + + // create a file here + char name[256]; + sprintf(name, "%s/amethyst%03x", dir_path, x); + uint8_t wbuf[SIZE]; + uint8_t ck = 0; + for (lfs_size_t j = 0; j < SIZE-1; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + ck = (ck + (wbuf[j] - 'a')) % 26; + } + // make the sum equal to 'a' mod 26 + if (SIZE > 0) { + wbuf[SIZE-1] = 'a' + ((26 - ck) % 26); + } + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // deleting a file? + } else if (op == 4) { + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % count; + // find the file + lfsr_dir_open(&lfs, &dir, dir_path) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + for (lfs_size_t k = 0; k <= j; k++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // delete this file + char name[256]; + assert(strlen(info.name) == strlen("amethyst...")); + sprintf(name, "%s/%s", dir_path, info.name); + lfsr_remove(&lfs, name) => 0; + + // renaming a file? + } else { + // choose a random file to rename + lfs_size_t j = TEST_PRNG(&prng) % count; + // find the file + lfsr_dir_open(&lfs, &dir, dir_path) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + for (lfs_size_t k = 0; k <= j; k++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // choose a random dir to rename to + lfs_size_t dir_j = TEST_PRNG(&prng) % dir_count; + // find the dir + struct lfs_info info_; + lfsr_dir_open(&lfs, &dir, "test") => 0; + lfsr_dir_read(&lfs, &dir, &info_) => 0; + lfsr_dir_read(&lfs, &dir, &info_) => 0; + for (lfs_size_t k = 0; k <= dir_j; k++) { + lfsr_dir_read(&lfs, &dir, &info_) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // choose a random file to rename to + lfs_size_t y = TEST_PRNG(&prng) % M; + + // rename this file + char old_name[256]; + assert(strlen(info.name) == strlen("amethyst...")); + sprintf(old_name, "%s/%s", dir_path, info.name); + char new_name[256]; + sprintf(new_name, "test/%s/amethyst%03x", info_.name, y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + } + + // update our state file + state.i = i; + state.prng = prng; + lfsr_file_rewind(&lfs, &state_file) => 0; + lfsr_file_write(&lfs, &state_file, &state, sizeof(state)) + => sizeof(state); + lfsr_file_sync(&lfs, &state_file) => 0; + } + + // go ahead and close our state file in case we remount + lfsr_file_close(&lfs, &state_file) => 0; + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that things look more-or-less ok + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "test") => 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); + while (true) { + int err = lfsr_dir_read(&lfs, &dir, &info); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + assert(strlen(info.name) == strlen("quartz...")); + assert(memcmp(info.name, "quartz", strlen("quartz")) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + + // check that our dirs look more-or-less ok + char name[256]; + sprintf(name, "test/%s", info.name); + lfsr_dir_t dir_; + lfsr_dir_open(&lfs, &dir_, name) => 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); + + while (true) { + err = lfsr_dir_read(&lfs, &dir_, &info_); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + assert(strlen(info_.name) == strlen("amethyst...")); + assert(memcmp( + info_.name, + "amethyst", strlen("amethyst")) == 0); + assert(info_.type == LFS_TYPE_REG); + assert(info_.size == SIZE); + + // at least try to read the files + sprintf(name, "test/%s/%s", info.name, info_.name); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + // all data should be lowercase ascii + for (lfs_size_t j = 0; j < SIZE; j++) { + assert(rbuf[j] >= 'a' && rbuf[j] <= 'z'); + } + // sum should be equal to 'a' mod 26 + uint8_t ck = 0; + for (lfs_size_t j = 0; j < SIZE; j++) { + ck = (ck + (rbuf[j] - 'a')) % 26; + } + assert(ck == 0); + lfsr_file_close(&lfs, &file) => 0; + } + lfsr_dir_close(&lfs, &dir_) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + } + + lfsr_unmount(&lfs) => 0; +''' + + + + + + ## There are already a number of tests that test general operations under ## power-loss (see the reentrant attribute). These tests are for explicitly ## testing specific corner cases. diff --git a/tests/test_relocations.toml b/tests/test_relocations.toml index b5b23d3e..7319c5ed 100644 --- a/tests/test_relocations.toml +++ b/tests/test_relocations.toml @@ -4,6 +4,7 @@ after = [ 'test_dirs', 'test_files', 'test_forphans', + 'test_powerloss', ] # Note that most of the delicate relocation operations are already tested @@ -11,6 +12,85 @@ after = [ # relatively aggressive wear-leveling. # dirs + relocations may create problems for gstate +[cases.test_relocations_dir_many] +defines.BLOCK_RECYCLES = [4, 1, 0] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // make this many directories + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + int err = lfsr_mkdir(&lfs, name); + assert(!err || (TEST_PLS && err == LFS_ERR_EXIST)); + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // grm should be zero here + assert(lfs.grm_p[0] == 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; + + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + lfsr_dir_open(&lfs, &dir, name) => 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); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + } + } + + lfsr_unmount(&lfs) => 0; +''' + [cases.test_relocations_dir_fuzz] defines.BLOCK_RECYCLES = [4, 1, 0] defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] @@ -162,6 +242,79 @@ code = ''' ''' # files + relocations may create problems for shrubs +[cases.test_relocations_file_many] +defines.BLOCK_RECYCLES = [4, 1, 0] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many files + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "amethyst%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; + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 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, "amethyst%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; +''' + [cases.test_relocations_file_fuzz] defines.BLOCK_RECYCLES = [4, 1, 0] defines.N = [1, 2, 4, 8, 16, 32, 64] @@ -1133,6 +1286,9 @@ code = ''' # and don't forget potential powerloss problems + +# A general purpose powerloss fuzz test +# # # Under powerloss, we can't really keep track of a sim reliably/ # efficiently, instead just do random operations, store a counter in a @@ -1140,7 +1296,7 @@ code = ''' # the best. Most likely an internal assert will trigger if anything goes # wrong. # -[cases.test_relocations_pl_fuzz] +[cases.test_relocations_file_pl_fuzz] defines.BLOCK_RECYCLES = [4, 1, 0] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.OPS = 256 @@ -1333,6 +1489,10 @@ code = ''' uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + // all data should be lowercase ascii + for (lfs_size_t j = 0; j < SIZE; j++) { + assert(rbuf[j] >= 'a' && rbuf[j] <= 'z'); + } // sum should be equal to 'a' mod 26 uint8_t ck = 0; for (lfs_size_t j = 0; j < SIZE; j++) { @@ -1347,6 +1507,370 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' +# A general purpose powerloss fuzz test, with directories! +# +# Under powerloss, we can't really keep track of a sim reliably/ +# efficiently, instead just do random operations, store a counter in a +# special file so we know how much progress has been made, and hope for +# the best. Most likely an internal assert will trigger if anything goes +# wrong. +# +[cases.test_relocations_filedir_pl_fuzz] +defines.BLOCK_RECYCLES = [4, 1, 0] +# note dirs x files grows O(n^2) +defines.N = [1, 2, 4, 8] +defines.M = 'N' +defines.OPS = 256 +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 'range(10)' +fuzz = 'SEED' +if = '(SIZE*N)/BLOCK_SIZE <= 16' +reentrant = true +code = ''' + // format once per test + lfs_t lfs; + int err = lfsr_mount(&lfs, CFG); + if (err) { + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // keep some test state on disk to survive powerloss + typedef struct fuzz_state { + lfs_size_t i; + uint32_t prng; + } fuzz_state_t; + fuzz_state_t state = {.i = 0, .prng = SEED}; + + lfsr_file_t state_file; + lfsr_file_open(&lfs, &state_file, "state", LFS_O_RDWR | LFS_O_CREAT) => 0; + lfs_ssize_t d = lfsr_file_read(&lfs, &state_file, &state, sizeof(state)); + assert(d == 0 || d == sizeof(state)); + + // keep test files in a separate directory + err = lfsr_mkdir(&lfs, "test"); + assert(!err || err == LFS_ERR_EXIST); + + uint32_t prng = state.prng; + for (lfs_size_t i = state.i; i < OPS; i++) { + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 6; + + // how many dirs do we have? + lfs_size_t dir_count = 0; + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "test") => 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); + while (true) { + int err = lfsr_dir_read(&lfs, &dir, &info); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + assert(strlen(info.name) == strlen("quartz...")); + assert(memcmp(info.name, "quartz", strlen("quartz")) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + dir_count++; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // dir op? + if (op < 3 || dir_count == 0) { + // creating a new dir? + if (op == 0 || dir_count == 0) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + + // create a dir here + char name[256]; + sprintf(name, "test/quartz%03x", x); + int err = lfsr_mkdir(&lfs, name); + assert(!err || err == LFS_ERR_EXIST); + + // deleting a dir? + } else if (op == 1) { + // choose a random dir to delete + lfs_size_t j = TEST_PRNG(&prng) % dir_count; + // find the dir + lfsr_dir_open(&lfs, &dir, "test") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + for (lfs_size_t k = 0; k <= j; k++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // try to delete this dir, ignore non-empty dirs! + char name[256]; + assert(strlen(info.name) == strlen("quartz...")); + sprintf(name, "test/%s", info.name); + int err = lfsr_remove(&lfs, name); + assert(!err || err == LFS_ERR_NOTEMPTY); + + // renaming a dir? + } else { + // choose a random dir to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % dir_count; + lfs_size_t y = TEST_PRNG(&prng) % N; + // find the dir + lfsr_dir_open(&lfs, &dir, "test") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + for (lfs_size_t k = 0; k <= j; k++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // rename this dir, ignore conflicts! + char old_name[256]; + assert(strlen(info.name) == strlen("quartz...")); + sprintf(old_name, "test/%s", info.name); + char new_name[256]; + sprintf(new_name, "test/quartz%03x", y); + int err = lfsr_rename(&lfs, old_name, new_name); + assert(!err || err == LFS_ERR_NOTEMPTY); + } + + // file op? + } else { + // choose a pseudo-random dir + lfs_size_t dir_i = TEST_PRNG(&prng) % dir_count; + // find the dir + lfsr_dir_open(&lfs, &dir, "test") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + for (lfs_size_t k = 0; k <= dir_i; k++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + char dir_path[256]; + sprintf(dir_path, "test/%s", info.name); + + // how many files do we have? + lfs_size_t count = 0; + lfsr_dir_open(&lfs, &dir, dir_path) => 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); + while (true) { + int err = lfsr_dir_read(&lfs, &dir, &info); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + assert(strlen(info.name) == strlen("amethyst...")); + assert(memcmp( + info.name, + "amethyst", strlen("amethyst")) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + count++; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // creating a new file? + if (op == 3 || count == 0) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % M; + uint32_t wprng = TEST_PRNG(&prng); + + // create a file here + char name[256]; + sprintf(name, "%s/amethyst%03x", dir_path, x); + uint8_t wbuf[SIZE]; + uint8_t ck = 0; + for (lfs_size_t j = 0; j < SIZE-1; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + ck = (ck + (wbuf[j] - 'a')) % 26; + } + // make the sum equal to 'a' mod 26 + if (SIZE > 0) { + wbuf[SIZE-1] = 'a' + ((26 - ck) % 26); + } + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // deleting a file? + } else if (op == 4) { + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % count; + // find the file + lfsr_dir_open(&lfs, &dir, dir_path) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + for (lfs_size_t k = 0; k <= j; k++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // delete this file + char name[256]; + assert(strlen(info.name) == strlen("amethyst...")); + sprintf(name, "%s/%s", dir_path, info.name); + lfsr_remove(&lfs, name) => 0; + + // renaming a file? + } else { + // choose a random file to rename + lfs_size_t j = TEST_PRNG(&prng) % count; + // find the file + lfsr_dir_open(&lfs, &dir, dir_path) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + for (lfs_size_t k = 0; k <= j; k++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // choose a random dir to rename to + lfs_size_t dir_j = TEST_PRNG(&prng) % dir_count; + // find the dir + struct lfs_info info_; + lfsr_dir_open(&lfs, &dir, "test") => 0; + lfsr_dir_read(&lfs, &dir, &info_) => 0; + lfsr_dir_read(&lfs, &dir, &info_) => 0; + for (lfs_size_t k = 0; k <= dir_j; k++) { + lfsr_dir_read(&lfs, &dir, &info_) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // choose a random file to rename to + lfs_size_t y = TEST_PRNG(&prng) % M; + + // rename this file + char old_name[256]; + assert(strlen(info.name) == strlen("amethyst...")); + sprintf(old_name, "%s/%s", dir_path, info.name); + char new_name[256]; + sprintf(new_name, "test/%s/amethyst%03x", info_.name, y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + } + + // update our state file + state.i = i; + state.prng = prng; + lfsr_file_rewind(&lfs, &state_file) => 0; + lfsr_file_write(&lfs, &state_file, &state, sizeof(state)) + => sizeof(state); + lfsr_file_sync(&lfs, &state_file) => 0; + } + + // go ahead and close our state file in case we remount + lfsr_file_close(&lfs, &state_file) => 0; + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that things look more-or-less ok + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "test") => 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); + while (true) { + int err = lfsr_dir_read(&lfs, &dir, &info); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + assert(strlen(info.name) == strlen("quartz...")); + assert(memcmp(info.name, "quartz", strlen("quartz")) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + + // check that our dirs look more-or-less ok + char name[256]; + sprintf(name, "test/%s", info.name); + lfsr_dir_t dir_; + lfsr_dir_open(&lfs, &dir_, name) => 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); + + while (true) { + err = lfsr_dir_read(&lfs, &dir_, &info_); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + assert(strlen(info_.name) == strlen("amethyst...")); + assert(memcmp( + info_.name, + "amethyst", strlen("amethyst")) == 0); + assert(info_.type == LFS_TYPE_REG); + assert(info_.size == SIZE); + + // at least try to read the files + sprintf(name, "test/%s/%s", info.name, info_.name); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + // all data should be lowercase ascii + for (lfs_size_t j = 0; j < SIZE; j++) { + assert(rbuf[j] >= 'a' && rbuf[j] <= 'z'); + } + // sum should be equal to 'a' mod 26 + uint8_t ck = 0; + for (lfs_size_t j = 0; j < SIZE; j++) { + ck = (ck + (rbuf[j] - 'a')) % 26; + } + assert(ck == 0); + lfsr_file_close(&lfs, &file) => 0; + } + lfsr_dir_close(&lfs, &dir_) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + } + + lfsr_unmount(&lfs) => 0; +''' + ## specific corner cases worth explicitly testing for