|
|
@@ -19,7 +19,7 @@
|
|
|
# x config chaining correct
|
|
|
# - why can't gdb see my defines?
|
|
|
# - say no to internal?
|
|
|
-# - buffering stdout issues?
|
|
|
+# x buffering stdout issues?
|
|
|
|
|
|
import toml
|
|
|
import glob
|
|
|
@@ -33,6 +33,9 @@ import base64
|
|
|
import sys
|
|
|
import copy
|
|
|
import shlex
|
|
|
+import pty
|
|
|
+import errno
|
|
|
+import signal
|
|
|
|
|
|
TESTDIR = 'tests_'
|
|
|
RULES = """
|
|
|
@@ -45,98 +48,31 @@ $(foreach target,$(SRC),$(eval $(FLATTEN)))
|
|
|
-include tests_/*.d
|
|
|
|
|
|
.SECONDARY:
|
|
|
-%.test: override CFLAGS += -fdiagnostics-color=always
|
|
|
-%.test: override CFLAGS += -ggdb
|
|
|
+%.test: override CFLAGS += -gdwarf-2
|
|
|
+%.test: override CFLAGS += -ggdb3
|
|
|
+%.test: override CFLAGS += -g3
|
|
|
%.test: %.test.o $(foreach f,$(subst /,.,$(SRC:.c=.o)),%.$f)
|
|
|
$(CC) $(CFLAGS) $^ $(LFLAGS) -o $@
|
|
|
"""
|
|
|
GLOBALS = """
|
|
|
//////////////// AUTOGENERATED TEST ////////////////
|
|
|
#include "lfs.h"
|
|
|
-#include "filebd/lfs_filebd.h"
|
|
|
-#include "rambd/lfs_rambd.h"
|
|
|
+#include "testbd/lfs_testbd.h"
|
|
|
#include <stdio.h>
|
|
|
-
|
|
|
-extern const char *lfs_testbd_disk;
|
|
|
-typedef union {
|
|
|
- lfs_filebd_t filebd;
|
|
|
- lfs_rambd_t rambd;
|
|
|
-} lfs_testbd_t;
|
|
|
-struct lfs_testbd_config {
|
|
|
- struct lfs_filebd_config filecfg;
|
|
|
- struct lfs_rambd_config ramcfg;
|
|
|
-};
|
|
|
-
|
|
|
-__attribute__((unused))
|
|
|
-static int lfs_testbd_createcfg(const struct lfs_config *cfg,
|
|
|
- const struct lfs_testbd_config *bdcfg) {
|
|
|
- if (lfs_testbd_disk) {
|
|
|
- return lfs_filebd_createcfg(cfg, lfs_testbd_disk, &bdcfg->filecfg);
|
|
|
- } else {
|
|
|
- return lfs_rambd_createcfg(cfg, &bdcfg->ramcfg);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-__attribute__((unused))
|
|
|
-static void lfs_testbd_destroy(const struct lfs_config *cfg) {
|
|
|
- if (lfs_testbd_disk) {
|
|
|
- lfs_filebd_destroy(cfg);
|
|
|
- } else {
|
|
|
- lfs_rambd_destroy(cfg);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-__attribute__((unused))
|
|
|
-static int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block,
|
|
|
- lfs_off_t off, void *buffer, lfs_size_t size) {
|
|
|
- if (lfs_testbd_disk) {
|
|
|
- return lfs_filebd_read(cfg, block, off, buffer, size);
|
|
|
- } else {
|
|
|
- return lfs_rambd_read(cfg, block, off, buffer, size);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-__attribute__((unused))
|
|
|
-static int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block,
|
|
|
- lfs_off_t off, const void *buffer, lfs_size_t size) {
|
|
|
- if (lfs_testbd_disk) {
|
|
|
- return lfs_filebd_prog(cfg, block, off, buffer, size);
|
|
|
- } else {
|
|
|
- return lfs_rambd_prog(cfg, block, off, buffer, size);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-__attribute__((unused))
|
|
|
-static int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) {
|
|
|
- if (lfs_testbd_disk) {
|
|
|
- return lfs_filebd_erase(cfg, block);
|
|
|
- } else {
|
|
|
- return lfs_rambd_erase(cfg, block);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-__attribute__((unused))
|
|
|
-static int lfs_testbd_sync(const struct lfs_config *cfg) {
|
|
|
- if (lfs_testbd_disk) {
|
|
|
- return lfs_filebd_sync(cfg);
|
|
|
- } else {
|
|
|
- return lfs_rambd_sync(cfg);
|
|
|
- }
|
|
|
-}
|
|
|
+extern const char *lfs_testbd_path;
|
|
|
+extern uint32_t lfs_testbd_cycles;
|
|
|
"""
|
|
|
DEFINES = {
|
|
|
- "LFS_BD_READ": "lfs_testbd_read",
|
|
|
- "LFS_BD_PROG": "lfs_testbd_prog",
|
|
|
- "LFS_BD_ERASE": "lfs_testbd_erase",
|
|
|
- "LFS_BD_SYNC": "lfs_testbd_sync",
|
|
|
- "LFS_READ_SIZE": 16,
|
|
|
- "LFS_PROG_SIZE": "LFS_READ_SIZE",
|
|
|
- "LFS_BLOCK_SIZE": 512,
|
|
|
- "LFS_BLOCK_COUNT": 1024,
|
|
|
- "LFS_BLOCK_CYCLES": -1,
|
|
|
- "LFS_CACHE_SIZE": "(64 % LFS_PROG_SIZE == 0 ? 64 : LFS_PROG_SIZE)",
|
|
|
- "LFS_LOOKAHEAD_SIZE": 16,
|
|
|
- "LFS_ERASE_VALUE": 0xff,
|
|
|
+ 'LFS_READ_SIZE': 16,
|
|
|
+ 'LFS_PROG_SIZE': 'LFS_READ_SIZE',
|
|
|
+ 'LFS_BLOCK_SIZE': 512,
|
|
|
+ 'LFS_BLOCK_COUNT': 1024,
|
|
|
+ 'LFS_BLOCK_CYCLES': -1,
|
|
|
+ 'LFS_CACHE_SIZE': '(64 % LFS_PROG_SIZE == 0 ? 64 : LFS_PROG_SIZE)',
|
|
|
+ 'LFS_LOOKAHEAD_SIZE': 16,
|
|
|
+ 'LFS_ERASE_VALUE': 0xff,
|
|
|
+ 'LFS_ERASE_CYCLES': 0,
|
|
|
+ 'LFS_BADBLOCK_BEHAVIOR': 'LFS_TESTBD_BADBLOCK_NOPROG',
|
|
|
}
|
|
|
PROLOGUE = """
|
|
|
// prologue
|
|
|
@@ -152,10 +88,10 @@ PROLOGUE = """
|
|
|
|
|
|
__attribute__((unused)) const struct lfs_config cfg = {
|
|
|
.context = &bd,
|
|
|
- .read = LFS_BD_READ,
|
|
|
- .prog = LFS_BD_PROG,
|
|
|
- .erase = LFS_BD_ERASE,
|
|
|
- .sync = LFS_BD_SYNC,
|
|
|
+ .read = lfs_testbd_read,
|
|
|
+ .prog = lfs_testbd_prog,
|
|
|
+ .erase = lfs_testbd_erase,
|
|
|
+ .sync = lfs_testbd_sync,
|
|
|
.read_size = LFS_READ_SIZE,
|
|
|
.prog_size = LFS_PROG_SIZE,
|
|
|
.block_size = LFS_BLOCK_SIZE,
|
|
|
@@ -166,15 +102,17 @@ PROLOGUE = """
|
|
|
};
|
|
|
|
|
|
__attribute__((unused)) const struct lfs_testbd_config bdcfg = {
|
|
|
- .filecfg.erase_value = LFS_ERASE_VALUE,
|
|
|
- .ramcfg.erase_value = LFS_ERASE_VALUE,
|
|
|
+ .erase_value = LFS_ERASE_VALUE,
|
|
|
+ .erase_cycles = LFS_ERASE_CYCLES,
|
|
|
+ .badblock_behavior = LFS_BADBLOCK_BEHAVIOR,
|
|
|
+ .power_cycles = lfs_testbd_cycles,
|
|
|
};
|
|
|
|
|
|
- lfs_testbd_createcfg(&cfg, &bdcfg) => 0;
|
|
|
+ lfs_testbd_createcfg(&cfg, lfs_testbd_path, &bdcfg) => 0;
|
|
|
"""
|
|
|
EPILOGUE = """
|
|
|
// epilogue
|
|
|
- lfs_testbd_destroy(&cfg);
|
|
|
+ lfs_testbd_destroy(&cfg) => 0;
|
|
|
"""
|
|
|
PASS = '\033[32m✓\033[0m'
|
|
|
FAIL = '\033[31m✗\033[0m'
|
|
|
@@ -224,15 +162,15 @@ class TestCase:
|
|
|
|
|
|
def build(self, f, **_):
|
|
|
# prologue
|
|
|
- f.write('void test_case%d(%s) {\n' % (self.caseno, ','.join(
|
|
|
+ for k, v in sorted(self.defines.items()):
|
|
|
+ if k not in self.suite.defines:
|
|
|
+ f.write('#define %s %s\n' % (k, v))
|
|
|
+
|
|
|
+ f.write('void test_case%d(%s) {' % (self.caseno, ','.join(
|
|
|
'\n'+8*' '+'__attribute__((unused)) intmax_t %s' % k
|
|
|
for k in sorted(self.perms[0].defines)
|
|
|
if k not in self.defines)))
|
|
|
|
|
|
- for k, v in sorted(self.defines.items()):
|
|
|
- if k not in self.suite.defines:
|
|
|
- f.write(4*' '+'#define %s %s\n' % (k, v))
|
|
|
-
|
|
|
f.write(PROLOGUE)
|
|
|
f.write('\n')
|
|
|
f.write(4*' '+'// test case %d\n' % self.caseno)
|
|
|
@@ -243,13 +181,11 @@ class TestCase:
|
|
|
|
|
|
# epilogue
|
|
|
f.write(EPILOGUE)
|
|
|
- f.write('\n')
|
|
|
+ f.write('}\n')
|
|
|
|
|
|
for k, v in sorted(self.defines.items()):
|
|
|
if k not in self.suite.defines:
|
|
|
- f.write(4*' '+'#undef %s\n' % k)
|
|
|
-
|
|
|
- f.write('}\n')
|
|
|
+ f.write('#undef %s\n' % k)
|
|
|
|
|
|
def shouldtest(self, **args):
|
|
|
if (self.filter is not None and
|
|
|
@@ -265,7 +201,8 @@ class TestCase:
|
|
|
else:
|
|
|
return True
|
|
|
|
|
|
- def test(self, exec=[], persist=False, gdb=False, failure=None, **args):
|
|
|
+ def test(self, exec=[], persist=False, cycles=None,
|
|
|
+ gdb=False, failure=None, **args):
|
|
|
# build command
|
|
|
cmd = exec + ['./%s.test' % self.suite.path,
|
|
|
repr(self.caseno), repr(self.permno)]
|
|
|
@@ -280,6 +217,10 @@ class TestCase:
|
|
|
|
|
|
cmd.append(self.suite.path + '.disk')
|
|
|
|
|
|
+ # simulate power-loss after n cycles?
|
|
|
+ if cycles:
|
|
|
+ cmd.append(str(cycles))
|
|
|
+
|
|
|
# failed? drop into debugger?
|
|
|
if gdb and failure:
|
|
|
ncmd = ['gdb']
|
|
|
@@ -295,19 +236,25 @@ class TestCase:
|
|
|
|
|
|
if args.get('verbose', False):
|
|
|
print(' '.join(shlex.quote(c) for c in ncmd))
|
|
|
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
|
sys.exit(sp.call(ncmd))
|
|
|
|
|
|
# run test case!
|
|
|
- stdout = []
|
|
|
- assert_ = None
|
|
|
+ mpty, spty = pty.openpty()
|
|
|
if args.get('verbose', False):
|
|
|
print(' '.join(shlex.quote(c) for c in cmd))
|
|
|
- proc = sp.Popen(cmd,
|
|
|
- universal_newlines=True,
|
|
|
- bufsize=1,
|
|
|
- stdout=sp.PIPE,
|
|
|
- stderr=sp.STDOUT)
|
|
|
- for line in iter(proc.stdout.readline, ''):
|
|
|
+ proc = sp.Popen(cmd, stdout=spty, stderr=spty)
|
|
|
+ os.close(spty)
|
|
|
+ mpty = os.fdopen(mpty, 'r', 1)
|
|
|
+ stdout = []
|
|
|
+ assert_ = None
|
|
|
+ while True:
|
|
|
+ try:
|
|
|
+ line = mpty.readline()
|
|
|
+ except OSError as e:
|
|
|
+ if e.errno == errno.EIO:
|
|
|
+ break
|
|
|
+ raise
|
|
|
stdout.append(line)
|
|
|
if args.get('verbose', False):
|
|
|
sys.stdout.write(line)
|
|
|
@@ -361,36 +308,23 @@ class ReentrantTestCase(TestCase):
|
|
|
return self.reentrant and super().shouldtest(**args)
|
|
|
|
|
|
def test(self, exec=[], persist=False, gdb=False, failure=None, **args):
|
|
|
- # clear disk first?
|
|
|
- if persist != 'noerase':
|
|
|
- try:
|
|
|
- os.remove(self.suite.path + '.disk')
|
|
|
- except FileNotFoundError:
|
|
|
- pass
|
|
|
-
|
|
|
for cycles in it.count(1):
|
|
|
+ # clear disk first?
|
|
|
+ if cycles == 1 and persist != 'noerase':
|
|
|
+ persist = 'erase'
|
|
|
+ else:
|
|
|
+ persist = 'noerase'
|
|
|
+
|
|
|
# exact cycle we should drop into debugger?
|
|
|
if gdb and failure and failure.cycleno == cycles:
|
|
|
- return super().test(exec=exec, persist='noerase',
|
|
|
- gdb=gdb, failure=failure, **args)
|
|
|
+ return super().test(gdb=gdb,
|
|
|
+ persist=persist, failure=failure, **args)
|
|
|
|
|
|
# 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_filebd_prog',
|
|
|
- '-ex', 'b lfs_filebd_erase',
|
|
|
- '-ex', 'r',
|
|
|
- ] + cycles*['-ex', 'c'] + [
|
|
|
- '-ex', 'q '
|
|
|
- '!$_isvoid($_exitsignal) ? $_exitsignal : '
|
|
|
- '!$_isvoid($_exitcode) ? $_exitcode : '
|
|
|
- '33',
|
|
|
- '--args']
|
|
|
try:
|
|
|
- return super().test(exec=nexec, persist='noerase', **args)
|
|
|
+ return super().test(persist=persist, cycles=cycles, **args)
|
|
|
except TestFailure as nfailure:
|
|
|
if nfailure.returncode == 33:
|
|
|
continue
|
|
|
@@ -535,11 +469,13 @@ class TestSuite:
|
|
|
case.build(tfs[case.in_], **args)
|
|
|
|
|
|
tf.write('\n')
|
|
|
- tf.write('const char *lfs_testbd_disk;\n')
|
|
|
+ tf.write('const char *lfs_testbd_path;\n')
|
|
|
+ tf.write('uint32_t lfs_testbd_cycles;\n')
|
|
|
tf.write('int main(int argc, char **argv) {\n')
|
|
|
- tf.write(4*' '+'int case_ = (argc >= 2) ? atoi(argv[1]) : 0;\n')
|
|
|
- tf.write(4*' '+'int perm = (argc >= 3) ? atoi(argv[2]) : 0;\n')
|
|
|
- tf.write(4*' '+'lfs_testbd_disk = (argc >= 4) ? argv[3] : NULL;\n')
|
|
|
+ tf.write(4*' '+'int case_ = (argc > 1) ? atoi(argv[1]) : 0;\n')
|
|
|
+ tf.write(4*' '+'int perm = (argc > 2) ? atoi(argv[2]) : 0;\n')
|
|
|
+ tf.write(4*' '+'lfs_testbd_path = (argc > 3) ? argv[3] : NULL;\n')
|
|
|
+ tf.write(4*' '+'lfs_testbd_cycles = (argc > 4) ? atoi(argv[4]) : 0;\n')
|
|
|
for perm in self.perms:
|
|
|
# test declaration
|
|
|
tf.write(4*' '+'extern void test_case%d(%s);\n' % (
|
|
|
@@ -671,15 +607,20 @@ def main(**args):
|
|
|
cmd = (['make', '-f', 'Makefile'] +
|
|
|
list(it.chain.from_iterable(['-f', m] for m in makefiles)) +
|
|
|
[target for target in targets])
|
|
|
- stdout = []
|
|
|
+ mpty, spty = pty.openpty()
|
|
|
if args.get('verbose', False):
|
|
|
print(' '.join(shlex.quote(c) for c in cmd))
|
|
|
- proc = sp.Popen(cmd,
|
|
|
- universal_newlines=True,
|
|
|
- bufsize=1,
|
|
|
- stdout=sp.PIPE,
|
|
|
- stderr=sp.STDOUT)
|
|
|
- for line in iter(proc.stdout.readline, ''):
|
|
|
+ proc = sp.Popen(cmd, stdout=spty, stderr=spty)
|
|
|
+ os.close(spty)
|
|
|
+ mpty = os.fdopen(mpty, 'r', 1)
|
|
|
+ stdout = []
|
|
|
+ while True:
|
|
|
+ try:
|
|
|
+ line = mpty.readline()
|
|
|
+ except OSError as e:
|
|
|
+ if e.errno == errno.EIO:
|
|
|
+ break
|
|
|
+ raise
|
|
|
stdout.append(line)
|
|
|
if args.get('verbose', False):
|
|
|
sys.stdout.write(line)
|