Files
littlefs/tests/test_fmulti.toml
T
Christopher Haster b76ff63e53 Added more sync tests, fixed some bugs, found some design flaws
Now mixing in truncate/fruncate, along with desync<->sync state
transitions.

Found bugs:

- Fixed propagating LFS_F_UNSYNCED/LFS_F_UNFLUSHED state during sync
  broadcasts. This is important for tracking small files correctly.

- We were not clearing the btree erased-state of other opened file
  handles when we started using it, leading other file handles to have
  out-of-date erased-state.

  I considered moving this into lfsr_btree_commit, but file btrees are
  really the only place where shared references make sense, and it feels
  weird to scan file btrees every time we commit to the mtree.

- Fixed syncs not propagating to other file handles when file is synced
  with disk.

  It's interesting that lfsr_file_sync can actually have an effect on
  the system when the disk in is-sync.

- Added O_FLUSH/O_SYNC support to lfsr_file_truncate/fruncate. This
  omission was just an oversight.

  Unfortunately this did add quite a bit more complexity to both
  functions.

You may notice in the fix for that last bug, that lfsr_file_ftruncate
sort of drops the ball with regards to error-idempotency. This is
because, as I was trying to figure out how to recoverably move the
buffer around when fruncating small files, I realized we don't handle
small files in lfsr_file_write correctly w.r.t. error-idempotency, and
that fixing this may be intractable...

The issue is how handle overwrites for unflushed buffers.

In general, the correct thing to do when an incoming write overlaps our
file buffer, is to just write over the buffer with the new data.

Ah, but if we do this, how do we get the old data back if we run into an
error writing the data to disk? It's gone!

For normal files, this is not an issue. We can always flush to disk to
reclaim our buffer, and since a flush doesn't change the file contents,
it's fine to make this our new fallback state.

But for small files, flush is a noop, we keep these entirely in RAM.

There are some possible workarounds:

- Flush small files to disk before overwriting, sort of defeats the
  purpose of caching these in RAM...

- Reread small files from disk, because that's definitely what you want
  to do when you hit an error...

  Also, to always have something we can read from disk implies flush
  on overwrite, see above.

- Sacrificing half our buffer for staging small files. Because RAM cost
  is totally not a priority...

Long story short, rethinking idempotent errors.
2024-02-03 18:14:57 -06:00

2175 lines
66 KiB
TOML

# Test multiple open file handles in different r/w configurations
after = 'test_fwrite'
# Test multiple readers, this shouldn't really have any issues
[cases.test_fmulti_rrrr]
defines.R = 4
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a file
uint32_t prng = 42;
uint8_t before[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
before[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &file, before, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// read R handles in parallel
lfsr_file_t readers[R];
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_open(&lfs, &readers[r], "jello", LFS_O_RDONLY) => 0;
}
for (lfs_size_t i = 0; i < SIZE; i += CHUNK) {
for (lfs_size_t r = 0; r < R; r++) {
uint8_t rbuf[CHUNK];
lfsr_file_read(&lfs, &readers[r], rbuf, CHUNK) => CHUNK;
assert(memcmp(rbuf, &before[i], CHUNK) == 0);
}
}
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_close(&lfs, &readers[r]) => 0;
}
lfsr_unmount(&lfs) => 0;
'''
[cases.test_fmulti_rrrr_fuzz]
defines.R = 4
defines.SEED = 'range(10)'
defines.N = 20
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a file
uint32_t prng = SEED;
uint8_t before[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
before[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &file, before, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// read R handles in parallel
lfsr_file_t readers[R];
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_open(&lfs, &readers[r], "jello", LFS_O_RDONLY) => 0;
}
for (lfs_size_t i = 0; i < N; i++) {
for (lfs_size_t r = 0; r < R; r++) {
// choose a random offset
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
lfs_size_t size = lfs_min32(CHUNK, SIZE - off);
lfsr_file_seek(&lfs, &readers[r], off, LFS_SEEK_SET) => off;
// read
uint8_t rbuf[CHUNK];
lfsr_file_read(&lfs, &readers[r], rbuf, CHUNK) => size;
assert(memcmp(rbuf, &before[off], size) == 0);
}
}
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_close(&lfs, &readers[r]) => 0;
}
lfsr_unmount(&lfs) => 0;
'''
# Test one writer, multiple readers
[cases.test_fmulti_wrrr]
defines.R = 4
# 0 => no sync, readers not updated
# 1 => sync via lfsr_file_sync
# 2 => sync via LFS_O_SYNC
defines.SYNC = [0, 1, 2]
# 0 => no flush
# 1 => flush via lfsr_file_flush
# 2 => flush via LFS_O_FLUSH
defines.FLUSH = [0, 1, 2]
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a file
uint32_t prng = 42;
uint8_t before[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
before[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
uint8_t after[SIZE];
memcpy(after, before, SIZE);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &file, before, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// write 1 handle, read R handles in parallel
lfsr_file_t writer;
lfsr_file_t readers[R];
lfsr_file_open(&lfs, &writer, "jello",
LFS_O_WRONLY
| ((FLUSH == 2) ? LFS_O_FLUSH : 0)
| ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0;
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_open(&lfs, &readers[r], "jello", LFS_O_RDONLY) => 0;
}
for (lfs_size_t i = 0; i < SIZE; i += CHUNK) {
uint8_t wbuf[CHUNK];
for (lfs_size_t j = 0; j < CHUNK; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &writer, wbuf, CHUNK) => CHUNK;
memcpy(&after[i], wbuf, CHUNK);
if (FLUSH == 1) {
lfsr_file_flush(&lfs, &writer) => 0;
}
if (SYNC == 1) {
lfsr_file_sync(&lfs, &writer) => 0;
}
for (lfs_size_t r = 0; r < R; r++) {
uint8_t rbuf[CHUNK];
lfsr_file_read(&lfs, &readers[r], rbuf, CHUNK) => CHUNK;
if (SYNC == 0) {
assert(memcmp(rbuf, &before[i], CHUNK) == 0);
} else {
assert(memcmp(rbuf, &after[i], CHUNK) == 0);
}
}
}
lfsr_file_close(&lfs, &writer) => 0;
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_close(&lfs, &readers[r]) => 0;
}
// check that file was written as expected
lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, after, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
[cases.test_fmulti_wrrr_fuzz]
defines.R = 4
# 0 => no sync, readers not updated
# 1 => sync via lfsr_file_sync
# 2 => sync via LFS_O_SYNC
defines.SYNC = [0, 1, 2]
# 0 => no flush
# 1 => flush via lfsr_file_flush
# 2 => flush via LFS_O_FLUSH
defines.FLUSH = [0, 1, 2]
defines.SEED = 'range(10)'
defines.N = 20
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a file
uint32_t prng = 42;
uint8_t before[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
before[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
uint8_t after[SIZE];
memcpy(after, before, SIZE);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &file, before, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// write 1 handle, read R handles in parallel
lfsr_file_t writer;
lfsr_file_t readers[R];
lfsr_file_open(&lfs, &writer, "jello",
LFS_O_WRONLY
| ((FLUSH == 2) ? LFS_O_FLUSH : 0)
| ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0;
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_open(&lfs, &readers[r], "jello", LFS_O_RDONLY) => 0;
}
for (lfs_size_t i = 0; i < N; i++) {
// choose a random offset
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
lfs_size_t size = lfs_min32(CHUNK, SIZE - off);
lfsr_file_seek(&lfs, &writer, off, LFS_SEEK_SET) => off;
// write
uint8_t wbuf[CHUNK];
for (lfs_size_t j = 0; j < size; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &writer, wbuf, size) => size;
memcpy(&after[off], wbuf, size);
if (FLUSH == 1) {
lfsr_file_flush(&lfs, &writer) => 0;
}
if (SYNC == 1) {
lfsr_file_sync(&lfs, &writer) => 0;
}
for (lfs_size_t r = 0; r < R; r++) {
// choose a random offset
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
lfs_size_t size = lfs_min32(CHUNK, SIZE - off);
lfsr_file_seek(&lfs, &readers[r], off, LFS_SEEK_SET) => off;
// read
uint8_t rbuf[CHUNK];
lfsr_file_read(&lfs, &readers[r], rbuf, CHUNK) => size;
if (SYNC == 0) {
assert(memcmp(rbuf, &before[off], size) == 0);
} else {
assert(memcmp(rbuf, &after[off], size) == 0);
}
}
}
lfsr_file_close(&lfs, &writer) => 0;
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_close(&lfs, &readers[r]) => 0;
}
// check that file was written as expected
lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, after, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# Test multiple writers
[cases.test_fmulti_wwww]
defines.W = 4
# 0 => no sync, readers not updated
# 1 => sync via lfsr_file_sync
# 2 => sync via LFS_O_SYNC
defines.SYNC = [0, 1, 2]
# 0 => no flush
# 1 => flush via lfsr_file_flush
# 2 => flush via LFS_O_FLUSH
defines.FLUSH = [0, 1, 2]
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a file
uint32_t prng = 42;
uint8_t before[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
before[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
uint8_t after[SIZE];
memcpy(after, before, SIZE);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &file, before, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// write W handles in parallel
lfsr_file_t writers[W];
for (lfs_size_t w = 0; w < W; w++) {
lfsr_file_open(&lfs, &writers[w], "jello",
LFS_O_WRONLY
| ((FLUSH == 2) ? LFS_O_FLUSH : 0)
| ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0;
}
for (lfs_size_t i = 0; i < SIZE; i += CHUNK) {
for (lfs_size_t w = 0; w < W; w++) {
uint8_t wbuf[CHUNK];
for (lfs_size_t j = 0; j < CHUNK; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &writers[w], wbuf, CHUNK) => CHUNK;
if (SYNC == 0) {
if (w == 0) {
memcpy(&after[i], wbuf, CHUNK);
}
} else {
if (FLUSH == 1) {
lfsr_file_flush(&lfs, &writers[w]) => 0;
}
if (SYNC == 1) {
lfsr_file_sync(&lfs, &writers[w]) => 0;
}
memcpy(&after[i], wbuf, CHUNK);
}
}
}
for (lfs_size_t w = 0; w < W; w++) {
lfsr_file_close(&lfs, &writers[w]) => 0;
}
// check that file was written as expected
lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, after, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
[cases.test_fmulti_wwww_fuzz]
defines.W = 4
# 0 => no sync, readers not updated
# 1 => sync via lfsr_file_sync
# 2 => sync via LFS_O_SYNC
defines.SYNC = [0, 1, 2]
# 0 => no flush
# 1 => flush via lfsr_file_flush
# 2 => flush via LFS_O_FLUSH
defines.FLUSH = [0, 1, 2]
defines.SEED = 'range(10)'
defines.N = 20
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a file
uint32_t prng = 42;
uint8_t before[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
before[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
uint8_t after[SIZE];
memcpy(after, before, SIZE);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &file, before, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// write W files in parallel
lfsr_file_t writers[W];
for (lfs_size_t w = 0; w < W; w++) {
lfsr_file_open(&lfs, &writers[w], "jello",
LFS_O_WRONLY
| ((FLUSH == 2) ? LFS_O_FLUSH : 0)
| ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0;
}
for (lfs_size_t i = 0; i < SIZE; i += CHUNK) {
for (lfs_size_t w = 0; w < W; w++) {
// choose a random offset
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
lfs_size_t size = lfs_min32(CHUNK, SIZE - off);
lfsr_file_seek(&lfs, &writers[w], off, LFS_SEEK_SET) => off;
// write
uint8_t wbuf[CHUNK];
for (lfs_size_t j = 0; j < size; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &writers[w], wbuf, size) => size;
if (SYNC == 0) {
if (w == 0) {
memcpy(&after[off], wbuf, size);
}
} else {
if (FLUSH == 1) {
lfsr_file_flush(&lfs, &writers[w]) => 0;
}
if (SYNC == 1) {
lfsr_file_sync(&lfs, &writers[w]) => 0;
}
memcpy(&after[off], wbuf, size);
}
}
}
for (lfs_size_t w = 0; w < W; w++) {
lfsr_file_close(&lfs, &writers[w]) => 0;
}
// check that file was written as expected
lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, after, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# Test multiple writers and multiple readers
[cases.test_fmulti_wwrr]
defines.W = 4
defines.R = 4
# 0 => no sync, readers not updated
# 1 => sync via lfsr_file_sync
# 2 => sync via LFS_O_SYNC
defines.SYNC = [0, 1, 2]
# 0 => no flush
# 1 => flush via lfsr_file_flush
# 2 => flush via LFS_O_FLUSH
defines.FLUSH = [0, 1, 2]
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a file
uint32_t prng = 42;
uint8_t before[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
before[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
uint8_t after[SIZE];
memcpy(after, before, SIZE);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &file, before, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// write W handles, R handles in parallel
lfsr_file_t writers[W];
lfsr_file_t readers[R];
for (lfs_size_t w = 0; w < W; w++) {
lfsr_file_open(&lfs, &writers[w], "jello",
LFS_O_WRONLY
| ((FLUSH == 2) ? LFS_O_FLUSH : 0)
| ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0;
}
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_open(&lfs, &readers[r], "jello", LFS_O_RDONLY) => 0;
}
for (lfs_size_t i = 0; i < SIZE; i += CHUNK) {
for (lfs_size_t w = 0; w < W; w++) {
uint8_t wbuf[CHUNK];
for (lfs_size_t j = 0; j < CHUNK; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &writers[w], wbuf, CHUNK) => CHUNK;
if (SYNC == 0) {
if (w == 0) {
memcpy(&after[i], wbuf, CHUNK);
}
} else {
if (FLUSH == 1) {
lfsr_file_flush(&lfs, &writers[w]) => 0;
}
if (SYNC == 1) {
lfsr_file_sync(&lfs, &writers[w]) => 0;
}
memcpy(&after[i], wbuf, CHUNK);
}
}
for (lfs_size_t r = 0; r < R; r++) {
uint8_t rbuf[CHUNK];
lfsr_file_read(&lfs, &readers[r], rbuf, CHUNK) => CHUNK;
if (SYNC == 0) {
assert(memcmp(rbuf, &before[i], CHUNK) == 0);
} else {
assert(memcmp(rbuf, &after[i], CHUNK) == 0);
}
}
}
for (lfs_size_t w = 0; w < W; w++) {
lfsr_file_close(&lfs, &writers[w]) => 0;
}
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_close(&lfs, &readers[r]) => 0;
}
// check that file was written as expected
lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, after, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
[cases.test_fmulti_wwrr_fuzz]
defines.W = 4
defines.R = 4
# 0 => no sync, readers not updated
# 1 => sync via lfsr_file_sync
# 2 => sync via LFS_O_SYNC
defines.SYNC = [0, 1, 2]
# 0 => no flush
# 1 => flush via lfsr_file_flush
# 2 => flush via LFS_O_FLUSH
defines.FLUSH = [0, 1, 2]
defines.SEED = 'range(10)'
defines.N = 20
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a file
uint32_t prng = 42;
uint8_t before[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
before[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
uint8_t after[SIZE];
memcpy(after, before, SIZE);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &file, before, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// write W files in parallel
lfsr_file_t writers[W];
lfsr_file_t readers[R];
for (lfs_size_t w = 0; w < W; w++) {
lfsr_file_open(&lfs, &writers[w], "jello",
LFS_O_WRONLY
| ((FLUSH == 2) ? LFS_O_FLUSH : 0)
| ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0;
}
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_open(&lfs, &readers[r], "jello", LFS_O_RDONLY) => 0;
}
for (lfs_size_t i = 0; i < SIZE; i += CHUNK) {
for (lfs_size_t w = 0; w < W; w++) {
// choose a random offset
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
lfs_size_t size = lfs_min32(CHUNK, SIZE - off);
lfsr_file_seek(&lfs, &writers[w], off, LFS_SEEK_SET) => off;
// write
uint8_t wbuf[CHUNK];
for (lfs_size_t j = 0; j < size; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &writers[w], wbuf, size) => size;
if (SYNC == 0) {
if (w == 0) {
memcpy(&after[off], wbuf, size);
}
} else {
if (FLUSH == 1) {
lfsr_file_flush(&lfs, &writers[w]) => 0;
}
if (SYNC == 1) {
lfsr_file_sync(&lfs, &writers[w]) => 0;
}
memcpy(&after[off], wbuf, size);
}
}
for (lfs_size_t r = 0; r < R; r++) {
// choose a random offset
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
lfs_size_t size = lfs_min32(CHUNK, SIZE - off);
lfsr_file_seek(&lfs, &readers[r], off, LFS_SEEK_SET) => off;
// read
uint8_t rbuf[CHUNK];
lfsr_file_read(&lfs, &readers[r], rbuf, CHUNK) => size;
if (SYNC == 0) {
assert(memcmp(rbuf, &before[off], size) == 0);
} else {
assert(memcmp(rbuf, &after[off], size) == 0);
}
}
}
for (lfs_size_t w = 0; w < W; w++) {
lfsr_file_close(&lfs, &writers[w]) => 0;
}
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_close(&lfs, &readers[r]) => 0;
}
// check that file was written as expected
lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, after, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# Test multiple rd/wrers
[cases.test_fmulti_rwrw]
defines.RW = 4
# 0 => no sync, readers not updated
# 1 => sync via lfsr_file_sync
# 2 => sync via LFS_O_SYNC
defines.SYNC = [0, 1, 2]
# 0 => no flush
# 1 => flush via lfsr_file_flush
# 2 => flush via LFS_O_FLUSH
defines.FLUSH = [0, 1, 2]
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a file
uint32_t prng = 42;
uint8_t before[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
before[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
uint8_t between[RW][SIZE];
for (lfs_size_t rw = 0; rw < RW; rw++) {
memcpy(between[rw], before, SIZE);
}
uint8_t after[SIZE];
memcpy(after, before, SIZE);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &file, before, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// write RW rdwrs in parallel
lfsr_file_t rdwrs[RW];
for (lfs_size_t rw = 0; rw < RW; rw++) {
lfsr_file_open(&lfs, &rdwrs[rw], "jello",
LFS_O_RDWR
| ((FLUSH == 2) ? LFS_O_FLUSH : 0)
| ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0;
}
for (lfs_size_t i = 0; i < SIZE; i += CHUNK) {
for (lfs_size_t rw = 0; rw < RW; rw++) {
uint8_t wbuf[CHUNK];
for (lfs_size_t j = 0; j < CHUNK; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_seek(&lfs, &rdwrs[rw], i, LFS_SEEK_SET) => i;
lfsr_file_write(&lfs, &rdwrs[rw], wbuf, CHUNK) => CHUNK;
memcpy(&between[rw][i], wbuf, CHUNK);
if (SYNC == 0) {
if (rw == 0) {
memcpy(&after[i], wbuf, CHUNK);
}
} else {
if (FLUSH == 1) {
lfsr_file_flush(&lfs, &rdwrs[rw]) => 0;
}
if (SYNC == 1) {
lfsr_file_sync(&lfs, &rdwrs[rw]) => 0;
}
memcpy(&after[i], wbuf, CHUNK);
}
}
for (lfs_size_t rw = 0; rw < RW; rw++) {
lfsr_file_seek(&lfs, &rdwrs[rw], i, LFS_SEEK_SET) => i;
uint8_t rbuf[CHUNK];
lfsr_file_read(&lfs, &rdwrs[rw], rbuf, CHUNK) => CHUNK;
if (SYNC == 0) {
assert(memcmp(rbuf, &between[rw][i], CHUNK) == 0);
} else {
assert(memcmp(rbuf, &after[i], CHUNK) == 0);
}
}
}
for (lfs_size_t rw = 0; rw < RW; rw++) {
lfsr_file_close(&lfs, &rdwrs[rw]) => 0;
}
// check that file was written as expected
lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, after, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
[cases.test_fmulti_rwrw_fuzz]
defines.RW = 4
# 0 => no sync, readers not updated
# 1 => sync via lfsr_file_sync
# 2 => sync via LFS_O_SYNC
defines.SYNC = [0, 1, 2]
# 0 => no flush
# 1 => flush via lfsr_file_flush
# 2 => flush via LFS_O_FLUSH
defines.FLUSH = [0, 1, 2]
defines.SEED = 'range(10)'
defines.N = 20
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a file
uint32_t prng = 42;
uint8_t before[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
before[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
uint8_t between[RW][SIZE];
for (lfs_size_t rw = 0; rw < RW; rw++) {
memcpy(between[rw], before, SIZE);
}
uint8_t after[SIZE];
memcpy(after, before, SIZE);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &file, before, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// write RW rdwrs in parallel
lfsr_file_t rdwrs[RW];
for (lfs_size_t rw = 0; rw < RW; rw++) {
lfsr_file_open(&lfs, &rdwrs[rw], "jello",
LFS_O_RDWR
| ((FLUSH == 2) ? LFS_O_FLUSH : 0)
| ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0;
}
for (lfs_size_t i = 0; i < SIZE; i += CHUNK) {
for (lfs_size_t rw = 0; rw < RW; rw++) {
// choose a random offset
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
lfs_size_t size = lfs_min32(CHUNK, SIZE - off);
lfsr_file_seek(&lfs, &rdwrs[rw], off, LFS_SEEK_SET) => off;
// write
uint8_t wbuf[CHUNK];
for (lfs_size_t j = 0; j < size; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &rdwrs[rw], wbuf, size) => size;
memcpy(&between[rw][off], wbuf, size);
if (SYNC == 0) {
if (rw == 0) {
memcpy(&after[off], wbuf, size);
}
} else {
if (FLUSH == 1) {
lfsr_file_flush(&lfs, &rdwrs[rw]) => 0;
}
if (SYNC == 1) {
lfsr_file_sync(&lfs, &rdwrs[rw]) => 0;
}
memcpy(&after[off], wbuf, size);
}
}
for (lfs_size_t rw = 0; rw < RW; rw++) {
// choose a random offset
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
lfs_size_t size = lfs_min32(CHUNK, SIZE - off);
lfsr_file_seek(&lfs, &rdwrs[rw], off, LFS_SEEK_SET) => off;
// read
uint8_t rbuf[CHUNK];
lfsr_file_read(&lfs, &rdwrs[rw], rbuf, CHUNK) => size;
if (SYNC == 0) {
assert(memcmp(rbuf, &between[rw][off], size) == 0);
} else {
assert(memcmp(rbuf, &after[off], size) == 0);
}
}
}
for (lfs_size_t rw = 0; rw < RW; rw++) {
lfsr_file_close(&lfs, &rdwrs[rw]) => 0;
}
// check that file was written as expected
lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, after, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# Test multiple rw files without fixed size
[cases.test_fmulti_rwrw_sparse_fuzz]
defines.RW = 4
# 0 => no sync, readers not updated
# 1 => sync via lfsr_file_sync
# 2 => sync via LFS_O_SYNC
defines.SYNC = [0, 1, 2]
# 0 => no flush
# 1 => flush via lfsr_file_flush
# 2 => flush via LFS_O_FLUSH
defines.FLUSH = [0, 1, 2]
defines.SEED = 'range(10)'
defines.N = 40
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
uint32_t prng = 42;
uint8_t between[RW][SIZE];
lfs_size_t between_size[RW];
for (lfs_size_t rw = 0; rw < RW; rw++) {
between_size[rw] = 0;
}
uint8_t after[SIZE];
lfs_size_t after_size = 0;
// write RW rdwrs in parallel
lfsr_file_t rdwrs[RW];
for (lfs_size_t rw = 0; rw < RW; rw++) {
// open files
lfsr_file_open(&lfs, &rdwrs[rw], "jello",
LFS_O_RDWR
| LFS_O_CREAT
| ((FLUSH == 2) ? LFS_O_FLUSH : 0)
| ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0;
}
for (lfs_size_t i = 0; i < SIZE; i += CHUNK) {
for (lfs_size_t rw = 0; rw < RW; rw++) {
// choose a random operation
uint8_t op = TEST_PRNG(&prng) % 2;
// writing?
if (op == 0) {
// choose a random offset
lfs_off_t off = (between_size[rw] > 0)
? TEST_PRNG(&prng) % between_size[rw]
: 0;
lfs_size_t size = lfs_min32(CHUNK, SIZE - off);
uint8_t wbuf[CHUNK];
for (lfs_size_t j = 0; j < size; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
// write
lfsr_file_seek(&lfs, &rdwrs[rw], off, LFS_SEEK_SET) => off;
lfsr_file_write(&lfs, &rdwrs[rw], wbuf, size) => size;
if (FLUSH == 1) {
lfsr_file_flush(&lfs, &rdwrs[rw]) => 0;
}
if (SYNC == 1) {
lfsr_file_sync(&lfs, &rdwrs[rw]) => 0;
}
// update sim
if (off > between_size[rw]) {
memset(&between[rw][between_size[rw]],
0,
off - between_size[rw]);
}
memcpy(&between[rw][off], wbuf, size);
between_size[rw] = lfs_max32(off + size, between_size[rw]);
// reading?
} else if (op == 1) {
// choose a random offset
lfs_off_t off = (between_size[rw] > 0)
? TEST_PRNG(&prng) % between_size[rw]
: 0;
lfs_size_t size = lfs_min32(CHUNK, between_size[rw] - off);
// read
lfsr_file_seek(&lfs, &rdwrs[rw], off, LFS_SEEK_SET) => off;
uint8_t rbuf[CHUNK];
lfsr_file_read(&lfs, &rdwrs[rw], rbuf, CHUNK) => size;
assert(memcmp(rbuf, &between[rw][off], size) == 0);
}
// broadcast sim?
if (SYNC) {
memcpy(after, between[rw], SIZE);
after_size = between_size[rw];
for (lfs_size_t rw_ = 0; rw_ < RW; rw_++) {
memcpy(between[rw_], between[rw], SIZE);
between_size[rw_] = between_size[rw];
}
}
}
}
for (lfs_size_t rw = 0; rw < RW; rw++) {
// close files
lfsr_file_close(&lfs, &rdwrs[rw]) => 0;
// broadcast sim one last time?
memcpy(after, between[rw], SIZE);
after_size = between_size[rw];
for (lfs_size_t rw_ = 0; rw_ < RW; rw_++) {
memcpy(between[rw_], between[rw], SIZE);
between_size[rw_] = between_size[rw];
}
}
// check that file was written as expected
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => after_size;
assert(memcmp(rbuf, after, after_size) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# Test multiple rw files while also truncating/fruncating
[cases.test_fmulti_rwtfrwtf_sparse_fuzz]
defines.RW = 4
# 0 => no sync, readers not updated
# 1 => sync via lfsr_file_sync
# 2 => sync via LFS_O_SYNC
defines.SYNC = [0, 1, 2]
# 0 => no flush
# 1 => flush via lfsr_file_flush
# 2 => flush via LFS_O_FLUSH
defines.FLUSH = [0, 1, 2]
defines.SEED = 'range(10)'
defines.N = 40
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
uint32_t prng = 42;
uint8_t between[RW][SIZE];
lfs_size_t between_size[RW];
for (lfs_size_t rw = 0; rw < RW; rw++) {
between_size[rw] = 0;
}
uint8_t after[SIZE];
lfs_size_t after_size = 0;
// write RW rdwrs in parallel
lfsr_file_t rdwrs[RW];
for (lfs_size_t rw = 0; rw < RW; rw++) {
// open files
lfsr_file_open(&lfs, &rdwrs[rw], "jello",
LFS_O_RDWR
| LFS_O_CREAT
| ((FLUSH == 2) ? LFS_O_FLUSH : 0)
| ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0;
}
for (lfs_size_t i = 0; i < SIZE; i += CHUNK) {
for (lfs_size_t rw = 0; rw < RW; rw++) {
// choose a random operation
uint8_t op = TEST_PRNG(&prng) % 4;
// writing?
if (op == 0) {
// choose a random offset
lfs_off_t off = (between_size[rw] > 0)
? TEST_PRNG(&prng) % between_size[rw]
: 0;
lfs_size_t size = lfs_min32(CHUNK, SIZE - off);
uint8_t wbuf[CHUNK];
for (lfs_size_t j = 0; j < size; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
// write
lfsr_file_seek(&lfs, &rdwrs[rw], off, LFS_SEEK_SET) => off;
lfsr_file_write(&lfs, &rdwrs[rw], wbuf, size) => size;
if (FLUSH == 1) {
lfsr_file_flush(&lfs, &rdwrs[rw]) => 0;
}
if (SYNC == 1) {
lfsr_file_sync(&lfs, &rdwrs[rw]) => 0;
}
// update sim
if (off > between_size[rw]) {
memset(&between[rw][between_size[rw]],
0,
off - between_size[rw]);
}
memcpy(&between[rw][off], wbuf, size);
between_size[rw] = lfs_max32(off + size, between_size[rw]);
// reading?
} else if (op == 1) {
// choose a random offset
lfs_off_t off = (between_size[rw] > 0)
? TEST_PRNG(&prng) % between_size[rw]
: 0;
lfs_size_t size = lfs_min32(CHUNK, between_size[rw] - off);
// read
lfsr_file_seek(&lfs, &rdwrs[rw], off, LFS_SEEK_SET) => off;
uint8_t rbuf[CHUNK];
lfsr_file_read(&lfs, &rdwrs[rw], rbuf, CHUNK) => size;
assert(memcmp(rbuf, &between[rw][off], size) == 0);
// truncating?
} else if (op == 2) {
// choose a random new file size
lfs_off_t size = TEST_PRNG(&prng) % SIZE;
// truncate
lfsr_file_truncate(&lfs, &rdwrs[rw], size) => 0;
if (FLUSH == 1) { // (flush does nothing)
lfsr_file_flush(&lfs, &rdwrs[rw]) => 0;
}
if (SYNC == 1) {
lfsr_file_sync(&lfs, &rdwrs[rw]) => 0;
}
// update the sim
if (size > between_size[rw]) {
memset(&between[rw][between_size[rw]],
0,
size - between_size[rw]);
}
between_size[rw] = size;
// fruncating?
} else if (op == 3) {
// choose a random new file size
lfs_off_t size = TEST_PRNG(&prng) % SIZE;
// fruncate
lfsr_file_fruncate(&lfs, &rdwrs[rw], size) => 0;
if (FLUSH == 1) { // (flush does nothing)
lfsr_file_flush(&lfs, &rdwrs[rw]) => 0;
}
if (SYNC == 1) {
lfsr_file_sync(&lfs, &rdwrs[rw]) => 0;
}
// update the sim
if (size > between_size[rw]) {
memmove(&between[rw][size - between_size[rw]],
between[rw],
between_size[rw]);
memset(between[rw],
0,
size - between_size[rw]);
} else {
memmove(between[rw],
&between[rw][between_size[rw] - size],
size);
}
between_size[rw] = size;
}
// broadcast sim?
if (SYNC) {
memcpy(after, between[rw], SIZE);
after_size = between_size[rw];
for (lfs_size_t rw_ = 0; rw_ < RW; rw_++) {
memcpy(between[rw_], between[rw], SIZE);
between_size[rw_] = between_size[rw];
}
}
}
}
for (lfs_size_t rw = 0; rw < RW; rw++) {
// close files
lfsr_file_close(&lfs, &rdwrs[rw]) => 0;
// broadcast sim one last time?
memcpy(after, between[rw], SIZE);
after_size = between_size[rw];
for (lfs_size_t rw_ = 0; rw_ < RW; rw_++) {
memcpy(between[rw_], between[rw], SIZE);
between_size[rw_] = between_size[rw];
}
}
// check that file was written as expected
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => after_size;
assert(memcmp(rbuf, after, after_size) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# Desynced files make things interesting
# Test one desynced writer, multiple readers
[cases.test_fmulti_drrr]
defines.R = 4
# 0 => no sync, readers not updated
# 1 => sync via lfsr_file_sync
# 2 => sync via LFS_O_SYNC
defines.SYNC = [0, 1, 2]
# 0 => no flush
# 1 => flush via lfsr_file_flush
# 2 => flush via LFS_O_FLUSH
defines.FLUSH = [0, 1, 2]
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a file
uint32_t prng = 42;
uint8_t before[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
before[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
uint8_t after[SIZE];
memcpy(after, before, SIZE);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &file, before, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// write 1 handle, read R handles in parallel
lfsr_file_t writer;
lfsr_file_t readers[R];
lfsr_file_open(&lfs, &writer, "jello",
LFS_O_WRONLY
| LFS_O_DESYNC
| ((FLUSH == 2) ? LFS_O_FLUSH : 0)
| ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0;
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_open(&lfs, &readers[r], "jello", LFS_O_RDONLY) => 0;
}
for (lfs_size_t i = 0; i < SIZE; i += CHUNK) {
uint8_t wbuf[CHUNK];
for (lfs_size_t j = 0; j < CHUNK; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &writer, wbuf, CHUNK) => CHUNK;
memcpy(&after[i], wbuf, CHUNK);
if (FLUSH == 1) {
lfsr_file_flush(&lfs, &writer) => 0;
}
if (SYNC == 1) {
lfsr_file_sync(&lfs, &writer) => 0;
}
for (lfs_size_t r = 0; r < R; r++) {
uint8_t rbuf[CHUNK];
lfsr_file_read(&lfs, &readers[r], rbuf, CHUNK) => CHUNK;
if (SYNC == 0) {
assert(memcmp(rbuf, &before[i], CHUNK) == 0);
} else {
assert(memcmp(rbuf, &after[i], CHUNK) == 0);
}
}
}
lfsr_file_close(&lfs, &writer) => 0;
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_close(&lfs, &readers[r]) => 0;
}
// check that file was written as expected
lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
if (SYNC == 0) {
assert(memcmp(rbuf, before, SIZE) == 0);
} else {
assert(memcmp(rbuf, after, SIZE) == 0);
}
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
[cases.test_fmulti_drrr_fuzz]
defines.R = 4
# 0 => no sync, readers not updated
# 1 => sync via lfsr_file_sync
# 2 => sync via LFS_O_SYNC
defines.SYNC = [0, 1, 2]
# 0 => no flush
# 1 => flush via lfsr_file_flush
# 2 => flush via LFS_O_FLUSH
defines.FLUSH = [0, 1, 2]
defines.SEED = 'range(10)'
defines.N = 20
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a file
uint32_t prng = 42;
uint8_t before[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
before[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
uint8_t after[SIZE];
memcpy(after, before, SIZE);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &file, before, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// write 1 handle, read R handles in parallel
lfsr_file_t writer;
lfsr_file_t readers[R];
lfsr_file_open(&lfs, &writer, "jello",
LFS_O_WRONLY
| LFS_O_DESYNC
| ((FLUSH == 2) ? LFS_O_FLUSH : 0)
| ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0;
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_open(&lfs, &readers[r], "jello", LFS_O_RDONLY) => 0;
}
for (lfs_size_t i = 0; i < N; i++) {
// choose a random offset
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
lfs_size_t size = lfs_min32(CHUNK, SIZE - off);
lfsr_file_seek(&lfs, &writer, off, LFS_SEEK_SET) => off;
// write
uint8_t wbuf[CHUNK];
for (lfs_size_t j = 0; j < size; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &writer, wbuf, size) => size;
memcpy(&after[off], wbuf, size);
if (FLUSH == 1) {
lfsr_file_flush(&lfs, &writer) => 0;
}
if (SYNC == 1) {
lfsr_file_sync(&lfs, &writer) => 0;
}
for (lfs_size_t r = 0; r < R; r++) {
// choose a random offset
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
lfs_size_t size = lfs_min32(CHUNK, SIZE - off);
lfsr_file_seek(&lfs, &readers[r], off, LFS_SEEK_SET) => off;
// read
uint8_t rbuf[CHUNK];
lfsr_file_read(&lfs, &readers[r], rbuf, CHUNK) => size;
if (SYNC == 0) {
assert(memcmp(rbuf, &before[off], size) == 0);
} else {
assert(memcmp(rbuf, &after[off], size) == 0);
}
}
}
lfsr_file_close(&lfs, &writer) => 0;
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_close(&lfs, &readers[r]) => 0;
}
// check that file was written as expected
lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
if (SYNC == 0) {
assert(memcmp(rbuf, before, SIZE) == 0);
} else {
assert(memcmp(rbuf, after, SIZE) == 0);
}
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# Test one writer, multiple desynced readers
[cases.test_fmulti_wddd]
defines.R = 4
# 0 => no sync, readers not updated
# 1 => sync via lfsr_file_sync
# 2 => sync via LFS_O_SYNC
defines.SYNC = [0, 1, 2]
# 0 => no flush
# 1 => flush via lfsr_file_flush
# 2 => flush via LFS_O_FLUSH
defines.FLUSH = [0, 1, 2]
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a file
uint32_t prng = 42;
uint8_t before[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
before[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
uint8_t after[SIZE];
memcpy(after, before, SIZE);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &file, before, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// write 1 handle, read R handles in parallel
lfsr_file_t writer;
lfsr_file_t readers[R];
lfsr_file_open(&lfs, &writer, "jello",
LFS_O_WRONLY
| ((FLUSH == 2) ? LFS_O_FLUSH : 0)
| ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0;
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_open(&lfs, &readers[r], "jello",
LFS_O_RDONLY | LFS_O_DESYNC) => 0;
}
for (lfs_size_t i = 0; i < SIZE; i += CHUNK) {
uint8_t wbuf[CHUNK];
for (lfs_size_t j = 0; j < CHUNK; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &writer, wbuf, CHUNK) => CHUNK;
memcpy(&after[i], wbuf, CHUNK);
if (FLUSH == 1) {
lfsr_file_flush(&lfs, &writer) => 0;
}
if (SYNC == 1) {
lfsr_file_sync(&lfs, &writer) => 0;
}
for (lfs_size_t r = 0; r < R; r++) {
uint8_t rbuf[CHUNK];
lfsr_file_read(&lfs, &readers[r], rbuf, CHUNK) => CHUNK;
assert(memcmp(rbuf, &before[i], CHUNK) == 0);
}
}
lfsr_file_close(&lfs, &writer) => 0;
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_close(&lfs, &readers[r]) => 0;
}
// check that file was written as expected
lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, after, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
[cases.test_fmulti_wddd_fuzz]
defines.R = 4
# 0 => no sync, readers not updated
# 1 => sync via lfsr_file_sync
# 2 => sync via LFS_O_SYNC
defines.SYNC = [0, 1, 2]
# 0 => no flush
# 1 => flush via lfsr_file_flush
# 2 => flush via LFS_O_FLUSH
defines.FLUSH = [0, 1, 2]
defines.SEED = 'range(10)'
defines.N = 20
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a file
uint32_t prng = 42;
uint8_t before[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
before[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
uint8_t after[SIZE];
memcpy(after, before, SIZE);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &file, before, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// write 1 handle, read R handles in parallel
lfsr_file_t writer;
lfsr_file_t readers[R];
lfsr_file_open(&lfs, &writer, "jello",
LFS_O_WRONLY
| ((FLUSH == 2) ? LFS_O_FLUSH : 0)
| ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0;
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_open(&lfs, &readers[r], "jello",
LFS_O_RDONLY | LFS_O_DESYNC) => 0;
}
for (lfs_size_t i = 0; i < N; i++) {
// choose a random offset
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
lfs_size_t size = lfs_min32(CHUNK, SIZE - off);
lfsr_file_seek(&lfs, &writer, off, LFS_SEEK_SET) => off;
// write
uint8_t wbuf[CHUNK];
for (lfs_size_t j = 0; j < size; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &writer, wbuf, size) => size;
memcpy(&after[off], wbuf, size);
if (FLUSH == 1) {
lfsr_file_flush(&lfs, &writer) => 0;
}
if (SYNC == 1) {
lfsr_file_sync(&lfs, &writer) => 0;
}
for (lfs_size_t r = 0; r < R; r++) {
// choose a random offset
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
lfs_size_t size = lfs_min32(CHUNK, SIZE - off);
lfsr_file_seek(&lfs, &readers[r], off, LFS_SEEK_SET) => off;
// read
uint8_t rbuf[CHUNK];
lfsr_file_read(&lfs, &readers[r], rbuf, CHUNK) => size;
assert(memcmp(rbuf, &before[off], size) == 0);
}
}
lfsr_file_close(&lfs, &writer) => 0;
for (lfs_size_t r = 0; r < R; r++) {
lfsr_file_close(&lfs, &readers[r]) => 0;
}
// check that file was written as expected
lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, after, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# Test multiple desynced rd/wrers
[cases.test_fmulti_rwdrwd]
defines.RW = 4
# 0 => no sync, readers not updated
# 1 => sync via lfsr_file_sync
# 2 => sync via LFS_O_SYNC
defines.SYNC = [0, 1, 2]
# 0 => no flush
# 1 => flush via lfsr_file_flush
# 2 => flush via LFS_O_FLUSH
defines.FLUSH = [0, 1, 2]
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a file
uint32_t prng = 42;
uint8_t before[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
before[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
uint8_t between[RW][SIZE];
for (lfs_size_t rw = 0; rw < RW; rw++) {
memcpy(between[rw], before, SIZE);
}
uint8_t after[SIZE];
memcpy(after, before, SIZE);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &file, before, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// write RW rdwrs in parallel
lfsr_file_t rdwrs[RW];
for (lfs_size_t rw = 0; rw < RW; rw++) {
lfsr_file_open(&lfs, &rdwrs[rw], "jello",
LFS_O_RDWR
| LFS_O_DESYNC
| ((FLUSH == 2) ? LFS_O_FLUSH : 0)
| ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0;
}
for (lfs_size_t i = 0; i < SIZE; i += CHUNK) {
for (lfs_size_t rw = 0; rw < RW; rw++) {
uint8_t wbuf[CHUNK];
for (lfs_size_t j = 0; j < CHUNK; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_seek(&lfs, &rdwrs[rw], i, LFS_SEEK_SET) => i;
lfsr_file_write(&lfs, &rdwrs[rw], wbuf, CHUNK) => CHUNK;
memcpy(&between[rw][i], wbuf, CHUNK);
if (SYNC == 0) {
if (rw == 0) {
memcpy(&after[i], wbuf, CHUNK);
}
} else {
if (FLUSH == 1) {
lfsr_file_flush(&lfs, &rdwrs[rw]) => 0;
}
if (SYNC == 1) {
lfsr_file_sync(&lfs, &rdwrs[rw]) => 0;
}
if (i == 0) {
memcpy(after, between[rw], SIZE);
} else {
memcpy(&after[i], wbuf, CHUNK);
}
}
}
for (lfs_size_t rw = 0; rw < RW; rw++) {
lfsr_file_seek(&lfs, &rdwrs[rw], i, LFS_SEEK_SET) => i;
uint8_t rbuf[CHUNK];
lfsr_file_read(&lfs, &rdwrs[rw], rbuf, CHUNK) => CHUNK;
if (SYNC == 0) {
assert(memcmp(rbuf, &between[rw][i], CHUNK) == 0);
} else {
assert(memcmp(rbuf, &after[i], CHUNK) == 0);
}
}
}
for (lfs_size_t rw = 0; rw < RW; rw++) {
lfsr_file_close(&lfs, &rdwrs[rw]) => 0;
}
// check that file was written as expected
lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
if (SYNC == 0) {
assert(memcmp(rbuf, before, SIZE) == 0);
} else {
assert(memcmp(rbuf, after, SIZE) == 0);
}
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
[cases.test_fmulti_rwdrwd_fuzz]
defines.RW = 4
# 0 => no sync, readers not updated
# 1 => sync via lfsr_file_sync
# 2 => sync via LFS_O_SYNC
defines.SYNC = [0, 1, 2]
# 0 => no flush
# 1 => flush via lfsr_file_flush
# 2 => flush via LFS_O_FLUSH
defines.FLUSH = [0, 1, 2]
defines.SEED = 'range(10)'
defines.N = 20
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
// create a file
uint32_t prng = 42;
uint8_t before[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
before[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
uint8_t between[RW][SIZE];
for (lfs_size_t rw = 0; rw < RW; rw++) {
memcpy(between[rw], before, SIZE);
}
uint8_t after[SIZE];
memcpy(after, before, SIZE);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &file, before, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// write RW rdwrs in parallel
lfsr_file_t rdwrs[RW];
for (lfs_size_t rw = 0; rw < RW; rw++) {
lfsr_file_open(&lfs, &rdwrs[rw], "jello",
LFS_O_RDWR
| LFS_O_DESYNC
| ((FLUSH == 2) ? LFS_O_FLUSH : 0)
| ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0;
}
for (lfs_size_t i = 0; i < SIZE; i += CHUNK) {
for (lfs_size_t rw = 0; rw < RW; rw++) {
// choose a random offset
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
lfs_size_t size = lfs_min32(CHUNK, SIZE - off);
lfsr_file_seek(&lfs, &rdwrs[rw], off, LFS_SEEK_SET) => off;
// write
uint8_t wbuf[CHUNK];
for (lfs_size_t j = 0; j < size; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &rdwrs[rw], wbuf, size) => size;
memcpy(&between[rw][off], wbuf, size);
if (SYNC == 0) {
if (rw == 0) {
memcpy(&after[off], wbuf, size);
}
} else {
if (FLUSH == 1) {
lfsr_file_flush(&lfs, &rdwrs[rw]) => 0;
}
if (SYNC == 1) {
lfsr_file_sync(&lfs, &rdwrs[rw]) => 0;
}
if (i == 0) {
memcpy(after, between[rw], SIZE);
} else {
memcpy(&after[off], wbuf, size);
}
}
}
for (lfs_size_t rw = 0; rw < RW; rw++) {
// choose a random offset
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
lfs_size_t size = lfs_min32(CHUNK, SIZE - off);
lfsr_file_seek(&lfs, &rdwrs[rw], off, LFS_SEEK_SET) => off;
// read
uint8_t rbuf[CHUNK];
lfsr_file_read(&lfs, &rdwrs[rw], rbuf, CHUNK) => size;
if (SYNC == 0) {
assert(memcmp(rbuf, &between[rw][off], size) == 0);
} else {
assert(memcmp(rbuf, &after[off], size) == 0);
}
}
}
for (lfs_size_t rw = 0; rw < RW; rw++) {
lfsr_file_close(&lfs, &rdwrs[rw]) => 0;
}
// check that file was written as expected
lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
if (SYNC == 0) {
assert(memcmp(rbuf, before, SIZE) == 0);
} else {
assert(memcmp(rbuf, after, SIZE) == 0);
}
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# Test multiple rwd files without fixed size
[cases.test_fmulti_rwdrwd_sparse_fuzz]
defines.RW = 4
# 0 => no flush
# 1 => flush via lfsr_file_flush
# 2 => flush via LFS_O_FLUSH
defines.FLUSH = [0, 1, 2]
defines.SEED = 'range(10)'
defines.N = 40
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
uint32_t prng = 42;
uint8_t between[RW][SIZE];
lfs_size_t between_size[RW];
bool between_desync[RW];
for (lfs_size_t rw = 0; rw < RW; rw++) {
between_size[rw] = 0;
between_desync[rw] = false;
}
uint8_t after[SIZE];
lfs_size_t after_size = 0;
// write RW rdwrs in parallel
lfsr_file_t rdwrs[RW];
for (lfs_size_t rw = 0; rw < RW; rw++) {
// open files
lfsr_file_open(&lfs, &rdwrs[rw], "jello",
LFS_O_RDWR
| LFS_O_CREAT
| ((FLUSH == 2) ? LFS_O_FLUSH : 0)) => 0;
}
for (lfs_size_t i = 0; i < SIZE; i += CHUNK) {
for (lfs_size_t rw = 0; rw < RW; rw++) {
// choose a random operation
uint8_t op = TEST_PRNG(&prng) % 2;
// choose a random sync state
uint8_t sync = TEST_PRNG(&prng) % 3;
// writing?
if (op == 0) {
// choose a random offset
lfs_off_t off = (between_size[rw] > 0)
? TEST_PRNG(&prng) % between_size[rw]
: 0;
lfs_size_t size = lfs_min32(CHUNK, SIZE - off);
uint8_t wbuf[CHUNK];
for (lfs_size_t j = 0; j < size; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
// write
lfsr_file_seek(&lfs, &rdwrs[rw], off, LFS_SEEK_SET) => off;
lfsr_file_write(&lfs, &rdwrs[rw], wbuf, size) => size;
if (FLUSH == 1) {
lfsr_file_flush(&lfs, &rdwrs[rw]) => 0;
}
// update sim
if (off > between_size[rw]) {
memset(&between[rw][between_size[rw]],
0,
off - between_size[rw]);
}
memcpy(&between[rw][off], wbuf, size);
between_size[rw] = lfs_max32(off + size, between_size[rw]);
// reading?
} else if (op == 1) {
// choose a random offset
lfs_off_t off = (between_size[rw] > 0)
? TEST_PRNG(&prng) % between_size[rw]
: 0;
lfs_size_t size = lfs_min32(CHUNK, between_size[rw] - off);
// read
lfsr_file_seek(&lfs, &rdwrs[rw], off, LFS_SEEK_SET) => off;
uint8_t rbuf[CHUNK];
lfsr_file_read(&lfs, &rdwrs[rw], rbuf, CHUNK) => size;
assert(memcmp(rbuf, &between[rw][off], size) == 0);
}
// desync?
if (sync == 0) {
lfsr_file_desync(&lfs, &rdwrs[rw]) => 0;
between_desync[rw] = true;
// sync?
} else if (sync == 1) {
lfsr_file_sync(&lfs, &rdwrs[rw]) => 0;
between_desync[rw] = false;
// otherwise no change
}
// broadcast sim?
if (sync == 1) {
memcpy(after, between[rw], SIZE);
after_size = between_size[rw];
for (lfs_size_t rw_ = 0; rw_ < RW; rw_++) {
if (!between_desync[rw_]) {
memcpy(between[rw_], between[rw], SIZE);
between_size[rw_] = between_size[rw];
}
}
}
}
}
for (lfs_size_t rw = 0; rw < RW; rw++) {
// close files
lfsr_file_close(&lfs, &rdwrs[rw]) => 0;
// broadcast sim one last time?
if (!between_desync[rw]) {
memcpy(after, between[rw], SIZE);
after_size = between_size[rw];
for (lfs_size_t rw_ = 0; rw_ < RW; rw_++) {
if (!between_desync[rw_]) {
memcpy(between[rw_], between[rw], SIZE);
between_size[rw_] = between_size[rw];
}
}
}
}
// check that file was written as expected
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => after_size;
assert(memcmp(rbuf, after, after_size) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# Test multiple rwd files while also truncating/fruncating
[cases.test_fmulti_rwtfdrwtfd_sparse_fuzz]
defines.RW = 4
# 0 => no flush
# 1 => flush via lfsr_file_flush
# 2 => flush via LFS_O_FLUSH
defines.FLUSH = [0, 1, 2]
defines.SEED = 'range(10)'
defines.N = 40
defines.SIZE = [
'CACHE_SIZE/2',
'2*CACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'4*BLOCK_SIZE',
]
defines.CHUNK = '(SIZE+16-1) / 16'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, CFG) => 0;
uint32_t prng = 42;
uint8_t between[RW][SIZE];
lfs_size_t between_size[RW];
bool between_desync[RW];
for (lfs_size_t rw = 0; rw < RW; rw++) {
between_size[rw] = 0;
between_desync[rw] = false;
}
uint8_t after[SIZE];
lfs_size_t after_size = 0;
// write RW rdwrs in parallel
lfsr_file_t rdwrs[RW];
for (lfs_size_t rw = 0; rw < RW; rw++) {
// open files
lfsr_file_open(&lfs, &rdwrs[rw], "jello",
LFS_O_RDWR
| LFS_O_CREAT
| ((FLUSH == 2) ? LFS_O_FLUSH : 0)) => 0;
}
for (lfs_size_t i = 0; i < SIZE; i += CHUNK) {
for (lfs_size_t rw = 0; rw < RW; rw++) {
// choose a random operation
uint8_t op = TEST_PRNG(&prng) % 4;
// choose a random sync state
uint8_t sync = TEST_PRNG(&prng) % 3;
// writing?
if (op == 0) {
// choose a random offset
lfs_off_t off = (between_size[rw] > 0)
? TEST_PRNG(&prng) % between_size[rw]
: 0;
lfs_size_t size = lfs_min32(CHUNK, SIZE - off);
uint8_t wbuf[CHUNK];
for (lfs_size_t j = 0; j < size; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
// write
lfsr_file_seek(&lfs, &rdwrs[rw], off, LFS_SEEK_SET) => off;
lfsr_file_write(&lfs, &rdwrs[rw], wbuf, size) => size;
if (FLUSH == 1) {
lfsr_file_flush(&lfs, &rdwrs[rw]) => 0;
}
// update sim
if (off > between_size[rw]) {
memset(&between[rw][between_size[rw]],
0,
off - between_size[rw]);
}
memcpy(&between[rw][off], wbuf, size);
between_size[rw] = lfs_max32(off + size, between_size[rw]);
// reading?
} else if (op == 1) {
// choose a random offset
lfs_off_t off = (between_size[rw] > 0)
? TEST_PRNG(&prng) % between_size[rw]
: 0;
lfs_size_t size = lfs_min32(CHUNK, between_size[rw] - off);
// read
lfsr_file_seek(&lfs, &rdwrs[rw], off, LFS_SEEK_SET) => off;
uint8_t rbuf[CHUNK];
lfsr_file_read(&lfs, &rdwrs[rw], rbuf, CHUNK) => size;
assert(memcmp(rbuf, &between[rw][off], size) == 0);
// truncating?
} else if (op == 2) {
// choose a random new file size
lfs_off_t size = TEST_PRNG(&prng) % SIZE;
// truncate
lfsr_file_truncate(&lfs, &rdwrs[rw], size) => 0;
// update the sim
if (size > between_size[rw]) {
memset(&between[rw][between_size[rw]],
0,
size - between_size[rw]);
}
between_size[rw] = size;
// fruncating?
} else if (op == 3) {
// choose a random new file size
lfs_off_t size = TEST_PRNG(&prng) % SIZE;
// fruncate
lfsr_file_fruncate(&lfs, &rdwrs[rw], size) => 0;
// update the sim
if (size > between_size[rw]) {
memmove(&between[rw][size - between_size[rw]],
between[rw],
between_size[rw]);
memset(between[rw],
0,
size - between_size[rw]);
} else {
memmove(between[rw],
&between[rw][between_size[rw] - size],
size);
}
between_size[rw] = size;
}
// desync?
if (sync == 0) {
lfsr_file_desync(&lfs, &rdwrs[rw]) => 0;
between_desync[rw] = true;
// sync?
} else if (sync == 1) {
lfsr_file_sync(&lfs, &rdwrs[rw]) => 0;
between_desync[rw] = false;
// otherwise no change
}
// broadcast sim?
if (sync == 1) {
memcpy(after, between[rw], SIZE);
after_size = between_size[rw];
for (lfs_size_t rw_ = 0; rw_ < RW; rw_++) {
if (!between_desync[rw_]) {
memcpy(between[rw_], between[rw], SIZE);
between_size[rw_] = between_size[rw];
}
}
}
}
}
for (lfs_size_t rw = 0; rw < RW; rw++) {
// close files
lfsr_file_close(&lfs, &rdwrs[rw]) => 0;
// broadcast sim one last time?
if (!between_desync[rw]) {
memcpy(after, between[rw], SIZE);
after_size = between_size[rw];
for (lfs_size_t rw_ = 0; rw_ < RW; rw_++) {
if (!between_desync[rw_]) {
memcpy(between[rw_], between[rw], SIZE);
between_size[rw_] = between_size[rw];
}
}
}
}
// check that file was written as expected
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => after_size;
assert(memcmp(rbuf, after, after_size) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''