Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
5a12c44
Revert "Don't bypass cache in `lfs_cache_prog()` and `lfs_cache_read()`"
FreddieChopin Aug 9, 2019
f42e007
Created initial implementation of revamped test.py
geky Dec 29, 2019
ed8341e
Reworked permutation generation in test framework and cleanup
geky Dec 30, 2019
53d2b02
Added reentrant and gdb testing mechanisms to test framework
geky Dec 31, 2019
626006a
Fix incorrect comment on `lfs_npw2`
jpdoyle Jan 2, 2020
eeaf536
Replaced emubd with rambd and filebd
geky Jan 3, 2020
1d2688a
Migrated test_files, test_dirs, test_format suites to new framework
geky Jan 11, 2020
b06ce54
Migrated the bulk of the feature-specific tests
geky Jan 13, 2020
5181ce6
Migrated the first of the tests with internal knowledge
geky Jan 14, 2020
ecc2857
Migrated bad-block tests
geky Jan 14, 2020
fb65057
Restructured block devices again for better test exploitation
geky Jan 16, 2020
9453ebd
Added/improved disk-reading debug scripts
geky Jan 19, 2020
f4b6a6b
Fixed issues with neighbor updates during moves
geky Jan 20, 2020
a5d614f
Added tests for power-cycled-relocations and fixed the bugs that fell…
geky Jan 22, 2020
b9d0695
Rewrote explode_asserts.py to be more efficient
geky Jan 25, 2020
52ef0c1
Fixed a crazy consistency issue in test.py
geky Jan 27, 2020
aab6aa0
Cleaned up test script and directory naming
geky Jan 27, 2020
c8e9a64
Indicate C99 standard as target for LittleFS code
henrygab Jan 28, 2020
4fb1883
Update SPEC.md
zqb-all Dec 19, 2019
517d341
Fixed more bugs, mostly related to ENOSPC on different geometries
geky Jan 29, 2020
77e3078
Added/fixed tests for noop writes (where bd error can't be trusted)
geky Jan 29, 2020
44d7112
Fixed tests/*.toml.* in .gitignore
geky Jan 29, 2020
f9c2fd9
Removed file outlining on ENOSPC in lfs_file_sync
geky Jan 30, 2020
6a55084
Modified readmdir/readtree to make reading non-truncated data easier
geky Jan 30, 2020
fe957de
Fixed broken wear-leveling when block_cycles = 2n-1
geky Feb 9, 2020
6530cb3
Fixed lfs_fs_size doubling metadata-pairs
geky Feb 9, 2020
02c84ac
Cleaned up dependent fixes on branch
geky Feb 9, 2020
b69cf89
Fixed CRC check when prog_size causes multiple CRCs per commit
geky Feb 10, 2020
9f546f1
Updated .travis.yml and added additional geometry constraints
geky Feb 10, 2020
f4b17b3
Added test.py support for tmpfs-backed disks
geky Feb 12, 2020
dcae185
Fixed typo in LFS_MKTAG_IF_ELSE
geky Feb 12, 2020
c7987a3
Restructured .travis.yml to span more jobs
geky Feb 16, 2020
d04b077
Fixed minor things to get CI passing again
geky Feb 17, 2020
0990296
Limited byte-level tests to native testing due to time
geky Feb 18, 2020
50fe8ae
Renamed test_format -> test_superblocks, tweaked superblock tests
geky Feb 19, 2020
a7dfae4
Minor tweaks to debugging scripts, fixed explode_asserts.py off-by-1
geky Feb 23, 2020
cb26157
Change assert to runtime check.
Dec 30, 2019
4677421
Added "evil" tests and detecion/recovery from bad pointers and infini…
geky Feb 24, 2020
d498b9f
(bugfix) adding line function to clear out all the global 'free' info…
Mar 24, 2020
5e5b5d8
(chore) updates from PR, we decided not to move forward with changing…
Mar 26, 2020
f17d3d7
Minor cleanup
Mar 26, 2020
f9dbec3
Added test case catching issues with errors during a lookahead scan
geky Mar 24, 2020
01e42ab
Merge pull request #401 from thrasher8390/bugfix/thrasher8390/issue-3…
geky Mar 29, 2020
ff84902
Moved out block device tracing into separate define
geky Mar 29, 2020
5137e4b
Last minute tweaks to debug scripts
geky Mar 30, 2020
6622f3d
Bumped minor version to v2.2
geky Mar 30, 2020
6372f51
Allow debug prints without __VA_ARGS__
hemmick Nov 5, 2019
6121495
Merge pull request #266 from FreddieChopin/revert-bypass-cache
geky Mar 31, 2020
4a9bac4
Merge pull request #322 from hemmick/master
geky Mar 31, 2020
38024d5
Merge pull request #356 from zqb-all/patch-1
geky Mar 31, 2020
02881e5
Merge pull request #360 from jpdoyle/master
geky Mar 31, 2020
2da340a
Merge pull request #373 from henrygab/patch-1
geky Mar 31, 2020
7257681
Merge branch 'master' into test-revamp
geky Mar 31, 2020
a049f13
Merge pull request #372 from ARMmbed/test-revamp
geky Mar 31, 2020
1b033e9
Fix -Wmissing-field-initializers
rojer Apr 3, 2020
5a9f38d
Remove -Wno-missing-field-initializers
rojer Apr 6, 2020
4c9146e
Merge pull request #405 from rojer/mfe
geky Apr 9, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Created initial implementation of revamped test.py
This is the start of reworking littlefs's testing framework based on
lessons learned from the initial testing framework.

1. The testing framework needs to be _flexible_. It was hacky, which by
   itself isn't a downside, but it wasn't _flexible_. This limited what
   could be done with the tests and there ended up being many
   workarounds just to reproduce bugs.

   The idea behind this revamped framework is to separate the
   description of tests (tests/test_dirs.toml) and the running of tests
   (scripts/test.py).

   Now, with the logic moved entirely to python, it's possible to run
   the test under varying environments. In addition to the "just don't
   assert" run, I'm also looking to run the tests in valgrind for memory
   checking, and an environment with simulated power-loss.

   The test description can also contain abstract attributes that help
   control how tests can be ran, such as "leaky" to identify tests where
   memory leaks are expected. This keeps test limitations at a minimum
   without limiting how the tests can be ran.

2. Multi-stage-process tests didn't really add value and limited what
   the testing environment.

   Unmounting + mounting can be done in a single process to test the
   same logic. It would be really difficult to make this fail only
   when memory is zeroed, though that can still be caught by
   power-resilient tests.

   Requiring every test to be a single process adds several options
   for test execution, such as using a RAM-backed block device for
   speed, or even running the tests on a device.

3. Added fancy assert interception. This wasn't really a requirement,
   but something I've been wanting to experiment with for a while.

   During testing, scripts/explode_asserts.py is added to the build
   process. This is a custom C-preprocessor that parses out assert
   statements and replaces them with _very_ verbose asserts that
   wouldn't normally be possible with just C macros.

   It even goes as far as to report the arguments to strcmp, since the
   lack of visibility here was very annoying.

   tests_/test_dirs.toml:186:assert: assert failed with "..", expected eq "..."
       assert(strcmp(info.name, "...") == 0);

   One downside is that simply parsing C in python is slower than the
   entire rest of the compilation, but fortunately this can be
   alleviated by parallelizing the test builds through make.

Other neat bits:
- All generated files are a suffix of the test description, this helps
  cleanup and means it's (theoretically) possible to parallelize the
  tests.
- The generated test.c is shoved base64 into an ad-hoc Makefile, this
  means it doesn't force a rebuild of tests all the time.
- Test parameterizing is now easier.
- Hopefully this framework can be repurposed also for benchmarks in the
  future.
  • Loading branch information
geky committed Dec 29, 2019
commit f42e00770915d36e7118b57cbc19531fa38ad9a2
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,20 @@ test: \
test_corrupt
@rm test.c
test_%: tests/test_%.sh

ifdef QUIET
@./$< | sed -nu '/^[-=]/p'
else
./$<
endif

test_:
./scripts/test_.py $(TFLAGS)

-include $(DEP)

%?:
@echo '$($*)'

lfs: $(OBJ)
$(CC) $(CFLAGS) $^ $(LFLAGS) -o $@

Expand Down
16 changes: 8 additions & 8 deletions emubd/lfs_emubd.c
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,9 @@ int lfs_emubd_read(const struct lfs_config *cfg, lfs_block_t block,
uint8_t *data = buffer;

// Check if read is valid
assert(off % cfg->read_size == 0);
assert(size % cfg->read_size == 0);
assert(block < cfg->block_count);
LFS_ASSERT(off % cfg->read_size == 0);
LFS_ASSERT(size % cfg->read_size == 0);
LFS_ASSERT(block < cfg->block_count);

// Zero out buffer for debugging
memset(data, 0, size);
Expand Down Expand Up @@ -213,9 +213,9 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block,
const uint8_t *data = buffer;

// Check if write is valid
assert(off % cfg->prog_size == 0);
assert(size % cfg->prog_size == 0);
assert(block < cfg->block_count);
LFS_ASSERT(off % cfg->prog_size == 0);
LFS_ASSERT(size % cfg->prog_size == 0);
LFS_ASSERT(block < cfg->block_count);

// Program data
snprintf(emu->child, LFS_NAME_MAX, "%" PRIx32, block);
Expand All @@ -228,7 +228,7 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block,
}

// Check that file was erased
assert(f);
LFS_ASSERT(f);

int err = fseek(f, off, SEEK_SET);
if (err) {
Expand Down Expand Up @@ -287,7 +287,7 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) {
lfs_emubd_t *emu = cfg->context;

// Check if erase is valid
assert(block < cfg->block_count);
LFS_ASSERT(block < cfg->block_count);

// Erase the block
snprintf(emu->child, LFS_NAME_MAX, "%" PRIx32, block);
Expand Down
8 changes: 4 additions & 4 deletions lfs_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,28 +51,28 @@ extern "C"
// Logging functions
#ifdef LFS_YES_TRACE
#define LFS_TRACE(fmt, ...) \
printf("lfs_trace:%d: " fmt "\n", __LINE__, __VA_ARGS__)
printf("%s:%d:trace: " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#else
#define LFS_TRACE(fmt, ...)
#endif

#ifndef LFS_NO_DEBUG
#define LFS_DEBUG(fmt, ...) \
printf("lfs_debug:%d: " fmt "\n", __LINE__, __VA_ARGS__)
printf("%s:%d:debug: " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#else
#define LFS_DEBUG(fmt, ...)
#endif

#ifndef LFS_NO_WARN
#define LFS_WARN(fmt, ...) \
printf("lfs_warn:%d: " fmt "\n", __LINE__, __VA_ARGS__)
printf("%s:%d:warn: " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#else
#define LFS_WARN(fmt, ...)
#endif

#ifndef LFS_NO_ERROR
#define LFS_ERROR(fmt, ...) \
printf("lfs_error:%d: " fmt "\n", __LINE__, __VA_ARGS__)
printf("%s:%d:error: " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#else
#define LFS_ERROR(fmt, ...)
#endif
Expand Down
211 changes: 211 additions & 0 deletions scripts/explode_asserts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
#!/usr/bin/env python3

import parsy as p
import re
import io
import sys

ASSERT_PATTERN = p.string('LFS_ASSERT') | p.string('assert')
ASSERT_CHARS = 'La'
ASSERT_TARGET = '__LFS_ASSERT_{TYPE}_{COMP}'
ASSERT_TESTS = {
'int': """
__typeof__({lh}) _lh = {lh};
__typeof__({lh}) _rh = (__typeof__({lh})){rh};
if (!(_lh {op} _rh)) {{
printf("%s:%d:assert: "
"assert failed with %"PRIiMAX", expected {comp} %"PRIiMAX"\\n",
{file}, {line}, (intmax_t)_lh, (intmax_t)_rh);
exit(-2);
}}
""",
'str': """
const char *_lh = {lh};
const char *_rh = {rh};
if (!(strcmp(_lh, _rh) {op} 0)) {{
printf("%s:%d:assert: "
"assert failed with \\\"%s\\\", expected {comp} \\\"%s\\\"\\n",
{file}, {line}, _lh, _rh);
exit(-2);
}}
""",
'bool': """
bool _lh = !!({lh});
bool _rh = !!({rh});
if (!(_lh {op} _rh)) {{
printf("%s:%d:assert: "
"assert failed with %s, expected {comp} %s\\n",
{file}, {line}, _lh ? "true" : "false", _rh ? "true" : "false");
exit(-2);
}}
""",
}

def mkassert(lh, rh='true', type='bool', comp='eq'):
return ((ASSERT_TARGET + "({lh}, {rh}, __FILE__, __LINE__, __func__)")
.format(
type=type, TYPE=type.upper(),
comp=comp, COMP=comp.upper(),
lh=lh.strip(' '),
rh=rh.strip(' ')))

def mkdecl(type, comp, op):
return ((
"#define "+ASSERT_TARGET+"(lh, rh, file, line, func)"
" do {{"+re.sub('\s+', ' ', ASSERT_TESTS[type])+"}} while (0)\n")
.format(
type=type, TYPE=type.upper(),
comp=comp, COMP=comp.upper(),
lh='lh', rh='rh', op=op,
file='file', line='line', func='func'))

# add custom until combinator
def until(self, end):
return end.should_fail('should fail').then(self).many()
p.Parser.until = until

pcomp = (
p.string('==').tag('eq') |
p.string('!=').tag('ne') |
p.string('<=').tag('le') |
p.string('>=').tag('ge') |
p.string('<').tag('lt') |
p.string('>').tag('gt'));

plogic = p.string('&&') | p.string('||')

@p.generate
def pstrassert():
yield ASSERT_PATTERN + p.regex('\s*') + p.string('(') + p.regex('\s*')
yield p.string('strcmp') + p.regex('\s*') + p.string('(') + p.regex('\s*')
lh = yield pexpr.until(p.string(',') | p.string(')') | plogic)
yield p.string(',') + p.regex('\s*')
rh = yield pexpr.until(p.string(')') | plogic)
yield p.string(')') + p.regex('\s*')
op = yield pcomp
yield p.regex('\s*') + p.string('0') + p.regex('\s*') + p.string(')')
return mkassert(''.join(lh), ''.join(rh), 'str', op[0])

@p.generate
def pintassert():
yield ASSERT_PATTERN + p.regex('\s*') + p.string('(') + p.regex('\s*')
lh = yield pexpr.until(pcomp | p.string(')') | plogic)
op = yield pcomp
rh = yield pexpr.until(p.string(')') | plogic)
yield p.string(')')
return mkassert(''.join(lh), ''.join(rh), 'int', op[0])

@p.generate
def pboolassert():
yield ASSERT_PATTERN + p.regex('\s*') + p.string('(') + p.regex('\s*')
expr = yield pexpr.until(p.string(')'))
yield p.string(')')
return mkassert(''.join(expr), 'true', 'bool', 'eq')

passert = p.peek(ASSERT_PATTERN) >> (pstrassert | pintassert | pboolassert)

@p.generate
def pcomment1():
yield p.string('//')
s = yield p.regex('[^\\n]*')
yield p.string('\n')
return '//' + s + '\n'

@p.generate
def pcomment2():
yield p.string('/*')
s = yield p.regex('((?!\*/).)*')
yield p.string('*/')
return '/*' + ''.join(s) + '*/'

@p.generate
def pcomment3():
yield p.string('#')
s = yield p.regex('[^\\n]*')
yield p.string('\n')
return '#' + s + '\n'

pws = p.regex('\s+') | pcomment1 | pcomment2 | pcomment3

@p.generate
def pstring():
q = yield p.regex('["\']')
s = yield (p.string('\\%s' % q) | p.regex('[^%s]' % q)).many()
yield p.string(q)
return q + ''.join(s) + q

@p.generate
def pnested():
l = yield p.string('(')
n = yield pexpr.until(p.string(')'))
r = yield p.string(')')
return l + ''.join(n) + r

pexpr = (
# shortcut for a bit better performance
p.regex('[^%s/#\'"();{}=><,&|-]+' % ASSERT_CHARS) |
pws |
passert |
pstring |
pnested |
p.string('->') |
p.regex('.', re.DOTALL))

@p.generate
def pstmt():
ws = yield pws.many()
lh = yield pexpr.until(p.string('=>') | p.regex('[;{}]'))
op = yield p.string('=>').optional()
if op == '=>':
rh = yield pstmt
return ''.join(ws) + mkassert(''.join(lh), rh, 'int', 'eq')
else:
return ''.join(ws) + ''.join(lh)

@p.generate
def pstmts():
a = yield pstmt
b = yield (p.regex('[;{}]') + pstmt).many()
return [a] + b

def main(args):
inf = open(args.input, 'r') if args.input else sys.stdin
outf = open(args.output, 'w') if args.output else sys.stdout

# parse C code
input = inf.read()
stmts = pstmts.parse(input)

# write extra verbose asserts
outf.write("#include <stdbool.h>\n")
outf.write("#include <stdint.h>\n")
outf.write("#include <inttypes.h>\n")
outf.write(mkdecl('int', 'eq', '=='))
outf.write(mkdecl('int', 'ne', '!='))
outf.write(mkdecl('int', 'lt', '<'))
outf.write(mkdecl('int', 'gt', '>'))
outf.write(mkdecl('int', 'le', '<='))
outf.write(mkdecl('int', 'ge', '>='))
outf.write(mkdecl('str', 'eq', '=='))
outf.write(mkdecl('str', 'ne', '!='))
outf.write(mkdecl('str', 'lt', '<'))
outf.write(mkdecl('str', 'gt', '>'))
outf.write(mkdecl('str', 'le', '<='))
outf.write(mkdecl('str', 'ge', '>='))
outf.write(mkdecl('bool', 'eq', '=='))
if args.input:
outf.write("#line %d \"%s\"\n" % (1, args.input))

# write parsed statements
for stmt in stmts:
outf.write(stmt)

if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description="Cpp step that increases assert verbosity")
parser.add_argument('input', nargs='?',
help="Input C file after cpp.")
parser.add_argument('-o', '--output',
help="Output C file.")
main(parser.parse_args())
Loading