mirror of
				https://github.com/eledio-devices/thirdparty-littlefs.git
				synced 2025-10-31 08:42:40 +01:00 
			
		
		
		
	Restructured block devices again for better test exploitation
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.
This commit is contained in:
		
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @@ -7,7 +7,7 @@ CC ?= gcc | |||||||
| AR ?= ar | AR ?= ar | ||||||
| SIZE ?= size | SIZE ?= size | ||||||
|  |  | ||||||
| SRC += $(wildcard *.c rambd/*.c filebd/*.c) | SRC += $(wildcard *.c rambd/*.c filebd/*.c testbd/*.c) | ||||||
| OBJ := $(SRC:.c=.o) | OBJ := $(SRC:.c=.o) | ||||||
| DEP := $(SRC:.c=.d) | DEP := $(SRC:.c=.d) | ||||||
| ASM := $(SRC:.c=.s) | ASM := $(SRC:.c=.s) | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ | |||||||
| #include <errno.h> | #include <errno.h> | ||||||
|  |  | ||||||
| int lfs_filebd_createcfg(const struct lfs_config *cfg, const char *path, | int lfs_filebd_createcfg(const struct lfs_config *cfg, const char *path, | ||||||
|         const struct lfs_filebd_config *filecfg) { |         const struct lfs_filebd_config *bdcfg) { | ||||||
|     LFS_TRACE("lfs_filebd_createcfg(%p {.context=%p, " |     LFS_TRACE("lfs_filebd_createcfg(%p {.context=%p, " | ||||||
|                 ".read=%p, .prog=%p, .erase=%p, .sync=%p, " |                 ".read=%p, .prog=%p, .erase=%p, .sync=%p, " | ||||||
|                 ".read_size=%"PRIu32", .prog_size=%"PRIu32", " |                 ".read_size=%"PRIu32", .prog_size=%"PRIu32", " | ||||||
| @@ -22,9 +22,9 @@ int lfs_filebd_createcfg(const struct lfs_config *cfg, const char *path, | |||||||
|             (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, |             (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, | ||||||
|             (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, |             (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, | ||||||
|             cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, |             cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, | ||||||
|             path, (void*)filecfg, filecfg->erase_value); |             path, (void*)bdcfg, bdcfg->erase_value); | ||||||
|     lfs_filebd_t *bd = cfg->context; |     lfs_filebd_t *bd = cfg->context; | ||||||
|     bd->cfg = filecfg; |     bd->cfg = bdcfg; | ||||||
|  |  | ||||||
|     // open file |     // open file | ||||||
|     bd->fd = open(path, O_RDWR | O_CREAT, 0666); |     bd->fd = open(path, O_RDWR | O_CREAT, 0666); | ||||||
| @@ -55,11 +55,17 @@ int lfs_filebd_create(const struct lfs_config *cfg, const char *path) { | |||||||
|     return err; |     return err; | ||||||
| } | } | ||||||
|  |  | ||||||
| void lfs_filebd_destroy(const struct lfs_config *cfg) { | int lfs_filebd_destroy(const struct lfs_config *cfg) { | ||||||
|     LFS_TRACE("lfs_filebd_destroy(%p)", (void*)cfg); |     LFS_TRACE("lfs_filebd_destroy(%p)", (void*)cfg); | ||||||
|     lfs_filebd_t *bd = cfg->context; |     lfs_filebd_t *bd = cfg->context; | ||||||
|     close(bd->fd); |     int err = close(bd->fd); | ||||||
|     LFS_TRACE("lfs_filebd_destroy -> %s", "void"); |     if (err < 0) { | ||||||
|  |         err = -errno; | ||||||
|  |         LFS_TRACE("lfs_filebd_destroy -> %d", err); | ||||||
|  |         return err; | ||||||
|  |     } | ||||||
|  |     LFS_TRACE("lfs_filebd_destroy -> %d", 0); | ||||||
|  |     return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block, | int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block, | ||||||
|   | |||||||
| @@ -17,8 +17,9 @@ extern "C" | |||||||
|  |  | ||||||
| // filebd config (optional) | // filebd config (optional) | ||||||
| struct lfs_filebd_config { | struct lfs_filebd_config { | ||||||
|     // 8-bit erase value to simulate erasing with. -1 indicates no erase |     // 8-bit erase value to use for simulating erases. -1 does not simulate | ||||||
|     // occurs, which is still a valid block device |     // erases, which can speed up testing by avoiding all the extra block-device | ||||||
|  |     // operations to store the erase value. | ||||||
|     int32_t erase_value; |     int32_t erase_value; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -32,10 +33,10 @@ typedef struct lfs_filebd { | |||||||
| // Create a file block device using the geometry in lfs_config | // Create a file block device using the geometry in lfs_config | ||||||
| int lfs_filebd_create(const struct lfs_config *cfg, const char *path); | int lfs_filebd_create(const struct lfs_config *cfg, const char *path); | ||||||
| int lfs_filebd_createcfg(const struct lfs_config *cfg, const char *path, | int lfs_filebd_createcfg(const struct lfs_config *cfg, const char *path, | ||||||
|         const struct lfs_filebd_config *ramcfg); |         const struct lfs_filebd_config *bdcfg); | ||||||
|  |  | ||||||
| // Clean up memory associated with block device | // Clean up memory associated with block device | ||||||
| void lfs_filebd_destroy(const struct lfs_config *cfg); | int lfs_filebd_destroy(const struct lfs_config *cfg); | ||||||
|  |  | ||||||
| // Read a block | // Read a block | ||||||
| int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block, | int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block, | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								lfs.c
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								lfs.c
									
									
									
									
									
								
							| @@ -1651,9 +1651,11 @@ relocate: | |||||||
|  |  | ||||||
|         // relocate half of pair |         // relocate half of pair | ||||||
|         int err = lfs_alloc(lfs, &dir->pair[1]); |         int err = lfs_alloc(lfs, &dir->pair[1]); | ||||||
|         if (err && (err != LFS_ERR_NOSPC && !exhausted)) { |         if (err && (err != LFS_ERR_NOSPC || !exhausted)) { | ||||||
|             return err; |             return err; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         exhausted = false; | ||||||
|         continue; |         continue; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ | |||||||
| #include "rambd/lfs_rambd.h" | #include "rambd/lfs_rambd.h" | ||||||
|  |  | ||||||
| int lfs_rambd_createcfg(const struct lfs_config *cfg, | int lfs_rambd_createcfg(const struct lfs_config *cfg, | ||||||
|         const struct lfs_rambd_config *ramcfg) { |         const struct lfs_rambd_config *bdcfg) { | ||||||
|     LFS_TRACE("lfs_rambd_createcfg(%p {.context=%p, " |     LFS_TRACE("lfs_rambd_createcfg(%p {.context=%p, " | ||||||
|                 ".read=%p, .prog=%p, .erase=%p, .sync=%p, " |                 ".read=%p, .prog=%p, .erase=%p, .sync=%p, " | ||||||
|                 ".read_size=%"PRIu32", .prog_size=%"PRIu32", " |                 ".read_size=%"PRIu32", .prog_size=%"PRIu32", " | ||||||
| @@ -17,9 +17,9 @@ int lfs_rambd_createcfg(const struct lfs_config *cfg, | |||||||
|             (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, |             (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, | ||||||
|             (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, |             (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, | ||||||
|             cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, |             cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, | ||||||
|             (void*)ramcfg, ramcfg->erase_value, ramcfg->buffer); |             (void*)bdcfg, bdcfg->erase_value, bdcfg->buffer); | ||||||
|     lfs_rambd_t *bd = cfg->context; |     lfs_rambd_t *bd = cfg->context; | ||||||
|     bd->cfg = ramcfg; |     bd->cfg = bdcfg; | ||||||
|  |  | ||||||
|     // allocate buffer? |     // allocate buffer? | ||||||
|     if (bd->cfg->buffer) { |     if (bd->cfg->buffer) { | ||||||
| @@ -57,14 +57,15 @@ int lfs_rambd_create(const struct lfs_config *cfg) { | |||||||
|     return err; |     return err; | ||||||
| } | } | ||||||
|  |  | ||||||
| void lfs_rambd_destroy(const struct lfs_config *cfg) { | int lfs_rambd_destroy(const struct lfs_config *cfg) { | ||||||
|     LFS_TRACE("lfs_rambd_destroy(%p)", (void*)cfg); |     LFS_TRACE("lfs_rambd_destroy(%p)", (void*)cfg); | ||||||
|     // clean up memory |     // clean up memory | ||||||
|     lfs_rambd_t *bd = cfg->context; |     lfs_rambd_t *bd = cfg->context; | ||||||
|     if (!bd->cfg->buffer) { |     if (!bd->cfg->buffer) { | ||||||
|         lfs_free(bd->buffer); |         lfs_free(bd->buffer); | ||||||
|     } |     } | ||||||
|     LFS_TRACE("lfs_rambd_destroy -> %s", "void"); |     LFS_TRACE("lfs_rambd_destroy -> %d", 0); | ||||||
|  |     return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| int lfs_rambd_read(const struct lfs_config *cfg, lfs_block_t block, | int lfs_rambd_read(const struct lfs_config *cfg, lfs_block_t block, | ||||||
|   | |||||||
| @@ -35,10 +35,10 @@ typedef struct lfs_rambd { | |||||||
| // Create a RAM block device using the geometry in lfs_config | // Create a RAM block device using the geometry in lfs_config | ||||||
| int lfs_rambd_create(const struct lfs_config *cfg); | int lfs_rambd_create(const struct lfs_config *cfg); | ||||||
| int lfs_rambd_createcfg(const struct lfs_config *cfg, | int lfs_rambd_createcfg(const struct lfs_config *cfg, | ||||||
|         const struct lfs_rambd_config *ramcfg); |         const struct lfs_rambd_config *bdcfg); | ||||||
|  |  | ||||||
| // Clean up memory associated with block device | // Clean up memory associated with block device | ||||||
| void lfs_rambd_destroy(const struct lfs_config *cfg); | int lfs_rambd_destroy(const struct lfs_config *cfg); | ||||||
|  |  | ||||||
| // Read a block | // Read a block | ||||||
| int lfs_rambd_read(const struct lfs_config *cfg, lfs_block_t block, | int lfs_rambd_read(const struct lfs_config *cfg, lfs_block_t block, | ||||||
|   | |||||||
| @@ -146,7 +146,7 @@ def pnested(): | |||||||
|  |  | ||||||
| pexpr = ( | pexpr = ( | ||||||
|     # shortcut for a bit better performance |     # shortcut for a bit better performance | ||||||
|     p.regex('[^%s/#\'"();{}=><,&|-]+' % ASSERT_CHARS) | |     p.regex('[^%s/#\'"():;{}=><,&|-]+' % ASSERT_CHARS) | | ||||||
|     pws | |     pws | | ||||||
|     passert | |     passert | | ||||||
|     pstring | |     pstring | | ||||||
| @@ -157,7 +157,7 @@ pexpr = ( | |||||||
| @p.generate | @p.generate | ||||||
| def pstmt(): | def pstmt(): | ||||||
|     ws = yield pws.many() |     ws = yield pws.many() | ||||||
|     lh = yield pexpr.until(p.string('=>') | p.regex('[;{}]')) |     lh = yield pexpr.until(p.string('=>') | p.regex('[:;{}]')) | ||||||
|     op = yield p.string('=>').optional() |     op = yield p.string('=>').optional() | ||||||
|     if op == '=>': |     if op == '=>': | ||||||
|         rh = yield pstmt |         rh = yield pstmt | ||||||
| @@ -168,7 +168,7 @@ def pstmt(): | |||||||
| @p.generate | @p.generate | ||||||
| def pstmts(): | def pstmts(): | ||||||
|     a = yield pstmt |     a = yield pstmt | ||||||
|     b = yield (p.regex('[;{}]') + pstmt).many() |     b = yield (p.regex('[:;{}]') + pstmt).many() | ||||||
|     return [a] + b |     return [a] + b | ||||||
|  |  | ||||||
| def main(args): | def main(args): | ||||||
|   | |||||||
							
								
								
									
										227
									
								
								scripts/test_.py
									
									
									
									
									
								
							
							
						
						
									
										227
									
								
								scripts/test_.py
									
									
									
									
									
								
							| @@ -19,7 +19,7 @@ | |||||||
| # x config chaining correct | # x config chaining correct | ||||||
| # - why can't gdb see my defines? | # - why can't gdb see my defines? | ||||||
| # - say no to internal? | # - say no to internal? | ||||||
| # - buffering stdout issues? | # x buffering stdout issues? | ||||||
|  |  | ||||||
| import toml | import toml | ||||||
| import glob | import glob | ||||||
| @@ -33,6 +33,9 @@ import base64 | |||||||
| import sys | import sys | ||||||
| import copy | import copy | ||||||
| import shlex | import shlex | ||||||
|  | import pty | ||||||
|  | import errno | ||||||
|  | import signal | ||||||
|  |  | ||||||
| TESTDIR = 'tests_' | TESTDIR = 'tests_' | ||||||
| RULES = """ | RULES = """ | ||||||
| @@ -45,98 +48,31 @@ $(foreach target,$(SRC),$(eval $(FLATTEN))) | |||||||
| -include tests_/*.d | -include tests_/*.d | ||||||
|  |  | ||||||
| .SECONDARY: | .SECONDARY: | ||||||
| %.test: override CFLAGS += -fdiagnostics-color=always | %.test: override CFLAGS += -gdwarf-2 | ||||||
| %.test: override CFLAGS += -ggdb | %.test: override CFLAGS += -ggdb3 | ||||||
|  | %.test: override CFLAGS += -g3 | ||||||
| %.test: %.test.o $(foreach f,$(subst /,.,$(SRC:.c=.o)),%.$f) | %.test: %.test.o $(foreach f,$(subst /,.,$(SRC:.c=.o)),%.$f) | ||||||
|     $(CC) $(CFLAGS) $^ $(LFLAGS) -o $@ |     $(CC) $(CFLAGS) $^ $(LFLAGS) -o $@ | ||||||
| """ | """ | ||||||
| GLOBALS = """ | GLOBALS = """ | ||||||
| //////////////// AUTOGENERATED TEST //////////////// | //////////////// AUTOGENERATED TEST //////////////// | ||||||
| #include "lfs.h" | #include "lfs.h" | ||||||
| #include "filebd/lfs_filebd.h" | #include "testbd/lfs_testbd.h" | ||||||
| #include "rambd/lfs_rambd.h" |  | ||||||
| #include <stdio.h> | #include <stdio.h> | ||||||
|  | extern const char *lfs_testbd_path; | ||||||
| extern const char *lfs_testbd_disk; | extern uint32_t lfs_testbd_cycles; | ||||||
| typedef union { |  | ||||||
|     lfs_filebd_t filebd; |  | ||||||
|     lfs_rambd_t rambd; |  | ||||||
| } lfs_testbd_t;  |  | ||||||
| struct lfs_testbd_config { |  | ||||||
|     struct lfs_filebd_config filecfg; |  | ||||||
|     struct lfs_rambd_config ramcfg; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| __attribute__((unused)) |  | ||||||
| static int lfs_testbd_createcfg(const struct lfs_config *cfg, |  | ||||||
|         const struct lfs_testbd_config *bdcfg) { |  | ||||||
|     if (lfs_testbd_disk) { |  | ||||||
|         return lfs_filebd_createcfg(cfg, lfs_testbd_disk, &bdcfg->filecfg); |  | ||||||
|     } else { |  | ||||||
|         return lfs_rambd_createcfg(cfg, &bdcfg->ramcfg); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| __attribute__((unused)) |  | ||||||
| static void lfs_testbd_destroy(const struct lfs_config *cfg) { |  | ||||||
|     if (lfs_testbd_disk) { |  | ||||||
|         lfs_filebd_destroy(cfg); |  | ||||||
|     } else { |  | ||||||
|         lfs_rambd_destroy(cfg); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| __attribute__((unused)) |  | ||||||
| static int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block, |  | ||||||
|         lfs_off_t off, void *buffer, lfs_size_t size) { |  | ||||||
|     if (lfs_testbd_disk) { |  | ||||||
|         return lfs_filebd_read(cfg, block, off, buffer, size); |  | ||||||
|     } else { |  | ||||||
|         return lfs_rambd_read(cfg, block, off, buffer, size); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| __attribute__((unused)) |  | ||||||
| static int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block, |  | ||||||
|         lfs_off_t off, const void *buffer, lfs_size_t size) { |  | ||||||
|     if (lfs_testbd_disk) { |  | ||||||
|         return lfs_filebd_prog(cfg, block, off, buffer, size); |  | ||||||
|     } else { |  | ||||||
|         return lfs_rambd_prog(cfg, block, off, buffer, size); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| __attribute__((unused)) |  | ||||||
| static int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) { |  | ||||||
|     if (lfs_testbd_disk) { |  | ||||||
|         return lfs_filebd_erase(cfg, block); |  | ||||||
|     } else { |  | ||||||
|         return lfs_rambd_erase(cfg, block); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| __attribute__((unused)) |  | ||||||
| static int lfs_testbd_sync(const struct lfs_config *cfg) { |  | ||||||
|     if (lfs_testbd_disk) { |  | ||||||
|         return lfs_filebd_sync(cfg); |  | ||||||
|     } else { |  | ||||||
|         return lfs_rambd_sync(cfg); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| """ | """ | ||||||
| DEFINES = { | DEFINES = { | ||||||
|     "LFS_BD_READ": "lfs_testbd_read", |     'LFS_READ_SIZE': 16, | ||||||
|     "LFS_BD_PROG": "lfs_testbd_prog", |     'LFS_PROG_SIZE': 'LFS_READ_SIZE', | ||||||
|     "LFS_BD_ERASE": "lfs_testbd_erase", |     'LFS_BLOCK_SIZE': 512, | ||||||
|     "LFS_BD_SYNC": "lfs_testbd_sync", |     'LFS_BLOCK_COUNT': 1024, | ||||||
|     "LFS_READ_SIZE": 16, |     'LFS_BLOCK_CYCLES': -1, | ||||||
|     "LFS_PROG_SIZE": "LFS_READ_SIZE", |     'LFS_CACHE_SIZE': '(64 % LFS_PROG_SIZE == 0 ? 64 : LFS_PROG_SIZE)', | ||||||
|     "LFS_BLOCK_SIZE": 512, |     'LFS_LOOKAHEAD_SIZE': 16, | ||||||
|     "LFS_BLOCK_COUNT": 1024, |     'LFS_ERASE_VALUE': 0xff, | ||||||
|     "LFS_BLOCK_CYCLES": -1, |     'LFS_ERASE_CYCLES': 0, | ||||||
|     "LFS_CACHE_SIZE": "(64 % LFS_PROG_SIZE == 0 ? 64 : LFS_PROG_SIZE)", |     'LFS_BADBLOCK_BEHAVIOR': 'LFS_TESTBD_BADBLOCK_NOPROG', | ||||||
|     "LFS_LOOKAHEAD_SIZE": 16, |  | ||||||
|     "LFS_ERASE_VALUE": 0xff, |  | ||||||
| } | } | ||||||
| PROLOGUE = """ | PROLOGUE = """ | ||||||
|     // prologue |     // prologue | ||||||
| @@ -152,10 +88,10 @@ PROLOGUE = """ | |||||||
|      |      | ||||||
|     __attribute__((unused)) const struct lfs_config cfg = { |     __attribute__((unused)) const struct lfs_config cfg = { | ||||||
|         .context        = &bd, |         .context        = &bd, | ||||||
|         .read           = LFS_BD_READ, |         .read           = lfs_testbd_read, | ||||||
|         .prog           = LFS_BD_PROG, |         .prog           = lfs_testbd_prog, | ||||||
|         .erase          = LFS_BD_ERASE, |         .erase          = lfs_testbd_erase, | ||||||
|         .sync           = LFS_BD_SYNC, |         .sync           = lfs_testbd_sync, | ||||||
|         .read_size      = LFS_READ_SIZE, |         .read_size      = LFS_READ_SIZE, | ||||||
|         .prog_size      = LFS_PROG_SIZE, |         .prog_size      = LFS_PROG_SIZE, | ||||||
|         .block_size     = LFS_BLOCK_SIZE, |         .block_size     = LFS_BLOCK_SIZE, | ||||||
| @@ -166,15 +102,17 @@ PROLOGUE = """ | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     __attribute__((unused)) const struct lfs_testbd_config bdcfg = { |     __attribute__((unused)) const struct lfs_testbd_config bdcfg = { | ||||||
|         .filecfg.erase_value = LFS_ERASE_VALUE, |         .erase_value        = LFS_ERASE_VALUE, | ||||||
|         .ramcfg.erase_value = LFS_ERASE_VALUE, |         .erase_cycles       = LFS_ERASE_CYCLES, | ||||||
|  |         .badblock_behavior  = LFS_BADBLOCK_BEHAVIOR, | ||||||
|  |         .power_cycles       = lfs_testbd_cycles, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     lfs_testbd_createcfg(&cfg, &bdcfg) => 0; |     lfs_testbd_createcfg(&cfg, lfs_testbd_path, &bdcfg) => 0; | ||||||
| """ | """ | ||||||
| EPILOGUE = """ | EPILOGUE = """ | ||||||
|     // epilogue |     // epilogue | ||||||
|     lfs_testbd_destroy(&cfg); |     lfs_testbd_destroy(&cfg) => 0; | ||||||
| """ | """ | ||||||
| PASS = '\033[32m✓\033[0m' | PASS = '\033[32m✓\033[0m' | ||||||
| FAIL = '\033[31m✗\033[0m' | FAIL = '\033[31m✗\033[0m' | ||||||
| @@ -224,15 +162,15 @@ class TestCase: | |||||||
|  |  | ||||||
|     def build(self, f, **_): |     def build(self, f, **_): | ||||||
|         # prologue |         # prologue | ||||||
|         f.write('void test_case%d(%s) {\n' % (self.caseno, ','.join( |         for k, v in sorted(self.defines.items()): | ||||||
|  |             if k not in self.suite.defines: | ||||||
|  |                 f.write('#define %s %s\n' % (k, v)) | ||||||
|  |  | ||||||
|  |         f.write('void test_case%d(%s) {' % (self.caseno, ','.join( | ||||||
|             '\n'+8*' '+'__attribute__((unused)) intmax_t %s' % k |             '\n'+8*' '+'__attribute__((unused)) intmax_t %s' % k | ||||||
|             for k in sorted(self.perms[0].defines) |             for k in sorted(self.perms[0].defines) | ||||||
|             if k not in self.defines))) |             if k not in self.defines))) | ||||||
|  |  | ||||||
|         for k, v in sorted(self.defines.items()): |  | ||||||
|             if k not in self.suite.defines: |  | ||||||
|                 f.write(4*' '+'#define %s %s\n' % (k, v)) |  | ||||||
|  |  | ||||||
|         f.write(PROLOGUE) |         f.write(PROLOGUE) | ||||||
|         f.write('\n') |         f.write('\n') | ||||||
|         f.write(4*' '+'// test case %d\n' % self.caseno) |         f.write(4*' '+'// test case %d\n' % self.caseno) | ||||||
| @@ -243,13 +181,11 @@ class TestCase: | |||||||
|  |  | ||||||
|         # epilogue |         # epilogue | ||||||
|         f.write(EPILOGUE) |         f.write(EPILOGUE) | ||||||
|         f.write('\n') |         f.write('}\n') | ||||||
|  |  | ||||||
|         for k, v in sorted(self.defines.items()): |         for k, v in sorted(self.defines.items()): | ||||||
|             if k not in self.suite.defines: |             if k not in self.suite.defines: | ||||||
|                 f.write(4*' '+'#undef %s\n' % k) |                 f.write('#undef %s\n' % k) | ||||||
|  |  | ||||||
|         f.write('}\n') |  | ||||||
|  |  | ||||||
|     def shouldtest(self, **args): |     def shouldtest(self, **args): | ||||||
|         if (self.filter is not None and |         if (self.filter is not None and | ||||||
| @@ -265,7 +201,8 @@ class TestCase: | |||||||
|         else: |         else: | ||||||
|             return True |             return True | ||||||
|  |  | ||||||
|     def test(self, exec=[], persist=False, gdb=False, failure=None, **args): |     def test(self, exec=[], persist=False, cycles=None, | ||||||
|  |             gdb=False, failure=None, **args): | ||||||
|         # build command |         # build command | ||||||
|         cmd = exec + ['./%s.test' % self.suite.path, |         cmd = exec + ['./%s.test' % self.suite.path, | ||||||
|             repr(self.caseno), repr(self.permno)] |             repr(self.caseno), repr(self.permno)] | ||||||
| @@ -280,6 +217,10 @@ class TestCase: | |||||||
|  |  | ||||||
|             cmd.append(self.suite.path + '.disk') |             cmd.append(self.suite.path + '.disk') | ||||||
|  |  | ||||||
|  |         # simulate power-loss after n cycles? | ||||||
|  |         if cycles: | ||||||
|  |             cmd.append(str(cycles)) | ||||||
|  |  | ||||||
|         # failed? drop into debugger? |         # failed? drop into debugger? | ||||||
|         if gdb and failure: |         if gdb and failure: | ||||||
|             ncmd = ['gdb'] |             ncmd = ['gdb'] | ||||||
| @@ -295,19 +236,25 @@ class TestCase: | |||||||
|  |  | ||||||
|             if args.get('verbose', False): |             if args.get('verbose', False): | ||||||
|                 print(' '.join(shlex.quote(c) for c in ncmd)) |                 print(' '.join(shlex.quote(c) for c in ncmd)) | ||||||
|  |             signal.signal(signal.SIGINT, signal.SIG_IGN) | ||||||
|             sys.exit(sp.call(ncmd)) |             sys.exit(sp.call(ncmd)) | ||||||
|  |  | ||||||
|         # run test case! |         # run test case! | ||||||
|         stdout = [] |         mpty, spty = pty.openpty() | ||||||
|         assert_ = None |  | ||||||
|         if args.get('verbose', False): |         if args.get('verbose', False): | ||||||
|             print(' '.join(shlex.quote(c) for c in cmd)) |             print(' '.join(shlex.quote(c) for c in cmd)) | ||||||
|         proc = sp.Popen(cmd, |         proc = sp.Popen(cmd, stdout=spty, stderr=spty) | ||||||
|             universal_newlines=True, |         os.close(spty) | ||||||
|             bufsize=1, |         mpty = os.fdopen(mpty, 'r', 1) | ||||||
|             stdout=sp.PIPE, |         stdout = [] | ||||||
|             stderr=sp.STDOUT) |         assert_ = None | ||||||
|         for line in iter(proc.stdout.readline, ''): |         while True: | ||||||
|  |             try: | ||||||
|  |                 line = mpty.readline() | ||||||
|  |             except OSError as e: | ||||||
|  |                 if e.errno == errno.EIO: | ||||||
|  |                     break | ||||||
|  |                 raise | ||||||
|             stdout.append(line) |             stdout.append(line) | ||||||
|             if args.get('verbose', False): |             if args.get('verbose', False): | ||||||
|                 sys.stdout.write(line) |                 sys.stdout.write(line) | ||||||
| @@ -361,36 +308,23 @@ class ReentrantTestCase(TestCase): | |||||||
|         return self.reentrant and super().shouldtest(**args) |         return self.reentrant and super().shouldtest(**args) | ||||||
|  |  | ||||||
|     def test(self, exec=[], persist=False, gdb=False, failure=None, **args): |     def test(self, exec=[], persist=False, gdb=False, failure=None, **args): | ||||||
|         # clear disk first? |  | ||||||
|         if persist != 'noerase': |  | ||||||
|             try: |  | ||||||
|                 os.remove(self.suite.path + '.disk') |  | ||||||
|             except FileNotFoundError: |  | ||||||
|                 pass |  | ||||||
|  |  | ||||||
|         for cycles in it.count(1): |         for cycles in it.count(1): | ||||||
|  |             # clear disk first? | ||||||
|  |             if cycles == 1 and persist != 'noerase': | ||||||
|  |                 persist = 'erase' | ||||||
|  |             else: | ||||||
|  |                 persist = 'noerase' | ||||||
|  |  | ||||||
|             # exact cycle we should drop into debugger? |             # exact cycle we should drop into debugger? | ||||||
|             if gdb and failure and failure.cycleno == cycles: |             if gdb and failure and failure.cycleno == cycles: | ||||||
|                 return super().test(exec=exec, persist='noerase', |                 return super().test(gdb=gdb, | ||||||
|                     gdb=gdb, failure=failure, **args) |                     persist=persist, failure=failure, **args) | ||||||
|  |  | ||||||
|             # run tests, but kill the program after prog/erase has |             # run tests, but kill the program after prog/erase has | ||||||
|             # been hit n cycles. We exit with a special return code if the |             # been hit n cycles. We exit with a special return code if the | ||||||
|             # program has not finished, since this isn't a test failure. |             # program has not finished, since this isn't a test failure. | ||||||
|             nexec = exec + [ |  | ||||||
|                 'gdb', '-batch-silent', |  | ||||||
|                 '-ex', 'handle all nostop', |  | ||||||
|                 '-ex', 'b lfs_filebd_prog', |  | ||||||
|                 '-ex', 'b lfs_filebd_erase', |  | ||||||
|                 '-ex', 'r', |  | ||||||
|                 ] + cycles*['-ex', 'c'] + [ |  | ||||||
|                 '-ex', 'q ' |  | ||||||
|                     '!$_isvoid($_exitsignal) ? $_exitsignal : ' |  | ||||||
|                     '!$_isvoid($_exitcode) ? $_exitcode : ' |  | ||||||
|                     '33', |  | ||||||
|                 '--args'] |  | ||||||
|             try: |             try: | ||||||
|                 return super().test(exec=nexec, persist='noerase', **args) |                 return super().test(persist=persist, cycles=cycles, **args) | ||||||
|             except TestFailure as nfailure: |             except TestFailure as nfailure: | ||||||
|                 if nfailure.returncode == 33: |                 if nfailure.returncode == 33: | ||||||
|                     continue |                     continue | ||||||
| @@ -535,11 +469,13 @@ class TestSuite: | |||||||
|             case.build(tfs[case.in_], **args) |             case.build(tfs[case.in_], **args) | ||||||
|  |  | ||||||
|         tf.write('\n') |         tf.write('\n') | ||||||
|         tf.write('const char *lfs_testbd_disk;\n') |         tf.write('const char *lfs_testbd_path;\n') | ||||||
|  |         tf.write('uint32_t lfs_testbd_cycles;\n') | ||||||
|         tf.write('int main(int argc, char **argv) {\n') |         tf.write('int main(int argc, char **argv) {\n') | ||||||
|         tf.write(4*' '+'int case_ = (argc >= 2) ? atoi(argv[1]) : 0;\n') |         tf.write(4*' '+'int case_         = (argc > 1) ? atoi(argv[1]) : 0;\n') | ||||||
|         tf.write(4*' '+'int perm = (argc >= 3) ? atoi(argv[2]) : 0;\n') |         tf.write(4*' '+'int perm          = (argc > 2) ? atoi(argv[2]) : 0;\n') | ||||||
|         tf.write(4*' '+'lfs_testbd_disk = (argc >= 4) ? argv[3] : NULL;\n') |         tf.write(4*' '+'lfs_testbd_path   = (argc > 3) ? argv[3] : NULL;\n') | ||||||
|  |         tf.write(4*' '+'lfs_testbd_cycles = (argc > 4) ? atoi(argv[4]) : 0;\n') | ||||||
|         for perm in self.perms: |         for perm in self.perms: | ||||||
|             # test declaration |             # test declaration | ||||||
|             tf.write(4*' '+'extern void test_case%d(%s);\n' % ( |             tf.write(4*' '+'extern void test_case%d(%s);\n' % ( | ||||||
| @@ -671,15 +607,20 @@ def main(**args): | |||||||
|     cmd = (['make', '-f', 'Makefile'] + |     cmd = (['make', '-f', 'Makefile'] + | ||||||
|         list(it.chain.from_iterable(['-f', m] for m in makefiles)) + |         list(it.chain.from_iterable(['-f', m] for m in makefiles)) + | ||||||
|         [target for target in targets]) |         [target for target in targets]) | ||||||
|     stdout = [] |     mpty, spty = pty.openpty() | ||||||
|     if args.get('verbose', False): |     if args.get('verbose', False): | ||||||
|         print(' '.join(shlex.quote(c) for c in cmd)) |         print(' '.join(shlex.quote(c) for c in cmd)) | ||||||
|     proc = sp.Popen(cmd, |     proc = sp.Popen(cmd, stdout=spty, stderr=spty) | ||||||
|         universal_newlines=True, |     os.close(spty) | ||||||
|         bufsize=1, |     mpty = os.fdopen(mpty, 'r', 1) | ||||||
|         stdout=sp.PIPE, |     stdout = [] | ||||||
|         stderr=sp.STDOUT) |     while True: | ||||||
|     for line in iter(proc.stdout.readline, ''): |         try: | ||||||
|  |             line = mpty.readline() | ||||||
|  |         except OSError as e: | ||||||
|  |             if e.errno == errno.EIO: | ||||||
|  |                 break | ||||||
|  |             raise | ||||||
|         stdout.append(line) |         stdout.append(line) | ||||||
|         if args.get('verbose', False): |         if args.get('verbose', False): | ||||||
|             sys.stdout.write(line) |             sys.stdout.write(line) | ||||||
|   | |||||||
							
								
								
									
										289
									
								
								testbd/lfs_testbd.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								testbd/lfs_testbd.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,289 @@ | |||||||
|  | /* | ||||||
|  |  * Testing block device, wraps filebd and rambd while providing a bunch | ||||||
|  |  * of hooks for testing littlefs in various conditions. | ||||||
|  |  * | ||||||
|  |  * Copyright (c) 2017, Arm Limited. All rights reserved. | ||||||
|  |  * SPDX-License-Identifier: BSD-3-Clause | ||||||
|  |  */ | ||||||
|  | #include "testbd/lfs_testbd.h" | ||||||
|  |  | ||||||
|  | #include <stdlib.h> | ||||||
|  |  | ||||||
|  |  | ||||||
|  | int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path, | ||||||
|  |         const struct lfs_testbd_config *bdcfg) { | ||||||
|  |     LFS_TRACE("lfs_testbd_createcfg(%p {.context=%p, " | ||||||
|  |                 ".read=%p, .prog=%p, .erase=%p, .sync=%p, " | ||||||
|  |                 ".read_size=%"PRIu32", .prog_size=%"PRIu32", " | ||||||
|  |                 ".block_size=%"PRIu32", .block_count=%"PRIu32"}, " | ||||||
|  |                 "\"%s\", " | ||||||
|  |                 "%p {.erase_value=%"PRId32", .erase_cycles=%"PRIu32", " | ||||||
|  |                 ".badblock_behavior=%"PRIu8", .power_cycles=%"PRIu32", " | ||||||
|  |                 ".buffer=%p, .wear_buffer=%p})", | ||||||
|  |             (void*)cfg, cfg->context, | ||||||
|  |             (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, | ||||||
|  |             (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, | ||||||
|  |             cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, | ||||||
|  |             path, (void*)bdcfg, bdcfg->erase_value, bdcfg->erase_cycles, | ||||||
|  |             bdcfg->badblock_behavior, bdcfg->power_cycles, | ||||||
|  |             bdcfg->buffer, bdcfg->wear_buffer); | ||||||
|  |     lfs_testbd_t *bd = cfg->context; | ||||||
|  |     bd->cfg = bdcfg; | ||||||
|  |  | ||||||
|  |     // setup testing things | ||||||
|  |     bd->persist = path; | ||||||
|  |     bd->power_cycles = bd->cfg->power_cycles; | ||||||
|  |  | ||||||
|  |     if (bd->cfg->erase_cycles) { | ||||||
|  |         if (bd->cfg->wear_buffer) { | ||||||
|  |             bd->wear = bd->cfg->wear_buffer; | ||||||
|  |         } else { | ||||||
|  |             bd->wear = lfs_malloc(sizeof(lfs_testbd_wear_t) * cfg->block_count); | ||||||
|  |             if (!bd->wear) { | ||||||
|  |                 LFS_TRACE("lfs_testbd_createcfg -> %d", LFS_ERR_NOMEM); | ||||||
|  |                 return LFS_ERR_NOMEM; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         memset(bd->wear, 0, sizeof(lfs_testbd_wear_t) * cfg->block_count); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // create underlying block device | ||||||
|  |     if (bd->persist) { | ||||||
|  |         bd->u.file.cfg = (struct lfs_filebd_config){ | ||||||
|  |             .erase_value = bd->cfg->erase_value, | ||||||
|  |         }; | ||||||
|  |         int err = lfs_filebd_createcfg(cfg, path, &bd->u.file.cfg); | ||||||
|  |         LFS_TRACE("lfs_testbd_createcfg -> %d", err); | ||||||
|  |         return err; | ||||||
|  |     } else { | ||||||
|  |         bd->u.ram.cfg = (struct lfs_rambd_config){ | ||||||
|  |             .erase_value = bd->cfg->erase_value, | ||||||
|  |             .buffer = bd->cfg->buffer, | ||||||
|  |         }; | ||||||
|  |         int err = lfs_rambd_createcfg(cfg, &bd->u.ram.cfg); | ||||||
|  |         LFS_TRACE("lfs_testbd_createcfg -> %d", err); | ||||||
|  |         return err; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int lfs_testbd_create(const struct lfs_config *cfg, const char *path) { | ||||||
|  |     LFS_TRACE("lfs_testbd_create(%p {.context=%p, " | ||||||
|  |                 ".read=%p, .prog=%p, .erase=%p, .sync=%p, " | ||||||
|  |                 ".read_size=%"PRIu32", .prog_size=%"PRIu32", " | ||||||
|  |                 ".block_size=%"PRIu32", .block_count=%"PRIu32"}, " | ||||||
|  |                 "\"%s\")", | ||||||
|  |             (void*)cfg, cfg->context, | ||||||
|  |             (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, | ||||||
|  |             (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, | ||||||
|  |             cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, | ||||||
|  |             path); | ||||||
|  |     static const struct lfs_testbd_config defaults = {.erase_value=-1}; | ||||||
|  |     int err = lfs_testbd_createcfg(cfg, path, &defaults); | ||||||
|  |     LFS_TRACE("lfs_testbd_create -> %d", err); | ||||||
|  |     return err; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int lfs_testbd_destroy(const struct lfs_config *cfg) { | ||||||
|  |     LFS_TRACE("lfs_testbd_destroy(%p)", (void*)cfg); | ||||||
|  |     lfs_testbd_t *bd = cfg->context; | ||||||
|  |     if (bd->cfg->erase_cycles && !bd->cfg->wear_buffer) { | ||||||
|  |         lfs_free(bd->wear); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (bd->persist) { | ||||||
|  |         int err = lfs_filebd_destroy(cfg); | ||||||
|  |         LFS_TRACE("lfs_testbd_destroy -> %d", err); | ||||||
|  |         return err; | ||||||
|  |     } else { | ||||||
|  |         int err = lfs_rambd_destroy(cfg); | ||||||
|  |         LFS_TRACE("lfs_testbd_destroy -> %d", err); | ||||||
|  |         return err; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Internal mapping to block devices /// | ||||||
|  | static int lfs_testbd_rawread(const struct lfs_config *cfg, lfs_block_t block, | ||||||
|  |         lfs_off_t off, void *buffer, lfs_size_t size) { | ||||||
|  |     lfs_testbd_t *bd = cfg->context; | ||||||
|  |     if (bd->persist) { | ||||||
|  |         return lfs_filebd_read(cfg, block, off, buffer, size); | ||||||
|  |     } else { | ||||||
|  |         return lfs_rambd_read(cfg, block, off, buffer, size); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static int lfs_testbd_rawprog(const struct lfs_config *cfg, lfs_block_t block, | ||||||
|  |         lfs_off_t off, const void *buffer, lfs_size_t size) { | ||||||
|  |     lfs_testbd_t *bd = cfg->context; | ||||||
|  |     if (bd->persist) { | ||||||
|  |         return lfs_filebd_prog(cfg, block, off, buffer, size); | ||||||
|  |     } else { | ||||||
|  |         return lfs_rambd_prog(cfg, block, off, buffer, size); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static int lfs_testbd_rawerase(const struct lfs_config *cfg, | ||||||
|  |         lfs_block_t block) { | ||||||
|  |     lfs_testbd_t *bd = cfg->context; | ||||||
|  |     if (bd->persist) { | ||||||
|  |         return lfs_filebd_erase(cfg, block); | ||||||
|  |     } else { | ||||||
|  |         return lfs_rambd_erase(cfg, block); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static int lfs_testbd_rawsync(const struct lfs_config *cfg) { | ||||||
|  |     lfs_testbd_t *bd = cfg->context; | ||||||
|  |     if (bd->persist) { | ||||||
|  |         return lfs_filebd_sync(cfg); | ||||||
|  |     } else { | ||||||
|  |         return lfs_rambd_sync(cfg); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// block device API /// | ||||||
|  | int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block, | ||||||
|  |         lfs_off_t off, void *buffer, lfs_size_t size) { | ||||||
|  |     LFS_TRACE("lfs_testbd_read(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", | ||||||
|  |             (void*)cfg, block, off, buffer, size); | ||||||
|  |     lfs_testbd_t *bd = cfg->context; | ||||||
|  |  | ||||||
|  |     // check if read is valid | ||||||
|  |     LFS_ASSERT(off  % cfg->read_size == 0); | ||||||
|  |     LFS_ASSERT(size % cfg->read_size == 0); | ||||||
|  |     LFS_ASSERT(block < cfg->block_count); | ||||||
|  |  | ||||||
|  |     // block bad? | ||||||
|  |     if (bd->cfg->erase_cycles && | ||||||
|  |             bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_NOREAD && | ||||||
|  |             bd->wear[block] >= bd->cfg->erase_cycles) { | ||||||
|  |         LFS_TRACE("lfs_testbd_read -> %d", LFS_ERR_CORRUPT); | ||||||
|  |         return LFS_ERR_CORRUPT; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // read | ||||||
|  |     int err = lfs_testbd_rawread(cfg, block, off, buffer, size); | ||||||
|  |     LFS_TRACE("lfs_testbd_read -> %d", err); | ||||||
|  |     return err; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block, | ||||||
|  |         lfs_off_t off, const void *buffer, lfs_size_t size) { | ||||||
|  |     LFS_TRACE("lfs_testbd_prog(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", | ||||||
|  |             (void*)cfg, block, off, buffer, size); | ||||||
|  |     lfs_testbd_t *bd = cfg->context; | ||||||
|  |  | ||||||
|  |     // check if write is valid | ||||||
|  |     LFS_ASSERT(off  % cfg->prog_size == 0); | ||||||
|  |     LFS_ASSERT(size % cfg->prog_size == 0); | ||||||
|  |     LFS_ASSERT(block < cfg->block_count); | ||||||
|  |  | ||||||
|  |     // block bad? | ||||||
|  |     if (bd->cfg->erase_cycles && | ||||||
|  |             bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_NOPROG && | ||||||
|  |             bd->wear[block] >= bd->cfg->erase_cycles) { | ||||||
|  |         LFS_TRACE("lfs_testbd_prog -> %d", LFS_ERR_CORRUPT); | ||||||
|  |         return LFS_ERR_CORRUPT; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // prog | ||||||
|  |     int err = lfs_testbd_rawprog(cfg, block, off, buffer, size); | ||||||
|  |     if (err) { | ||||||
|  |         LFS_TRACE("lfs_testbd_prog -> %d", err); | ||||||
|  |         return err; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // lose power? | ||||||
|  |     if (bd->power_cycles > 0) { | ||||||
|  |         bd->power_cycles -= 1; | ||||||
|  |         if (bd->power_cycles == 0) { | ||||||
|  |             // sync to make sure we persist the last changes | ||||||
|  |             lfs_testbd_rawsync(cfg); | ||||||
|  |             // simulate power loss | ||||||
|  |             exit(33); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     LFS_TRACE("lfs_testbd_prog -> %d", 0); | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) { | ||||||
|  |     LFS_TRACE("lfs_testbd_erase(%p, 0x%"PRIx32")", (void*)cfg, block); | ||||||
|  |     lfs_testbd_t *bd = cfg->context; | ||||||
|  |  | ||||||
|  |     // check if erase is valid | ||||||
|  |     LFS_ASSERT(block < cfg->block_count); | ||||||
|  |  | ||||||
|  |     // block bad? | ||||||
|  |     if (bd->cfg->erase_cycles) { | ||||||
|  |         if (bd->wear[block] >= bd->cfg->erase_cycles) { | ||||||
|  |             if (bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_NOERASE) { | ||||||
|  |                 LFS_TRACE("lfs_testbd_erase -> %d", LFS_ERR_CORRUPT); | ||||||
|  |                 return LFS_ERR_CORRUPT; | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             // mark wear | ||||||
|  |             bd->wear[block] += 1; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // erase | ||||||
|  |     int err = lfs_testbd_rawerase(cfg, block); | ||||||
|  |     if (err) { | ||||||
|  |         LFS_TRACE("lfs_testbd_erase -> %d", err); | ||||||
|  |         return err; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // lose power? | ||||||
|  |     if (bd->power_cycles > 0) { | ||||||
|  |         bd->power_cycles -= 1; | ||||||
|  |         if (bd->power_cycles == 0) { | ||||||
|  |             // sync to make sure we persist the last changes | ||||||
|  |             lfs_testbd_rawsync(cfg); | ||||||
|  |             // simulate power loss | ||||||
|  |             exit(33); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     LFS_TRACE("lfs_testbd_prog -> %d", 0); | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int lfs_testbd_sync(const struct lfs_config *cfg) { | ||||||
|  |     LFS_TRACE("lfs_testbd_sync(%p)", (void*)cfg); | ||||||
|  |     int err = lfs_testbd_rawsync(cfg); | ||||||
|  |     LFS_TRACE("lfs_testbd_sync -> %d", err); | ||||||
|  |     return err; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// simulated wear operations /// | ||||||
|  | lfs_testbd_swear_t lfs_testbd_getwear(const struct lfs_config *cfg, | ||||||
|  |         lfs_block_t block) { | ||||||
|  |     LFS_TRACE("lfs_testbd_getwear(%p, %"PRIu32")", (void*)cfg, block); | ||||||
|  |     lfs_testbd_t *bd = cfg->context; | ||||||
|  |  | ||||||
|  |     // check if block is valid | ||||||
|  |     LFS_ASSERT(bd->cfg->erase_cycles); | ||||||
|  |     LFS_ASSERT(block < cfg->block_count); | ||||||
|  |  | ||||||
|  |     LFS_TRACE("lfs_testbd_getwear -> %"PRIu32, bd->wear[block]); | ||||||
|  |     return bd->wear[block]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int lfs_testbd_setwear(const struct lfs_config *cfg, | ||||||
|  |         lfs_block_t block, lfs_testbd_wear_t wear) { | ||||||
|  |     LFS_TRACE("lfs_testbd_setwear(%p, %"PRIu32")", (void*)cfg, block); | ||||||
|  |     lfs_testbd_t *bd = cfg->context; | ||||||
|  |  | ||||||
|  |     // check if block is valid | ||||||
|  |     LFS_ASSERT(bd->cfg->erase_cycles); | ||||||
|  |     LFS_ASSERT(block < cfg->block_count); | ||||||
|  |  | ||||||
|  |     bd->wear[block] = wear; | ||||||
|  |  | ||||||
|  |     LFS_TRACE("lfs_testbd_setwear -> %d", 0); | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
							
								
								
									
										130
									
								
								testbd/lfs_testbd.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								testbd/lfs_testbd.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | |||||||
|  | /* | ||||||
|  |  * Testing block device, wraps filebd and rambd while providing a bunch | ||||||
|  |  * of hooks for testing littlefs in various conditions. | ||||||
|  |  * | ||||||
|  |  * Copyright (c) 2017, Arm Limited. All rights reserved. | ||||||
|  |  * SPDX-License-Identifier: BSD-3-Clause | ||||||
|  |  */ | ||||||
|  | #ifndef LFS_TESTBD_H | ||||||
|  | #define LFS_TESTBD_H | ||||||
|  |  | ||||||
|  | #include "lfs.h" | ||||||
|  | #include "lfs_util.h" | ||||||
|  | #include "rambd/lfs_rambd.h" | ||||||
|  | #include "filebd/lfs_filebd.h" | ||||||
|  |  | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" | ||||||
|  | { | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  | ||||||
|  | // Mode determining how "bad blocks" behave during testing. This | ||||||
|  | // simulates some real-world circumstances such as writes not | ||||||
|  | // going through (noprog), erases not sticking (noerase), and ECC | ||||||
|  | // failures (noread). | ||||||
|  | enum lfs_testbd_badblock_behavior { | ||||||
|  |     LFS_TESTBD_BADBLOCK_NOPROG  = 0, | ||||||
|  |     LFS_TESTBD_BADBLOCK_NOERASE = 1, | ||||||
|  |     LFS_TESTBD_BADBLOCK_NOREAD  = 2, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // Type for measuring wear | ||||||
|  | typedef uint32_t lfs_testbd_wear_t; | ||||||
|  | typedef int32_t  lfs_testbd_swear_t; | ||||||
|  |  | ||||||
|  | // testbd config, this is required for testing | ||||||
|  | struct lfs_testbd_config { | ||||||
|  |     // 8-bit erase value to use for simulating erases. -1 does not simulate | ||||||
|  |     // erases, which can speed up testing by avoiding all the extra block-device | ||||||
|  |     // operations to store the erase value. | ||||||
|  |     int32_t erase_value; | ||||||
|  |  | ||||||
|  |     // Number of erase cycles before a block becomes "bad". The exact behavior | ||||||
|  |     // of bad blocks is controlled by the badblock_mode. | ||||||
|  |     uint32_t erase_cycles; | ||||||
|  |  | ||||||
|  |     // The mode determining how bad blocks fail | ||||||
|  |     uint8_t badblock_behavior; | ||||||
|  |  | ||||||
|  |     // Number of write operations (erase/prog) before forcefully killing | ||||||
|  |     // the program with exit. Simulates power-loss. 0 disables. | ||||||
|  |     uint32_t power_cycles; | ||||||
|  |  | ||||||
|  |     // Optional buffer for RAM block device. | ||||||
|  |     void *buffer; | ||||||
|  |  | ||||||
|  |     // Optional buffer for wear | ||||||
|  |     void *wear_buffer; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // testbd state | ||||||
|  | typedef struct lfs_testbd { | ||||||
|  |     union { | ||||||
|  |         struct { | ||||||
|  |             lfs_filebd_t bd; | ||||||
|  |             struct lfs_filebd_config cfg; | ||||||
|  |         } file; | ||||||
|  |         struct { | ||||||
|  |             lfs_rambd_t bd; | ||||||
|  |             struct lfs_rambd_config cfg; | ||||||
|  |         } ram; | ||||||
|  |     } u; | ||||||
|  |  | ||||||
|  |     bool persist; | ||||||
|  |     uint32_t power_cycles; | ||||||
|  |     lfs_testbd_wear_t *wear; | ||||||
|  |  | ||||||
|  |     const struct lfs_testbd_config *cfg; | ||||||
|  | } lfs_testbd_t; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// Block device API /// | ||||||
|  |  | ||||||
|  | // Create a test block device using the geometry in lfs_config | ||||||
|  | //  | ||||||
|  | // Note that filebd is used if a path is provided, if path is NULL | ||||||
|  | // testbd will use rambd which can be much faster. | ||||||
|  | int lfs_testbd_create(const struct lfs_config *cfg, const char *path); | ||||||
|  | int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path, | ||||||
|  |         const struct lfs_testbd_config *bdcfg); | ||||||
|  |  | ||||||
|  | // Clean up memory associated with block device | ||||||
|  | int lfs_testbd_destroy(const struct lfs_config *cfg); | ||||||
|  |  | ||||||
|  | // Read a block | ||||||
|  | int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block, | ||||||
|  |         lfs_off_t off, void *buffer, lfs_size_t size); | ||||||
|  |  | ||||||
|  | // Program a block | ||||||
|  | // | ||||||
|  | // The block must have previously been erased. | ||||||
|  | int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block, | ||||||
|  |         lfs_off_t off, const void *buffer, lfs_size_t size); | ||||||
|  |  | ||||||
|  | // Erase a block | ||||||
|  | // | ||||||
|  | // A block must be erased before being programmed. The | ||||||
|  | // state of an erased block is undefined. | ||||||
|  | int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block); | ||||||
|  |  | ||||||
|  | // Sync the block device | ||||||
|  | int lfs_testbd_sync(const struct lfs_config *cfg); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// Additional extended API for driving test features /// | ||||||
|  |  | ||||||
|  | // Get simulated wear on a given block | ||||||
|  | lfs_testbd_swear_t lfs_testbd_getwear(const struct lfs_config *cfg, | ||||||
|  |         lfs_block_t block); | ||||||
|  |  | ||||||
|  | // Manually set simulated wear on a given block | ||||||
|  | int lfs_testbd_setwear(const struct lfs_config *cfg, | ||||||
|  |         lfs_block_t block, lfs_testbd_wear_t wear); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } /* extern "C" */ | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #endif | ||||||
| @@ -503,6 +503,8 @@ code = ''' | |||||||
|         lfs_file_write(&lfs, &file, buffer, size) => size; |         lfs_file_write(&lfs, &file, buffer, size) => size; | ||||||
|     } |     } | ||||||
|     lfs_file_close(&lfs, &file) => 0; |     lfs_file_close(&lfs, &file) => 0; | ||||||
|  |  | ||||||
|  |     lfs_unmount(&lfs) => 0; | ||||||
| ''' | ''' | ||||||
|  |  | ||||||
| [[case]] # outdated lookahead and split dir test | [[case]] # outdated lookahead and split dir test | ||||||
|   | |||||||
| @@ -85,8 +85,8 @@ code = ''' | |||||||
|     lfs_file_write(&lfs, &file, "hello", strlen("hello")) => strlen("hello"); |     lfs_file_write(&lfs, &file, "hello", strlen("hello")) => strlen("hello"); | ||||||
|     lfs_file_close(&lfs, &file); |     lfs_file_close(&lfs, &file); | ||||||
|     lfs_unmount(&lfs) => 0; |     lfs_unmount(&lfs) => 0; | ||||||
|     lfs_mount(&lfs, &cfg) => 0; |  | ||||||
|  |  | ||||||
|  |     lfs_mount(&lfs, &cfg) => 0; | ||||||
|     memset(buffer, 0, sizeof(buffer)); |     memset(buffer, 0, sizeof(buffer)); | ||||||
|     lfs_setattr(&lfs, "/", 'A', "aaaa",   4) => 0; |     lfs_setattr(&lfs, "/", 'A', "aaaa",   4) => 0; | ||||||
|     lfs_setattr(&lfs, "/", 'B', "bbbbbb", 6) => 0; |     lfs_setattr(&lfs, "/", 'B', "bbbbbb", 6) => 0; | ||||||
| @@ -162,7 +162,6 @@ code = ''' | |||||||
|     lfs_file_write(&lfs, &file, "hello", strlen("hello")) => strlen("hello"); |     lfs_file_write(&lfs, &file, "hello", strlen("hello")) => strlen("hello"); | ||||||
|     lfs_file_close(&lfs, &file); |     lfs_file_close(&lfs, &file); | ||||||
|     lfs_unmount(&lfs) => 0; |     lfs_unmount(&lfs) => 0; | ||||||
|     lfs_mount(&lfs, &cfg) => 0; |  | ||||||
|  |  | ||||||
|     lfs_mount(&lfs, &cfg) => 0; |     lfs_mount(&lfs, &cfg) => 0; | ||||||
|     memset(buffer, 0, sizeof(buffer)); |     memset(buffer, 0, sizeof(buffer)); | ||||||
|   | |||||||
| @@ -1,50 +1,12 @@ | |||||||
| # special bad-block block device hook for simulating blocks |  | ||||||
| # that are no longer writable |  | ||||||
| code = ''' |  | ||||||
| lfs_block_t *bbbd_badblocks; |  | ||||||
| size_t bbbd_badblocks_count; |  | ||||||
|  |  | ||||||
| int bbbd_read(const struct lfs_config *c, lfs_block_t block, |  | ||||||
|         lfs_off_t off, void *buffer, lfs_size_t size) { |  | ||||||
|     for (size_t i = 0; i < bbbd_badblocks_count; i++) { |  | ||||||
|         if (bbbd_badblocks[i] == block) { |  | ||||||
|             return LFS_ERR_CORRUPT; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return lfs_testbd_read(c, block, off, buffer, size); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int bbbd_prog(const struct lfs_config *c, lfs_block_t block, |  | ||||||
|         lfs_off_t off, const void *buffer, lfs_size_t size) { |  | ||||||
|     for (size_t i = 0; i < bbbd_badblocks_count; i++) { |  | ||||||
|         if (bbbd_badblocks[i] == block) { |  | ||||||
|             return LFS_ERR_CORRUPT; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return lfs_testbd_prog(c, block, off, buffer, size); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int bbbd_erase(const struct lfs_config *c, lfs_block_t block) { |  | ||||||
|     for (size_t i = 0; i < bbbd_badblocks_count; i++) { |  | ||||||
|         if (bbbd_badblocks[i] == block) { |  | ||||||
|             return LFS_ERR_CORRUPT; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return lfs_testbd_erase(c, block); |  | ||||||
| } |  | ||||||
| ''' |  | ||||||
|  |  | ||||||
| [[case]] # single bad blocks | [[case]] # single bad blocks | ||||||
| define.LFS_BD_PROG = 'bbbd_prog' | define.LFS_ERASE_CYCLES = 0xffffffff | ||||||
|  | define.LFS_BADBLOCK_BEHAVIOR = 'LFS_TESTBD_BADBLOCK_NOPROG' | ||||||
| define.NAMEMULT = 64 | define.NAMEMULT = 64 | ||||||
| define.FILEMULT = 1 | define.FILEMULT = 1 | ||||||
| code = ''' | code = ''' | ||||||
|     for (lfs_block_t badblock = 2; badblock < LFS_BLOCK_COUNT; badblock++) { |     for (lfs_block_t badblock = 2; badblock < LFS_BLOCK_COUNT; badblock++) { | ||||||
|         bbbd_badblocks = &(lfs_block_t){badblock}; |         lfs_testbd_setwear(&cfg, badblock-1, 0) => 0; | ||||||
|         bbbd_badblocks_count = 1; |         lfs_testbd_setwear(&cfg, badblock, 0xffffffff) => 0; | ||||||
|          |          | ||||||
|         lfs_format(&lfs, &cfg) => 0; |         lfs_format(&lfs, &cfg) => 0; | ||||||
|  |  | ||||||
| @@ -103,13 +65,14 @@ code = ''' | |||||||
| ''' | ''' | ||||||
|  |  | ||||||
| [[case]] # single persistent blocks (can't erase) | [[case]] # single persistent blocks (can't erase) | ||||||
| define.LFS_BD_ERASE = 'bbbd_erase' | define.LFS_ERASE_CYCLES = 0xffffffff | ||||||
|  | define.LFS_BADBLOCK_BEHAVIOR = 'LFS_TESTBD_BADBLOCK_NOERASE' | ||||||
| define.NAMEMULT = 64 | define.NAMEMULT = 64 | ||||||
| define.FILEMULT = 1 | define.FILEMULT = 1 | ||||||
| code = ''' | code = ''' | ||||||
|     for (lfs_block_t badblock = 2; badblock < LFS_BLOCK_COUNT; badblock++) { |     for (lfs_block_t badblock = 2; badblock < LFS_BLOCK_COUNT; badblock++) { | ||||||
|         bbbd_badblocks = &(lfs_block_t){badblock}; |         lfs_testbd_setwear(&cfg, badblock-1, 0) => 0; | ||||||
|         bbbd_badblocks_count = 1; |         lfs_testbd_setwear(&cfg, badblock, 0xffffffff) => 0; | ||||||
|          |          | ||||||
|         lfs_format(&lfs, &cfg) => 0; |         lfs_format(&lfs, &cfg) => 0; | ||||||
|  |  | ||||||
| @@ -168,13 +131,14 @@ code = ''' | |||||||
| ''' | ''' | ||||||
|  |  | ||||||
| [[case]] # single unreadable blocks (can't read) | [[case]] # single unreadable blocks (can't read) | ||||||
| define.LFS_BD_READ = 'bbbd_read' | define.LFS_ERASE_CYCLES = 0xffffffff | ||||||
|  | define.LFS_BADBLOCK_BEHAVIOR = 'LFS_TESTBD_BADBLOCK_NOREAD' | ||||||
| define.NAMEMULT = 64 | define.NAMEMULT = 64 | ||||||
| define.FILEMULT = 1 | define.FILEMULT = 1 | ||||||
| code = ''' | code = ''' | ||||||
|     for (lfs_block_t badblock = 2; badblock < LFS_BLOCK_COUNT; badblock++) { |     for (lfs_block_t badblock = 2; badblock < LFS_BLOCK_COUNT/2; badblock++) { | ||||||
|         bbbd_badblocks = &(lfs_block_t){badblock}; |         lfs_testbd_setwear(&cfg, badblock-1, 0) => 0; | ||||||
|         bbbd_badblocks_count = 1; |         lfs_testbd_setwear(&cfg, badblock, 0xffffffff) => 0; | ||||||
|          |          | ||||||
|         lfs_format(&lfs, &cfg) => 0; |         lfs_format(&lfs, &cfg) => 0; | ||||||
|  |  | ||||||
| @@ -233,17 +197,17 @@ code = ''' | |||||||
| ''' | ''' | ||||||
|  |  | ||||||
| [[case]] # region corruption (causes cascading failures) | [[case]] # region corruption (causes cascading failures) | ||||||
| define.LFS_BD_PROG  = '"BADTYPE == 0 ? bbbd_prog  : lfs_testbd_prog "' | define.LFS_ERASE_CYCLES = 0xffffffff | ||||||
| define.LFS_BD_ERASE = '"BADTYPE == 1 ? bbbd_erase : lfs_testbd_erase"' | define.LFS_BADBLOCK_BEHAVIOR = [ | ||||||
| define.LFS_BD_READ  = '"BADTYPE == 2 ? bbbd_read  : lfs_testbd_read "' |     'LFS_TESTBD_BADBLOCK_NOPROG', | ||||||
| define.BADTYPE = 'range(3)' |     'LFS_TESTBD_BADBLOCK_NOERASE', | ||||||
|  |     'LFS_TESTBD_BADBLOCK_NOREAD', | ||||||
|  | ] | ||||||
| define.NAMEMULT = 64 | define.NAMEMULT = 64 | ||||||
| define.FILEMULT = 1 | define.FILEMULT = 1 | ||||||
| code = ''' | code = ''' | ||||||
|     bbbd_badblocks_count = LFS_BLOCK_COUNT/2; |     for (lfs_block_t i = 0; i < (LFS_BLOCK_COUNT-2)/2; i++) { | ||||||
|     bbbd_badblocks = malloc(bbbd_badblocks_count*sizeof(lfs_block_t)); |         lfs_testbd_setwear(&cfg, i+2, 0xffffffff) => 0; | ||||||
|     for (size_t i = 0; i < bbbd_badblocks_count; i++) { |  | ||||||
|         bbbd_badblocks[i] = i+2; |  | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     lfs_format(&lfs, &cfg) => 0; |     lfs_format(&lfs, &cfg) => 0; | ||||||
| @@ -299,22 +263,20 @@ code = ''' | |||||||
|         lfs_file_close(&lfs, &file) => 0; |         lfs_file_close(&lfs, &file) => 0; | ||||||
|     } |     } | ||||||
|     lfs_unmount(&lfs) => 0; |     lfs_unmount(&lfs) => 0; | ||||||
|  |  | ||||||
|     free(bbbd_badblocks); |  | ||||||
| ''' | ''' | ||||||
|  |  | ||||||
| [[case]] # alternating corruption (causes cascading failures) | [[case]] # alternating corruption (causes cascading failures) | ||||||
| define.LFS_BD_PROG  = '"BADTYPE == 0 ? bbbd_prog  : lfs_testbd_prog "' | define.LFS_ERASE_CYCLES = 0xffffffff | ||||||
| define.LFS_BD_ERASE = '"BADTYPE == 1 ? bbbd_erase : lfs_testbd_erase"' | define.LFS_BADBLOCK_BEHAVIOR = [ | ||||||
| define.LFS_BD_READ  = '"BADTYPE == 2 ? bbbd_read  : lfs_testbd_read "' |     'LFS_TESTBD_BADBLOCK_NOPROG', | ||||||
| define.BADTYPE = 'range(3)' |     'LFS_TESTBD_BADBLOCK_NOERASE', | ||||||
|  |     'LFS_TESTBD_BADBLOCK_NOREAD', | ||||||
|  | ] | ||||||
| define.NAMEMULT = 64 | define.NAMEMULT = 64 | ||||||
| define.FILEMULT = 1 | define.FILEMULT = 1 | ||||||
| code = ''' | code = ''' | ||||||
|     bbbd_badblocks_count = LFS_BLOCK_COUNT/2; |     for (lfs_block_t i = 0; i < (LFS_BLOCK_COUNT-2)/2; i++) { | ||||||
|     bbbd_badblocks = malloc(bbbd_badblocks_count*sizeof(lfs_block_t)); |         lfs_testbd_setwear(&cfg, (2*i) + 2, 0xffffffff) => 0; | ||||||
|     for (size_t i = 0; i < bbbd_badblocks_count; i++) { |  | ||||||
|         bbbd_badblocks[i] = (2*i) + 2; |  | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     lfs_format(&lfs, &cfg) => 0; |     lfs_format(&lfs, &cfg) => 0; | ||||||
| @@ -370,6 +332,20 @@ code = ''' | |||||||
|         lfs_file_close(&lfs, &file) => 0; |         lfs_file_close(&lfs, &file) => 0; | ||||||
|     } |     } | ||||||
|     lfs_unmount(&lfs) => 0; |     lfs_unmount(&lfs) => 0; | ||||||
|  | ''' | ||||||
|     free(bbbd_badblocks); |  | ||||||
|  | # other corner cases | ||||||
|  | [[case]] # bad superblocks (corrupt 1 or 0) | ||||||
|  | define.LFS_ERASE_CYCLES = 0xffffffff | ||||||
|  | define.LFS_BADBLOCK_BEHAVIOR = [ | ||||||
|  |     'LFS_TESTBD_BADBLOCK_NOPROG', | ||||||
|  |     'LFS_TESTBD_BADBLOCK_NOERASE', | ||||||
|  |     'LFS_TESTBD_BADBLOCK_NOREAD', | ||||||
|  | ] | ||||||
|  | code = ''' | ||||||
|  |     lfs_testbd_setwear(&cfg, 0, 0xffffffff) => 0; | ||||||
|  |     lfs_testbd_setwear(&cfg, 1, 0xffffffff) => 0; | ||||||
|  |  | ||||||
|  |     lfs_format(&lfs, &cfg) => LFS_ERR_NOSPC; | ||||||
|  |     lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT; | ||||||
| ''' | ''' | ||||||
|   | |||||||
							
								
								
									
										341
									
								
								tests_/test_exhaustion.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										341
									
								
								tests_/test_exhaustion.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,341 @@ | |||||||
|  | [[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); | ||||||
|  | ''' | ||||||
| @@ -26,8 +26,6 @@ code = ''' | |||||||
|     lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT; |     lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT; | ||||||
| ''' | ''' | ||||||
|  |  | ||||||
| # TODO invalid superblock? (corrupt 1, 0) |  | ||||||
|  |  | ||||||
| [[case]] # expanding superblock | [[case]] # expanding superblock | ||||||
| define.BLOCK_CYCLES = [32, 33, 1] | define.BLOCK_CYCLES = [32, 33, 1] | ||||||
| define.N = [10, 100, 1000] | define.N = [10, 100, 1000] | ||||||
|   | |||||||
							
								
								
									
										143
									
								
								tests_/test_relocations.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								tests_/test_relocations.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | |||||||
|  | # specific corner cases worth explicitly testing for | ||||||
|  |  | ||||||
|  | [[case]] # dangling split dir test | ||||||
|  | define.ITERATIONS = 20 | ||||||
|  | define.COUNT = 10 | ||||||
|  | code = ''' | ||||||
|  |     lfs_format(&lfs, &cfg) => 0; | ||||||
|  |     // fill up filesystem so only ~16 blocks are left | ||||||
|  |     lfs_mount(&lfs, &cfg) => 0; | ||||||
|  |     lfs_file_open(&lfs, &file, "padding", LFS_O_CREAT | LFS_O_WRONLY) => 0; | ||||||
|  |     memset(buffer, 0, 512); | ||||||
|  |     while (LFS_BLOCK_COUNT - lfs_fs_size(&lfs) > 16) { | ||||||
|  |         lfs_file_write(&lfs, &file, buffer, 512) => 512; | ||||||
|  |     } | ||||||
|  |     lfs_file_close(&lfs, &file) => 0; | ||||||
|  |     // make a child dir to use in bounded space | ||||||
|  |     lfs_mkdir(&lfs, "child") => 0; | ||||||
|  |     lfs_unmount(&lfs) => 0; | ||||||
|  |  | ||||||
|  |     lfs_mount(&lfs, &cfg) => 0; | ||||||
|  |     for (int j = 0; j < ITERATIONS; j++) { | ||||||
|  |         for (int i = 0; i < COUNT; i++) { | ||||||
|  |             sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); | ||||||
|  |             lfs_file_open(&lfs, &file, path, LFS_O_CREAT | LFS_O_WRONLY) => 0; | ||||||
|  |             lfs_file_close(&lfs, &file) => 0; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         lfs_dir_open(&lfs, &dir, "child") => 0; | ||||||
|  |         lfs_dir_read(&lfs, &dir, &info) => 1; | ||||||
|  |         lfs_dir_read(&lfs, &dir, &info) => 1; | ||||||
|  |         for (int i = 0; i < COUNT; i++) { | ||||||
|  |             sprintf(path, "test%03d_loooooooooooooooooong_name", i); | ||||||
|  |             lfs_dir_read(&lfs, &dir, &info) => 1; | ||||||
|  |             strcmp(info.name, path) => 0; | ||||||
|  |         } | ||||||
|  |         lfs_dir_read(&lfs, &dir, &info) => 0; | ||||||
|  |         lfs_dir_close(&lfs, &dir) => 0; | ||||||
|  |  | ||||||
|  |         if (j == ITERATIONS-1) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         for (int i = 0; i < COUNT; i++) { | ||||||
|  |             sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); | ||||||
|  |             lfs_remove(&lfs, path) => 0; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     lfs_unmount(&lfs) => 0; | ||||||
|  |  | ||||||
|  |     lfs_mount(&lfs, &cfg) => 0; | ||||||
|  |     lfs_dir_open(&lfs, &dir, "child") => 0; | ||||||
|  |     lfs_dir_read(&lfs, &dir, &info) => 1; | ||||||
|  |     lfs_dir_read(&lfs, &dir, &info) => 1; | ||||||
|  |     for (int i = 0; i < COUNT; i++) { | ||||||
|  |         sprintf(path, "test%03d_loooooooooooooooooong_name", i); | ||||||
|  |         lfs_dir_read(&lfs, &dir, &info) => 1; | ||||||
|  |         strcmp(info.name, path) => 0; | ||||||
|  |     } | ||||||
|  |     lfs_dir_read(&lfs, &dir, &info) => 0; | ||||||
|  |     lfs_dir_close(&lfs, &dir) => 0; | ||||||
|  |     for (int i = 0; i < COUNT; i++) { | ||||||
|  |         sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); | ||||||
|  |         lfs_remove(&lfs, path) => 0; | ||||||
|  |     } | ||||||
|  |     lfs_unmount(&lfs) => 0; | ||||||
|  | ''' | ||||||
|  |  | ||||||
|  | [[case]] # outdated head test | ||||||
|  | define.ITERATIONS = 20 | ||||||
|  | define.COUNT = 10 | ||||||
|  | code = ''' | ||||||
|  |     lfs_format(&lfs, &cfg) => 0; | ||||||
|  |     // fill up filesystem so only ~16 blocks are left | ||||||
|  |     lfs_mount(&lfs, &cfg) => 0; | ||||||
|  |     lfs_file_open(&lfs, &file, "padding", LFS_O_CREAT | LFS_O_WRONLY) => 0; | ||||||
|  |     memset(buffer, 0, 512); | ||||||
|  |     while (LFS_BLOCK_COUNT - lfs_fs_size(&lfs) > 16) { | ||||||
|  |         lfs_file_write(&lfs, &file, buffer, 512) => 512; | ||||||
|  |     } | ||||||
|  |     lfs_file_close(&lfs, &file) => 0; | ||||||
|  |     // make a child dir to use in bounded space | ||||||
|  |     lfs_mkdir(&lfs, "child") => 0; | ||||||
|  |     lfs_unmount(&lfs) => 0; | ||||||
|  |  | ||||||
|  |     lfs_mount(&lfs, &cfg) => 0; | ||||||
|  |     for (int j = 0; j < ITERATIONS; j++) { | ||||||
|  |         for (int i = 0; i < COUNT; i++) { | ||||||
|  |             sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); | ||||||
|  |             lfs_file_open(&lfs, &file, path, LFS_O_CREAT | LFS_O_WRONLY) => 0; | ||||||
|  |             lfs_file_close(&lfs, &file) => 0; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         lfs_dir_open(&lfs, &dir, "child") => 0; | ||||||
|  |         lfs_dir_read(&lfs, &dir, &info) => 1; | ||||||
|  |         lfs_dir_read(&lfs, &dir, &info) => 1; | ||||||
|  |         for (int i = 0; i < COUNT; i++) { | ||||||
|  |             sprintf(path, "test%03d_loooooooooooooooooong_name", i); | ||||||
|  |             lfs_dir_read(&lfs, &dir, &info) => 1; | ||||||
|  |             strcmp(info.name, path) => 0; | ||||||
|  |             info.size => 0; | ||||||
|  |  | ||||||
|  |             sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); | ||||||
|  |             lfs_file_open(&lfs, &file, path, LFS_O_WRONLY) => 0; | ||||||
|  |             lfs_file_write(&lfs, &file, "hi", 2) => 2; | ||||||
|  |             lfs_file_close(&lfs, &file) => 0; | ||||||
|  |         } | ||||||
|  |         lfs_dir_read(&lfs, &dir, &info) => 0; | ||||||
|  |  | ||||||
|  |         lfs_dir_rewind(&lfs, &dir) => 0; | ||||||
|  |         lfs_dir_read(&lfs, &dir, &info) => 1; | ||||||
|  |         lfs_dir_read(&lfs, &dir, &info) => 1; | ||||||
|  |         for (int i = 0; i < COUNT; i++) { | ||||||
|  |             sprintf(path, "test%03d_loooooooooooooooooong_name", i); | ||||||
|  |             lfs_dir_read(&lfs, &dir, &info) => 1; | ||||||
|  |             strcmp(info.name, path) => 0; | ||||||
|  |             info.size => 2; | ||||||
|  |  | ||||||
|  |             sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); | ||||||
|  |             lfs_file_open(&lfs, &file, path, LFS_O_WRONLY) => 0; | ||||||
|  |             lfs_file_write(&lfs, &file, "hi", 2) => 2; | ||||||
|  |             lfs_file_close(&lfs, &file) => 0; | ||||||
|  |         } | ||||||
|  |         lfs_dir_read(&lfs, &dir, &info) => 0; | ||||||
|  |  | ||||||
|  |         lfs_dir_rewind(&lfs, &dir) => 0; | ||||||
|  |         lfs_dir_read(&lfs, &dir, &info) => 1; | ||||||
|  |         lfs_dir_read(&lfs, &dir, &info) => 1; | ||||||
|  |         for (int i = 0; i < COUNT; i++) { | ||||||
|  |             sprintf(path, "test%03d_loooooooooooooooooong_name", i); | ||||||
|  |             lfs_dir_read(&lfs, &dir, &info) => 1; | ||||||
|  |             strcmp(info.name, path) => 0; | ||||||
|  |             info.size => 2; | ||||||
|  |         } | ||||||
|  |         lfs_dir_read(&lfs, &dir, &info) => 0; | ||||||
|  |         lfs_dir_close(&lfs, &dir) => 0; | ||||||
|  |  | ||||||
|  |         for (int i = 0; i < COUNT; i++) { | ||||||
|  |             sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); | ||||||
|  |             lfs_remove(&lfs, path) => 0; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     lfs_unmount(&lfs) => 0; | ||||||
|  | ''' | ||||||
		Reference in New Issue
	
	Block a user