diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 04546dbe..46e57e5b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -374,6 +374,29 @@ jobs: run: | CFLAGS="$CFLAGS -DLFS2_NO_INTRINSICS" make test + # run with all trace options enabled to at least make sure these + # all compile + test-yes-trace: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: install + run: | + # need a few things + sudo apt-get update -qq + sudo apt-get install -qq gcc python3 python3-pip + pip3 install toml + gcc --version + python3 --version + - name: test-yes-trace + run: | + CFLAGS="$CFLAGS \ + -DLFS2_YES_TRACE \ + -DLFS2_RAMBD_YES_TRACE \ + -DLFS2_FILEBD_YES_TRACE \ + -DLFS2_RAMBD_YES_TRACE" \ + make test + # run LFS2_MULTIVERSION tests test-multiversion: runs-on: ubuntu-latest diff --git a/README.md b/README.md index 60a5b8b5..dabd8875 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,47 @@ The tests assume a Linux environment and can be started with make: make test ``` +Tests are implemented in C in the .toml files found in the `tests` directory. +When developing a feature or fixing a bug, it is frequently useful to run a +single test case or suite of tests: + +``` bash +./scripts/test.py -l runners/test_runner # list available test suites +./scripts/test.py -L runners/test_runner test_dirs # list available test cases +./scripts/test.py runners/test_runner test_dirs # run a specific test suite +``` + +If an assert fails in a test, test.py will try to print information about the +failure: + +``` bash +tests/test_dirs.toml:1:failure: test_dirs_root:1g12gg2 (PROG_SIZE=16, ERASE_SIZE=512) failed +tests/test_dirs.toml:5:assert: assert failed with 0, expected eq 42 + lfs2_mount(&lfs2, cfg) => 42; +``` + +This includes the test id, which can be passed to test.py to run only that +specific test permutation: + +``` bash +./scripts/test.py runners/test_runner test_dirs_root:1g12gg2 # run a specific test permutation +./scripts/test.py runners/test_runner test_dirs_root:1g12gg2 --gdb # drop into gdb on failure +``` + +Some other flags that may be useful: + +```bash +./scripts/test.py runners/test_runner -b -j # run tests in parallel +./scripts/test.py runners/test_runner -v -O- # redirect stdout to stdout +./scripts/test.py runners/test_runner -ddisk # capture resulting disk image +``` + +See `-h/--help` for a full list of available flags: + +``` bash +./scripts/test.py --help +``` + ## License The littlefs is provided under the [BSD-3-Clause] license. See diff --git a/bd/lfs2_filebd.c b/bd/lfs2_filebd.c index 9da10370..41cb688c 100644 --- a/bd/lfs2_filebd.c +++ b/bd/lfs2_filebd.c @@ -133,7 +133,7 @@ int lfs2_filebd_prog(const struct lfs2_config *cfg, lfs2_block_t block, int lfs2_filebd_erase(const struct lfs2_config *cfg, lfs2_block_t block) { LFS2_FILEBD_TRACE("lfs2_filebd_erase(%p, 0x%"PRIx32" (%"PRIu32"))", - (void*)cfg, block, ((lfs2_file_t*)cfg->context)->cfg->erase_size); + (void*)cfg, block, ((lfs2_filebd_t*)cfg->context)->cfg->erase_size); lfs2_filebd_t *bd = cfg->context; // check if erase is valid diff --git a/lfs2.c b/lfs2.c index 16ca0a35..f9ce2cf2 100644 --- a/lfs2.c +++ b/lfs2.c @@ -404,18 +404,15 @@ struct lfs2_diskoff { // operations on global state static inline void lfs2_gstate_xor(lfs2_gstate_t *a, const lfs2_gstate_t *b) { - for (int i = 0; i < 3; i++) { - ((uint32_t*)a)[i] ^= ((const uint32_t*)b)[i]; - } + a->tag ^= b->tag; + a->pair[0] ^= b->pair[0]; + a->pair[1] ^= b->pair[1]; } static inline bool lfs2_gstate_iszero(const lfs2_gstate_t *a) { - for (int i = 0; i < 3; i++) { - if (((uint32_t*)a)[i] != 0) { - return false; - } - } - return true; + return a->tag == 0 + && a->pair[0] == 0 + && a->pair[1] == 0; } #ifndef LFS2_READONLY @@ -2369,7 +2366,8 @@ fixmlist:; if (d->m.pair != pair) { for (int i = 0; i < attrcount; i++) { if (lfs2_tag_type3(attrs[i].tag) == LFS2_TYPE_DELETE && - d->id == lfs2_tag_id(attrs[i].tag)) { + d->id == lfs2_tag_id(attrs[i].tag) && + d->type != LFS2_TYPE_DIR) { d->m.pair[0] = LFS2_BLOCK_NULL; d->m.pair[1] = LFS2_BLOCK_NULL; } else if (lfs2_tag_type3(attrs[i].tag) == LFS2_TYPE_DELETE && @@ -2558,7 +2556,7 @@ static int lfs2_dir_orphaningcommit(lfs2_t *lfs2, lfs2_mdir_t *dir, if (err != LFS2_ERR_NOENT) { if (lfs2_gstate_hasorphans(&lfs2->gstate)) { // next step, clean up orphans - err = lfs2_fs_preporphans(lfs2, -hasparent); + err = lfs2_fs_preporphans(lfs2, -(int8_t)hasparent); if (err) { return err; } @@ -6288,7 +6286,7 @@ lfs2_soff_t lfs2_file_size(lfs2_t *lfs2, lfs2_file_t *file) { lfs2_soff_t res = lfs2_file_size_(lfs2, file); - LFS2_TRACE("lfs2_file_size -> %"PRId32, res); + LFS2_TRACE("lfs2_file_size -> %"PRIu32, res); LFS2_UNLOCK(lfs2->cfg); return res; } diff --git a/scripts/prettyasserts.py b/scripts/prettyasserts.py index c94fad75..ef3d44eb 100755 --- a/scripts/prettyasserts.py +++ b/scripts/prettyasserts.py @@ -35,10 +35,10 @@ LEXEMES = { 'assert': ['assert'], 'arrow': ['=>'], 'string': [r'"(?:\\.|[^"])*"', r"'(?:\\.|[^'])\'"], - 'paren': ['\(', '\)'], + 'paren': [r'\(', r'\)'], 'cmp': CMP.keys(), - 'logic': ['\&\&', '\|\|'], - 'sep': [':', ';', '\{', '\}', ','], + 'logic': [r'\&\&', r'\|\|'], + 'sep': [':', ';', r'\{', r'\}', ','], 'op': ['->'], # specifically ops that conflict with cmp } diff --git a/scripts/test.py b/scripts/test.py index 70a3818c..19f9a9e8 100755 --- a/scripts/test.py +++ b/scripts/test.py @@ -102,9 +102,9 @@ class TestCase: # the runner itself. for v_ in csplit(v): m = re.search(r'\brange\b\s*\(' - '(?P[^,\s]*)' - '\s*(?:,\s*(?P[^,\s]*)' - '\s*(?:,\s*(?P[^,\s]*)\s*)?)?\)', + r'(?P[^,\s]*)' + r'\s*(?:,\s*(?P[^,\s]*)' + r'\s*(?:,\s*(?P[^,\s]*)\s*)?)?\)', v_) if m: start = (int(m.group('start'), 0) @@ -163,8 +163,8 @@ class TestSuite: code_linenos = [] for i, line in enumerate(f): match = re.match( - '(?P\[\s*cases\s*\.\s*(?P\w+)\s*\])' - '|' '(?Pcode\s*=)', + r'(?P\[\s*cases\s*\.\s*(?P\w+)\s*\])' + r'|' r'(?Pcode\s*=)', line) if match and match.group('case'): case_linenos.append((i+1, match.group('name'))) @@ -602,9 +602,9 @@ def find_perms(runner_, ids=[], **args): errors='replace', close_fds=False) pattern = re.compile( - '^(?P[^\s]+)' - '\s+(?P[^\s]+)' - '\s+(?P\d+)/(?P\d+)') + r'^(?P[^\s]+)' + r'\s+(?P[^\s]+)' + r'\s+(?P\d+)/(?P\d+)') # skip the first line for line in it.islice(proc.stdout, 1, None): m = pattern.match(line) @@ -632,8 +632,8 @@ def find_perms(runner_, ids=[], **args): errors='replace', close_fds=False) pattern = re.compile( - '^(?P[^\s]+)' - '\s+(?P[^:]+):(?P\d+)') + r'^(?P[^\s]+)' + r'\s+(?P[^:]+):(?P\d+)') # skip the first line for line in it.islice(proc.stdout, 1, None): m = pattern.match(line) @@ -676,8 +676,8 @@ def find_path(runner_, id, **args): errors='replace', close_fds=False) pattern = re.compile( - '^(?P[^\s]+)' - '\s+(?P[^:]+):(?P\d+)') + r'^(?P[^\s]+)' + r'\s+(?P[^:]+):(?P\d+)') # skip the first line for line in it.islice(proc.stdout, 1, None): m = pattern.match(line) @@ -706,7 +706,7 @@ def find_defines(runner_, id, **args): errors='replace', close_fds=False) defines = co.OrderedDict() - pattern = re.compile('^(?P\w+)=(?P.+)') + pattern = re.compile(r'^(?P\w+)=(?P.+)') for line in proc.stdout: m = pattern.match(line) if m: @@ -781,12 +781,12 @@ def run_stage(name, runner_, ids, stdout_, trace_, output_, **args): failures = [] killed = False - pattern = re.compile('^(?:' - '(?Prunning|finished|skipped|powerloss) ' - '(?P(?P[^:]+)[^\s]*)' - '|' '(?P[^:]+):(?P\d+):(?Passert):' - ' *(?P.*)' - ')$') + pattern = re.compile(r'^(?:' + r'(?Prunning|finished|skipped|powerloss) ' + r'(?P(?P[^:]+)[^\s]*)' + r'|' r'(?P[^:]+):(?P\d+):(?Passert):' + r' *(?P.*)' + r')$') locals = th.local() children = set() diff --git a/tests/test_dirs.toml b/tests/test_dirs.toml index 6f6b0234..9e377dd3 100644 --- a/tests/test_dirs.toml +++ b/tests/test_dirs.toml @@ -725,6 +725,82 @@ code = ''' lfs2_unmount(&lfs2) => 0; ''' +[cases.test_dirs_remove_read] +defines.N = 10 +if = 'N < BLOCK_COUNT/2' +code = ''' + lfs2_t lfs2; + lfs2_format(&lfs2, cfg) => 0; + lfs2_mount(&lfs2, cfg) => 0; + lfs2_mkdir(&lfs2, "prickly-pear") => 0; + for (int i = 0; i < N; i++) { + char path[1024]; + sprintf(path, "prickly-pear/cactus%03d", i); + lfs2_mkdir(&lfs2, path) => 0; + } + lfs2_dir_t dir; + lfs2_dir_open(&lfs2, &dir, "prickly-pear") => 0; + struct lfs2_info info; + lfs2_dir_read(&lfs2, &dir, &info) => 1; + assert(info.type == LFS2_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs2_dir_read(&lfs2, &dir, &info) => 1; + assert(info.type == LFS2_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + for (int i = 0; i < N; i++) { + char path[1024]; + sprintf(path, "cactus%03d", i); + lfs2_dir_read(&lfs2, &dir, &info) => 1; + assert(info.type == LFS2_TYPE_DIR); + assert(strcmp(info.name, path) == 0); + } + lfs2_dir_read(&lfs2, &dir, &info) => 0; + lfs2_dir_close(&lfs2, &dir) => 0; + lfs2_unmount(&lfs2); + + for (lfs2_size_t k = 0; k < N; k++) { + for (lfs2_size_t j = 0; j < N; j++) { + lfs2_mount(&lfs2, cfg) => 0; + lfs2_dir_open(&lfs2, &dir, "prickly-pear") => 0; + lfs2_dir_read(&lfs2, &dir, &info) => 1; + assert(info.type == LFS2_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs2_dir_read(&lfs2, &dir, &info) => 1; + assert(info.type == LFS2_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + // iterate over dirs < j + for (unsigned i = 0; i < j; i++) { + char path[1024]; + sprintf(path, "cactus%03d", i); + lfs2_dir_read(&lfs2, &dir, &info) => 1; + assert(info.type == LFS2_TYPE_DIR); + assert(strcmp(info.name, path) == 0); + } + + // remove k while iterating + char path[1024]; + sprintf(path, "prickly-pear/cactus%03d", k); + lfs2_remove(&lfs2, path) => 0; + + // iterate over dirs >= j + for (unsigned i = j; i < ((k >= j) ? N-1 : N); i++) { + char path[1024]; + sprintf(path, "cactus%03d", (k >= j && i >= k) ? i+1 : i); + lfs2_dir_read(&lfs2, &dir, &info) => 1; + assert(info.type == LFS2_TYPE_DIR); + assert(strcmp(info.name, path) == 0); + } + lfs2_dir_read(&lfs2, &dir, &info) => 0; + lfs2_dir_close(&lfs2, &dir) => 0; + + // recreate k + sprintf(path, "prickly-pear/cactus%03d", k); + lfs2_mkdir(&lfs2, path) => 0; + lfs2_unmount(&lfs2) => 0; + } + } +''' + [cases.test_dirs_other_errors] code = ''' lfs2_t lfs2;