# test running a filesystem to exhaustion [cases.test_exhaustion_normal] defines.ERASE_CYCLES = 10 defines.ERASE_COUNT = 256 # small bd so test runs faster defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2' defines.BADBLOCK_BEHAVIOR = [ 'LFS2_EMUBD_BADBLOCK_PROGERROR', 'LFS2_EMUBD_BADBLOCK_ERASEERROR', 'LFS2_EMUBD_BADBLOCK_READERROR', 'LFS2_EMUBD_BADBLOCK_PROGNOOP', 'LFS2_EMUBD_BADBLOCK_ERASENOOP', ] defines.FILES = 10 code = ''' lfs2_t lfs2; lfs2_format(&lfs2, cfg) => 0; lfs2_mount(&lfs2, cfg) => 0; lfs2_mkdir(&lfs2, "roadrunner") => 0; lfs2_unmount(&lfs2) => 0; uint32_t cycle = 0; while (true) { lfs2_mount(&lfs2, cfg) => 0; for (uint32_t i = 0; i < FILES; i++) { // chose name, roughly random seed, and random 2^n size char path[1024]; sprintf(path, "roadrunner/test%d", i); uint32_t prng = cycle * i; lfs2_size_t size = 1 << ((TEST_PRNG(&prng) % 10)+2); lfs2_file_t file; lfs2_file_open(&lfs2, &file, path, LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0; for (lfs2_size_t j = 0; j < size; j++) { char c = 'a' + (TEST_PRNG(&prng) % 26); lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, &c, 1); assert(res == 1 || res == LFS2_ERR_NOSPC); if (res == LFS2_ERR_NOSPC) { int err = lfs2_file_close(&lfs2, &file); assert(err == 0 || err == LFS2_ERR_NOSPC); lfs2_unmount(&lfs2) => 0; goto exhausted; } } int err = lfs2_file_close(&lfs2, &file); assert(err == 0 || err == LFS2_ERR_NOSPC); if (err == LFS2_ERR_NOSPC) { lfs2_unmount(&lfs2) => 0; goto exhausted; } } for (uint32_t i = 0; i < FILES; i++) { // check for errors char path[1024]; sprintf(path, "roadrunner/test%d", i); uint32_t prng = cycle * i; lfs2_size_t size = 1 << ((TEST_PRNG(&prng) % 10)+2); lfs2_file_t file; lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0; for (lfs2_size_t j = 0; j < size; j++) { char c = 'a' + (TEST_PRNG(&prng) % 26); char r; lfs2_file_read(&lfs2, &file, &r, 1) => 1; assert(r == c); } lfs2_file_close(&lfs2, &file) => 0; } lfs2_unmount(&lfs2) => 0; cycle += 1; } exhausted: // should still be readable lfs2_mount(&lfs2, cfg) => 0; for (uint32_t i = 0; i < FILES; i++) { // check for errors char path[1024]; sprintf(path, "roadrunner/test%d", i); struct lfs2_info info; lfs2_stat(&lfs2, path, &info) => 0; } lfs2_unmount(&lfs2) => 0; LFS2_WARN("completed %d cycles", cycle); ''' # test running a filesystem to exhaustion # which also requires expanding superblocks [cases.test_exhaustion_superblocks] defines.ERASE_CYCLES = 10 defines.ERASE_COUNT = 256 # small bd so test runs faster defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2' defines.BADBLOCK_BEHAVIOR = [ 'LFS2_EMUBD_BADBLOCK_PROGERROR', 'LFS2_EMUBD_BADBLOCK_ERASEERROR', 'LFS2_EMUBD_BADBLOCK_READERROR', 'LFS2_EMUBD_BADBLOCK_PROGNOOP', 'LFS2_EMUBD_BADBLOCK_ERASENOOP', ] defines.FILES = 10 code = ''' lfs2_t lfs2; lfs2_format(&lfs2, cfg) => 0; uint32_t cycle = 0; while (true) { lfs2_mount(&lfs2, cfg) => 0; for (uint32_t i = 0; i < FILES; i++) { // chose name, roughly random seed, and random 2^n size char path[1024]; sprintf(path, "test%d", i); uint32_t prng = cycle * i; lfs2_size_t size = 1 << ((TEST_PRNG(&prng) % 10)+2); lfs2_file_t file; lfs2_file_open(&lfs2, &file, path, LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0; for (lfs2_size_t j = 0; j < size; j++) { char c = 'a' + (TEST_PRNG(&prng) % 26); lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, &c, 1); assert(res == 1 || res == LFS2_ERR_NOSPC); if (res == LFS2_ERR_NOSPC) { int err = lfs2_file_close(&lfs2, &file); assert(err == 0 || err == LFS2_ERR_NOSPC); lfs2_unmount(&lfs2) => 0; goto exhausted; } } int err = lfs2_file_close(&lfs2, &file); assert(err == 0 || err == LFS2_ERR_NOSPC); if (err == LFS2_ERR_NOSPC) { lfs2_unmount(&lfs2) => 0; goto exhausted; } } for (uint32_t i = 0; i < FILES; i++) { // check for errors char path[1024]; sprintf(path, "test%d", i); uint32_t prng = cycle * i; lfs2_size_t size = 1 << ((TEST_PRNG(&prng) % 10)+2); lfs2_file_t file; lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0; for (lfs2_size_t j = 0; j < size; j++) { char c = 'a' + (TEST_PRNG(&prng) % 26); char r; lfs2_file_read(&lfs2, &file, &r, 1) => 1; assert(r == c); } lfs2_file_close(&lfs2, &file) => 0; } lfs2_unmount(&lfs2) => 0; cycle += 1; } exhausted: // should still be readable lfs2_mount(&lfs2, cfg) => 0; for (uint32_t i = 0; i < FILES; i++) { // check for errors char path[1024]; struct lfs2_info info; sprintf(path, "test%d", i); lfs2_stat(&lfs2, path, &info) => 0; } lfs2_unmount(&lfs2) => 0; LFS2_WARN("completed %d cycles", cycle); ''' # These are a sort of high-level litmus test for wear-leveling. One definition # of wear-leveling is that increasing a block device's space translates directly # into increasing the block devices lifetime. This is something we can actually # check for. # wear-level test running a filesystem to exhaustion [cases.test_exhuastion_wear_leveling] defines.ERASE_CYCLES = 20 defines.ERASE_COUNT = 256 # small bd so test runs faster defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2' defines.FILES = 10 code = ''' uint32_t run_cycles[2]; const uint32_t run_block_count[2] = {BLOCK_COUNT/2, BLOCK_COUNT}; for (int run = 0; run < 2; run++) { for (lfs2_block_t b = 0; b < BLOCK_COUNT; b++) { lfs2_emubd_setwear(cfg, b, (b < run_block_count[run]) ? 0 : ERASE_CYCLES) => 0; } lfs2_t lfs2; lfs2_format(&lfs2, cfg) => 0; lfs2_mount(&lfs2, cfg) => 0; lfs2_mkdir(&lfs2, "roadrunner") => 0; lfs2_unmount(&lfs2) => 0; uint32_t cycle = 0; while (true) { lfs2_mount(&lfs2, cfg) => 0; for (uint32_t i = 0; i < FILES; i++) { // chose name, roughly random seed, and random 2^n size char path[1024]; sprintf(path, "roadrunner/test%d", i); uint32_t prng = cycle * i; lfs2_size_t size = 1 << ((TEST_PRNG(&prng) % 10)+2); lfs2_file_t file; lfs2_file_open(&lfs2, &file, path, LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0; for (lfs2_size_t j = 0; j < size; j++) { char c = 'a' + (TEST_PRNG(&prng) % 26); lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, &c, 1); assert(res == 1 || res == LFS2_ERR_NOSPC); if (res == LFS2_ERR_NOSPC) { int err = lfs2_file_close(&lfs2, &file); assert(err == 0 || err == LFS2_ERR_NOSPC); lfs2_unmount(&lfs2) => 0; goto exhausted; } } int err = lfs2_file_close(&lfs2, &file); assert(err == 0 || err == LFS2_ERR_NOSPC); if (err == LFS2_ERR_NOSPC) { lfs2_unmount(&lfs2) => 0; goto exhausted; } } for (uint32_t i = 0; i < FILES; i++) { // check for errors char path[1024]; sprintf(path, "roadrunner/test%d", i); uint32_t prng = cycle * i; lfs2_size_t size = 1 << ((TEST_PRNG(&prng) % 10)+2); lfs2_file_t file; lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0; for (lfs2_size_t j = 0; j < size; j++) { char c = 'a' + (TEST_PRNG(&prng) % 26); char r; lfs2_file_read(&lfs2, &file, &r, 1) => 1; assert(r == c); } lfs2_file_close(&lfs2, &file) => 0; } lfs2_unmount(&lfs2) => 0; cycle += 1; } exhausted: // should still be readable lfs2_mount(&lfs2, cfg) => 0; for (uint32_t i = 0; i < FILES; i++) { // check for errors char path[1024]; struct lfs2_info info; sprintf(path, "roadrunner/test%d", i); lfs2_stat(&lfs2, path, &info) => 0; } lfs2_unmount(&lfs2) => 0; run_cycles[run] = cycle; LFS2_WARN("completed %d blocks %d cycles", run_block_count[run], run_cycles[run]); } // check we increased the lifetime by 2x with ~10% error LFS2_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]); ''' # wear-level test + expanding superblock [cases.test_exhaustion_wear_leveling_superblocks] defines.ERASE_CYCLES = 20 defines.ERASE_COUNT = 256 # small bd so test runs faster defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2' defines.FILES = 10 code = ''' uint32_t run_cycles[2]; const uint32_t run_block_count[2] = {BLOCK_COUNT/2, BLOCK_COUNT}; for (int run = 0; run < 2; run++) { for (lfs2_block_t b = 0; b < BLOCK_COUNT; b++) { lfs2_emubd_setwear(cfg, b, (b < run_block_count[run]) ? 0 : ERASE_CYCLES) => 0; } lfs2_t lfs2; lfs2_format(&lfs2, cfg) => 0; uint32_t cycle = 0; while (true) { lfs2_mount(&lfs2, cfg) => 0; for (uint32_t i = 0; i < FILES; i++) { // chose name, roughly random seed, and random 2^n size char path[1024]; sprintf(path, "test%d", i); uint32_t prng = cycle * i; lfs2_size_t size = 1 << ((TEST_PRNG(&prng) % 10)+2); lfs2_file_t file; lfs2_file_open(&lfs2, &file, path, LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0; for (lfs2_size_t j = 0; j < size; j++) { char c = 'a' + (TEST_PRNG(&prng) % 26); lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, &c, 1); assert(res == 1 || res == LFS2_ERR_NOSPC); if (res == LFS2_ERR_NOSPC) { int err = lfs2_file_close(&lfs2, &file); assert(err == 0 || err == LFS2_ERR_NOSPC); lfs2_unmount(&lfs2) => 0; goto exhausted; } } int err = lfs2_file_close(&lfs2, &file); assert(err == 0 || err == LFS2_ERR_NOSPC); if (err == LFS2_ERR_NOSPC) { lfs2_unmount(&lfs2) => 0; goto exhausted; } } for (uint32_t i = 0; i < FILES; i++) { // check for errors char path[1024]; sprintf(path, "test%d", i); uint32_t prng = cycle * i; lfs2_size_t size = 1 << ((TEST_PRNG(&prng) % 10)+2); lfs2_file_t file; lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0; for (lfs2_size_t j = 0; j < size; j++) { char c = 'a' + (TEST_PRNG(&prng) % 26); char r; lfs2_file_read(&lfs2, &file, &r, 1) => 1; assert(r == c); } lfs2_file_close(&lfs2, &file) => 0; } lfs2_unmount(&lfs2) => 0; cycle += 1; } exhausted: // should still be readable lfs2_mount(&lfs2, cfg) => 0; for (uint32_t i = 0; i < FILES; i++) { // check for errors char path[1024]; struct lfs2_info info; sprintf(path, "test%d", i); lfs2_stat(&lfs2, path, &info) => 0; } lfs2_unmount(&lfs2) => 0; run_cycles[run] = cycle; LFS2_WARN("completed %d blocks %d cycles", run_block_count[run], run_cycles[run]); } // check we increased the lifetime by 2x with ~10% error LFS2_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]); ''' # test that we wear blocks roughly evenly [cases.test_exhaustion_wear_distribution] defines.ERASE_CYCLES = 0xffffffff defines.ERASE_COUNT = 256 # small bd so test runs faster defines.BLOCK_CYCLES = [5, 4, 3, 2, 1] defines.CYCLES = 100 defines.FILES = 10 if = 'BLOCK_CYCLES < CYCLES/10' code = ''' lfs2_t lfs2; lfs2_format(&lfs2, cfg) => 0; lfs2_mount(&lfs2, cfg) => 0; lfs2_mkdir(&lfs2, "roadrunner") => 0; lfs2_unmount(&lfs2) => 0; uint32_t cycle = 0; while (cycle < CYCLES) { lfs2_mount(&lfs2, cfg) => 0; for (uint32_t i = 0; i < FILES; i++) { // chose name, roughly random seed, and random 2^n size char path[1024]; sprintf(path, "roadrunner/test%d", i); uint32_t prng = cycle * i; lfs2_size_t size = 1 << 4; //((TEST_PRNG(&prng) % 10)+2); lfs2_file_t file; lfs2_file_open(&lfs2, &file, path, LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0; for (lfs2_size_t j = 0; j < size; j++) { char c = 'a' + (TEST_PRNG(&prng) % 26); lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, &c, 1); assert(res == 1 || res == LFS2_ERR_NOSPC); if (res == LFS2_ERR_NOSPC) { int err = lfs2_file_close(&lfs2, &file); assert(err == 0 || err == LFS2_ERR_NOSPC); lfs2_unmount(&lfs2) => 0; goto exhausted; } } int err = lfs2_file_close(&lfs2, &file); assert(err == 0 || err == LFS2_ERR_NOSPC); if (err == LFS2_ERR_NOSPC) { lfs2_unmount(&lfs2) => 0; goto exhausted; } } for (uint32_t i = 0; i < FILES; i++) { // check for errors char path[1024]; sprintf(path, "roadrunner/test%d", i); uint32_t prng = cycle * i; lfs2_size_t size = 1 << 4; //((TEST_PRNG(&prng) % 10)+2); lfs2_file_t file; lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0; for (lfs2_size_t j = 0; j < size; j++) { char c = 'a' + (TEST_PRNG(&prng) % 26); char r; lfs2_file_read(&lfs2, &file, &r, 1) => 1; assert(r == c); } lfs2_file_close(&lfs2, &file) => 0; } lfs2_unmount(&lfs2) => 0; cycle += 1; } exhausted: // should still be readable lfs2_mount(&lfs2, cfg) => 0; for (uint32_t i = 0; i < FILES; i++) { // check for errors char path[1024]; struct lfs2_info info; sprintf(path, "roadrunner/test%d", i); lfs2_stat(&lfs2, path, &info) => 0; } lfs2_unmount(&lfs2) => 0; LFS2_WARN("completed %d cycles", cycle); // check the wear on our block device lfs2_emubd_wear_t minwear = -1; lfs2_emubd_wear_t totalwear = 0; lfs2_emubd_wear_t maxwear = 0; // skip 0 and 1 as superblock movement is intentionally avoided for (lfs2_block_t b = 2; b < BLOCK_COUNT; b++) { lfs2_emubd_wear_t wear = lfs2_emubd_wear(cfg, b); printf("%08x: wear %d\n", b, wear); assert(wear >= 0); if (wear < minwear) { minwear = wear; } if (wear > maxwear) { maxwear = wear; } totalwear += wear; } lfs2_emubd_wear_t avgwear = totalwear / BLOCK_COUNT; LFS2_WARN("max wear: %d cycles", maxwear); LFS2_WARN("avg wear: %d cycles", totalwear / (int)BLOCK_COUNT); LFS2_WARN("min wear: %d cycles", minwear); // find standard deviation^2 lfs2_emubd_wear_t dev2 = 0; for (lfs2_block_t b = 2; b < BLOCK_COUNT; b++) { lfs2_emubd_wear_t wear = lfs2_emubd_wear(cfg, b); assert(wear >= 0); lfs2_emubd_swear_t diff = wear - avgwear; dev2 += diff*diff; } dev2 /= totalwear; LFS2_WARN("std dev^2: %d", dev2); assert(dev2 < 8); '''