Files
littlefs/tests/test_gbmap.toml
T
Christopher Haster b5a94f3397 gbmap: Added mkgbmap and rmgbmap for enabling/disabling the gbmap
These two functions allow changing whether or not the gbmap is in use
after format:

  // Enable the global on-disk block-map
  //
  // Returns a negative error code on failure. Does nothing if a gbmap
  // already exists.
  int lfs3_fs_mkgbmap(lfs3_t *lfs3);

  // Disable the global on-disk block-map
  //
  // Returns a negative error code on failure. Does nothing if no gbmap
  // is found.
  int lfs3_fs_rmgbmap(lfs3_t *lfs3);

rmgbmap was easy enough, but implementing mkgbmap turned out to be
surprisingly tricky due to how gstate permeates the system:

- Even if we zero gstate when we removing the gbmap, mounting the
  image on a driver that doesn't understand the gbmap results in garbage
  gstate over time as mdir compacts drop unknown gdeltas.

  I think this sort of implicit gdelta cleanup is a good thing, but the
  possibility of garbage gstate is a bit annoying.

  Example A: the dbg scripts are currently printing a bunch of warnings
  for corrupt gstate that can be safely ignored.

  To support recovering from garbage gstate in mkgbmap, I changed
  lfs3_fs_commitgdelta to _always_ track p state even when disabled. We
  already needed to do this in lfs3_fs_flush/consumegdelta anyways,
  since we don't know if the gbmap is used until parsing wcompat flags.

- The commit that enables the gbmap is tricky. We need the gbmap enabled
  to calculate the new gdelta, but we also need it disabled so we don't
  traverse the existing gbmap_p (which may be garbage).

  As a workaround I added gbmap.b_p, which is in theory redundant with
  gbmap_p, but (1) avoids needing to decode gbmap_p during traversals,
  and (2) allows the two to temporarily fall out-of-sync in mkgbmap.

  This means we potentially have 5 (!) snaphots flying around when
  rebuilding the gbmap, which is starting to get a bit silly. But this
  was also motivated by gbmap_p decoding adding roughly the same amount
  of RAM to lfs3_mtree_traverse_, so the total RAM usage should in
  theory be roughly the same.

  There might be a better solution, but this at least gets mkgbmap
  working. The gbmap builds are not our most RAM senstive configurations
  anyways.

---

Also added a couple more tests in test_gbmap to test these:

- test_gbmap_files
- test_gbmap_rmgbmap
- test_gbmap_mkgbmap
- test_gbmap_rmmkgbmap
- test_gbmap_mkrmgbmap

And an explicit wraparound test to test_alloc. This was loosely implied
by the nospc tests, but it's probably better to have an explicit test.
The only downside is this implementation is limited to files:

- test_alloc_wraparound_files

---

Note we are currently dealing with three different configurations:
no-gbmap (the default), yes-gbmap (LFS3_YES_GBMAP), and maybe-gbmap
(LFS3_GBMAP + LFS3_F_GBMAP at runtime).

It only makes sense to include these in maybe-gbmap mode, so this is the
only mode with a notable code increase. However these functions are
relatively cheap. The stack/ctx changes also affect yes-gbmap, but
should mostly cancel out, see above:

                       code          stack          ctx
  no-gbmap before:    37168           2352          684
  no-gbmap after:     37168 (+0.0%)   2352 (+0.0%)  684 (+0.0%)

                       code          stack          ctx
  maybe-gbmap before: 39292           2456          800
  maybe-gbmap after:  39688 (+1.0%)   2392 (-2.6%)  852 (+6.5%)

                       code          stack          ctx
  yes-gbmap before:   39116           2456          800
  yes-gbmap after:    39156 (+0.1%)   2392 (-2.6%)  852 (+6.5%)
2025-10-17 14:02:05 -05:00

1170 lines
34 KiB
TOML

# Test some low-level block-map operations
#
# Note these is very much not-exhaustive, but the entire test suite can
# be run with the gbmap if you define LFS3_YES_GBMAP:
#
# LFS3_YES_GBMAP=1 make test -j
#
after = ['test_btree', 'test_mtree']
ifdef = 'LFS3_GBMAP'
# test low-level gbmap operations
# test simple set operations
[cases.test_gbmap_set_split]
in = 'lfs3.c'
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_alloc_ckpoint(&lfs3);
// create an initial gbmap
lfs3_btree_t gbmap;
lfs3_btree_init(&gbmap);
lfs3_gbmap_commit(&lfs3, &gbmap, 0, LFS3_RATTRS(
LFS3_RATTR(LFS3_TAG_BMFREE, +BLOCK_COUNT))) => 0;
// set some blocks as in-use, avoid any weird merges for now
lfs3_gbmap_set(&lfs3, &gbmap, 6, LFS3_TAG_BMINUSE) => 0;
lfs3_gbmap_set(&lfs3, &gbmap, 8, LFS3_TAG_BMINUSE) => 0;
lfs3_gbmap_set(&lfs3, &gbmap, 10, LFS3_TAG_BMINUSE) => 0;
printf("gbmap: w%d 0x%x.%x\n",
gbmap.r.weight,
gbmap.r.blocks[0],
gbmap.r.trunk);
// weight should stay the same
assert(gbmap.r.weight == BLOCK_COUNT);
// check ranges
lfs3_bid_t bid_;
lfs3_bid_t weight_;
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 0,
&bid_, &weight_) => LFS3_TAG_BMFREE;
assert(bid_ == 5);
assert(weight_ == 6);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 6,
&bid_, &weight_) => LFS3_TAG_BMINUSE;
assert(bid_ == 6);
assert(weight_ == 1);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 7,
&bid_, &weight_) => LFS3_TAG_BMFREE;
assert(bid_ == 7);
assert(weight_ == 1);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 8,
&bid_, &weight_) => LFS3_TAG_BMINUSE;
assert(bid_ == 8);
assert(weight_ == 1);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 9,
&bid_, &weight_) => LFS3_TAG_BMFREE;
assert(bid_ == 9);
assert(weight_ == 1);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 10,
&bid_, &weight_) => LFS3_TAG_BMINUSE;
assert(bid_ == 10);
assert(weight_ == 1);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 11,
&bid_, &weight_) => LFS3_TAG_BMFREE;
assert(bid_ == BLOCK_COUNT-1);
assert(weight_ == BLOCK_COUNT-11);
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_gbmap_set_replace]
in = 'lfs3.c'
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_alloc_ckpoint(&lfs3);
// create an initial gbmap
lfs3_btree_t gbmap;
lfs3_btree_init(&gbmap);
lfs3_gbmap_commit(&lfs3, &gbmap, 0, LFS3_RATTRS(
LFS3_RATTR(LFS3_TAG_BMFREE, +BLOCK_COUNT))) => 0;
// set some blocks as in-use, avoid any weird merges for now
lfs3_gbmap_set(&lfs3, &gbmap, 6, LFS3_TAG_BMINUSE) => 0;
lfs3_gbmap_set(&lfs3, &gbmap, 8, LFS3_TAG_BMINUSE) => 0;
lfs3_gbmap_set(&lfs3, &gbmap, 10, LFS3_TAG_BMINUSE) => 0;
// replace those blocks as bad, this tests deleting ranges
lfs3_gbmap_set(&lfs3, &gbmap, 6, LFS3_TAG_BMBAD) => 0;
lfs3_gbmap_set(&lfs3, &gbmap, 8, LFS3_TAG_BMBAD) => 0;
lfs3_gbmap_set(&lfs3, &gbmap, 10, LFS3_TAG_BMBAD) => 0;
printf("gbmap: w%d 0x%x.%x\n",
gbmap.r.weight,
gbmap.r.blocks[0],
gbmap.r.trunk);
// weight should stay the same
assert(gbmap.r.weight == BLOCK_COUNT);
// check ranges
lfs3_bid_t bid_;
lfs3_bid_t weight_;
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 0,
&bid_, &weight_) => LFS3_TAG_BMFREE;
assert(bid_ == 5);
assert(weight_ == 6);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 6,
&bid_, &weight_) => LFS3_TAG_BMBAD;
assert(bid_ == 6);
assert(weight_ == 1);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 7,
&bid_, &weight_) => LFS3_TAG_BMFREE;
assert(bid_ == 7);
assert(weight_ == 1);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 8,
&bid_, &weight_) => LFS3_TAG_BMBAD;
assert(bid_ == 8);
assert(weight_ == 1);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 9,
&bid_, &weight_) => LFS3_TAG_BMFREE;
assert(bid_ == 9);
assert(weight_ == 1);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 10,
&bid_, &weight_) => LFS3_TAG_BMBAD;
assert(bid_ == 10);
assert(weight_ == 1);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 11,
&bid_, &weight_) => LFS3_TAG_BMFREE;
assert(bid_ == BLOCK_COUNT-1);
assert(weight_ == BLOCK_COUNT-11);
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_gbmap_set_merge]
in = 'lfs3.c'
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_alloc_ckpoint(&lfs3);
// create an initial gbmap
lfs3_btree_t gbmap;
lfs3_btree_init(&gbmap);
lfs3_gbmap_commit(&lfs3, &gbmap, 0, LFS3_RATTRS(
LFS3_RATTR(LFS3_TAG_BMFREE, +BLOCK_COUNT))) => 0;
// set some blocks as in-use, avoid any weird merges for now
lfs3_gbmap_set(&lfs3, &gbmap, 6, LFS3_TAG_BMINUSE) => 0;
lfs3_gbmap_set(&lfs3, &gbmap, 8, LFS3_TAG_BMINUSE) => 0;
lfs3_gbmap_set(&lfs3, &gbmap, 10, LFS3_TAG_BMINUSE) => 0;
lfs3_gbmap_set(&lfs3, &gbmap, 12, LFS3_TAG_BMINUSE) => 0;
// set neighboring blocks as in-use, triggering merges
lfs3_gbmap_set(&lfs3, &gbmap, 5, LFS3_TAG_BMINUSE) => 0;
lfs3_gbmap_set(&lfs3, &gbmap, 9, LFS3_TAG_BMINUSE) => 0;
lfs3_gbmap_set(&lfs3, &gbmap, 13, LFS3_TAG_BMINUSE) => 0;
printf("gbmap: w%d 0x%x.%x\n",
gbmap.r.weight,
gbmap.r.blocks[0],
gbmap.r.trunk);
// weight should stay the same
assert(gbmap.r.weight == BLOCK_COUNT);
// check ranges
lfs3_bid_t bid_;
lfs3_bid_t weight_;
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 0,
&bid_, &weight_) => LFS3_TAG_BMFREE;
assert(bid_ == 4);
assert(weight_ == 5);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 5,
&bid_, &weight_) => LFS3_TAG_BMINUSE;
assert(bid_ == 6);
assert(weight_ == 2);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 7,
&bid_, &weight_) => LFS3_TAG_BMFREE;
assert(bid_ == 7);
assert(weight_ == 1);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 8,
&bid_, &weight_) => LFS3_TAG_BMINUSE;
assert(bid_ == 10);
assert(weight_ == 3);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 11,
&bid_, &weight_) => LFS3_TAG_BMFREE;
assert(bid_ == 11);
assert(weight_ == 1);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 12,
&bid_, &weight_) => LFS3_TAG_BMINUSE;
assert(bid_ == 13);
assert(weight_ == 2);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 14,
&bid_, &weight_) => LFS3_TAG_BMFREE;
assert(bid_ == BLOCK_COUNT-1);
assert(weight_ == BLOCK_COUNT-14);
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_gbmap_set_noop]
in = 'lfs3.c'
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_alloc_ckpoint(&lfs3);
// create an initial gbmap
lfs3_btree_t gbmap;
lfs3_btree_init(&gbmap);
lfs3_gbmap_commit(&lfs3, &gbmap, 0, LFS3_RATTRS(
LFS3_RATTR(LFS3_TAG_BMFREE, +BLOCK_COUNT))) => 0;
// set some blocks as in-use, avoid any weird merges for now
lfs3_gbmap_set(&lfs3, &gbmap, 6, LFS3_TAG_BMINUSE) => 0;
lfs3_gbmap_set(&lfs3, &gbmap, 8, LFS3_TAG_BMINUSE) => 0;
lfs3_gbmap_set(&lfs3, &gbmap, 10, LFS3_TAG_BMINUSE) => 0;
// test a bunch of noops
lfs3_gbmap_set(&lfs3, &gbmap, 6, LFS3_TAG_BMINUSE) => 0;
lfs3_gbmap_set(&lfs3, &gbmap, 7, LFS3_TAG_BMFREE) => 0;
lfs3_gbmap_set(&lfs3, &gbmap, 8, LFS3_TAG_BMINUSE) => 0;
lfs3_gbmap_set(&lfs3, &gbmap, 9, LFS3_TAG_BMFREE) => 0;
lfs3_gbmap_set(&lfs3, &gbmap, 10, LFS3_TAG_BMINUSE) => 0;
printf("gbmap: w%d 0x%x.%x\n",
gbmap.r.weight,
gbmap.r.blocks[0],
gbmap.r.trunk);
// weight should stay the same
assert(gbmap.r.weight == BLOCK_COUNT);
// check ranges
lfs3_bid_t bid_;
lfs3_bid_t weight_;
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 0,
&bid_, &weight_) => LFS3_TAG_BMFREE;
assert(bid_ == 5);
assert(weight_ == 6);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 6,
&bid_, &weight_) => LFS3_TAG_BMINUSE;
assert(bid_ == 6);
assert(weight_ == 1);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 7,
&bid_, &weight_) => LFS3_TAG_BMFREE;
assert(bid_ == 7);
assert(weight_ == 1);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 8,
&bid_, &weight_) => LFS3_TAG_BMINUSE;
assert(bid_ == 8);
assert(weight_ == 1);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 9,
&bid_, &weight_) => LFS3_TAG_BMFREE;
assert(bid_ == 9);
assert(weight_ == 1);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 10,
&bid_, &weight_) => LFS3_TAG_BMINUSE;
assert(bid_ == 10);
assert(weight_ == 1);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 11,
&bid_, &weight_) => LFS3_TAG_BMFREE;
assert(bid_ == BLOCK_COUNT-1);
assert(weight_ == BLOCK_COUNT-11);
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_gbmap_set_bounds]
in = 'lfs3.c'
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_alloc_ckpoint(&lfs3);
// create an initial gbmap
lfs3_btree_t gbmap;
lfs3_btree_init(&gbmap);
lfs3_gbmap_commit(&lfs3, &gbmap, 0, LFS3_RATTRS(
LFS3_RATTR(LFS3_TAG_BMFREE, +BLOCK_COUNT))) => 0;
// test that setting the first and last blocks don't break anything
//
// though in theory blocks 0x{0,1} are immutable...
lfs3_gbmap_set(&lfs3, &gbmap, 0, LFS3_TAG_BMINUSE) => 0;
lfs3_gbmap_set(&lfs3, &gbmap, BLOCK_COUNT-1, LFS3_TAG_BMINUSE) => 0;
printf("gbmap: w%d 0x%x.%x\n",
gbmap.r.weight,
gbmap.r.blocks[0],
gbmap.r.trunk);
// weight should stay the same
assert(gbmap.r.weight == BLOCK_COUNT);
// check ranges
lfs3_bid_t bid_;
lfs3_bid_t weight_;
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 0,
&bid_, &weight_) => LFS3_TAG_BMINUSE;
assert(bid_ == 0);
assert(weight_ == 1);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, 1,
&bid_, &weight_) => LFS3_TAG_BMFREE;
assert(bid_ == BLOCK_COUNT-2);
assert(weight_ == BLOCK_COUNT-2);
lfs3_gbmap_lookupnext(&lfs3, &gbmap, BLOCK_COUNT-1,
&bid_, &weight_) => LFS3_TAG_BMINUSE;
assert(bid_ == BLOCK_COUNT-1);
assert(weight_ == 1);
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_gbmap_set_fuzz]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
defines.TYPES = [2, 3, 4]
defines.SEED = 'range(20)'
fuzz = 'SEED'
in = 'lfs3.c'
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_alloc_ckpoint(&lfs3);
// create an initial gbmap
lfs3_btree_t gbmap;
lfs3_btree_init(&gbmap);
lfs3_gbmap_commit(&lfs3, &gbmap, 0, LFS3_RATTRS(
LFS3_RATTR(LFS3_TAG_BMFREE, +BLOCK_COUNT))) => 0;
// create a simulation to compare against
lfs3_tag_t *sim = malloc(BLOCK_COUNT*sizeof(lfs3_tag_t));
for (lfs3_size_t i = 0; i < BLOCK_COUNT; i++) {
sim[i] = LFS3_TAG_BMFREE;
}
uint32_t prng = SEED;
for (lfs3_size_t i = 0; i < N; i++) {
// choose a pseudo-random block
lfs3_block_t block = TEST_PRNG(&prng) % BLOCK_COUNT;
// and pseudo-random gbmap type
lfs3_tag_t tag = LFS3_TAG_BMRANGE + (TEST_PRNG(&prng) % TYPES);
// set in gbmap
lfs3_gbmap_set(&lfs3, &gbmap, block, tag) => 0;
// and set in sim
sim[block] = tag;
}
printf("gbmap: w%d 0x%x.%x\n",
gbmap.r.weight,
gbmap.r.blocks[0],
gbmap.r.trunk);
// check if gbmap matches sim
lfs3_size_t i = 0;
while (i < BLOCK_COUNT) {
// we need to convert our sim's raw blocks to compressed ranges,
// in theory our gbmap is optimal
lfs3_tag_t tag = sim[i];
lfs3_size_t d = 1;
while (i+d < BLOCK_COUNT && sim[i+d] == tag) {
d += 1;
}
// does our gbmap contain the optimal range?
lfs3_bid_t bid_;
lfs3_bid_t weight_;
lfs3_gbmap_lookupnext(&lfs3, &gbmap, i,
&bid_, &weight_) => tag;
assert(bid_ == i+(weight_-1));
assert(weight_ == d);
i += d;
}
free(sim);
lfs3_unmount(&lfs3) => 0;
'''
# test high-level gbmap operations
# test that the gbmap generally works
[cases.test_gbmap_files]
defines.COUNT = [
'BLOCK_COUNT',
'BLOCK_COUNT-1',
'BLOCK_COUNT/2',
'BLOCK_COUNT/4',
'5',
'3',
]
defines.SIZE = [
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
defines.N = ['5', 'BLOCK_COUNT/2']
defines.WRAPAROUND = 3
if = [
'COUNT >= 3',
'COUNT >= 2*N*SIZE/BLOCK_SIZE',
]
code = '''
// test various block counts
struct lfs3_cfg cfg = *CFG;
cfg.block_count = COUNT;
lfs3_t lfs3;
// note the gbmap flag
lfs3_format(&lfs3, LFS3_F_RDWR | LFS3_F_GBMAP, &cfg) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
// check that we were formatted with the gbmap
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags & LFS3_I_GBMAP);
// create n files repeatedly until we're sure we've wrapped around
// a few times
uint32_t prng = 42;
uint32_t prng_ = prng;
for (lfs3_size_t i = 0;
i < (WRAPAROUND*COUNT)
/ (N*(SIZE/BLOCK_SIZE));
i++) {
prng = prng_;
for (lfs3_size_t n = 0; n < N; n++) {
char name[256];
sprintf(name, "file%08d", n);
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26);
}
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name,
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC) => 0;
lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE;
lfs3_file_close(&lfs3, &file) => 0;
}
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
}
// check that our file writes worked
prng_ = prng;
for (lfs3_size_t i = 0; i < N; i++) {
// check with stat
char name[256];
sprintf(name, "file%08d", i);
struct lfs3_info info;
lfs3_stat(&lfs3, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == SIZE);
// try reading the file, note we reset prng above
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26);
}
lfs3_file_t file;
uint8_t rbuf[SIZE];
lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfs3_file_close(&lfs3, &file) => 0;
}
}
lfs3_unmount(&lfs3) => 0;
'''
# test that we can remove the gbmap and things still work
[cases.test_gbmap_rmgbmap]
ifndef = 'LFS3_YES_GBMAP'
defines.COUNT = [
'BLOCK_COUNT',
'BLOCK_COUNT-1',
'BLOCK_COUNT/2',
'BLOCK_COUNT/4',
'5',
'3',
]
defines.SIZE = [
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
defines.N = ['5', 'BLOCK_COUNT/2']
defines.WRAPAROUND = 3
# REMOUNT=0 => don't remount
# REMOUNT=1 => remount after
# REMOUNT=2 => remount before
# REMOUNT=3 => remount both after and before
defines.REMOUNT = [0, 1, 2, 3]
if = [
'COUNT >= 3',
'COUNT >= 2*N*SIZE/BLOCK_SIZE',
]
code = '''
// test various block counts
struct lfs3_cfg cfg = *CFG;
cfg.block_count = COUNT;
lfs3_t lfs3;
// note the gbmap flag
lfs3_format(&lfs3, LFS3_F_RDWR | LFS3_F_GBMAP, &cfg) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
// check that we were formatted with the gbmap
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags & LFS3_I_GBMAP);
// create n files repeatedly until we're sure we've wrapped around
// a few times
uint32_t prng = 42;
uint32_t prng_ = prng;
for (lfs3_size_t i = 0;
i < (WRAPAROUND*COUNT)
/ (N*(SIZE/BLOCK_SIZE));
i++) {
prng = prng_;
for (lfs3_size_t n = 0; n < N; n++) {
char name[256];
sprintf(name, "file%08d", n);
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26);
}
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name,
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC) => 0;
lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE;
lfs3_file_close(&lfs3, &file) => 0;
}
}
// remount before?
if (REMOUNT & 2) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
}
// now remove the gbmap
lfs3_fs_rmgbmap(&lfs3) => 0;
// remount after?
if (REMOUNT & 1) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
}
// we should no longer have a gbmap
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(!(fsinfo.flags & LFS3_I_GBMAP));
// create n files repeatedly until we're sure we've wrapped around
// a few times
for (lfs3_size_t i = 0;
i < (WRAPAROUND*COUNT)
/ (N*(SIZE/BLOCK_SIZE));
i++) {
prng = prng_;
for (lfs3_size_t n = 0; n < N; n++) {
char name[256];
sprintf(name, "file%08d", n);
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26);
}
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name,
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC) => 0;
lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE;
lfs3_file_close(&lfs3, &file) => 0;
}
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
}
// check that our file writes worked
prng_ = prng;
for (lfs3_size_t i = 0; i < N; i++) {
// check with stat
char name[256];
sprintf(name, "file%08d", i);
struct lfs3_info info;
lfs3_stat(&lfs3, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == SIZE);
// try reading the file, note we reset prng above
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26);
}
lfs3_file_t file;
uint8_t rbuf[SIZE];
lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfs3_file_close(&lfs3, &file) => 0;
}
}
lfs3_unmount(&lfs3) => 0;
'''
# test that we can make the gbmap and things still work
[cases.test_gbmap_mkgbmap]
ifndef = 'LFS3_YES_GBMAP'
defines.COUNT = [
'BLOCK_COUNT',
'BLOCK_COUNT-1',
'BLOCK_COUNT/2',
'BLOCK_COUNT/4',
'5',
'3',
]
defines.SIZE = [
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
defines.N = ['5', 'BLOCK_COUNT/2']
defines.WRAPAROUND = 3
# REMOUNT=0 => don't remount
# REMOUNT=1 => remount after
# REMOUNT=2 => remount before
# REMOUNT=3 => remount both after and before
defines.REMOUNT = [0, 1, 2, 3]
if = [
'COUNT >= 3',
'COUNT >= 2*N*SIZE/BLOCK_SIZE',
]
code = '''
// test various block counts
struct lfs3_cfg cfg = *CFG;
cfg.block_count = COUNT;
lfs3_t lfs3;
// note the lack of gbmap flag
lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
// check that we were formatted without the gbmap
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(!(fsinfo.flags & LFS3_I_GBMAP));
// create n files repeatedly until we're sure we've wrapped around
// a few times
uint32_t prng = 42;
uint32_t prng_ = prng;
for (lfs3_size_t i = 0;
i < (WRAPAROUND*COUNT)
/ (N*(SIZE/BLOCK_SIZE));
i++) {
prng = prng_;
for (lfs3_size_t n = 0; n < N; n++) {
char name[256];
sprintf(name, "file%08d", n);
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26);
}
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name,
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC) => 0;
lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE;
lfs3_file_close(&lfs3, &file) => 0;
}
}
// remount before?
if (REMOUNT & 2) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
}
// now make the gbmap
lfs3_fs_mkgbmap(&lfs3) => 0;
// remount after?
if (REMOUNT & 1) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
}
// we should now have a gbmap
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags & LFS3_I_GBMAP);
// create n files repeatedly until we're sure we've wrapped around
// a few times
for (lfs3_size_t i = 0;
i < (WRAPAROUND*COUNT)
/ (N*(SIZE/BLOCK_SIZE));
i++) {
prng = prng_;
for (lfs3_size_t n = 0; n < N; n++) {
char name[256];
sprintf(name, "file%08d", n);
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26);
}
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name,
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC) => 0;
lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE;
lfs3_file_close(&lfs3, &file) => 0;
}
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
}
// check that our file writes worked
prng_ = prng;
for (lfs3_size_t i = 0; i < N; i++) {
// check with stat
char name[256];
sprintf(name, "file%08d", i);
struct lfs3_info info;
lfs3_stat(&lfs3, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == SIZE);
// try reading the file, note we reset prng above
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26);
}
lfs3_file_t file;
uint8_t rbuf[SIZE];
lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfs3_file_close(&lfs3, &file) => 0;
}
}
lfs3_unmount(&lfs3) => 0;
'''
# test that we can remove and then remake the gbmap and things still
# work
#
# this leaves the disk with garbage gstate that mkgbmap needs to clean
# up
[cases.test_gbmap_rmmkgbmap]
ifndef = 'LFS3_YES_GBMAP'
defines.COUNT = [
'BLOCK_COUNT',
'BLOCK_COUNT-1',
'BLOCK_COUNT/2',
'BLOCK_COUNT/4',
'5',
'3',
]
defines.SIZE = [
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
defines.N = ['5', 'BLOCK_COUNT/2']
defines.WRAPAROUND = 3
# REMOUNT=0x1 => remount after
# REMOUNT=0x2 => remount before make
# REMOUNT=0x4 => remount after remove
# REMOUNT=0x8 => remount before
defines.REMOUNT = 'range(0x10)'
if = [
'COUNT >= 3',
'COUNT >= 2*N*SIZE/BLOCK_SIZE',
]
code = '''
// test various block counts
struct lfs3_cfg cfg = *CFG;
cfg.block_count = COUNT;
lfs3_t lfs3;
// note the gbmap flag
lfs3_format(&lfs3, LFS3_F_RDWR | LFS3_F_GBMAP, &cfg) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
// check that we were formatted with the gbmap
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags & LFS3_I_GBMAP);
// create n files repeatedly until we're sure we've wrapped around
// a few times
uint32_t prng = 42;
uint32_t prng_ = prng;
for (lfs3_size_t i = 0;
i < (WRAPAROUND*COUNT)
/ (N*(SIZE/BLOCK_SIZE));
i++) {
prng = prng_;
for (lfs3_size_t n = 0; n < N; n++) {
char name[256];
sprintf(name, "file%08d", n);
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26);
}
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name,
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC) => 0;
lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE;
lfs3_file_close(&lfs3, &file) => 0;
}
}
// remount before?
if (REMOUNT & 8) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
}
// now remove the gbmap
lfs3_fs_rmgbmap(&lfs3) => 0;
// remount after remove?
if (REMOUNT & 4) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
}
// we should no longer have a gbmap
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(!(fsinfo.flags & LFS3_I_GBMAP));
// create n files repeatedly until we're sure we've wrapped around
// a few times
for (lfs3_size_t i = 0;
i < (WRAPAROUND*COUNT)
/ (N*(SIZE/BLOCK_SIZE));
i++) {
prng = prng_;
for (lfs3_size_t n = 0; n < N; n++) {
char name[256];
sprintf(name, "file%08d", n);
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26);
}
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name,
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC) => 0;
lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE;
lfs3_file_close(&lfs3, &file) => 0;
}
}
// remount before make?
if (REMOUNT & 2) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
}
// now remake the gbmap
lfs3_fs_mkgbmap(&lfs3) => 0;
// remount after?
if (REMOUNT & 1) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
}
// we should now have a gbmap
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags & LFS3_I_GBMAP);
// create n files repeatedly until we're sure we've wrapped around
// a few times
for (lfs3_size_t i = 0;
i < (WRAPAROUND*COUNT)
/ (N*(SIZE/BLOCK_SIZE));
i++) {
prng = prng_;
for (lfs3_size_t n = 0; n < N; n++) {
char name[256];
sprintf(name, "file%08d", n);
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26);
}
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name,
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC) => 0;
lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE;
lfs3_file_close(&lfs3, &file) => 0;
}
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
}
// check that our file writes worked
prng_ = prng;
for (lfs3_size_t i = 0; i < N; i++) {
// check with stat
char name[256];
sprintf(name, "file%08d", i);
struct lfs3_info info;
lfs3_stat(&lfs3, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == SIZE);
// try reading the file, note we reset prng above
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26);
}
lfs3_file_t file;
uint8_t rbuf[SIZE];
lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfs3_file_close(&lfs3, &file) => 0;
}
}
lfs3_unmount(&lfs3) => 0;
'''
# test that we can make and then remove the gbmap and things still
# work
#
# not exactly sure what this tests, but including for completeness
[cases.test_gbmap_mkrmgbmap]
ifndef = 'LFS3_YES_GBMAP'
defines.COUNT = [
'BLOCK_COUNT',
'BLOCK_COUNT-1',
'BLOCK_COUNT/2',
'BLOCK_COUNT/4',
'5',
'3',
]
defines.SIZE = [
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
defines.N = ['5', 'BLOCK_COUNT/2']
defines.WRAPAROUND = 3
# REMOUNT=0x1 => remount after
# REMOUNT=0x2 => remount before remove
# REMOUNT=0x4 => remount after make
# REMOUNT=0x8 => remount before
defines.REMOUNT = 'range(0x10)'
if = [
'COUNT >= 3',
'COUNT >= 2*N*SIZE/BLOCK_SIZE',
]
code = '''
// test various block counts
struct lfs3_cfg cfg = *CFG;
cfg.block_count = COUNT;
lfs3_t lfs3;
// note the lack of gbmap flag
lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
// check that we were formatted without the gbmap
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(!(fsinfo.flags & LFS3_I_GBMAP));
// create n files repeatedly until we're sure we've wrapped around
// a few times
uint32_t prng = 42;
uint32_t prng_ = prng;
for (lfs3_size_t i = 0;
i < (WRAPAROUND*COUNT)
/ (N*(SIZE/BLOCK_SIZE));
i++) {
prng = prng_;
for (lfs3_size_t n = 0; n < N; n++) {
char name[256];
sprintf(name, "file%08d", n);
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26);
}
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name,
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC) => 0;
lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE;
lfs3_file_close(&lfs3, &file) => 0;
}
}
// remount before?
if (REMOUNT & 8) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
}
// now make the gbmap
lfs3_fs_mkgbmap(&lfs3) => 0;
// remount after make?
if (REMOUNT & 4) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
}
// we should now have a gbmap
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags & LFS3_I_GBMAP);
// create n files repeatedly until we're sure we've wrapped around
// a few times
for (lfs3_size_t i = 0;
i < (WRAPAROUND*COUNT)
/ (N*(SIZE/BLOCK_SIZE));
i++) {
prng = prng_;
for (lfs3_size_t n = 0; n < N; n++) {
char name[256];
sprintf(name, "file%08d", n);
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26);
}
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name,
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC) => 0;
lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE;
lfs3_file_close(&lfs3, &file) => 0;
}
}
// remount before remove?
if (REMOUNT & 2) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
}
// now remove the gbmap
lfs3_fs_rmgbmap(&lfs3) => 0;
// remount after?
if (REMOUNT & 1) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
}
// we should no longer have a gbmap
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(!(fsinfo.flags & LFS3_I_GBMAP));
// create n files repeatedly until we're sure we've wrapped around
// a few times
for (lfs3_size_t i = 0;
i < (WRAPAROUND*COUNT)
/ (N*(SIZE/BLOCK_SIZE));
i++) {
prng = prng_;
for (lfs3_size_t n = 0; n < N; n++) {
char name[256];
sprintf(name, "file%08d", n);
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26);
}
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name,
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC) => 0;
lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE;
lfs3_file_close(&lfs3, &file) => 0;
}
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
}
// check that our file writes worked
prng_ = prng;
for (lfs3_size_t i = 0; i < N; i++) {
// check with stat
char name[256];
sprintf(name, "file%08d", i);
struct lfs3_info info;
lfs3_stat(&lfs3, name, &info) => 0;
assert(strcmp(info.name, name) == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == SIZE);
// try reading the file, note we reset prng above
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26);
}
lfs3_file_t file;
uint8_t rbuf[SIZE];
lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfs3_file_close(&lfs3, &file) => 0;
}
}
lfs3_unmount(&lfs3) => 0;
'''