mirror of
				https://github.com/eledio-devices/thirdparty-littlefs.git
				synced 2025-10-31 00:32:38 +01:00 
			
		
		
		
	Also finished migrating tests with test_relocations and test_exhaustion. The issue I was running into when migrating these tests was a lack of flexibility with what you could do with the block devices. It was possible to hack in some hooks for things like bad blocks and power loss, but it wasn't clean or easily extendable. The solution here was to just put all of these test extensions into a third block device, testbd, that uses the other two example block devices internally. testbd has several useful features for testing. Note this makes it a pretty terrible block device _example_ since these hooks look more complicated than a block device needs to be. - testbd can simulate different erase values, supporting 1s, 0s, other byte patterns, or no erases at all (which can cause surprising bugs). This actually depends on the simulated erase values in ramdb and filebd. I did try to move this out of rambd/filebd, but it's not possible to simulate erases in testbd without buffering entire blocks and creating an excessive amount of extra write operations. - testbd also helps simulate power-loss by containing a "power cycles" counter that is decremented every write operation until it calls exit. This is notably faster than the previous gdb approach, which is valuable since the reentrant tests tend to take a while to resolve. - testbd also tracks wear, which can be manually set and read. This is very useful for testing things like bad block handling, wear leveling, or even changing the effective size of the block device at runtime.
		
			
				
	
	
		
			342 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			TOML
		
	
	
	
	
	
			
		
		
	
	
			342 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			TOML
		
	
	
	
	
	
| [[case]] # test running a filesystem to exhaustion
 | |
| define.LFS_ERASE_CYCLES = 10
 | |
| define.LFS_BLOCK_COUNT = 256 # small bd so it runs faster
 | |
| define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2'
 | |
| define.LFS_BADBLOCK_BEHAVIOR = [
 | |
|     'LFS_TESTBD_BADBLOCK_NOPROG',
 | |
|     'LFS_TESTBD_BADBLOCK_NOERASE',
 | |
|     'LFS_TESTBD_BADBLOCK_NOREAD',
 | |
| ]
 | |
| define.FILES = 10
 | |
| code = '''
 | |
|     lfs_format(&lfs, &cfg) => 0;
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_mkdir(&lfs, "roadrunner") => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| 
 | |
|     uint32_t cycle = 0;
 | |
|     while (true) {
 | |
|         lfs_mount(&lfs, &cfg) => 0;
 | |
|         for (uint32_t i = 0; i < FILES; i++) {
 | |
|             // chose name, roughly random seed, and random 2^n size
 | |
|             sprintf(path, "roadrunner/test%d", i);
 | |
|             srand(cycle * i);
 | |
|             size = 1 << ((rand() % 10)+2);
 | |
| 
 | |
|             lfs_file_open(&lfs, &file, path,
 | |
|                     LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
 | |
| 
 | |
|             for (lfs_size_t j = 0; j < size; j++) {
 | |
|                 char c = 'a' + (rand() % 26);
 | |
|                 lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
 | |
|                 assert(res == 1 || res == LFS_ERR_NOSPC);
 | |
|                 if (res == LFS_ERR_NOSPC) {
 | |
|                     goto exhausted;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             err = lfs_file_close(&lfs, &file);
 | |
|             assert(err == 0 || err == LFS_ERR_NOSPC);
 | |
|             if (err == LFS_ERR_NOSPC) {
 | |
|                 goto exhausted;
 | |
|             }
 | |
|         }    
 | |
| 
 | |
|         for (uint32_t i = 0; i < FILES; i++) {
 | |
|             // check for errors
 | |
|             sprintf(path, "roadrunner/test%d", i);
 | |
|             srand(cycle * i);
 | |
|             size = 1 << ((rand() % 10)+2);
 | |
| 
 | |
|             lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
 | |
|             for (lfs_size_t j = 0; j < size; j++) {
 | |
|                 char c = 'a' + (rand() % 26);
 | |
|                 char r;
 | |
|                 lfs_file_read(&lfs, &file, &r, 1) => 1;
 | |
|                 assert(r == c);
 | |
|             }
 | |
| 
 | |
|             lfs_file_close(&lfs, &file) => 0;
 | |
|         }    
 | |
|         lfs_unmount(&lfs) => 0;
 | |
| 
 | |
|         cycle += 1;
 | |
|     }
 | |
| 
 | |
| exhausted:
 | |
|     // should still be readable
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     for (uint32_t i = 0; i < FILES; i++) {
 | |
|         // check for errors
 | |
|         sprintf(path, "roadrunner/test%d", i);
 | |
|         lfs_stat(&lfs, path, &info) => 0;
 | |
|     }    
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| 
 | |
|     LFS_WARN("completed %d cycles", cycle);
 | |
| '''
 | |
| 
 | |
| [[case]] # test running a filesystem to exhaustion
 | |
|          # which also requires expanding superblocks
 | |
| define.LFS_ERASE_CYCLES = 10
 | |
| define.LFS_BLOCK_COUNT = 256 # small bd so it runs faster
 | |
| define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2'
 | |
| define.LFS_BADBLOCK_BEHAVIOR = [
 | |
|     'LFS_TESTBD_BADBLOCK_NOPROG',
 | |
|     'LFS_TESTBD_BADBLOCK_NOERASE',
 | |
|     'LFS_TESTBD_BADBLOCK_NOREAD',
 | |
| ]
 | |
| define.FILES = 10
 | |
| code = '''
 | |
|     lfs_format(&lfs, &cfg) => 0;
 | |
| 
 | |
|     uint32_t cycle = 0;
 | |
|     while (true) {
 | |
|         lfs_mount(&lfs, &cfg) => 0;
 | |
|         for (uint32_t i = 0; i < FILES; i++) {
 | |
|             // chose name, roughly random seed, and random 2^n size
 | |
|             sprintf(path, "test%d", i);
 | |
|             srand(cycle * i);
 | |
|             size = 1 << ((rand() % 10)+2);
 | |
| 
 | |
|             lfs_file_open(&lfs, &file, path,
 | |
|                     LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
 | |
| 
 | |
|             for (lfs_size_t j = 0; j < size; j++) {
 | |
|                 char c = 'a' + (rand() % 26);
 | |
|                 lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
 | |
|                 assert(res == 1 || res == LFS_ERR_NOSPC);
 | |
|                 if (res == LFS_ERR_NOSPC) {
 | |
|                     goto exhausted;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             err = lfs_file_close(&lfs, &file);
 | |
|             assert(err == 0 || err == LFS_ERR_NOSPC);
 | |
|             if (err == LFS_ERR_NOSPC) {
 | |
|                 goto exhausted;
 | |
|             }
 | |
|         }    
 | |
| 
 | |
|         for (uint32_t i = 0; i < FILES; i++) {
 | |
|             // check for errors
 | |
|             sprintf(path, "test%d", i);
 | |
|             srand(cycle * i);
 | |
|             size = 1 << ((rand() % 10)+2);
 | |
| 
 | |
|             lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
 | |
|             for (lfs_size_t j = 0; j < size; j++) {
 | |
|                 char c = 'a' + (rand() % 26);
 | |
|                 char r;
 | |
|                 lfs_file_read(&lfs, &file, &r, 1) => 1;
 | |
|                 assert(r == c);
 | |
|             }
 | |
| 
 | |
|             lfs_file_close(&lfs, &file) => 0;
 | |
|         }    
 | |
|         lfs_unmount(&lfs) => 0;
 | |
| 
 | |
|         cycle += 1;
 | |
|     }
 | |
| 
 | |
| exhausted:
 | |
|     // should still be readable
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     for (uint32_t i = 0; i < FILES; i++) {
 | |
|         // check for errors
 | |
|         sprintf(path, "test%d", i);
 | |
|         lfs_stat(&lfs, path, &info) => 0;
 | |
|     }    
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| 
 | |
|     LFS_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.
 | |
| 
 | |
| [[case]] # wear-level test running a filesystem to exhaustion
 | |
| define.LFS_ERASE_CYCLES = 10
 | |
| define.LFS_BLOCK_COUNT = 256 # small bd so it runs faster
 | |
| define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2'
 | |
| define.LFS_BADBLOCK_BEHAVIOR = [
 | |
|     'LFS_TESTBD_BADBLOCK_NOPROG',
 | |
|     'LFS_TESTBD_BADBLOCK_NOERASE',
 | |
|     'LFS_TESTBD_BADBLOCK_NOREAD',
 | |
| ]
 | |
| define.FILES = 10
 | |
| code = '''
 | |
|     lfs_format(&lfs, &cfg) => 0;
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_mkdir(&lfs, "roadrunner") => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| 
 | |
|     uint32_t run_cycles[2];
 | |
|     const uint32_t run_block_count[2] = {LFS_BLOCK_COUNT/2, LFS_BLOCK_COUNT};
 | |
| 
 | |
|     for (int run = 0; run < 2; run++) {
 | |
|         for (lfs_block_t b = 0; b < LFS_BLOCK_COUNT; b++) {
 | |
|             lfs_testbd_setwear(&cfg, b,
 | |
|                     (b < run_block_count[run]) ? 0 : LFS_ERASE_CYCLES) => 0;
 | |
|         }
 | |
| 
 | |
|         uint32_t cycle = 0;
 | |
|         while (true) {
 | |
|             lfs_mount(&lfs, &cfg) => 0;
 | |
|             for (uint32_t i = 0; i < FILES; i++) {
 | |
|                 // chose name, roughly random seed, and random 2^n size
 | |
|                 sprintf(path, "roadrunner/test%d", i);
 | |
|                 srand(cycle * i);
 | |
|                 size = 1 << ((rand() % 10)+2);
 | |
| 
 | |
|                 lfs_file_open(&lfs, &file, path,
 | |
|                         LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
 | |
| 
 | |
|                 for (lfs_size_t j = 0; j < size; j++) {
 | |
|                     char c = 'a' + (rand() % 26);
 | |
|                     lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
 | |
|                     assert(res == 1 || res == LFS_ERR_NOSPC);
 | |
|                     if (res == LFS_ERR_NOSPC) {
 | |
|                         goto exhausted;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 err = lfs_file_close(&lfs, &file);
 | |
|                 assert(err == 0 || err == LFS_ERR_NOSPC);
 | |
|                 if (err == LFS_ERR_NOSPC) {
 | |
|                     goto exhausted;
 | |
|                 }
 | |
|             }    
 | |
| 
 | |
|             for (uint32_t i = 0; i < FILES; i++) {
 | |
|                 // check for errors
 | |
|                 sprintf(path, "roadrunner/test%d", i);
 | |
|                 srand(cycle * i);
 | |
|                 size = 1 << ((rand() % 10)+2);
 | |
| 
 | |
|                 lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
 | |
|                 for (lfs_size_t j = 0; j < size; j++) {
 | |
|                     char c = 'a' + (rand() % 26);
 | |
|                     char r;
 | |
|                     lfs_file_read(&lfs, &file, &r, 1) => 1;
 | |
|                     assert(r == c);
 | |
|                 }
 | |
| 
 | |
|                 lfs_file_close(&lfs, &file) => 0;
 | |
|             }    
 | |
|             lfs_unmount(&lfs) => 0;
 | |
| 
 | |
|             cycle += 1;
 | |
|         }
 | |
| 
 | |
| exhausted:
 | |
|         // should still be readable
 | |
|         lfs_mount(&lfs, &cfg) => 0;
 | |
|         for (uint32_t i = 0; i < FILES; i++) {
 | |
|             // check for errors
 | |
|             sprintf(path, "roadrunner/test%d", i);
 | |
|             lfs_stat(&lfs, path, &info) => 0;
 | |
|         }    
 | |
|         lfs_unmount(&lfs) => 0;
 | |
| 
 | |
|         run_cycles[run] = cycle;
 | |
|         LFS_WARN("completed %d blocks %d cycles",
 | |
|                 run_block_count[run], run_cycles[run]);
 | |
|     }
 | |
| 
 | |
|     // check we increased the lifetime by 2x with ~5% error
 | |
|     LFS_ASSERT(run_cycles[1] > 2*run_cycles[0]-run_cycles[0]/20);
 | |
| '''
 | |
| 
 | |
| [[case]] # wear-level test + expanding superblock
 | |
| define.LFS_ERASE_CYCLES = 10
 | |
| define.LFS_BLOCK_COUNT = 256 # small bd so it runs faster
 | |
| define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2'
 | |
| define.LFS_BADBLOCK_BEHAVIOR = [
 | |
|     'LFS_TESTBD_BADBLOCK_NOPROG',
 | |
|     'LFS_TESTBD_BADBLOCK_NOERASE',
 | |
|     'LFS_TESTBD_BADBLOCK_NOREAD',
 | |
| ]
 | |
| define.FILES = 10
 | |
| code = '''
 | |
|     lfs_format(&lfs, &cfg) => 0;
 | |
| 
 | |
|     uint32_t run_cycles[2];
 | |
|     const uint32_t run_block_count[2] = {LFS_BLOCK_COUNT/2, LFS_BLOCK_COUNT};
 | |
| 
 | |
|     for (int run = 0; run < 2; run++) {
 | |
|         for (lfs_block_t b = 0; b < LFS_BLOCK_COUNT; b++) {
 | |
|             lfs_testbd_setwear(&cfg, b,
 | |
|                     (b < run_block_count[run]) ? 0 : LFS_ERASE_CYCLES) => 0;
 | |
|         }
 | |
| 
 | |
|         uint32_t cycle = 0;
 | |
|         while (true) {
 | |
|             lfs_mount(&lfs, &cfg) => 0;
 | |
|             for (uint32_t i = 0; i < FILES; i++) {
 | |
|                 // chose name, roughly random seed, and random 2^n size
 | |
|                 sprintf(path, "test%d", i);
 | |
|                 srand(cycle * i);
 | |
|                 size = 1 << ((rand() % 10)+2);
 | |
| 
 | |
|                 lfs_file_open(&lfs, &file, path,
 | |
|                         LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
 | |
| 
 | |
|                 for (lfs_size_t j = 0; j < size; j++) {
 | |
|                     char c = 'a' + (rand() % 26);
 | |
|                     lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
 | |
|                     assert(res == 1 || res == LFS_ERR_NOSPC);
 | |
|                     if (res == LFS_ERR_NOSPC) {
 | |
|                         goto exhausted;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 err = lfs_file_close(&lfs, &file);
 | |
|                 assert(err == 0 || err == LFS_ERR_NOSPC);
 | |
|                 if (err == LFS_ERR_NOSPC) {
 | |
|                     goto exhausted;
 | |
|                 }
 | |
|             }    
 | |
| 
 | |
|             for (uint32_t i = 0; i < FILES; i++) {
 | |
|                 // check for errors
 | |
|                 sprintf(path, "test%d", i);
 | |
|                 srand(cycle * i);
 | |
|                 size = 1 << ((rand() % 10)+2);
 | |
| 
 | |
|                 lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
 | |
|                 for (lfs_size_t j = 0; j < size; j++) {
 | |
|                     char c = 'a' + (rand() % 26);
 | |
|                     char r;
 | |
|                     lfs_file_read(&lfs, &file, &r, 1) => 1;
 | |
|                     assert(r == c);
 | |
|                 }
 | |
| 
 | |
|                 lfs_file_close(&lfs, &file) => 0;
 | |
|             }    
 | |
|             lfs_unmount(&lfs) => 0;
 | |
| 
 | |
|             cycle += 1;
 | |
|         }
 | |
| 
 | |
| exhausted:
 | |
|         // should still be readable
 | |
|         lfs_mount(&lfs, &cfg) => 0;
 | |
|         for (uint32_t i = 0; i < FILES; i++) {
 | |
|             // check for errors
 | |
|             sprintf(path, "test%d", i);
 | |
|             lfs_stat(&lfs, path, &info) => 0;
 | |
|         }    
 | |
|         lfs_unmount(&lfs) => 0;
 | |
| 
 | |
|         run_cycles[run] = cycle;
 | |
|         LFS_WARN("completed %d blocks %d cycles",
 | |
|                 run_block_count[run], run_cycles[run]);
 | |
|     }
 | |
| 
 | |
|     // check we increased the lifetime by 2x with ~5% error
 | |
|     LFS_ASSERT(run_cycles[1] > 2*run_cycles[0]-run_cycles[0]/20);
 | |
| '''
 |