forked from Imagelibrary/littlefs
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e251dd675 | ||
|
|
25b9a4af85 | ||
|
|
2acf939a00 | ||
|
|
d5a86fd28d | ||
|
|
2349ac8c96 | ||
|
|
0755b00c21 | ||
|
|
8365bbb7a2 | ||
|
|
16ceb67934 | ||
|
|
8434536f0a | ||
|
|
523319b685 | ||
|
|
ba250a3075 | ||
|
|
8c458fa6bd | ||
|
|
3149201ae5 | ||
|
|
6a43f3cdc3 | ||
|
|
d73fb8ef3c | ||
|
|
c1bf7cee84 | ||
|
|
b26bf3494c | ||
|
|
0115cf6b74 | ||
|
|
bff4dfd1b1 | ||
|
|
edaaaf88ea | ||
|
|
7d79423972 | ||
|
|
7782d3dfa3 | ||
|
|
f4a1bb328a | ||
|
|
9b8f802b43 | ||
|
|
a3d6bec5f0 | ||
|
|
0634d13e07 | ||
|
|
2105e502c5 | ||
|
|
b823728420 | ||
|
|
0d861b7916 | ||
|
|
26bee8ad36 | ||
|
|
8ed63b27be | ||
|
|
a666730044 | ||
|
|
47e738b788 | ||
|
|
81b0db0cdc | ||
|
|
63ab1ffb65 | ||
|
|
ca1081e7c4 | ||
|
|
76027f1502 | ||
|
|
61a1b0b496 | ||
|
|
ffafb9cbb1 | ||
|
|
5281a20f6c | ||
|
|
f55520380d | ||
|
|
936919d134 | ||
|
|
d2c3a47627 | ||
|
|
0320e7db0e | ||
|
|
caba4f31df | ||
|
|
152d03043c | ||
|
|
8d01895b32 | ||
|
|
0494ce7169 | ||
|
|
366100b140 |
50
.github/workflows/test.yml
vendored
50
.github/workflows/test.yml
vendored
@@ -374,6 +374,45 @@ jobs:
|
||||
run: |
|
||||
CFLAGS="$CFLAGS -DLFS_NO_INTRINSICS" make test
|
||||
|
||||
test-shrink:
|
||||
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-no-intrinsics
|
||||
run: |
|
||||
CFLAGS="$CFLAGS -DLFS_SHRINKNONRELOCATING" 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 \
|
||||
-DLFS_YES_TRACE \
|
||||
-DLFS_RAMBD_YES_TRACE \
|
||||
-DLFS_FILEBD_YES_TRACE \
|
||||
-DLFS_RAMBD_YES_TRACE" \
|
||||
make test
|
||||
|
||||
# run LFS_MULTIVERSION tests
|
||||
test-multiversion:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -431,8 +470,7 @@ jobs:
|
||||
TESTFLAGS="$TESTFLAGS --valgrind --context=1024 -Gdefault -Pnone" \
|
||||
make test
|
||||
|
||||
# test that compilation is warning free under clang
|
||||
# run with Clang, mostly to check for Clang-specific warnings
|
||||
# compile/run with Clang, mostly to check for Clang-specific warnings
|
||||
test-clang:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -446,12 +484,8 @@ jobs:
|
||||
python3 --version
|
||||
- name: test-clang
|
||||
run: |
|
||||
# override CFLAGS since Clang does not support -fcallgraph-info
|
||||
# and -ftrack-macro-expansions
|
||||
make \
|
||||
CC=clang \
|
||||
CFLAGS="$CFLAGS -MMD -g3 -I. -std=c99 -Wall -Wextra -pedantic" \
|
||||
test
|
||||
CC=clang \
|
||||
make test
|
||||
|
||||
# run benchmarks
|
||||
#
|
||||
|
||||
12
Makefile
12
Makefile
@@ -18,6 +18,12 @@ VALGRIND ?= valgrind
|
||||
GDB ?= gdb
|
||||
PERF ?= perf
|
||||
|
||||
# guess clang or gcc (clang sometimes masquerades as gcc because of
|
||||
# course it does)
|
||||
ifneq ($(shell $(CC) --version | grep clang),)
|
||||
NO_GCC = 1
|
||||
endif
|
||||
|
||||
SRC ?= $(filter-out $(wildcard *.t.* *.b.*),$(wildcard *.c))
|
||||
OBJ := $(SRC:%.c=$(BUILDDIR)/%.o)
|
||||
DEP := $(SRC:%.c=$(BUILDDIR)/%.d)
|
||||
@@ -59,12 +65,15 @@ BENCH_PERF := $(BENCH_RUNNER:%=%.perf)
|
||||
BENCH_TRACE := $(BENCH_RUNNER:%=%.trace)
|
||||
BENCH_CSV := $(BENCH_RUNNER:%=%.csv)
|
||||
|
||||
CFLAGS += -fcallgraph-info=su
|
||||
CFLAGS += -g3
|
||||
CFLAGS += -I.
|
||||
CFLAGS += -std=c99 -Wall -Wextra -pedantic
|
||||
CFLAGS += -Wmissing-prototypes
|
||||
ifndef NO_GCC
|
||||
CFLAGS += -fcallgraph-info=su
|
||||
CFLAGS += -ftrack-macro-expansion=0
|
||||
endif
|
||||
|
||||
ifdef DEBUG
|
||||
CFLAGS += -O0
|
||||
else
|
||||
@@ -466,6 +475,7 @@ benchmarks-diff: $(BENCH_CSV)
|
||||
# rules
|
||||
-include $(DEP)
|
||||
-include $(TEST_DEP)
|
||||
-include $(BENCH_DEP)
|
||||
.SUFFIXES:
|
||||
.SECONDARY:
|
||||
|
||||
|
||||
41
README.md
41
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
|
||||
lfs_mount(&lfs, 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
|
||||
|
||||
@@ -133,7 +133,7 @@ int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block,
|
||||
|
||||
int lfs_filebd_erase(const struct lfs_config *cfg, lfs_block_t block) {
|
||||
LFS_FILEBD_TRACE("lfs_filebd_erase(%p, 0x%"PRIx32" (%"PRIu32"))",
|
||||
(void*)cfg, block, ((lfs_file_t*)cfg->context)->cfg->erase_size);
|
||||
(void*)cfg, block, ((lfs_filebd_t*)cfg->context)->cfg->erase_size);
|
||||
lfs_filebd_t *bd = cfg->context;
|
||||
|
||||
// check if erase is valid
|
||||
|
||||
124
lfs.c
124
lfs.c
@@ -404,18 +404,15 @@ struct lfs_diskoff {
|
||||
|
||||
// operations on global state
|
||||
static inline void lfs_gstate_xor(lfs_gstate_t *a, const lfs_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 lfs_gstate_iszero(const lfs_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 LFS_READONLY
|
||||
@@ -831,9 +828,6 @@ static int lfs_dir_getread(lfs_t *lfs, const lfs_mdir_t *dir,
|
||||
size -= diff;
|
||||
continue;
|
||||
}
|
||||
|
||||
// rcache takes priority
|
||||
diff = lfs_min(diff, rcache->off-off);
|
||||
}
|
||||
|
||||
// load to cache, first condition can no longer fail
|
||||
@@ -2369,7 +2363,8 @@ fixmlist:;
|
||||
if (d->m.pair != pair) {
|
||||
for (int i = 0; i < attrcount; i++) {
|
||||
if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE &&
|
||||
d->id == lfs_tag_id(attrs[i].tag)) {
|
||||
d->id == lfs_tag_id(attrs[i].tag) &&
|
||||
d->type != LFS_TYPE_DIR) {
|
||||
d->m.pair[0] = LFS_BLOCK_NULL;
|
||||
d->m.pair[1] = LFS_BLOCK_NULL;
|
||||
} else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE &&
|
||||
@@ -2558,7 +2553,7 @@ static int lfs_dir_orphaningcommit(lfs_t *lfs, lfs_mdir_t *dir,
|
||||
if (err != LFS_ERR_NOENT) {
|
||||
if (lfs_gstate_hasorphans(&lfs->gstate)) {
|
||||
// next step, clean up orphans
|
||||
err = lfs_fs_preporphans(lfs, -hasparent);
|
||||
err = lfs_fs_preporphans(lfs, -(int8_t)hasparent);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
@@ -3725,13 +3720,8 @@ static lfs_soff_t lfs_file_seek_(lfs_t *lfs, lfs_file_t *file,
|
||||
|
||||
// if we're only reading and our new offset is still in the file's cache
|
||||
// we can avoid flushing and needing to reread the data
|
||||
if (
|
||||
#ifndef LFS_READONLY
|
||||
!(file->flags & LFS_F_WRITING)
|
||||
#else
|
||||
true
|
||||
#endif
|
||||
) {
|
||||
if ((file->flags & LFS_F_READING)
|
||||
&& file->off != lfs->cfg->block_size) {
|
||||
int oindex = lfs_ctz_index(lfs, &(lfs_off_t){file->pos});
|
||||
lfs_off_t noff = npos;
|
||||
int nindex = lfs_ctz_index(lfs, &noff);
|
||||
@@ -3939,7 +3929,9 @@ static int lfs_remove_(lfs_t *lfs, const char *path) {
|
||||
}
|
||||
|
||||
lfs->mlist = dir.next;
|
||||
if (lfs_tag_type3(tag) == LFS_TYPE_DIR) {
|
||||
if (lfs_gstate_hasorphans(&lfs->gstate)) {
|
||||
LFS_ASSERT(lfs_tag_type3(tag) == LFS_TYPE_DIR);
|
||||
|
||||
// fix orphan
|
||||
err = lfs_fs_preporphans(lfs, -1);
|
||||
if (err) {
|
||||
@@ -4083,8 +4075,10 @@ static int lfs_rename_(lfs_t *lfs, const char *oldpath, const char *newpath) {
|
||||
}
|
||||
|
||||
lfs->mlist = prevdir.next;
|
||||
if (prevtag != LFS_ERR_NOENT
|
||||
&& lfs_tag_type3(prevtag) == LFS_TYPE_DIR) {
|
||||
if (lfs_gstate_hasorphans(&lfs->gstate)) {
|
||||
LFS_ASSERT(prevtag != LFS_ERR_NOENT
|
||||
&& lfs_tag_type3(prevtag) == LFS_TYPE_DIR);
|
||||
|
||||
// fix orphan
|
||||
err = lfs_fs_preporphans(lfs, -1);
|
||||
if (err) {
|
||||
@@ -5228,7 +5222,9 @@ static int lfs_fs_gc_(lfs_t *lfs) {
|
||||
}
|
||||
|
||||
// try to populate the lookahead buffer, unless it's already full
|
||||
if (lfs->lookahead.size < 8*lfs->cfg->lookahead_size) {
|
||||
if (lfs->lookahead.size < lfs_min(
|
||||
8 * lfs->cfg->lookahead_size,
|
||||
lfs->block_count)) {
|
||||
err = lfs_alloc_scan(lfs);
|
||||
if (err) {
|
||||
return err;
|
||||
@@ -5240,40 +5236,64 @@ static int lfs_fs_gc_(lfs_t *lfs) {
|
||||
#endif
|
||||
|
||||
#ifndef LFS_READONLY
|
||||
#ifdef LFS_SHRINKNONRELOCATING
|
||||
static int lfs_shrink_checkblock(void *data, lfs_block_t block) {
|
||||
lfs_size_t threshold = *((lfs_size_t*)data);
|
||||
if (block >= threshold) {
|
||||
return LFS_ERR_NOTEMPTY;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int lfs_fs_grow_(lfs_t *lfs, lfs_size_t block_count) {
|
||||
int err;
|
||||
|
||||
if (block_count == lfs->block_count) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#ifndef LFS_SHRINKNONRELOCATING
|
||||
// shrinking is not supported
|
||||
LFS_ASSERT(block_count >= lfs->block_count);
|
||||
|
||||
if (block_count > lfs->block_count) {
|
||||
lfs->block_count = block_count;
|
||||
|
||||
// fetch the root
|
||||
lfs_mdir_t root;
|
||||
int err = lfs_dir_fetch(lfs, &root, lfs->root);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// update the superblock
|
||||
lfs_superblock_t superblock;
|
||||
lfs_stag_t tag = lfs_dir_get(lfs, &root, LFS_MKTAG(0x7ff, 0x3ff, 0),
|
||||
LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
|
||||
&superblock);
|
||||
if (tag < 0) {
|
||||
return tag;
|
||||
}
|
||||
lfs_superblock_fromle32(&superblock);
|
||||
|
||||
superblock.block_count = lfs->block_count;
|
||||
|
||||
lfs_superblock_tole32(&superblock);
|
||||
err = lfs_dir_commit(lfs, &root, LFS_MKATTRS(
|
||||
{tag, &superblock}));
|
||||
#endif
|
||||
#ifdef LFS_SHRINKNONRELOCATING
|
||||
if (block_count < lfs->block_count) {
|
||||
err = lfs_fs_traverse_(lfs, lfs_shrink_checkblock, &block_count, true);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
lfs->block_count = block_count;
|
||||
|
||||
// fetch the root
|
||||
lfs_mdir_t root;
|
||||
err = lfs_dir_fetch(lfs, &root, lfs->root);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// update the superblock
|
||||
lfs_superblock_t superblock;
|
||||
lfs_stag_t tag = lfs_dir_get(lfs, &root, LFS_MKTAG(0x7ff, 0x3ff, 0),
|
||||
LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
|
||||
&superblock);
|
||||
if (tag < 0) {
|
||||
return tag;
|
||||
}
|
||||
lfs_superblock_fromle32(&superblock);
|
||||
|
||||
superblock.block_count = lfs->block_count;
|
||||
|
||||
lfs_superblock_tole32(&superblock);
|
||||
err = lfs_dir_commit(lfs, &root, LFS_MKATTRS(
|
||||
{tag, &superblock}));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -6293,7 +6313,7 @@ lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) {
|
||||
|
||||
lfs_soff_t res = lfs_file_size_(lfs, file);
|
||||
|
||||
LFS_TRACE("lfs_file_size -> %"PRId32, res);
|
||||
LFS_TRACE("lfs_file_size -> %"PRIu32, res);
|
||||
LFS_UNLOCK(lfs->cfg);
|
||||
return res;
|
||||
}
|
||||
|
||||
8
lfs.h
8
lfs.h
@@ -21,7 +21,7 @@ extern "C"
|
||||
// Software library version
|
||||
// Major (top-nibble), incremented on backwards incompatible changes
|
||||
// Minor (bottom-nibble), incremented on feature additions
|
||||
#define LFS_VERSION 0x0002000a
|
||||
#define LFS_VERSION 0x0002000b
|
||||
#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
|
||||
#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0))
|
||||
|
||||
@@ -766,7 +766,11 @@ int lfs_fs_gc(lfs_t *lfs);
|
||||
// Grows the filesystem to a new size, updating the superblock with the new
|
||||
// block count.
|
||||
//
|
||||
// Note: This is irreversible.
|
||||
// If LFS_SHRINKNONRELOCATING is defined, this function will also accept
|
||||
// block_counts smaller than the current configuration, after checking
|
||||
// that none of the blocks that are being removed are in use.
|
||||
// Note that littlefs's pseudorandom block allocation means that
|
||||
// this is very unlikely to work in the general case.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count);
|
||||
|
||||
20
lfs_util.h
20
lfs_util.h
@@ -195,10 +195,10 @@ static inline uint32_t lfs_fromle32(uint32_t a) {
|
||||
(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
|
||||
return __builtin_bswap32(a);
|
||||
#else
|
||||
return (((uint8_t*)&a)[0] << 0) |
|
||||
(((uint8_t*)&a)[1] << 8) |
|
||||
(((uint8_t*)&a)[2] << 16) |
|
||||
(((uint8_t*)&a)[3] << 24);
|
||||
return ((uint32_t)((uint8_t*)&a)[0] << 0) |
|
||||
((uint32_t)((uint8_t*)&a)[1] << 8) |
|
||||
((uint32_t)((uint8_t*)&a)[2] << 16) |
|
||||
((uint32_t)((uint8_t*)&a)[3] << 24);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -218,10 +218,10 @@ static inline uint32_t lfs_frombe32(uint32_t a) {
|
||||
(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
|
||||
return a;
|
||||
#else
|
||||
return (((uint8_t*)&a)[0] << 24) |
|
||||
(((uint8_t*)&a)[1] << 16) |
|
||||
(((uint8_t*)&a)[2] << 8) |
|
||||
(((uint8_t*)&a)[3] << 0);
|
||||
return ((uint32_t)((uint8_t*)&a)[0] << 24) |
|
||||
((uint32_t)((uint8_t*)&a)[1] << 16) |
|
||||
((uint32_t)((uint8_t*)&a)[2] << 8) |
|
||||
((uint32_t)((uint8_t*)&a)[3] << 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -231,8 +231,8 @@ static inline uint32_t lfs_tobe32(uint32_t a) {
|
||||
|
||||
// Calculate CRC-32 with polynomial = 0x04c11db7
|
||||
#ifdef LFS_CRC
|
||||
uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) {
|
||||
return LFS_CRC(crc, buffer, size)
|
||||
static inline uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) {
|
||||
return LFS_CRC(crc, buffer, size);
|
||||
}
|
||||
#else
|
||||
uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size);
|
||||
|
||||
@@ -123,8 +123,13 @@ typedef struct bench_id {
|
||||
|
||||
|
||||
// bench suites are linked into a custom ld section
|
||||
#if defined(__APPLE__)
|
||||
extern struct bench_suite __start__bench_suites __asm("section$start$__DATA$_bench_suites");
|
||||
extern struct bench_suite __stop__bench_suites __asm("section$end$__DATA$_bench_suites");
|
||||
#else
|
||||
extern struct bench_suite __start__bench_suites;
|
||||
extern struct bench_suite __stop__bench_suites;
|
||||
#endif
|
||||
|
||||
const struct bench_suite *bench_suites = &__start__bench_suites;
|
||||
#define BENCH_SUITE_COUNT \
|
||||
|
||||
@@ -136,8 +136,13 @@ typedef struct test_id {
|
||||
|
||||
|
||||
// test suites are linked into a custom ld section
|
||||
#if defined(__APPLE__)
|
||||
extern struct test_suite __start__test_suites __asm("section$start$__DATA$_test_suites");
|
||||
extern struct test_suite __stop__test_suites __asm("section$end$__DATA$_test_suites");
|
||||
#else
|
||||
extern struct test_suite __start__test_suites;
|
||||
extern struct test_suite __stop__test_suites;
|
||||
#endif
|
||||
|
||||
const struct test_suite *test_suites = &__start__test_suites;
|
||||
#define TEST_SUITE_COUNT \
|
||||
|
||||
@@ -404,12 +404,15 @@ def compile(bench_paths, **args):
|
||||
f.writeln()
|
||||
|
||||
# create suite struct
|
||||
#
|
||||
f.writeln('#if defined(__APPLE__)')
|
||||
f.writeln('__attribute__((section("__DATA,_bench_suites")))')
|
||||
f.writeln('#else')
|
||||
# note we place this in the custom bench_suites section with
|
||||
# minimum alignment, otherwise GCC ups the alignment to
|
||||
# 32-bytes for some reason
|
||||
f.writeln('__attribute__((section("_bench_suites"), '
|
||||
'aligned(1)))')
|
||||
f.writeln('#endif')
|
||||
f.writeln('const struct bench_suite __bench__%s__suite = {'
|
||||
% suite.name)
|
||||
f.writeln(4*' '+'.name = "%s",' % suite.name)
|
||||
|
||||
@@ -73,7 +73,7 @@ def changefile(from_prefix, to_prefix, from_path, to_path, *,
|
||||
shutil.copystat(from_path, to_path)
|
||||
|
||||
if to_path_temp:
|
||||
os.rename(to_path, from_path)
|
||||
shutil.move(to_path, from_path)
|
||||
elif from_path != '-':
|
||||
os.remove(from_path)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -102,9 +102,9 @@ class TestCase:
|
||||
# the runner itself.
|
||||
for v_ in csplit(v):
|
||||
m = re.search(r'\brange\b\s*\('
|
||||
'(?P<start>[^,\s]*)'
|
||||
'\s*(?:,\s*(?P<stop>[^,\s]*)'
|
||||
'\s*(?:,\s*(?P<step>[^,\s]*)\s*)?)?\)',
|
||||
r'(?P<start>[^,\s]*)'
|
||||
r'\s*(?:,\s*(?P<stop>[^,\s]*)'
|
||||
r'\s*(?:,\s*(?P<step>[^,\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<case>\[\s*cases\s*\.\s*(?P<name>\w+)\s*\])'
|
||||
'|' '(?P<code>code\s*=)',
|
||||
r'(?P<case>\[\s*cases\s*\.\s*(?P<name>\w+)\s*\])'
|
||||
r'|' r'(?P<code>code\s*=)',
|
||||
line)
|
||||
if match and match.group('case'):
|
||||
case_linenos.append((i+1, match.group('name')))
|
||||
@@ -412,12 +412,15 @@ def compile(test_paths, **args):
|
||||
f.writeln()
|
||||
|
||||
# create suite struct
|
||||
#
|
||||
f.writeln('#if defined(__APPLE__)')
|
||||
f.writeln('__attribute__((section("__DATA,_test_suites")))')
|
||||
f.writeln('#else')
|
||||
# note we place this in the custom test_suites section with
|
||||
# minimum alignment, otherwise GCC ups the alignment to
|
||||
# 32-bytes for some reason
|
||||
f.writeln('__attribute__((section("_test_suites"), '
|
||||
'aligned(1)))')
|
||||
f.writeln('#endif')
|
||||
f.writeln('const struct test_suite __test__%s__suite = {'
|
||||
% suite.name)
|
||||
f.writeln(4*' '+'.name = "%s",' % suite.name)
|
||||
@@ -602,9 +605,9 @@ def find_perms(runner_, ids=[], **args):
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
pattern = re.compile(
|
||||
'^(?P<case>[^\s]+)'
|
||||
'\s+(?P<flags>[^\s]+)'
|
||||
'\s+(?P<filtered>\d+)/(?P<perms>\d+)')
|
||||
r'^(?P<case>[^\s]+)'
|
||||
r'\s+(?P<flags>[^\s]+)'
|
||||
r'\s+(?P<filtered>\d+)/(?P<perms>\d+)')
|
||||
# skip the first line
|
||||
for line in it.islice(proc.stdout, 1, None):
|
||||
m = pattern.match(line)
|
||||
@@ -632,8 +635,8 @@ def find_perms(runner_, ids=[], **args):
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
pattern = re.compile(
|
||||
'^(?P<case>[^\s]+)'
|
||||
'\s+(?P<path>[^:]+):(?P<lineno>\d+)')
|
||||
r'^(?P<case>[^\s]+)'
|
||||
r'\s+(?P<path>[^:]+):(?P<lineno>\d+)')
|
||||
# skip the first line
|
||||
for line in it.islice(proc.stdout, 1, None):
|
||||
m = pattern.match(line)
|
||||
@@ -676,8 +679,8 @@ def find_path(runner_, id, **args):
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
pattern = re.compile(
|
||||
'^(?P<case>[^\s]+)'
|
||||
'\s+(?P<path>[^:]+):(?P<lineno>\d+)')
|
||||
r'^(?P<case>[^\s]+)'
|
||||
r'\s+(?P<path>[^:]+):(?P<lineno>\d+)')
|
||||
# skip the first line
|
||||
for line in it.islice(proc.stdout, 1, None):
|
||||
m = pattern.match(line)
|
||||
@@ -706,7 +709,7 @@ def find_defines(runner_, id, **args):
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
defines = co.OrderedDict()
|
||||
pattern = re.compile('^(?P<define>\w+)=(?P<value>.+)')
|
||||
pattern = re.compile(r'^(?P<define>\w+)=(?P<value>.+)')
|
||||
for line in proc.stdout:
|
||||
m = pattern.match(line)
|
||||
if m:
|
||||
@@ -781,12 +784,12 @@ def run_stage(name, runner_, ids, stdout_, trace_, output_, **args):
|
||||
failures = []
|
||||
killed = False
|
||||
|
||||
pattern = re.compile('^(?:'
|
||||
'(?P<op>running|finished|skipped|powerloss) '
|
||||
'(?P<id>(?P<case>[^:]+)[^\s]*)'
|
||||
'|' '(?P<path>[^:]+):(?P<lineno>\d+):(?P<op_>assert):'
|
||||
' *(?P<message>.*)'
|
||||
')$')
|
||||
pattern = re.compile(r'^(?:'
|
||||
r'(?P<op>running|finished|skipped|powerloss) '
|
||||
r'(?P<id>(?P<case>[^:]+)[^\s]*)'
|
||||
r'|' r'(?P<path>[^:]+):(?P<lineno>\d+):(?P<op_>assert):'
|
||||
r' *(?P<message>.*)'
|
||||
r')$')
|
||||
locals = th.local()
|
||||
children = set()
|
||||
|
||||
|
||||
@@ -725,6 +725,82 @@ code = '''
|
||||
lfs_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
[cases.test_dirs_remove_read]
|
||||
defines.N = 10
|
||||
if = 'N < BLOCK_COUNT/2'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfs_format(&lfs, cfg) => 0;
|
||||
lfs_mount(&lfs, cfg) => 0;
|
||||
lfs_mkdir(&lfs, "prickly-pear") => 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
char path[1024];
|
||||
sprintf(path, "prickly-pear/cactus%03d", i);
|
||||
lfs_mkdir(&lfs, path) => 0;
|
||||
}
|
||||
lfs_dir_t dir;
|
||||
lfs_dir_open(&lfs, &dir, "prickly-pear") => 0;
|
||||
struct lfs_info info;
|
||||
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
for (int i = 0; i < N; i++) {
|
||||
char path[1024];
|
||||
sprintf(path, "cactus%03d", i);
|
||||
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
}
|
||||
lfs_dir_read(&lfs, &dir, &info) => 0;
|
||||
lfs_dir_close(&lfs, &dir) => 0;
|
||||
lfs_unmount(&lfs);
|
||||
|
||||
for (lfs_size_t k = 0; k < N; k++) {
|
||||
for (lfs_size_t j = 0; j < N; j++) {
|
||||
lfs_mount(&lfs, cfg) => 0;
|
||||
lfs_dir_open(&lfs, &dir, "prickly-pear") => 0;
|
||||
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||
assert(info.type == LFS_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);
|
||||
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
}
|
||||
|
||||
// remove k while iterating
|
||||
char path[1024];
|
||||
sprintf(path, "prickly-pear/cactus%03d", k);
|
||||
lfs_remove(&lfs, 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);
|
||||
lfs_dir_read(&lfs, &dir, &info) => 1;
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
}
|
||||
lfs_dir_read(&lfs, &dir, &info) => 0;
|
||||
lfs_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// recreate k
|
||||
sprintf(path, "prickly-pear/cactus%03d", k);
|
||||
lfs_mkdir(&lfs, path) => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
[cases.test_dirs_other_errors]
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
|
||||
@@ -207,7 +207,8 @@ code = '''
|
||||
[cases.test_orphans_reentrant]
|
||||
reentrant = true
|
||||
# TODO fix this case, caused by non-DAG trees
|
||||
if = '!(DEPTH == 3 && CACHE_SIZE != 64)'
|
||||
# NOTE the second condition is required
|
||||
if = '!(DEPTH == 3 && CACHE_SIZE != 64) && 2*FILES < BLOCK_COUNT'
|
||||
defines = [
|
||||
{FILES=6, DEPTH=1, CYCLES=20},
|
||||
{FILES=26, DEPTH=1, CYCLES=20},
|
||||
@@ -271,3 +272,69 @@ code = '''
|
||||
lfs_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# non-reentrant testing for orphans, this is the same as reentrant
|
||||
# testing, but we test way more states than we could under powerloss
|
||||
[cases.test_orphans_nonreentrant]
|
||||
# TODO fix this case, caused by non-DAG trees
|
||||
# NOTE the second condition is required
|
||||
if = '!(DEPTH == 3 && CACHE_SIZE != 64) && 2*FILES < BLOCK_COUNT'
|
||||
defines = [
|
||||
{FILES=6, DEPTH=1, CYCLES=2000},
|
||||
{FILES=26, DEPTH=1, CYCLES=2000},
|
||||
{FILES=3, DEPTH=3, CYCLES=2000},
|
||||
]
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfs_format(&lfs, cfg) => 0;
|
||||
lfs_mount(&lfs, cfg) => 0;
|
||||
|
||||
uint32_t prng = 1;
|
||||
const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
|
||||
for (unsigned i = 0; i < CYCLES; i++) {
|
||||
// create random path
|
||||
char full_path[256];
|
||||
for (unsigned d = 0; d < DEPTH; d++) {
|
||||
sprintf(&full_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]);
|
||||
}
|
||||
|
||||
// if it does not exist, we create it, else we destroy
|
||||
struct lfs_info info;
|
||||
int res = lfs_stat(&lfs, full_path, &info);
|
||||
if (res == LFS_ERR_NOENT) {
|
||||
// create each directory in turn, ignore if dir already exists
|
||||
for (unsigned d = 0; d < DEPTH; d++) {
|
||||
char path[1024];
|
||||
strcpy(path, full_path);
|
||||
path[2*d+2] = '\0';
|
||||
int err = lfs_mkdir(&lfs, path);
|
||||
assert(!err || err == LFS_ERR_EXIST);
|
||||
}
|
||||
|
||||
for (unsigned d = 0; d < DEPTH; d++) {
|
||||
char path[1024];
|
||||
strcpy(path, full_path);
|
||||
path[2*d+2] = '\0';
|
||||
lfs_stat(&lfs, path, &info) => 0;
|
||||
assert(strcmp(info.name, &path[2*d+1]) == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
}
|
||||
} else {
|
||||
// is valid dir?
|
||||
assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
|
||||
// try to delete path in reverse order, ignore if dir is not empty
|
||||
for (int d = DEPTH-1; d >= 0; d--) {
|
||||
char path[1024];
|
||||
strcpy(path, full_path);
|
||||
path[2*d+2] = '\0';
|
||||
int err = lfs_remove(&lfs, path);
|
||||
assert(!err || err == LFS_ERR_NOTEMPTY);
|
||||
}
|
||||
|
||||
lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT;
|
||||
}
|
||||
}
|
||||
lfs_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
|
||||
@@ -341,3 +341,171 @@ code = '''
|
||||
}
|
||||
lfs_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# non-reentrant testing for orphans, this is the same as reentrant
|
||||
# testing, but we test way more states than we could under powerloss
|
||||
[cases.test_relocations_nonreentrant]
|
||||
# TODO fix this case, caused by non-DAG trees
|
||||
# NOTE the second condition is required
|
||||
if = '!(DEPTH == 3 && CACHE_SIZE != 64) && 2*FILES < BLOCK_COUNT'
|
||||
defines = [
|
||||
{FILES=6, DEPTH=1, CYCLES=2000, BLOCK_CYCLES=1},
|
||||
{FILES=26, DEPTH=1, CYCLES=2000, BLOCK_CYCLES=1},
|
||||
{FILES=3, DEPTH=3, CYCLES=2000, BLOCK_CYCLES=1},
|
||||
]
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfs_format(&lfs, cfg) => 0;
|
||||
lfs_mount(&lfs, cfg) => 0;
|
||||
|
||||
uint32_t prng = 1;
|
||||
const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
|
||||
for (unsigned i = 0; i < CYCLES; i++) {
|
||||
// create random path
|
||||
char full_path[256];
|
||||
for (unsigned d = 0; d < DEPTH; d++) {
|
||||
sprintf(&full_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]);
|
||||
}
|
||||
|
||||
// if it does not exist, we create it, else we destroy
|
||||
struct lfs_info info;
|
||||
int res = lfs_stat(&lfs, full_path, &info);
|
||||
if (res == LFS_ERR_NOENT) {
|
||||
// create each directory in turn, ignore if dir already exists
|
||||
for (unsigned d = 0; d < DEPTH; d++) {
|
||||
char path[1024];
|
||||
strcpy(path, full_path);
|
||||
path[2*d+2] = '\0';
|
||||
int err = lfs_mkdir(&lfs, path);
|
||||
assert(!err || err == LFS_ERR_EXIST);
|
||||
}
|
||||
|
||||
for (unsigned d = 0; d < DEPTH; d++) {
|
||||
char path[1024];
|
||||
strcpy(path, full_path);
|
||||
path[2*d+2] = '\0';
|
||||
lfs_stat(&lfs, path, &info) => 0;
|
||||
assert(strcmp(info.name, &path[2*d+1]) == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
}
|
||||
} else {
|
||||
// is valid dir?
|
||||
assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
|
||||
// try to delete path in reverse order, ignore if dir is not empty
|
||||
for (unsigned d = DEPTH-1; d+1 > 0; d--) {
|
||||
char path[1024];
|
||||
strcpy(path, full_path);
|
||||
path[2*d+2] = '\0';
|
||||
int err = lfs_remove(&lfs, path);
|
||||
assert(!err || err == LFS_ERR_NOTEMPTY);
|
||||
}
|
||||
|
||||
lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT;
|
||||
}
|
||||
}
|
||||
lfs_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# non-reentrant testing for relocations, but now with random renames!
|
||||
[cases.test_relocations_nonreentrant_renames]
|
||||
# TODO fix this case, caused by non-DAG trees
|
||||
# NOTE the second condition is required
|
||||
if = '!(DEPTH == 3 && CACHE_SIZE != 64) && 2*FILES < BLOCK_COUNT'
|
||||
defines = [
|
||||
{FILES=6, DEPTH=1, CYCLES=2000, BLOCK_CYCLES=1},
|
||||
{FILES=26, DEPTH=1, CYCLES=2000, BLOCK_CYCLES=1},
|
||||
{FILES=3, DEPTH=3, CYCLES=2000, BLOCK_CYCLES=1},
|
||||
]
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfs_format(&lfs, cfg) => 0;
|
||||
lfs_mount(&lfs, cfg) => 0;
|
||||
|
||||
uint32_t prng = 1;
|
||||
const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
|
||||
for (unsigned i = 0; i < CYCLES; i++) {
|
||||
// create random path
|
||||
char full_path[256];
|
||||
for (unsigned d = 0; d < DEPTH; d++) {
|
||||
sprintf(&full_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]);
|
||||
}
|
||||
|
||||
// if it does not exist, we create it, else we destroy
|
||||
struct lfs_info info;
|
||||
int res = lfs_stat(&lfs, full_path, &info);
|
||||
assert(!res || res == LFS_ERR_NOENT);
|
||||
if (res == LFS_ERR_NOENT) {
|
||||
// create each directory in turn, ignore if dir already exists
|
||||
for (unsigned d = 0; d < DEPTH; d++) {
|
||||
char path[1024];
|
||||
strcpy(path, full_path);
|
||||
path[2*d+2] = '\0';
|
||||
int err = lfs_mkdir(&lfs, path);
|
||||
assert(!err || err == LFS_ERR_EXIST);
|
||||
}
|
||||
|
||||
for (unsigned d = 0; d < DEPTH; d++) {
|
||||
char path[1024];
|
||||
strcpy(path, full_path);
|
||||
path[2*d+2] = '\0';
|
||||
lfs_stat(&lfs, path, &info) => 0;
|
||||
assert(strcmp(info.name, &path[2*d+1]) == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
}
|
||||
} else {
|
||||
assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
|
||||
// create new random path
|
||||
char new_path[256];
|
||||
for (unsigned d = 0; d < DEPTH; d++) {
|
||||
sprintf(&new_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]);
|
||||
}
|
||||
|
||||
// if new path does not exist, rename, otherwise destroy
|
||||
res = lfs_stat(&lfs, new_path, &info);
|
||||
assert(!res || res == LFS_ERR_NOENT);
|
||||
if (res == LFS_ERR_NOENT) {
|
||||
// stop once some dir is renamed
|
||||
for (unsigned d = 0; d < DEPTH; d++) {
|
||||
char path[1024];
|
||||
strcpy(&path[2*d], &full_path[2*d]);
|
||||
path[2*d+2] = '\0';
|
||||
strcpy(&path[128+2*d], &new_path[2*d]);
|
||||
path[128+2*d+2] = '\0';
|
||||
int err = lfs_rename(&lfs, path, path+128);
|
||||
assert(!err || err == LFS_ERR_NOTEMPTY);
|
||||
if (!err) {
|
||||
strcpy(path, path+128);
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned d = 0; d < DEPTH; d++) {
|
||||
char path[1024];
|
||||
strcpy(path, new_path);
|
||||
path[2*d+2] = '\0';
|
||||
lfs_stat(&lfs, path, &info) => 0;
|
||||
assert(strcmp(info.name, &path[2*d+1]) == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
}
|
||||
|
||||
lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT;
|
||||
} else {
|
||||
// try to delete path in reverse order,
|
||||
// ignore if dir is not empty
|
||||
for (unsigned d = DEPTH-1; d+1 > 0; d--) {
|
||||
char path[1024];
|
||||
strcpy(path, full_path);
|
||||
path[2*d+2] = '\0';
|
||||
int err = lfs_remove(&lfs, path);
|
||||
assert(!err || err == LFS_ERR_NOTEMPTY);
|
||||
}
|
||||
|
||||
lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
lfs_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
@@ -137,6 +137,130 @@ code = '''
|
||||
lfs_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# boundary seek and reads
|
||||
[cases.test_seek_boundary_read]
|
||||
defines.COUNT = 132
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfs_format(&lfs, cfg) => 0;
|
||||
lfs_mount(&lfs, cfg) => 0;
|
||||
lfs_file_t file;
|
||||
lfs_file_open(&lfs, &file, "kitty",
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
|
||||
size_t size = strlen("kittycatcat");
|
||||
uint8_t buffer[1024];
|
||||
memcpy(buffer, "kittycatcat", size);
|
||||
for (int j = 0; j < COUNT; j++) {
|
||||
lfs_file_write(&lfs, &file, buffer, size);
|
||||
}
|
||||
lfs_file_close(&lfs, &file) => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
lfs_mount(&lfs, cfg) => 0;
|
||||
lfs_file_open(&lfs, &file, "kitty", LFS_O_RDONLY) => 0;
|
||||
|
||||
size = strlen("kittycatcat");
|
||||
const lfs_soff_t offsets[] = {
|
||||
512,
|
||||
1024-4,
|
||||
512+1,
|
||||
1024-4+1,
|
||||
512-1,
|
||||
1024-4-1,
|
||||
|
||||
512-strlen("kittycatcat"),
|
||||
1024-4-strlen("kittycatcat"),
|
||||
512-strlen("kittycatcat")+1,
|
||||
1024-4-strlen("kittycatcat")+1,
|
||||
512-strlen("kittycatcat")-1,
|
||||
1024-4-strlen("kittycatcat")-1,
|
||||
|
||||
strlen("kittycatcat")*(COUNT-2)-1,
|
||||
};
|
||||
|
||||
for (unsigned i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) {
|
||||
lfs_soff_t off = offsets[i];
|
||||
// read @ offset
|
||||
lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
|
||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
||||
memcmp(buffer,
|
||||
&"kittycatcatkittycatcat"[off % strlen("kittycatcat")],
|
||||
size) => 0;
|
||||
// read after
|
||||
lfs_file_seek(&lfs, &file, off+strlen("kittycatcat")+1, LFS_SEEK_SET)
|
||||
=> off+strlen("kittycatcat")+1;
|
||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
||||
memcmp(buffer,
|
||||
&"kittycatcatkittycatcat"[(off+1) % strlen("kittycatcat")],
|
||||
size) => 0;
|
||||
// read before
|
||||
lfs_file_seek(&lfs, &file, off-strlen("kittycatcat")-1, LFS_SEEK_SET)
|
||||
=> off-strlen("kittycatcat")-1;
|
||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
||||
memcmp(buffer,
|
||||
&"kittycatcatkittycatcat"[(off-1) % strlen("kittycatcat")],
|
||||
size) => 0;
|
||||
|
||||
// read @ 0
|
||||
lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
|
||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
// read @ offset
|
||||
lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
|
||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
||||
memcmp(buffer,
|
||||
&"kittycatcatkittycatcat"[off % strlen("kittycatcat")],
|
||||
size) => 0;
|
||||
// read after
|
||||
lfs_file_seek(&lfs, &file, off+strlen("kittycatcat")+1, LFS_SEEK_SET)
|
||||
=> off+strlen("kittycatcat")+1;
|
||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
||||
memcmp(buffer,
|
||||
&"kittycatcatkittycatcat"[(off+1) % strlen("kittycatcat")],
|
||||
size) => 0;
|
||||
// read before
|
||||
lfs_file_seek(&lfs, &file, off-strlen("kittycatcat")-1, LFS_SEEK_SET)
|
||||
=> off-strlen("kittycatcat")-1;
|
||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
||||
memcmp(buffer,
|
||||
&"kittycatcatkittycatcat"[(off-1) % strlen("kittycatcat")],
|
||||
size) => 0;
|
||||
|
||||
// sync
|
||||
lfs_file_sync(&lfs, &file) => 0;
|
||||
|
||||
// read @ 0
|
||||
lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
|
||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
// read @ offset
|
||||
lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
|
||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
||||
memcmp(buffer,
|
||||
&"kittycatcatkittycatcat"[off % strlen("kittycatcat")],
|
||||
size) => 0;
|
||||
// read after
|
||||
lfs_file_seek(&lfs, &file, off+strlen("kittycatcat")+1, LFS_SEEK_SET)
|
||||
=> off+strlen("kittycatcat")+1;
|
||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
||||
memcmp(buffer,
|
||||
&"kittycatcatkittycatcat"[(off+1) % strlen("kittycatcat")],
|
||||
size) => 0;
|
||||
// read before
|
||||
lfs_file_seek(&lfs, &file, off-strlen("kittycatcat")-1, LFS_SEEK_SET)
|
||||
=> off-strlen("kittycatcat")-1;
|
||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
||||
memcmp(buffer,
|
||||
&"kittycatcatkittycatcat"[(off-1) % strlen("kittycatcat")],
|
||||
size) => 0;
|
||||
}
|
||||
|
||||
lfs_file_close(&lfs, &file) => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# boundary seek and writes
|
||||
[cases.test_seek_boundary_write]
|
||||
defines.COUNT = 132
|
||||
@@ -160,31 +284,54 @@ code = '''
|
||||
lfs_file_open(&lfs, &file, "kitty", LFS_O_RDWR) => 0;
|
||||
|
||||
size = strlen("hedgehoghog");
|
||||
const lfs_soff_t offsets[] = {512, 1020, 513, 1021, 511, 1019, 1441};
|
||||
const lfs_soff_t offsets[] = {
|
||||
512,
|
||||
1024-4,
|
||||
512+1,
|
||||
1024-4+1,
|
||||
512-1,
|
||||
1024-4-1,
|
||||
|
||||
512-strlen("kittycatcat"),
|
||||
1024-4-strlen("kittycatcat"),
|
||||
512-strlen("kittycatcat")+1,
|
||||
1024-4-strlen("kittycatcat")+1,
|
||||
512-strlen("kittycatcat")-1,
|
||||
1024-4-strlen("kittycatcat")-1,
|
||||
|
||||
strlen("kittycatcat")*(COUNT-2)-1,
|
||||
};
|
||||
|
||||
for (unsigned i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) {
|
||||
lfs_soff_t off = offsets[i];
|
||||
// write @ offset
|
||||
memcpy(buffer, "hedgehoghog", size);
|
||||
lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
|
||||
lfs_file_write(&lfs, &file, buffer, size) => size;
|
||||
|
||||
// read @ offset
|
||||
lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
|
||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
||||
memcmp(buffer, "hedgehoghog", size) => 0;
|
||||
|
||||
// read @ 0
|
||||
lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
|
||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
// read @ offset
|
||||
lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
|
||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
||||
memcmp(buffer, "hedgehoghog", size) => 0;
|
||||
|
||||
lfs_file_sync(&lfs, &file) => 0;
|
||||
|
||||
// read @ 0
|
||||
lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
|
||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
// read @ offset
|
||||
lfs_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
|
||||
lfs_file_read(&lfs, &file, buffer, size) => size;
|
||||
memcmp(buffer, "hedgehoghog", size) => 0;
|
||||
|
||||
109
tests/test_shrink.toml
Normal file
109
tests/test_shrink.toml
Normal file
@@ -0,0 +1,109 @@
|
||||
# simple shrink
|
||||
[cases.test_shrink_simple]
|
||||
defines.BLOCK_COUNT = [10, 15, 20]
|
||||
defines.AFTER_BLOCK_COUNT = [5, 10, 15, 19]
|
||||
|
||||
if = "AFTER_BLOCK_COUNT <= BLOCK_COUNT"
|
||||
code = '''
|
||||
#ifdef LFS_SHRINKNONRELOCATING
|
||||
lfs_t lfs;
|
||||
lfs_format(&lfs, cfg) => 0;
|
||||
lfs_mount(&lfs, cfg) => 0;
|
||||
lfs_fs_grow(&lfs, AFTER_BLOCK_COUNT) => 0;
|
||||
lfs_unmount(&lfs);
|
||||
if (BLOCK_COUNT != AFTER_BLOCK_COUNT) {
|
||||
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
|
||||
}
|
||||
lfs_t lfs2 = lfs;
|
||||
struct lfs_config cfg2 = *cfg;
|
||||
cfg2.block_count = AFTER_BLOCK_COUNT;
|
||||
lfs2.cfg = &cfg2;
|
||||
lfs_mount(&lfs2, &cfg2) => 0;
|
||||
lfs_unmount(&lfs2) => 0;
|
||||
#endif
|
||||
'''
|
||||
|
||||
# shrinking full
|
||||
[cases.test_shrink_full]
|
||||
defines.BLOCK_COUNT = [10, 15, 20]
|
||||
defines.AFTER_BLOCK_COUNT = [5, 7, 10, 12, 15, 17, 20]
|
||||
defines.FILES_COUNT = [7, 8, 9, 10]
|
||||
if = "AFTER_BLOCK_COUNT <= BLOCK_COUNT && FILES_COUNT + 2 < BLOCK_COUNT"
|
||||
code = '''
|
||||
#ifdef LFS_SHRINKNONRELOCATING
|
||||
lfs_t lfs;
|
||||
lfs_format(&lfs, cfg) => 0;
|
||||
// create FILES_COUNT files of BLOCK_SIZE - 50 bytes (to avoid inlining)
|
||||
lfs_mount(&lfs, cfg) => 0;
|
||||
for (int i = 0; i < FILES_COUNT + 1; i++) {
|
||||
lfs_file_t file;
|
||||
char path[1024];
|
||||
sprintf(path, "file_%03d", i);
|
||||
lfs_file_open(&lfs, &file, path,
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
char wbuffer[BLOCK_SIZE];
|
||||
memset(wbuffer, 'b', BLOCK_SIZE);
|
||||
// Ensure one block is taken per file, but that files are not inlined.
|
||||
lfs_size_t size = BLOCK_SIZE - 0x40;
|
||||
sprintf(wbuffer, "Hi %03d", i);
|
||||
lfs_file_write(&lfs, &file, wbuffer, size) => size;
|
||||
lfs_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
|
||||
int err = lfs_fs_grow(&lfs, AFTER_BLOCK_COUNT);
|
||||
if (err == 0) {
|
||||
for (int i = 0; i < FILES_COUNT + 1; i++) {
|
||||
lfs_file_t file;
|
||||
char path[1024];
|
||||
sprintf(path, "file_%03d", i);
|
||||
lfs_file_open(&lfs, &file, path,
|
||||
LFS_O_RDONLY ) => 0;
|
||||
lfs_size_t size = BLOCK_SIZE - 0x40;
|
||||
char wbuffer[size];
|
||||
char wbuffer_ref[size];
|
||||
// Ensure one block is taken per file, but that files are not inlined.
|
||||
memset(wbuffer_ref, 'b', size);
|
||||
sprintf(wbuffer_ref, "Hi %03d", i);
|
||||
lfs_file_read(&lfs, &file, wbuffer, BLOCK_SIZE) => size;
|
||||
lfs_file_close(&lfs, &file) => 0;
|
||||
for (lfs_size_t j = 0; j < size; j++) {
|
||||
wbuffer[j] => wbuffer_ref[j];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(err == LFS_ERR_NOTEMPTY);
|
||||
}
|
||||
|
||||
lfs_unmount(&lfs) => 0;
|
||||
if (err == 0 ) {
|
||||
if ( AFTER_BLOCK_COUNT != BLOCK_COUNT ) {
|
||||
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
|
||||
}
|
||||
|
||||
lfs_t lfs2 = lfs;
|
||||
struct lfs_config cfg2 = *cfg;
|
||||
cfg2.block_count = AFTER_BLOCK_COUNT;
|
||||
lfs2.cfg = &cfg2;
|
||||
lfs_mount(&lfs2, &cfg2) => 0;
|
||||
for (int i = 0; i < FILES_COUNT + 1; i++) {
|
||||
lfs_file_t file;
|
||||
char path[1024];
|
||||
sprintf(path, "file_%03d", i);
|
||||
lfs_file_open(&lfs2, &file, path,
|
||||
LFS_O_RDONLY ) => 0;
|
||||
lfs_size_t size = BLOCK_SIZE - 0x40;
|
||||
char wbuffer[size];
|
||||
char wbuffer_ref[size];
|
||||
// Ensure one block is taken per file, but that files are not inlined.
|
||||
memset(wbuffer_ref, 'b', size);
|
||||
sprintf(wbuffer_ref, "Hi %03d", i);
|
||||
lfs_file_read(&lfs2, &file, wbuffer, BLOCK_SIZE) => size;
|
||||
lfs_file_close(&lfs2, &file) => 0;
|
||||
for (lfs_size_t j = 0; j < size; j++) {
|
||||
wbuffer[j] => wbuffer_ref[j];
|
||||
}
|
||||
}
|
||||
lfs_unmount(&lfs2);
|
||||
}
|
||||
#endif
|
||||
'''
|
||||
@@ -524,6 +524,114 @@ code = '''
|
||||
lfs_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
|
||||
# mount and grow the filesystem
|
||||
[cases.test_superblocks_shrink]
|
||||
defines.BLOCK_COUNT = 'ERASE_COUNT'
|
||||
defines.BLOCK_COUNT_2 = ['ERASE_COUNT/2', 'ERASE_COUNT/4', '2']
|
||||
defines.KNOWN_BLOCK_COUNT = [true, false]
|
||||
code = '''
|
||||
#ifdef LFS_SHRINKNONRELOCATING
|
||||
lfs_t lfs;
|
||||
lfs_format(&lfs, cfg) => 0;
|
||||
|
||||
if (KNOWN_BLOCK_COUNT) {
|
||||
cfg->block_count = BLOCK_COUNT;
|
||||
} else {
|
||||
cfg->block_count = 0;
|
||||
}
|
||||
|
||||
// mount with block_size < erase_size
|
||||
lfs_mount(&lfs, cfg) => 0;
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfs_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.block_size == BLOCK_SIZE);
|
||||
assert(fsinfo.block_count == BLOCK_COUNT);
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
// same size is a noop
|
||||
lfs_mount(&lfs, cfg) => 0;
|
||||
lfs_fs_grow(&lfs, BLOCK_COUNT) => 0;
|
||||
lfs_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.block_size == BLOCK_SIZE);
|
||||
assert(fsinfo.block_count == BLOCK_COUNT);
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
lfs_mount(&lfs, cfg) => 0;
|
||||
lfs_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.block_size == BLOCK_SIZE);
|
||||
assert(fsinfo.block_count == BLOCK_COUNT);
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
// grow to new size
|
||||
lfs_mount(&lfs, cfg) => 0;
|
||||
lfs_fs_grow(&lfs, BLOCK_COUNT_2) => 0;
|
||||
lfs_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.block_size == BLOCK_SIZE);
|
||||
assert(fsinfo.block_count == BLOCK_COUNT_2);
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
if (KNOWN_BLOCK_COUNT) {
|
||||
cfg->block_count = BLOCK_COUNT_2;
|
||||
} else {
|
||||
cfg->block_count = 0;
|
||||
}
|
||||
|
||||
lfs_mount(&lfs, cfg) => 0;
|
||||
lfs_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.block_size == BLOCK_SIZE);
|
||||
assert(fsinfo.block_count == BLOCK_COUNT_2);
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
// mounting with the previous size should fail
|
||||
cfg->block_count = BLOCK_COUNT;
|
||||
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
|
||||
|
||||
if (KNOWN_BLOCK_COUNT) {
|
||||
cfg->block_count = BLOCK_COUNT_2;
|
||||
} else {
|
||||
cfg->block_count = 0;
|
||||
}
|
||||
|
||||
// same size is a noop
|
||||
lfs_mount(&lfs, cfg) => 0;
|
||||
lfs_fs_grow(&lfs, BLOCK_COUNT_2) => 0;
|
||||
lfs_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.block_size == BLOCK_SIZE);
|
||||
assert(fsinfo.block_count == BLOCK_COUNT_2);
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
lfs_mount(&lfs, cfg) => 0;
|
||||
lfs_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.block_size == BLOCK_SIZE);
|
||||
assert(fsinfo.block_count == BLOCK_COUNT_2);
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
// do some work
|
||||
lfs_mount(&lfs, cfg) => 0;
|
||||
lfs_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.block_size == BLOCK_SIZE);
|
||||
assert(fsinfo.block_count == BLOCK_COUNT_2);
|
||||
lfs_file_t file;
|
||||
lfs_file_open(&lfs, &file, "test",
|
||||
LFS_O_CREAT | LFS_O_EXCL | LFS_O_WRONLY) => 0;
|
||||
lfs_file_write(&lfs, &file, "hello!", 6) => 6;
|
||||
lfs_file_close(&lfs, &file) => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
|
||||
lfs_mount(&lfs, cfg) => 0;
|
||||
lfs_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.block_size == BLOCK_SIZE);
|
||||
assert(fsinfo.block_count == BLOCK_COUNT_2);
|
||||
lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
|
||||
uint8_t buffer[256];
|
||||
lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => 6;
|
||||
lfs_file_close(&lfs, &file) => 0;
|
||||
assert(memcmp(buffer, "hello!", 6) == 0);
|
||||
lfs_unmount(&lfs) => 0;
|
||||
#endif
|
||||
'''
|
||||
|
||||
# test that metadata_max does not cause problems for superblock compaction
|
||||
[cases.test_superblocks_metadata_max]
|
||||
defines.METADATA_MAX = [
|
||||
|
||||
Reference in New Issue
Block a user