Commit Graph

22 Commits

Author SHA1 Message Date
Christopher Haster 0ee6d73560 (Re)implemented lfsr_fs_gc
This just provides a simple, easy-to-call, wrapper over the new
traversal API:

  int lfsr_fs_gc(lfs_t *lfs, uint32_t flags);

The main difference from its previous incarnation, is that lfsr_fs_gc
now takes a flags argument to indicate exactly what gc operations to
perform. This gives the user more control, and may also make the API
more robust towards adding new features:

  LFS_GC_MTREEONLY    = 0x0010, // Only traverse the mtree
  LFS_GC_MKCONSISTENT = 0x0020, // Make the filesystem consistent
  LFS_GC_LOOKAHEAD    = 0x0040, // Populate lookahead buffer
  LFS_GC_COMPACT      = 0x0080, // Compact metadata logs
  LFS_GC_CKMETA       = 0x0100, // Check metadata checksums
  LFS_GC_CKDATA       = 0x0200, // Check metadata + data checksums
  LFS_GC_REPAIRMETA+  = 0x0400, // Repair metadata blocks
  LFS_GC_REPAIRDATA+  = 0x0800, // Repair metadata + data blocks

  + Planned

Alternatively, gc_flags could have been added as a config option. But
making gc_flags a function argument matches other flag APIs (open
mainly), and is slightly more flexible in that it allows a system to do
different gc operations in different system states (though this could
also be accomplished with the hypothetical lfsr_fs_gccfg, which would
probably be good to add anyways).

Worst case, defining a system-wide define that you always pass to
lfsr_fs_gc accomplishes roughly the same thing.

---

This adds a bit more code, mainly to check if we actually need to
traverse, and to make sure traversals accomplish all of the requested
work.

           code          stack
  before: 35448           2680
  after:  35708 (+0.7%)   2672 (-0.3%)

Curiously it also saved a bit of stack, which is a bit silly given this
commit is purely code addition. Apparently something in lfs_alloc and
lfsr_fs_gc is shared, getting uninlined, and messing with the stack
measurement. lfs_alloc is quite sensitive to stack changes after all.
2024-07-17 17:10:20 -05:00
Christopher Haster 5b72973f8f t: Filled out rest of test_traversal
There can always be more tests, but I think these give a nice set of
coverage over corner-cases in our traversal clobbering scheme.

These did find a couple bugs:

- If we clobber an inlined mroot, we need to adjust the mid by two
  mdirs, but only if there is no mtree/mdirs.

  To avoid this and other mid-related headaches, we just provide the new
  mid in lfsr_mdir_commit, since we always know it here.

- lfsr_mdir_commit compares mdirs by mptr, which means we need to
  clobber traversal's mdir's mptrs or else lfsr_mdir_commit will clobber
  already-clobbered traversals.

  There may be a better way to solve this, but it will probably get into
  the weeds with how lfsr_mdir_commit relies on mids vs mptrs...

Code changes:

           code          stack
  before: 34570           2624
  after:  34566 (-0.0%)   2624 (+0.0%)

Now that the dust has settled and we sort of know what the traversal
implementation will look like, we can look at the before and after to
get a rough idea of how much the traversal API actually costs:

                          code          stack
  no-traversal (before): 33886           2560
  yes-traversal (after): 34566 (+2.0%)   2624 (+2.5%)

Note this still includes the annoying lfsr_btree_traverse inlining stack
cost, which isn't really the traversal API's fault and may be avoidable
in the future.
2024-06-20 13:13:58 -05:00
Christopher Haster 74d382b48f Dropped lfs_*32/16 suffixed utils
We don't actually need these, all we need are utils defined for the
largest integer size we operate on, currently uint32_t.
Counterintuitively this should make it easier to adopt different integer
widths in the future.

Or maybe this will bite us when lfs_off_t >> lfs_size_t? Oh well, if
that's the case we can fix it then.

No code changes:

           code          stack
  before: 33886           2560
  after:  33886 (+0.0%)   2560 (+0.0%)
2024-06-20 13:04:25 -05:00
Christopher Haster 9ad59dcfe6 (Re)implemented lfsr_fs_grow, variable block counts, etc
Well this turned into a never-ending can of worms...

I guess the good news is our newly added lfsr_grow_incr_* tests are
_very_ good at finding post-error-resume bugs.

Implementation-wise, this was fairly straightforward thanks to prior
work by BrianPugh, kaetemi, and myself:

1. Made block_count pseudo-optional by adding lfs.block_count so we can
   mutate it based on what we find on-disk.

   This was done a bit different from the previous implementation,
   instead of setting block_count=0 to read the block_count from disk,
   we allow any block_count <= the configured block_count.

   This matches how we handle name_limit/file_limit/etc, and allows
   users to mount a filesystem with unknown block_count while asserting
   an upper bound.

2. Added lfsr_fs_grow, which can grow the filesystem.

   The is basically the same as the previous implementation except we're
   a bit more careful with the lookahead buffer.

   I thought the previous impl might have been broken w.r.t. lookahead
   buffer, but fortunately it's only broken in a way that makes us think
   newly available blocks are temporarily in-use. Which is a bit funny.

   One interesting thing that came out with more aggressive tests is
   that it's possible to get locked-up in lfsr_fs_preparemutation trying
   to clean up grms/orphans before we change the filesystem size.

   Fortunately it turns out we don't _really_ need to call
   lfsr_fs_preparemutation here. This gets a bit delicate, but means we
   should always be able to grow a full filesystem.

To test this I've added both the simple grow/error tests from the
previous version, as well as a set of fuzz tests (a la test_relocations
and friends) that incrementally grow the filesystem when encountering
LFS_ERR_NOSPC. These have a surprising amount coverage, testing
lfsr_fs_grow, lfsr_fs_stat, lfsr_fs_size, and resuming operations after
encountering an error.

Which also means they found bugs:

- lfs_alloc_setinuse was not broken before, because lookahead.start was
  always a multiple of lookahead_size. But now with lfs_alloc_discard,
  this invariant may not be true.

  I've just changed all lookahead.start updates to mod block_count. This
  adds a bit of code, but is much easier to reason about.

  While fixing this, I also added an assert to never allocate blocks
  {0,1} in lfs_alloc. This is a good assert to have, but did require
  some tweaks to test_btree to avoid these blocks.

- We were incorrectly patching grms in lfsr_mdir_commit when mdelta=0.

  Funnily enough we also proceed to ignore the patched grm most of the
  time when mdelta=0, so this went unnoticed.

- It turns out we're completely ignoring rid=-1 attrs if we split the
  mroot. Not sure how this was missed. It's a bit important.

  Note this is still broken. Fixing this requires some rather invasive
  changes to lfsr_mdir_commit's internal logic that should probably be
  in another commit...

Note again fwrite_fuzz is omitted. Currently the state of data in opened
files is undefined after a failed write, so this wouldn't really be
testing anything interesting...

More features = more code, and all of this bug fixing meant several
things contributed to code/stack changes in this commit:

                           code          stack
  before:                 33654           2592
  +variable block_count:  33646 (-0.0%)   2584 (+0.0%)
  +lfsr_fs_grow:          33818 (+0.5%)   2584 (-0.3%)
  +lookahead-start-fix:   33842 (+0.6%)   2584 (-0.3%)
  +grm-patch-fix (after): 33850 (+0.6%)   2584 (-0.3%)

Wild that variable block_count actually saves code/stack. I guess the
indirect lfs->cfg->block_count load can get costly...
2024-06-20 13:03:04 -05:00
Christopher Haster 76ffb0e7b6 Fixed mroot death returning LFS_ERR_CORRUPT -> LFS_ERR_NOSPC
It's counter-intuitive, but no top-level API should return
LFS_ERR_CORRUPT. Instead, if we can't make progress because of a corrupt
block, we should return LFS_ERR_NOSPC. This makes it easier for users to
write code that is well behaved even when a device is end-of-life.

It's up to our mroot extension algorithm to make sure this case can't be
reached in normal operation unless the device is _actually_ at
end-of-life.

Because mroot extension is a bit of a special case, we weren't
converting these corrupt errors to nospc errors consistently. This is
fixed now, along with a couple more hopefully-useful logging statements.

Found while playing around with test_exhaustion + block_recycles=-1.
This should assert on bad wear-leveling, but LFS_ERR_CORRUPT was
unexpected. Added an explicit test because this is an easy thing to let
split through:

- test_badblocks_mrootanchor_wear

Code changes were surprisingly minimal, I wonder if constants are being
swapped out somewhere low-level?

           code          stack
  before: 33766           2600
  after:  33770 (+0.0%)   2600 (+0.0%)
2024-06-05 00:02:32 -05:00
Christopher Haster 9914897e39 Locked down out-of-order writes, more tests
The main test additions are the test_powerloss tests, intended to be
high-level tests over difficult/weird powerloss environments (such as
out-of-order writes!):

- test_powerloss_dir_many - 2242 pls
- test_powerloss_file_many - 8856 pls
- test_powerloss_file_pl_fuzz - 384508 pls
- test_powerloss_filedir_pl_fuzz - 268339 pls

But there was also a bunch of other test movement in the late-stage/
high-level tests. I'm trying to keep the core of these tests somewhat
consistent so we have a nice template to extend for future testing, in
case we want to test other environmentalish concerns, but not all of
these tests make sense in all of these contexts:

                        badblocks  powerloss  relocations  exhaustion
  dir_many                      y          y            y
  dir_fuzz                      y                       y           y
  file_many                     y          y            y
  file_fuzz                     y                       y           y
  fwrite_fuzz                   y                                   y
  orphanzombie_fuzz             y                       y           y
  orphanzombiedir_fuzz          y                       y           y
  file_pl_fuzz                             y            y
  filedir_pl_fuzz                          y            y

Why not:

- dir/file_many+exhaustion? - Needs to be unbounded
- dir/file_fuzz+powerloss? - Takes O(n^2)
- fwrite_fuzz+powerloss? - Takes O(n^2)
- fwrite_fuzz+relocations? - Doesn't really test anything
- orphanzombie*_fuzz+powerloss? - Powerloss kills zombies
- file*_pl_fuzz+badblocks? - PL + Badblocks currently incompactible
- file*_pl_fuzz+exhaustion? - PL + Badblocks currently incompactible

---

Of course, in order to actually get out-of-order write testing working,
we need to implement out-of-order write syncing.

Fortunately this was a simple exercise in placing lfsr_bd_sync calls
before any mdir commits where we may have unsynced data:

- in lfsr_file_sync, to sync any pending file data
- in lfsr_mdir_commit, to sync any mroot/mtree changes

We also call lfsr_bd_sync _after_ mdir commits in case users expect to
sequence any filesystem-external operations such as network, UI, etc. In
theory this could be optional, but no users have really requested it
yet, so leave that for a potential future improvement:

- in lfsr_mdir_commit
- in lfsr_formatinited (really just because we don't go through
  lfsr_mdir_commit)

Note that lfsr_rbyd_commit has been relaxed in the scheme. It only
flushes caches, and does _not_ call lfsr_bd_sync. This is useful for
allowing multiple B-tree nodes to be written out-of-order, also long as
the whole thing is synchronized before any mdir commit.

All of these lfsr_bd_sync calls add a bit of code, but not really an
amount to care about:

           code          stack
  before: 33678           2600
  after:  33766 (+0.3%)   2600 (+0.0%)
2024-06-01 03:34:32 -05:00
Christopher Haster 1ecb346cec Renamed fbuffer_size -> file_buffer_size 2024-05-30 11:52:07 -05:00
Christopher Haster 8e77a5eebc Switched to clobbering rcache for prog checking
While exploring the test_badblocks ERASENOOP failure more, I realized
the problem is that we are nesting crc32cs.

To be clear, using crc32cs to validate progs in general is not an issue,
that is perfectly fine on paper. The issue is that we were using crc32cs
to validate progs _that contain crc32cs_.

Looking at the collision, we can see the fully expanded lleb128s we use
for our cksum tags:

  00 00 00 ff b0 02 00 87 80 80 00 3e c0 7f 7e => bdfa9b10
  ab 77 de c2 b0 03 00 87 80 80 00 3e 38 d5 22 => bdfa9b10
              '-.-'  ^ '----.----' '----.----'
                '----|------|-----------|-- cksum tag
                     '------|-----------|-- cksum weight (0)
                            '-----------|-- cksum size + padding
                                        '-- cksum crc32c

So we ended up perfectly aligning the cksum's crc32c with our cache
line. Lucky us.

Unfortunately funny math makes it so that whenever a crc32c contains a
crc32c, the inner crc32c sort of cancels itself out from the outer
crc32c. So these two messages end up mathematically equivalent, even
though they contain different data:

  crc(m) = m(x) x^|P|-1 mod P
  crc(m ++ crc(m)) = (m(x) x^|P|-1 + (m(x) x^|P|-1 mod P)) x^|P|-1 mod P
  crc(m ++ crc(m)) = (m(x) x^|P|-1 + m(x) x^|P|-1) x^|P|-1 mod P
  crc(m ++ crc(m)) = 0 x^|P|-1 mod P
  crc(m ++ crc(m)) = 0

So using a crc32c to check progs is not fit for purpose.

This leaves us with a couple options:

1. Use a different checksum, or do something like rearranging bytes to
   avoid this cancelling out issue. Unfortunately this gets tricky since
   crc32cs are linear, simply using an xor mask won't work...

2. Don't check progs at such a low-level, but at a high-level using the
   rbyd/data block crc32cs. Since this would mean only one crc32c, this
   would avoid nesting issues. Unfortunately this would probably come
   with quite a high code cost to try to keep track of both the
   before+after rbyd cksums everywhere...

3. Just read back the data into the rcache to compare at the byte-level,
   which would mean clobbering our rcache when prog checking is enabled.

This commit goes with option 3., which is probably the simplest. It also
removes any question of crc32c collision, which could be a real nuisance
when debugging low-level block device operations, a use case where prog
checking will hopefully be quite valuable.

Clobbering the rcache also has the advantage of reverting the prog
>= read requirement, which is nice for flexibility. Though this needs to
be tested.

---

There was a bit of a hiccup, and that was how prog checking interacts
with lfsr_bd_cpy. lfsr_bd_cpy used the rcache to hold data being copied
to/from disk, but this data needs to be checked, and prog checking would
clobber the rcache. Problems! I guess this is one footgun of the
internal lfsr_bd_readnext API...

The solution is to instead turn this around and use the pcache to hold
any copied data, since this would not be clobbered when prog checking.

This has some other knock-on effects, mainly that we can't take
advantage of read hints in lfsr_bd_cpy, but has the added advantage of
potentially not clobbering the rcache at all when no checking progs.

Code changes were fairly minimal:

           code          stack
  before: 33718           2608
  after:  33690 (-0.1%)   2608 (+0.0%)
2024-05-30 00:23:27 -05:00
Christopher Haster 89f7f98fba Reworked bd layers with prog >= read assumption
The initial goal was the simplify these layers. Keyword being initial.
Unfortunately these layers are both complex and subtle, so the goal
shifted more to be rigorous and reliable.

This mainly meant rearranging our prog/read loops to follow a consistent
style, with higher-priority buffers being sorted out before flushing
things. This gets a bit tricky with wanting to support both cache
bypassing and buffer-lending prognext/readnext, but with some redundant
prognext/readnext calls it's doable.

We also now aggressively discard rcaches on pcache conflicts. This
change does rely on the prog >= read assumption. Discarding rcaches
means we should no longer have overlapping caches, so hopefully no more
zombie rcache issues.

Our bypassing heuristic was also tweaked a bit. Now, in addition to
alignment, >= read/prog_size, and >= hint requirements, we also require
operations to be >= r/pcache_size. This should improve cache usage when
r/pcache_size >> read/prog_size, since we were too eager to bypass
before.

Long story short, this ended up being more just things shifting around
than a significant simplification of the bd layers. At least we ended up
with a nice bit of stack savings:

          code           stack
  before: 33682           2640
  after:  33718 (+0.1%)   2608 (-1.2%)

Also, test_badblocks with LFS_EMUBD_BADBLOCK_ERASENOOP is now failing. I
was worried the amount of fuzz testing we do would eventually end up
with a naturally occuring crc32c collision, and sure enough we did! Yayy
yyyy...

  00 00 00 ff b0 02 00 87 80 80 00 3e c0 7f 7e => bdfa9b10
  ab 77 de c2 b0 03 00 87 80 80 00 3e 38 d5 22 => bdfa9b10

Need to think about what to do with this... For now I've just commented
out the problematic test.
2024-05-30 00:04:41 -05:00
Christopher Haster c648f96dc5 Added check_progs for immediate prog validation
This configuration option enables the previous behavior of reading back
every prog to check that the data was written correctly.

Unfortunately, this brings a bit of baggage, thanks to our cache
interactions being more complicated now:

- We really want to reuse the rcache for prog validation, despite the
  cache performance implications. Unfortunately, we simply can't, thanks
  to the new bd utility functions tying up the rcache. lfsr_bd_cpy, for
  example, does not expect rcache to be invalidated between a read and
  prog, and if it is, things break (I may or may not have found this by
  experience).

  These bd utilities are valuable, so we really need some other way to
  validate our progs.

- Since we can't rely on the rcache, this leaves checksumming as the
  only option for validating progs. Checksumming isn't perfect, as there
  is a decent chance of false negatives, but to be honest it's probably
  good enough for anything that's not malicious.

- This also adds the new constraint that we need to be able to read back
  any prog into the pcache, which implies read_size <= prog_size. This
  constraint didn't exist when we could clobber our rcache, but this is
  not worth throwing away the new bd utilities. Not to mention
  clobbering our rcache could hurt cache performance.

  Why not make read_size <= prog_size conditional on check_progs?

  The main reason is convenience. One very compelling use case for
  check_progs is to help debug unknown filesystem/integration failures,
  buf if you can't enable check_progs without changing the filesystem
  configuration, you can't really rely on check_progs for debugging.

  This helps future proof what we expect from block devices, in case
  future error detection/correction mechanisms can benefit from our
  prog_size always being readable.

Code changes were not that significant, however there was a surprising
stack cost. This seems to be because lfsr_bd_read__ can now be called
from multiple places, causing it to no longer be inlined in
lfsr_bd_read_, costing a bit of stack for the additional function call:

  before: 33566           2624
  after:  33682 (+0.3%)   2640 (+0.6%)
2024-05-29 23:09:41 -05:00
Christopher Haster 9c9a409524 Added fuzz test attribute
This acts as a marker to indicate a fuzz test. It should reference a
define, usually SEED, that can be randomized to get interesting test
permutations.

This is currently unused, but could lead to some interesting uses such
as time-based fuzz testing. It's also just useful for inspecting the
tests (make test-list).
2024-05-28 12:44:44 -05:00
Christopher Haster 1c363b428a Replaced REMOUNT with small post-test loops where possible
We've been wasting a lot of test cycles thanks to REMOUNT. Using a test
define for this effectively duplicates the test, when we really just
want to run more post-test code without additional mutation.

The main reason for REMOUNT has been to save typing, which, well, is not
a bad reason, these tests involve a lot of typing...

But this is probably a hammer/nail situation. If we replace these with a
small post-test loop, we can save quite a bit of time:

  make test -j before: 5791.9s
  make test -j after:  5123.8s (-11.5%)

Some tests still use a REMOUNT define, but these should be limited to
cases where remount actually changes the test's behavior.
2024-05-28 03:10:03 -05:00
Christopher Haster 5d03416c82 Added LFSR_BTREE/SHRUB_NULL, dropped lfsr_btree_alloc
Our B-trees lazily allocate their root blocks, so it makes more sense
for this to be a macro. Added/adopted a similar LFSR_SHRUB_NULL for
consistency.

Unfortunately this added a bit of code. I think because GCC struggles to
optimize compound literals, which both LFSR_BTREE_NULL and
LFSR_SHRUB_NULL expand into:

           code          stack
  before: 33538           2624
  after:  33550 (+0.0%)   2624 (+0.0%)
2024-05-27 15:30:44 -05:00
Christopher Haster 15da817af5 Replace fuzz DENSITY with explicit OPS in tests
This sort of inverts the previous logic. Tests can still define
OPS='2*N' to scale the number of ops roughly with the number of entries,
but this fits better into the test framework, allows overriding, scaling
can be more easily tweaked, can be swapped out with a constant (like in
test_wl), etc.

Also tweaked some of the related N constants/filter conditions in tests
since these are now being effectively doubled... This should leave the
resulting number of ops unchanged.
2024-05-27 15:30:23 -05:00
Christopher Haster fe11a33416 (Re)implemented bad prog/erase recovery
This (re)implements the heavy-hitting tests in test_badblocks that rakes
filesystem operations over various types of prog/erase failures:

- test_badblocks_[one|region|alternating]_btree - force tall B-trees
- test_badblocks_[one|region|alternating]_dirs - large mtree
- test_badblocks_[one|region|alternating]_files - mixed mtree + files
- test_badblocks_[one|region|alternating]_fwrite_fuzz - complex files
- test_badblocks_[one|region|alternating]_orphanzombiedir_fuzz - complex
- test_badblocks_mrootanchor - uh, format fails, cheap test though

Where:

- test_badblocks_one_* - runs with every possible bad block
- test_badblocks_region_* - runs with a large region of bad blocks
- test_badblocks_alternating_* - runs with alternating bad blocks, this
  one is rough for block pair allocations

This required quite a bit of rewiring of internal block allocations. I
knew this would eventually need to be (re)implemented, but the jump from
infallible to fallible progs everywhere was still quite involved:

- lfs_alloc no longer returns LFS_ERR_CORRUPT if erase fails, instead it
  will keep searching for a block where an erase "sticks" or return
  LFS_ERR_NOENT. This simplifies above layers.

  This actually turned out to be required since the lookahead traversal
  can also return LFS_ERR_CORRUPT... which needs to be treated as a hard
  error and bail.

- In lfsr_btree_commit_ all inner-node compactions needed alloc loops.
  This really complements B-tree's copy-on-write behavior, but does make
  lfsr_btree_commit_ a bit of a goto soup...

- Same for lfsr_btree_commit/lfsr_bshrub_commit, but fortunately there
  are nice and self-contained.

- lfsr_mdir_alloc__/lfsr_mdir_swap__ needed a bit of an overhaul to be
  able to handle bad progs. lfsr_mdir_alloc__ now takes a bool `all`
  parameter to know if it should allocate one or two of the mdir blocks.

  You could argue it's simpler/cheaper to always allocate two blocks at
  a time, but this could lead to premature filesystem death on
  unfortunate bad block patterns. test_badblocks_alternating_*
  specifically tests for this. Note we still allocate both on
  relocation, but only on the first commit attempt.

  This also rearranges things to move the overcompacting logic out of
  lfsr_mdir_swap__ and into lfsr_mdir_commit_, since we only want to
  overcompact after trying to program all possible free blocks.

- lfsr_file_flush_ now needs to rewrite the entire block of data if a
  prog fails, even if appending an existing data block.

  Humorously, this was really easy, since we already align everything to
  any existing blocks as a part of our crystallization algorithm. Almost
  too easy... (no new code! only a couple gotos! scary!)

Note some of these may be transformable into simpler while loops, but I
decided to avoid this and prefer explicit `relocate` gotos because: 1.
in some functions these end up deeply nested in existing loops and I was
already bitten by a shadowed continue, 2. the "good" path does not loop,
with a loop you need an easy to miss break and the intention is less
clear, and 3. consistency is good.

We are _not_ testing read errors yet. This is because we no longer read
back progs and the relaxed rcache/pcache alignment requirements make
this a bit difficult to (re)implement. User feedback also suggests we
may want to make this optional... So need to think on how to address
this.

Some other notes:

- Our low-level bd wrappers, lfsr_bd_*__, now log bad ops via LFS_DEBUG.

- Overcompaction is now an LFS_WARN.

- The pcache is now correctly dropped if we error during flush.

- I noticed lfsr_btree_alloc double allocated for new B-trees, it
  doesn't now, maybe change this function?

- Our B-tree tests all stop on LFS_ERR_NOSPC, but this isn't guaranteed
  since our filesystem isn't in a valid state. We should make sure none
  of our B-tree tests actually rely on this...

Honestly, considering how much new logic was introduced, this really did
not impact code cost as much as I thought it would. Probably thanks to
the underlying data structures being built to easily discard blocks in
the first place:

           code          stack
  before: 33474           2640
  after:  33618 (+0.4%)   2648 (+0.3%)
2024-05-27 03:00:44 -05:00
Christopher Haster 4ff7c1f771 Commenting out outdated functions for now
This makes it easier to evaluate the code/stack/etc sizes and run tests
without bringing in all of the outdated code.

I guess this officially makes this branch more-or-less a full rewrite,
though the benefit of commenting vs deleting this code is that it can be
easily pulled back in when useful.
2023-06-16 01:51:29 -05:00
Christopher Haster 4fe0738ff4 Added bench.py and bench_runner.c for benchmarking
These are really just different flavors of test.py and test_runner.c
without support for power-loss testing, but with support for measuring
the cumulative number of bytes read, programmed, and erased.

Note that the existing define parameterization should work perfectly
fine for running benchmarks across various dimensions:

./scripts/bench.py \
    runners/bench_runner \
    bench_file_read \
    -gnor \
    -DSIZE='range(0,131072,1024)'

Also added a couple basic benchmarks as a starting point.
2022-11-15 13:33:34 -06:00
Christopher Haster 11d6d1251e Dropped namespacing of test cases
The main benefit is small test ids everywhere, though this is with the
downside of needing longer names to properly prefix and avoid
collisions. But this fits into the rest of the scripts with globally
unique names a bit better. This is a C project after all.

The other small benefit is test generators may have an easier time since
per-case symbols can expect to be unique.
2022-09-17 03:03:39 -05:00
Christopher Haster 0781f50edb Ported tests to new framework
This mostly required names for each test case, declarations of
previously-implicit variables since the new test framework is more
conservative with what it declares (the small extra effort to add
declarations is well worth the simplicity and improved readability),
and tweaks to work with not-really-constant defines.

Also renamed test_ -> test, replacing the old ./scripts/test.py,
unfortunately git seems to have had a hard time with this.
2022-06-06 01:35:03 -05:00
Christopher Haster 9f546f154f Updated .travis.yml and added additional geometry constraints
Moved .travis.yml over to use the new test framework. A part of this
involved testing all of the configurations ran on the old framework
and deciding which to carry over. The new framework duplicates some of
the cases tested by the configurations so some configurations could be
dropped.

The .travis.yml includes some extreme ones, such as no inline files,
relocations every cycle, no intrinsics, power-loss every byte, unaligned
block_count and lookahead, and odd read_sizes.

There were several configurations were some tests failed because of
limitations in the tests themselves, so many conditions were added
to make sure the configurations can run on as many tests as possible.
2020-02-11 16:01:57 -06:00
Christopher Haster 77e3078b9f Added/fixed tests for noop writes (where bd error can't be trusted)
It's interesting how many ways block devices can show failed writes:
1. prog can error
2. erase can error
3. read can error after writing (ECC failure)
4. prog doesn't error but doesn't write the data correctly
5. erase doesn't error but doesn't erase correctly

Can read fail without an error? Yes, though this appears the same as
prog and erase failing.

These weren't all simulated by testbd since I unintentionally assumed
the block device could always error. Fixed by added additional bad-black
behaviors to testbd.

Note: This also includes a small fix where we can miss bad writes if the
underlying block device contains a valid commit with the exact same
size in the exact same offset.
2020-02-09 12:00:22 -06:00
Christopher Haster aab6aa0ed9 Cleaned up test script and directory naming
- Removed old tests and test scripts
- Reorganize the block devices to live under one directory
- Plugged new test framework into Makefile

renamed:
- scripts/test_.py -> scripts/test.py
- tests_ -> tests
- {file,ram,test}bd/* -> bd/*

It took a surprising amount of effort to make the Makefile behave since
it turns out the "test_%" rule could override "tests/test_%.toml.test"
which is generated as part of test.py.
2020-01-27 10:16:29 -06:00