# Test basic file operations after = ['test_dirs', 'test_btree'] # test creation/deletion [cases.test_files_create] code = ''' 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) => 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 == 0); // 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 == 0); 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) => 0; // try reading uint8_t rbuf[8192]; lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => 0; lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; ''' # test we can write some data, should be inlined [cases.test_files_hello] code = ''' 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) => 0; uint8_t wbuf[8192]; strcpy((char*)wbuf, "Hello World!"); lfs_size_t wsize = strlen((const char*)wbuf); lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; 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 == wsize); // 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 == wsize); 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) => wsize; // try reading uint8_t rbuf[8192]; memset(rbuf, 0xaa, sizeof(rbuf)); lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => wsize; assert(memcmp(rbuf, wbuf, wsize) == 0); lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; ''' # test we can rewrite a file [cases.test_files_trunc] defines.REMOUNT = [false, true] code = ''' 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_TRUNC) => 0; uint8_t wbuf[8192]; strcpy((char*)wbuf, "Oh no!"); lfs_size_t wsize = strlen((const char*)wbuf); lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; lfsr_file_close(&lfs, &file) => 0; // remount? if (REMOUNT) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // rewrite the file lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; strcpy((char*)wbuf, "Hello World!"); wsize = strlen((const char*)wbuf); lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; 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 == wsize); // 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 == wsize); 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) => wsize; // try reading uint8_t rbuf[8192]; memset(rbuf, 0xaa, sizeof(rbuf)); lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => wsize; assert(memcmp(rbuf, wbuf, wsize) == 0); lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; ''' # check for LFS_F_EXCL errors [cases.test_files_excl] defines.REMOUNT = [false, true] code = ''' 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; uint8_t wbuf[8192]; strcpy((char*)wbuf, "Hello World!"); lfs_size_t wsize = strlen((const char*)wbuf); lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; lfsr_file_close(&lfs, &file) => 0; // remount? if (REMOUNT) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // try to recreate file, this should error lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST; 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 == wsize); // 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 == wsize); 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) => wsize; // try reading uint8_t rbuf[8192]; memset(rbuf, 0xaa, sizeof(rbuf)); lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => wsize; assert(memcmp(rbuf, wbuf, wsize) == 0); lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; ''' # a file is not a directory [cases.test_files_file_not_dir] defines.REMOUNT = [false, true] code = ''' 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; uint8_t wbuf[8192]; strcpy((char*)wbuf, "Hello World!"); lfs_size_t wsize = strlen((const char*)wbuf); lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; lfsr_file_close(&lfs, &file) => 0; // remount? if (REMOUNT) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // try to open our file as a directory lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "hello") => LFS_ERR_NOTDIR; // try to create a directory on top of our file lfsr_mkdir(&lfs, "hello") => LFS_ERR_EXIST; // try to rename a directory onto our file lfsr_mkdir(&lfs, "not_hello") => 0; lfsr_rename(&lfs, "not_hello", "hello") => LFS_ERR_NOTDIR; 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 == wsize); // and with dir read 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 == wsize); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "not_hello") == 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; // try reading our file lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; // is size correct? lfsr_file_size(&lfs, &file) => wsize; // try reading uint8_t rbuf[8192]; memset(rbuf, 0xaa, sizeof(rbuf)); lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => wsize; assert(memcmp(rbuf, wbuf, wsize) == 0); lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; ''' # a directory is not a file [cases.test_files_dir_not_file] code = ''' lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, CFG) => 0; // create a directory lfsr_mkdir(&lfs, "hello") => 0; // try reading our directory as a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => LFS_ERR_ISDIR; // try writing our directory as a file lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => LFS_ERR_ISDIR; lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY | LFS_O_TRUNC) => LFS_ERR_ISDIR; lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR; lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => LFS_ERR_ISDIR; // try rename a file on top of our directory lfsr_file_open(&lfs, &file, "not_hello", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf[8192]; strcpy((char*)wbuf, "Hello World!"); lfs_size_t wsize = strlen((const char*)wbuf); lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; lfsr_file_close(&lfs, &file) => 0; lfsr_rename(&lfs, "not_hello", "hello") => LFS_ERR_ISDIR; for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // check our dir with stat struct lfs_info info; lfsr_stat(&lfs, "hello", &info) => 0; assert(strcmp(info.name, "hello") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); // 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_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "not_hello") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == wsize); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // did we corrupt our renaming file? // try reading our file lfsr_file_open(&lfs, &file, "not_hello", LFS_O_RDONLY) => 0; // is size correct? lfsr_file_size(&lfs, &file) => wsize; // try reading uint8_t rbuf[8192]; memset(rbuf, 0xaa, sizeof(rbuf)); lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => wsize; assert(memcmp(rbuf, wbuf, wsize) == 0); lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; ''' # root is also not a file [cases.test_files_root_not_file] code = ''' lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, CFG) => 0; // try reading our root as a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "/", LFS_O_RDONLY) => LFS_ERR_ISDIR; // try writing our root as a file lfsr_file_open(&lfs, &file, "/", LFS_O_WRONLY) => LFS_ERR_ISDIR; lfsr_file_open(&lfs, &file, "/", LFS_O_WRONLY | LFS_O_TRUNC) => LFS_ERR_ISDIR; lfsr_file_open(&lfs, &file, "/", LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR; lfsr_file_open(&lfs, &file, "/", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => LFS_ERR_ISDIR; // try rename a file on top of our directory lfsr_file_open(&lfs, &file, "not_hello", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf[8192]; strcpy((char*)wbuf, "Hello World!"); lfs_size_t wsize = strlen((const char*)wbuf); lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; lfsr_file_close(&lfs, &file) => 0; lfsr_rename(&lfs, "not_hello", "/") => LFS_ERR_INVAL; for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // check our root with stat struct lfs_info info; lfsr_stat(&lfs, "/", &info) => 0; assert(strcmp(info.name, "/") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); // 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, "not_hello") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == wsize); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // did we corrupt our renaming file? // try reading our file lfsr_file_open(&lfs, &file, "not_hello", LFS_O_RDONLY) => 0; // is size correct? lfsr_file_size(&lfs, &file) => wsize; // try reading uint8_t rbuf[8192]; memset(rbuf, 0xaa, sizeof(rbuf)); lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => wsize; assert(memcmp(rbuf, wbuf, wsize) == 0); lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; ''' # an invalid path is also not a file (kind of?) [cases.test_files_noent_not_file] code = ''' lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, CFG) => 0; // try reading our invalid path as a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "no/hello", LFS_O_RDONLY) => LFS_ERR_NOENT; // try writing our root as a file lfsr_file_open(&lfs, &file, "no/hello", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file, "no/hello", LFS_O_WRONLY | LFS_O_TRUNC) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file, "no/hello", LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file, "no/hello", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => LFS_ERR_NOENT; // try rename a file on top of our invalid path lfsr_file_open(&lfs, &file, "not_hello", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf[8192]; strcpy((char*)wbuf, "Hello World!"); lfs_size_t wsize = strlen((const char*)wbuf); lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; lfsr_file_close(&lfs, &file) => 0; lfsr_rename(&lfs, "not_hello", "no/hello") => LFS_ERR_NOENT; for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // check our root with stat struct lfs_info info; lfsr_stat(&lfs, "/", &info) => 0; assert(strcmp(info.name, "/") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); // 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, "not_hello") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == wsize); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // did we corrupt our renaming file? // try reading our file lfsr_file_open(&lfs, &file, "not_hello", LFS_O_RDONLY) => 0; // is size correct? lfsr_file_size(&lfs, &file) => wsize; // try reading uint8_t rbuf[8192]; memset(rbuf, 0xaa, sizeof(rbuf)); lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => wsize; assert(memcmp(rbuf, wbuf, wsize) == 0); lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; ''' # try writing larger files # # note: # - at 2*FILE_BUFFER_SIZE we need a shrub # - at BLOCK_SIZE/2 we need a block pointer # - at 2*BLOCK_SIZE we need a btree # [cases.test_files_more] defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.FILE_BUFFER_SIZE = 64 code = ''' 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) => 0; uint8_t wbuf[SIZE]; uint32_t prng = 42; for (lfs_size_t i = 0; i < SIZE; i++) { wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26); } 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 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; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; ''' # test creating multiple files [cases.test_files_many] 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', # limit powerloss testing due to time # TODO can this be increased after optimizing file writes? '!TEST_PLS || (SIZE*N) <= 1*BLOCK_SIZE', ] 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); } // TODO remove this eventually? // // file may exist from a previous run, but we need to check // for pesky zero-sized files lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT) => 0; lfs_ssize_t size = lfsr_file_size(&lfs, &file); assert(size == 0 || size == SIZE); if (size == 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 test file creation [cases.test_files_fuzz] defines.N = [1, 2, 4, 8, 16, 32, 64] # do more ops than files to encourage file rewrites 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 = 'range(20)' fuzz = 'SEED' if = '(SIZE*N)/BLOCK_SIZE <= 16' code = ''' 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 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; } 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; ''' # test removing files of various sizes # # to be honest, this doesn't really test much and is just included # for completeness # [cases.test_files_rm] defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.REMOUNT = [false, true] defines.FILE_BUFFER_SIZE = 64 code = ''' 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, "amethyst", LFS_O_WRONLY | LFS_O_CREAT) => 0; uint8_t wbuf[SIZE]; uint32_t prng = 42; for (lfs_size_t i = 0; i < SIZE; i++) { wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; // remount? if (REMOUNT) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // remove our file lfsr_remove(&lfs, "amethyst") => 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, "amethyst", &info) => LFS_ERR_NOENT; // 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) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; } lfsr_unmount(&lfs) => 0; ''' # test creating and deleting multiple files [cases.test_files_rm_many] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.REMAINING = [4, 1, 0] defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.REMOUNT = [false, true] if = [ 'N > REMAINING', '(SIZE*N)/BLOCK_SIZE <= 32', # limit powerloss testing due to time # TODO can this be increased after optimizing file writes? '!TEST_PLS || ((SIZE*N) <= BLOCK_SIZE/4 && N <= 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; } // 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); } // TODO remove this eventually? // // file may exist from a previous run, but we need to check // for pesky zero-sized files lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT) => 0; lfs_ssize_t size = lfsr_file_size(&lfs, &file); assert(size == 0 || size == SIZE); if (size == 0) { lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; } lfsr_file_close(&lfs, &file) => 0; } // 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; } // now remove some number of files for (lfs_size_t i = 0; i < N-REMAINING; i++) { char name[256]; sprintf(name, "amethyst%03x", i); lfsr_remove(&lfs, name) => 0; // 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 removes worked // // note we need to keep the prng in sync // prng = 42; for (lfs_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "amethyst%03x", i); // keep prng in sync uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } if (i < N-REMAINING) { // check with stat struct lfs_info info; lfsr_stat(&lfs, name, &info) => LFS_ERR_NOENT; // and try to open lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => LFS_ERR_NOENT; } else { // check with stat 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 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 test file creation and deletion [cases.test_files_rm_fuzz] defines.N = [1, 2, 4, 8, 16, 32, 64] # do more ops than files to encourage file rewrites 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 = 'range(20)' fuzz = 'SEED' if = '(SIZE*N)/BLOCK_SIZE <= 16' code = ''' 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) % 2; // 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 { // 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; } } 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; ''' # test renaming files of various sizes [cases.test_files_mv] defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.N = [0, 128] defines.REMOUNT = [false, true] defines.FILE_BUFFER_SIZE = 64 code = ''' lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, CFG) => 0; // create a number of directories to distance our files for (lfs_size_t i = 0; i < N; i++) { char path[256]; sprintf(path, "basalt%03x", i); lfsr_mkdir(&lfs, path) => 0; } // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "amethyst", LFS_O_WRONLY | LFS_O_CREAT) => 0; uint8_t wbuf[SIZE]; uint32_t prng = 42; for (lfs_size_t i = 0; i < SIZE; i++) { wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; // remount? if (REMOUNT) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // rename the file lfsr_rename(&lfs, "amethyst", "calcite") => 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, "calcite", &info) => 0; assert(strcmp(info.name, "calcite") == 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); for (lfs_size_t i = 0; i < N; i++) { char path[256]; sprintf(path, "basalt%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, path) == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); } lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "calcite") == 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, "calcite", 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; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; ''' # test renaming files of various sizes over existing files [cases.test_files_mv_replace] defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.N = [0, 128] defines.REMOUNT = [false, true] defines.FILE_BUFFER_SIZE = 64 code = ''' lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, CFG) => 0; // create a number of directories to distance our files for (lfs_size_t i = 0; i < N; i++) { char path[256]; sprintf(path, "basalt%03x", i); lfsr_mkdir(&lfs, path) => 0; } // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "amethyst", LFS_O_WRONLY | LFS_O_CREAT) => 0; uint8_t wbuf[SIZE]; uint32_t prng = 42; for (lfs_size_t i = 0; i < SIZE; i++) { wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; // create another file lfsr_file_open(&lfs, &file, "calcite", LFS_O_WRONLY | LFS_O_CREAT) => 0; uint8_t wbuf_[SIZE]; for (lfs_size_t i = 0; i < SIZE; i++) { wbuf_[i] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf_, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; // remount? if (REMOUNT) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // rename the file lfsr_rename(&lfs, "amethyst", "calcite") => 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, "amethyst", &info) => LFS_ERR_NOENT; lfsr_stat(&lfs, "calcite", &info) => 0; assert(strcmp(info.name, "calcite") == 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); for (lfs_size_t i = 0; i < N; i++) { char path[256]; sprintf(path, "basalt%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, path) == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); } lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "calcite") == 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, "calcite", 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; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; ''' # test renaming a file to itself [cases.test_files_mv_noop] defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.REMOUNT = [false, true] defines.FILE_BUFFER_SIZE = 64 code = ''' 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, "amethyst", LFS_O_WRONLY | LFS_O_CREAT) => 0; uint8_t wbuf[SIZE]; uint32_t prng = 42; for (lfs_size_t i = 0; i < SIZE; i++) { wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; // remount? if (REMOUNT) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // rename the file lfsr_rename(&lfs, "amethyst", "amethyst") => 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, "amethyst", &info) => 0; assert(strcmp(info.name, "amethyst") == 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, "amethyst") == 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, "amethyst", 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; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; ''' # test renaming a file onto a dir [cases.test_files_mv_not_file] defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.REMOUNT = [false, true] defines.FILE_BUFFER_SIZE = 64 code = ''' 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, "amethyst", LFS_O_WRONLY | LFS_O_CREAT) => 0; uint8_t wbuf[SIZE]; uint32_t prng = 42; for (lfs_size_t i = 0; i < SIZE; i++) { wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; // create a dir lfsr_mkdir(&lfs, "basalt") => 0; // remount? if (REMOUNT) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // rename the file lfsr_rename(&lfs, "amethyst", "basalt") => LFS_ERR_ISDIR; for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // check that nothing changed in our file/dir with stat struct lfs_info info; lfsr_stat(&lfs, "amethyst", &info) => 0; assert(strcmp(info.name, "amethyst") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_stat(&lfs, "basalt", &info) => 0; assert(strcmp(info.name, "basalt") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); // 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, "amethyst") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "basalt") == 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; // try reading our file lfsr_file_open(&lfs, &file, "amethyst", 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; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; ''' # test renaming a dir onto a file [cases.test_files_mv_not_dir] defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.REMOUNT = [false, true] defines.FILE_BUFFER_SIZE = 64 code = ''' 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, "amethyst", LFS_O_WRONLY | LFS_O_CREAT) => 0; uint8_t wbuf[SIZE]; uint32_t prng = 42; for (lfs_size_t i = 0; i < SIZE; i++) { wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; // create a dir lfsr_mkdir(&lfs, "basalt") => 0; // remount? if (REMOUNT) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // rename the dir lfsr_rename(&lfs, "basalt", "amethyst") => LFS_ERR_NOTDIR; for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // check that nothing changed in our file/dir with stat struct lfs_info info; lfsr_stat(&lfs, "amethyst", &info) => 0; assert(strcmp(info.name, "amethyst") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_stat(&lfs, "basalt", &info) => 0; assert(strcmp(info.name, "basalt") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); // 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, "amethyst") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "basalt") == 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; // try reading our file lfsr_file_open(&lfs, &file, "amethyst", 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; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; ''' # test renaming a file onto root [cases.test_files_mv_not_root] defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.REMOUNT = [false, true] defines.FILE_BUFFER_SIZE = 64 code = ''' 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, "amethyst", LFS_O_WRONLY | LFS_O_CREAT) => 0; uint8_t wbuf[SIZE]; uint32_t prng = 42; for (lfs_size_t i = 0; i < SIZE; i++) { wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; // remount? if (REMOUNT) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // rename the file lfsr_rename(&lfs, "amethyst", "/") => LFS_ERR_INVAL; for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // check that nothing changed in our file/dir with stat struct lfs_info info; lfsr_stat(&lfs, "amethyst", &info) => 0; assert(strcmp(info.name, "amethyst") == 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, "amethyst") == 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, "amethyst", 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; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; ''' # test renaming a file onto an invalid path [cases.test_files_mv_not_noent] defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.REMOUNT = [false, true] defines.FILE_BUFFER_SIZE = 64 code = ''' 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, "amethyst", LFS_O_WRONLY | LFS_O_CREAT) => 0; uint8_t wbuf[SIZE]; uint32_t prng = 42; for (lfs_size_t i = 0; i < SIZE; i++) { wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; // remount? if (REMOUNT) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // rename the file lfsr_rename(&lfs, "amethyst", "no/amethyst") => LFS_ERR_NOENT; for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // check that nothing changed in our file/dir with stat struct lfs_info info; lfsr_stat(&lfs, "amethyst", &info) => 0; assert(strcmp(info.name, "amethyst") == 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, "amethyst") == 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, "amethyst", 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; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; ''' # test renaming multiple files [cases.test_files_mv_many] 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', ] defines.REMOUNT = [false, true] if = [ '(SIZE*N)/BLOCK_SIZE <= 32', # limit powerloss testing due to time # TODO can this be increased after optimizing file writes? '!TEST_PLS || ((SIZE*N) <= BLOCK_SIZE/4 && N <= 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; } // 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); } // TODO remove this eventually? // // file may exist from a previous run, but we need to check // for pesky zero-sized files lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT) => 0; lfs_ssize_t size = lfsr_file_size(&lfs, &file); assert(size == 0 || size == SIZE); if (size == 0) { lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; } lfsr_file_close(&lfs, &file) => 0; } // 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; } // now rename our files for (lfs_size_t i = 0; i < N; i++) { char old_name[256]; sprintf(old_name, "amethyst%03x", i); char new_name[256]; sprintf(new_name, "basalt%03x", i); lfsr_rename(&lfs, old_name, new_name) => 0; // 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 renames 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) => LFS_ERR_NOENT; sprintf(name, "basalt%03x", i); 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; ''' # one particularly nasty case is renaming over an mdir split, since shrubs # can be moved around quite a few times when that happens # # here we spam renames over an increasing number of files to hopefully hit # that case # [cases.test_files_mv_split] 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 while renaming uint32_t prng = 42; for (lfs_size_t i = 0; i < N; i++) { // always create as first file lfsr_file_t file; lfsr_file_open(&lfs, &file, "amethyst", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; // rename! char name[256]; sprintf(name, "basalt%03x", i); lfsr_rename(&lfs, "amethyst", name) => 0; } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // check that renames worked prng = 42; struct lfs_info info; lfsr_stat(&lfs, "amethyst", &info) => LFS_ERR_NOENT; for (lfs_size_t i = 0; i < N; i++) { // check with stat char name[256]; sprintf(name, "basalt%03x", i); 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 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_files_mv_split_backwards] 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 while renaming uint32_t prng = 42; for (lfs_size_t i = 0; i < N; i++) { // always create as last file lfsr_file_t file; lfsr_file_open(&lfs, &file, "cobalt", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; // rename! char name[256]; sprintf(name, "basalt%03x", (uint32_t)(N-1-i)); lfsr_rename(&lfs, "cobalt", name) => 0; } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, CFG) => 0; } // check that renames worked prng = 42; struct lfs_info info; lfsr_stat(&lfs, "cobalt", &info) => LFS_ERR_NOENT; for (lfs_size_t i = 0; i < N; i++) { // check with stat char name[256]; sprintf(name, "basalt%03x", (uint32_t)(N-1-i)); 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 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 test file creation and rename [cases.test_files_mv_fuzz] defines.N = [1, 2, 4, 8, 16, 32, 64] # do more ops than files to encourage file rewrites 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 = 'range(20)' fuzz = 'SEED' if = '(SIZE*N)/BLOCK_SIZE <= 16' code = ''' 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) % 2; // 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; // 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; ''' # fuzz test file creation/deletion/rename [cases.test_files_mvrm_fuzz] defines.N = [1, 2, 4, 8, 16, 32, 64] # do more ops than files to encourage file rewrites 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 = 'range(20)' fuzz = 'SEED' if = '(SIZE*N)/BLOCK_SIZE <= 16' code = ''' 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; ''' # fuzz test file ops under powerloss # # 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_files_pl_fuzz] defines.N = [1, 2, 4, 8, 16, 32, 64] # do more ops than files to encourage file rewrites 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 = 'range(20)' 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; ''' # TODO # [cases.test_files_multi_readers] # [cases.test_files_multi_readers_one_writer] # [cases.test_files_multi_writers] # [cases.test_files_multi_readers_multi_writers] # [cases.test_files_interleaved] # [cases.test_files_interleaved_fuzz] # [cases.test_files_interleaved_fuzz_fuzz] # [cases.test_files_dtree_fuzz] # [cases.test_files_dtree_fuzz_fuzz]