Replaced emubd with rambd and filebd

The idea behind emubd (file per block), was neat, but doesn't add much
value over a block device that just operates on a single linear file
(other than adding a significant amount of overhead). Initially it
helped with debugging, but when the metadata format became more complex
in v2, most debugging ends up going through the debug.py script anyways.

Aside from being simpler, moving to filebd means it is also possible to
mount disk images directly.

Also introduced rambd, which keeps the disk contents in RAM. This is
very useful for testing where it increases the speed _significantly_.
- test_dirs w/ filebd - 0m7.170s
- test_dirs w/ rambd  - 0m0.966s

These follow the emubd model of using the lfs_config for geometry. I'm
not convinced this is the best approach, but it gets the job done.

I've also added lfs_ramdb_createcfg to add additional config similar to
lfs_file_opencfg. This is useful for specifying erase_value, which tells
the block device to simulate erases similar to flash devices. Note that
the default (-1) meets the minimum block device requirements and is the
most performant.
This commit is contained in:
Christopher Haster
2020-01-02 18:36:53 -06:00
parent 53d2b02f2a
commit eeaf536eca
8 changed files with 534 additions and 528 deletions

View File

@@ -15,7 +15,6 @@ import subprocess as sp
import base64
import sys
import copy
import shutil
import shlex
TESTDIR = 'tests_'
@@ -40,8 +39,10 @@ $(foreach target,$(SRC),$(eval $(FLATTEN)))
GLOBALS = """
//////////////// AUTOGENERATED TEST ////////////////
#include "lfs.h"
#include "emubd/lfs_emubd.h"
#include "filebd/lfs_filebd.h"
#include "rambd/lfs_rambd.h"
#include <stdio.h>
const char *LFS_DISK = NULL;
"""
DEFINES = {
"LFS_READ_SIZE": 16,
@@ -51,23 +52,25 @@ DEFINES = {
"LFS_BLOCK_CYCLES": 1024,
"LFS_CACHE_SIZE": "(64 % LFS_PROG_SIZE == 0 ? 64 : LFS_PROG_SIZE)",
"LFS_LOOKAHEAD_SIZE": 16,
"LFS_ERASE_VALUE": 0xff,
}
PROLOGUE = """
// prologue
__attribute__((unused)) lfs_t lfs;
__attribute__((unused)) lfs_emubd_t bd;
__attribute__((unused)) lfs_filebd_t filebd;
__attribute__((unused)) lfs_rambd_t rambd;
__attribute__((unused)) lfs_file_t file;
__attribute__((unused)) lfs_dir_t dir;
__attribute__((unused)) struct lfs_info info;
__attribute__((unused)) uint8_t buffer[1024];
__attribute__((unused)) char path[1024];
__attribute__((unused)) const struct lfs_config cfg = {
.context = &bd,
.read = &lfs_emubd_read,
.prog = &lfs_emubd_prog,
.erase = &lfs_emubd_erase,
.sync = &lfs_emubd_sync,
.context = LFS_DISK ? (void*)&filebd : (void*)&rambd,
.read = LFS_DISK ? &lfs_filebd_read : &lfs_rambd_read,
.prog = LFS_DISK ? &lfs_filebd_prog : &lfs_rambd_prog,
.erase = LFS_DISK ? &lfs_filebd_erase : &lfs_rambd_erase,
.sync = LFS_DISK ? &lfs_filebd_sync : &lfs_rambd_sync,
.read_size = LFS_READ_SIZE,
.prog_size = LFS_PROG_SIZE,
@@ -78,11 +81,26 @@ PROLOGUE = """
.lookahead_size = LFS_LOOKAHEAD_SIZE,
};
lfs_emubd_create(&cfg, "blocks");
__attribute__((unused)) const struct lfs_filebd_config filecfg = {
.erase_value = LFS_ERASE_VALUE,
};
__attribute__((unused)) const struct lfs_rambd_config ramcfg = {
.erase_value = LFS_ERASE_VALUE,
};
if (LFS_DISK) {
lfs_filebd_createcfg(&cfg, LFS_DISK, &filecfg);
} else {
lfs_rambd_createcfg(&cfg, &ramcfg);
}
"""
EPILOGUE = """
// epilogue
lfs_emubd_destroy(&cfg);
if (LFS_DISK) {
lfs_filebd_destroy(&cfg);
} else {
lfs_rambd_destroy(&cfg);
}
"""
PASS = '\033[32m✓\033[0m'
FAIL = '\033[31m✗\033[0m'
@@ -133,7 +151,8 @@ class TestCase:
f.write(') {\n')
for k, v in sorted(self.defines.items()):
f.write(4*' '+'#define %s %s\n' % (k, v))
if k not in self.suite.defines:
f.write(4*' '+'#define %s %s\n' % (k, v))
f.write(PROLOGUE)
f.write('\n')
@@ -148,27 +167,34 @@ class TestCase:
f.write('\n')
for k, v in sorted(self.defines.items()):
f.write(4*' '+'#undef %s\n' % k)
if k not in self.suite.defines:
f.write(4*' '+'#undef %s\n' % k)
f.write('}\n')
def test(self, exec=[], persist=False, gdb=False, failure=None, **args):
# clear disk first
if not persist:
shutil.rmtree('blocks', True)
# build command
cmd = exec + ['./%s.test' % self.suite.path,
repr(self.caseno), repr(self.permno)]
if persist:
cmd.append(self.suite.path + '.test.disk')
# failed? drop into debugger?
if gdb and failure:
cmd = (['gdb', '-ex', 'r'
] + (['-ex', 'up'] if failure.assert_ else []) + [
'--args'] + cmd)
ncmd = ['gdb']
if gdb == 'assert':
ncmd.extend(['-ex', 'r'])
if failure.assert_:
ncmd.extend(['-ex', 'up'])
elif gdb == 'start':
ncmd.extend([
'-ex', 'b %s:%d' % (self.suite.path, self.lineno),
'-ex', 'r'])
ncmd.extend(['--args'] + cmd)
if args.get('verbose', False):
print(' '.join(shlex.quote(c) for c in cmd))
sys.exit(sp.call(cmd))
print(' '.join(shlex.quote(c) for c in ncmd))
sys.exit(sp.call(ncmd))
# run test case!
stdout = []
@@ -231,22 +257,27 @@ class ReentrantTestCase(TestCase):
if not self.reentrant:
return
for cycles in it.count(1):
npersist = persist or cycles > 1
# clear disk first?
if not persist:
try:
os.remove(self.suite.path + '.test.disk')
except FileNotFoundError:
pass
for cycles in it.count(1):
# exact cycle we should drop into debugger?
if gdb and failure and failure.cycleno == cycles:
return super().test(exec=exec, persist=npersist,
return super().test(exec=exec, persist=True,
gdb=gdb, failure=failure, **args)
# run tests, but kill the program after lfs_emubd_prog/erase has
# run tests, but kill the program after prog/erase has
# been hit n cycles. We exit with a special return code if the
# program has not finished, since this isn't a test failure.
nexec = exec + [
'gdb', '-batch-silent',
'-ex', 'handle all nostop',
'-ex', 'b lfs_emubd_prog',
'-ex', 'b lfs_emubd_erase',
'-ex', 'b lfs_filebd_prog',
'-ex', 'b lfs_filebd_erase',
'-ex', 'r',
] + cycles*['-ex', 'c'] + [
'-ex', 'q '
@@ -255,7 +286,7 @@ class ReentrantTestCase(TestCase):
'33',
'--args']
try:
return super().test(exec=nexec, persist=npersist, **args)
return super().test(exec=nexec, persist=True, **args)
except TestFailure as nfailure:
if nfailure.returncode == 33:
continue
@@ -370,10 +401,11 @@ class TestSuite:
f.write('\n')
f.write('int main(int argc, char **argv) {\n')
f.write(4*' '+'int case_ = (argc == 3) ? atoi(argv[1]) : 0;\n')
f.write(4*' '+'int perm = (argc == 3) ? atoi(argv[2]) : 0;\n')
f.write(4*' '+'int case_ = (argc >= 3) ? atoi(argv[1]) : 0;\n')
f.write(4*' '+'int perm = (argc >= 3) ? atoi(argv[2]) : 0;\n')
f.write(4*' '+'LFS_DISK = (argc >= 4) ? argv[3] : NULL;\n')
for perm in self.perms:
f.write(4*' '+'if (argc != 3 || '
f.write(4*' '+'if (argc < 3 || '
'(case_ == %d && perm == %d)) { ' % (
perm.caseno, perm.permno))
f.write('test_case%d(' % perm.caseno)
@@ -590,8 +622,9 @@ if __name__ == "__main__":
help="Run all tests instead of stopping on first error. Useful for CI.")
parser.add_argument('-p', '--persist', action='store_true',
help="Don't reset the tests disk before each test.")
parser.add_argument('-g', '--gdb', action='store_true',
help="Drop into gdb on failure.")
parser.add_argument('-g', '--gdb', choices=['init', 'start', 'assert'],
nargs='?', const='assert',
help="Drop into gdb on test failure.")
parser.add_argument('--valgrind', action='store_true',
help="Run non-leaky tests under valgrind to check for memory leaks.")
parser.add_argument('--reentrant', action='store_true',