test_.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. #!/usr/bin/env python3
  2. # This script manages littlefs tests, which are configured with
  3. # .toml files stored in the tests directory.
  4. #
  5. import toml
  6. import glob
  7. import re
  8. import os
  9. import io
  10. import itertools as it
  11. import collections.abc as abc
  12. import subprocess as sp
  13. import base64
  14. import sys
  15. import copy
  16. import shlex
  17. TESTDIR = 'tests_'
  18. RULES = """
  19. define FLATTEN
  20. %$(subst /,.,$(target:.c=.t.c)): $(target)
  21. cat <(echo '#line 1 "$$<"') $$< > $$@
  22. endef
  23. $(foreach target,$(SRC),$(eval $(FLATTEN)))
  24. -include tests_/*.d
  25. .SECONDARY:
  26. %.c: %.t.c
  27. ./scripts/explode_asserts.py $< -o $@
  28. %.test: override CFLAGS += -fdiagnostics-color=always
  29. %.test: override CFLAGS += -ggdb
  30. %.test: %.test.o $(foreach f,$(subst /,.,$(SRC:.c=.o)),%.test.$f)
  31. $(CC) $(CFLAGS) $^ $(LFLAGS) -o $@
  32. """
  33. GLOBALS = """
  34. //////////////// AUTOGENERATED TEST ////////////////
  35. #include "lfs.h"
  36. #include "filebd/lfs_filebd.h"
  37. #include "rambd/lfs_rambd.h"
  38. #include <stdio.h>
  39. const char *LFS_DISK = NULL;
  40. """
  41. DEFINES = {
  42. "LFS_READ_SIZE": 16,
  43. "LFS_PROG_SIZE": "LFS_READ_SIZE",
  44. "LFS_BLOCK_SIZE": 512,
  45. "LFS_BLOCK_COUNT": 1024,
  46. "LFS_BLOCK_CYCLES": 1024,
  47. "LFS_CACHE_SIZE": "(64 % LFS_PROG_SIZE == 0 ? 64 : LFS_PROG_SIZE)",
  48. "LFS_LOOKAHEAD_SIZE": 16,
  49. "LFS_ERASE_VALUE": 0xff,
  50. }
  51. PROLOGUE = """
  52. // prologue
  53. __attribute__((unused)) lfs_t lfs;
  54. __attribute__((unused)) lfs_filebd_t filebd;
  55. __attribute__((unused)) lfs_rambd_t rambd;
  56. __attribute__((unused)) lfs_file_t file;
  57. __attribute__((unused)) lfs_dir_t dir;
  58. __attribute__((unused)) struct lfs_info info;
  59. __attribute__((unused)) char path[1024];
  60. __attribute__((unused)) uint8_t buffer[1024];
  61. __attribute__((unused)) lfs_size_t size;
  62. __attribute__((unused)) int err;
  63. __attribute__((unused)) const struct lfs_config cfg = {
  64. .context = LFS_DISK ? (void*)&filebd : (void*)&rambd,
  65. .read = LFS_DISK ? &lfs_filebd_read : &lfs_rambd_read,
  66. .prog = LFS_DISK ? &lfs_filebd_prog : &lfs_rambd_prog,
  67. .erase = LFS_DISK ? &lfs_filebd_erase : &lfs_rambd_erase,
  68. .sync = LFS_DISK ? &lfs_filebd_sync : &lfs_rambd_sync,
  69. .read_size = LFS_READ_SIZE,
  70. .prog_size = LFS_PROG_SIZE,
  71. .block_size = LFS_BLOCK_SIZE,
  72. .block_count = LFS_BLOCK_COUNT,
  73. .block_cycles = LFS_BLOCK_CYCLES,
  74. .cache_size = LFS_CACHE_SIZE,
  75. .lookahead_size = LFS_LOOKAHEAD_SIZE,
  76. };
  77. __attribute__((unused)) const struct lfs_filebd_config filecfg = {
  78. .erase_value = LFS_ERASE_VALUE,
  79. };
  80. __attribute__((unused)) const struct lfs_rambd_config ramcfg = {
  81. .erase_value = LFS_ERASE_VALUE,
  82. };
  83. if (LFS_DISK) {
  84. lfs_filebd_createcfg(&cfg, LFS_DISK, &filecfg);
  85. } else {
  86. lfs_rambd_createcfg(&cfg, &ramcfg);
  87. }
  88. """
  89. EPILOGUE = """
  90. // epilogue
  91. if (LFS_DISK) {
  92. lfs_filebd_destroy(&cfg);
  93. } else {
  94. lfs_rambd_destroy(&cfg);
  95. }
  96. """
  97. PASS = '\033[32m✓\033[0m'
  98. FAIL = '\033[31m✗\033[0m'
  99. class TestFailure(Exception):
  100. def __init__(self, case, returncode=None, stdout=None, assert_=None):
  101. self.case = case
  102. self.returncode = returncode
  103. self.stdout = stdout
  104. self.assert_ = assert_
  105. class TestCase:
  106. def __init__(self, config, suite=None, caseno=None, lineno=None, **_):
  107. self.suite = suite
  108. self.caseno = caseno
  109. self.lineno = lineno
  110. self.code = config['code']
  111. self.defines = config.get('define', {})
  112. self.leaky = config.get('leaky', False)
  113. def __str__(self):
  114. if hasattr(self, 'permno'):
  115. return '%s[%d,%d]' % (
  116. self.suite.name, self.caseno, self.permno)
  117. else:
  118. return '%s[%d]' % (
  119. self.suite.name, self.caseno)
  120. def permute(self, defines, permno=None, **_):
  121. ncase = copy.copy(self)
  122. ncase.case = self
  123. ncase.perms = [ncase]
  124. ncase.permno = permno
  125. ncase.defines = defines
  126. return ncase
  127. def build(self, f, **_):
  128. # prologue
  129. f.write('void test_case%d(' % self.caseno)
  130. first = True
  131. for k, v in sorted(self.perms[0].defines.items()):
  132. if k not in self.defines:
  133. if not first:
  134. f.write(',')
  135. else:
  136. first = False
  137. f.write('\n')
  138. f.write(8*' '+'__attribute__((unused)) intmax_t %s' % k)
  139. f.write(') {\n')
  140. for k, v in sorted(self.defines.items()):
  141. if k not in self.suite.defines:
  142. f.write(4*' '+'#define %s %s\n' % (k, v))
  143. f.write(PROLOGUE)
  144. f.write('\n')
  145. f.write(4*' '+'// test case %d\n' % self.caseno)
  146. f.write(4*' '+'#line %d "%s"\n' % (self.lineno, self.suite.path))
  147. # test case goes here
  148. f.write(self.code)
  149. # epilogue
  150. f.write(EPILOGUE)
  151. f.write('\n')
  152. for k, v in sorted(self.defines.items()):
  153. if k not in self.suite.defines:
  154. f.write(4*' '+'#undef %s\n' % k)
  155. f.write('}\n')
  156. def test(self, exec=[], persist=False, gdb=False, failure=None, **args):
  157. # build command
  158. cmd = exec + ['./%s.test' % self.suite.path,
  159. repr(self.caseno), repr(self.permno)]
  160. if persist:
  161. cmd.append(self.suite.path + '.test.disk')
  162. # failed? drop into debugger?
  163. if gdb and failure:
  164. ncmd = ['gdb']
  165. if gdb == 'assert':
  166. ncmd.extend(['-ex', 'r'])
  167. if failure.assert_:
  168. ncmd.extend(['-ex', 'up'])
  169. elif gdb == 'start':
  170. ncmd.extend([
  171. '-ex', 'b %s:%d' % (self.suite.path, self.lineno),
  172. '-ex', 'r'])
  173. ncmd.extend(['--args'] + cmd)
  174. if args.get('verbose', False):
  175. print(' '.join(shlex.quote(c) for c in ncmd))
  176. sys.exit(sp.call(ncmd))
  177. # run test case!
  178. stdout = []
  179. assert_ = None
  180. if args.get('verbose', False):
  181. print(' '.join(shlex.quote(c) for c in cmd))
  182. proc = sp.Popen(cmd,
  183. universal_newlines=True,
  184. bufsize=1,
  185. stdout=sp.PIPE,
  186. stderr=sp.STDOUT)
  187. for line in iter(proc.stdout.readline, ''):
  188. stdout.append(line)
  189. if args.get('verbose', False):
  190. sys.stdout.write(line)
  191. # intercept asserts
  192. m = re.match(
  193. '^{0}([^:]+):(\d+):(?:\d+:)?{0}{1}:{0}(.*)$'
  194. .format('(?:\033\[[\d;]*.| )*', 'assert'),
  195. line)
  196. if m and assert_ is None:
  197. try:
  198. with open(m.group(1)) as f:
  199. lineno = int(m.group(2))
  200. line = next(it.islice(f, lineno-1, None)).strip('\n')
  201. assert_ = {
  202. 'path': m.group(1),
  203. 'line': line,
  204. 'lineno': lineno,
  205. 'message': m.group(3)}
  206. except:
  207. pass
  208. proc.wait()
  209. # did we pass?
  210. if proc.returncode != 0:
  211. raise TestFailure(self, proc.returncode, stdout, assert_)
  212. else:
  213. return PASS
  214. class ValgrindTestCase(TestCase):
  215. def __init__(self, config, **args):
  216. self.leaky = config.get('leaky', False)
  217. super().__init__(config, **args)
  218. def test(self, exec=[], **args):
  219. if self.leaky:
  220. return
  221. exec = exec + [
  222. 'valgrind',
  223. '--leak-check=full',
  224. '--error-exitcode=4',
  225. '-q']
  226. return super().test(exec=exec, **args)
  227. class ReentrantTestCase(TestCase):
  228. def __init__(self, config, **args):
  229. self.reentrant = config.get('reentrant', False)
  230. super().__init__(config, **args)
  231. def test(self, exec=[], persist=False, gdb=False, failure=None, **args):
  232. if not self.reentrant:
  233. return
  234. # clear disk first?
  235. if not persist:
  236. try:
  237. os.remove(self.suite.path + '.test.disk')
  238. except FileNotFoundError:
  239. pass
  240. for cycles in it.count(1):
  241. # exact cycle we should drop into debugger?
  242. if gdb and failure and failure.cycleno == cycles:
  243. return super().test(exec=exec, persist=True,
  244. gdb=gdb, failure=failure, **args)
  245. # run tests, but kill the program after prog/erase has
  246. # been hit n cycles. We exit with a special return code if the
  247. # program has not finished, since this isn't a test failure.
  248. nexec = exec + [
  249. 'gdb', '-batch-silent',
  250. '-ex', 'handle all nostop',
  251. '-ex', 'b lfs_filebd_prog',
  252. '-ex', 'b lfs_filebd_erase',
  253. '-ex', 'r',
  254. ] + cycles*['-ex', 'c'] + [
  255. '-ex', 'q '
  256. '!$_isvoid($_exitsignal) ? $_exitsignal : '
  257. '!$_isvoid($_exitcode) ? $_exitcode : '
  258. '33',
  259. '--args']
  260. try:
  261. return super().test(exec=nexec, persist=True, **args)
  262. except TestFailure as nfailure:
  263. if nfailure.returncode == 33:
  264. continue
  265. else:
  266. nfailure.cycleno = cycles
  267. raise
  268. class TestSuite:
  269. def __init__(self, path, TestCase=TestCase, **args):
  270. self.name = os.path.basename(path)
  271. if self.name.endswith('.toml'):
  272. self.name = self.name[:-len('.toml')]
  273. self.path = path
  274. self.TestCase = TestCase
  275. with open(path) as f:
  276. # load tests
  277. config = toml.load(f)
  278. # find line numbers
  279. f.seek(0)
  280. linenos = []
  281. for i, line in enumerate(f):
  282. if re.match(r'^\s*code\s*=\s*(\'\'\'|""")', line):
  283. linenos.append(i + 2)
  284. # grab global config
  285. self.defines = config.get('define', {})
  286. # create initial test cases
  287. self.cases = []
  288. for i, (case, lineno) in enumerate(zip(config['case'], linenos)):
  289. self.cases.append(self.TestCase(case,
  290. suite=self, caseno=i, lineno=lineno, **args))
  291. def __str__(self):
  292. return self.name
  293. def __lt__(self, other):
  294. return self.name < other.name
  295. def permute(self, defines={}, **args):
  296. for case in self.cases:
  297. # lets find all parameterized definitions, in one of [args.D,
  298. # suite.defines, case.defines, DEFINES]. Note that each of these
  299. # can be either a dict of defines, or a list of dicts, expressing
  300. # an initial set of permutations.
  301. pending = [{}]
  302. for inits in [defines, self.defines, case.defines, DEFINES]:
  303. if not isinstance(inits, list):
  304. inits = [inits]
  305. npending = []
  306. for init, pinit in it.product(inits, pending):
  307. ninit = pinit.copy()
  308. for k, v in init.items():
  309. if k not in ninit:
  310. try:
  311. ninit[k] = eval(v)
  312. except:
  313. ninit[k] = v
  314. npending.append(ninit)
  315. pending = npending
  316. # expand permutations
  317. pending = list(reversed(pending))
  318. expanded = []
  319. while pending:
  320. perm = pending.pop()
  321. for k, v in sorted(perm.items()):
  322. if not isinstance(v, str) and isinstance(v, abc.Iterable):
  323. for nv in reversed(v):
  324. nperm = perm.copy()
  325. nperm[k] = nv
  326. pending.append(nperm)
  327. break
  328. else:
  329. expanded.append(perm)
  330. # generate permutations
  331. case.perms = []
  332. for i, perm in enumerate(expanded):
  333. case.perms.append(case.permute(perm, permno=i, **args))
  334. # also track non-unique defines
  335. case.defines = {}
  336. for k, v in case.perms[0].defines.items():
  337. if all(perm.defines[k] == v for perm in case.perms):
  338. case.defines[k] = v
  339. # track all perms and non-unique defines
  340. self.perms = []
  341. for case in self.cases:
  342. self.perms.extend(case.perms)
  343. self.defines = {}
  344. for k, v in self.perms[0].defines.items():
  345. if all(perm.defines.get(k, None) == v for perm in self.perms):
  346. self.defines[k] = v
  347. return self.perms
  348. def build(self, **args):
  349. # build test.c
  350. f = io.StringIO()
  351. f.write(GLOBALS)
  352. for case in self.cases:
  353. f.write('\n')
  354. case.build(f, **args)
  355. f.write('\n')
  356. f.write('int main(int argc, char **argv) {\n')
  357. f.write(4*' '+'int case_ = (argc >= 2) ? atoi(argv[1]) : 0;\n')
  358. f.write(4*' '+'int perm = (argc >= 3) ? atoi(argv[2]) : 0;\n')
  359. f.write(4*' '+'LFS_DISK = (argc >= 4) ? argv[3] : NULL;\n')
  360. for perm in self.perms:
  361. f.write(4*' '+'if (argc < 3 || '
  362. '(case_ == %d && perm == %d)) { ' % (
  363. perm.caseno, perm.permno))
  364. f.write('test_case%d(' % perm.caseno)
  365. first = True
  366. for k, v in sorted(perm.defines.items()):
  367. if k not in perm.case.defines:
  368. if not first:
  369. f.write(', ')
  370. else:
  371. first = False
  372. f.write(str(v))
  373. f.write('); }\n')
  374. f.write('}\n')
  375. # add test-related rules
  376. rules = RULES.replace(4*' ', '\t')
  377. with open(self.path + '.test.mk', 'w') as mk:
  378. mk.write(rules)
  379. mk.write('\n')
  380. # add truely global defines globally
  381. for k, v in sorted(self.defines.items()):
  382. mk.write('%s: override CFLAGS += -D%s=%r\n' % (
  383. self.path+'.test', k, v))
  384. # write test.c in base64 so make can decide when to rebuild
  385. mk.write('%s: %s\n' % (self.path+'.test.t.c', self.path))
  386. mk.write('\t@base64 -d <<< ')
  387. mk.write(base64.b64encode(
  388. f.getvalue().encode('utf8')).decode('utf8'))
  389. mk.write(' > $@\n')
  390. self.makefile = self.path + '.test.mk'
  391. self.target = self.path + '.test'
  392. return self.makefile, self.target
  393. def test(self, caseno=None, permno=None, **args):
  394. # run test suite!
  395. if not args.get('verbose', True):
  396. sys.stdout.write(self.name + ' ')
  397. sys.stdout.flush()
  398. for perm in self.perms:
  399. if caseno is not None and perm.caseno != caseno:
  400. continue
  401. if permno is not None and perm.permno != permno:
  402. continue
  403. try:
  404. result = perm.test(**args)
  405. except TestFailure as failure:
  406. perm.result = failure
  407. if not args.get('verbose', True):
  408. sys.stdout.write(FAIL)
  409. sys.stdout.flush()
  410. if not args.get('keep_going', False):
  411. if not args.get('verbose', True):
  412. sys.stdout.write('\n')
  413. raise
  414. else:
  415. if result == PASS:
  416. perm.result = PASS
  417. if not args.get('verbose', True):
  418. sys.stdout.write(PASS)
  419. sys.stdout.flush()
  420. if not args.get('verbose', True):
  421. sys.stdout.write('\n')
  422. def main(**args):
  423. testpath = args['testpath']
  424. # optional brackets for specific test
  425. m = re.search(r'\[(\d+)(?:,(\d+))?\]$', testpath)
  426. if m:
  427. caseno = int(m.group(1))
  428. permno = int(m.group(2)) if m.group(2) is not None else None
  429. testpath = testpath[:m.start()]
  430. else:
  431. caseno = None
  432. permno = None
  433. # figure out the suite's toml file
  434. if os.path.isdir(testpath):
  435. testpath = testpath + '/test_*.toml'
  436. elif os.path.isfile(testpath):
  437. testpath = testpath
  438. elif testpath.endswith('.toml'):
  439. testpath = TESTDIR + '/' + testpath
  440. else:
  441. testpath = TESTDIR + '/' + testpath + '.toml'
  442. # find tests
  443. suites = []
  444. for path in glob.glob(testpath):
  445. if args.get('valgrind', False):
  446. suites.append(TestSuite(path, TestCase=ValgrindTestCase, **args))
  447. elif args.get('reentrant', False):
  448. suites.append(TestSuite(path, TestCase=ReentrantTestCase, **args))
  449. else:
  450. suites.append(TestSuite(path, **args))
  451. # sort for reproducability
  452. suites = sorted(suites)
  453. # generate permutations
  454. defines = {}
  455. for define in args['D']:
  456. k, v, *_ = define.split('=', 2) + ['']
  457. defines[k] = v
  458. for suite in suites:
  459. suite.permute(defines, **args)
  460. # build tests in parallel
  461. print('====== building ======')
  462. makefiles = []
  463. targets = []
  464. for suite in suites:
  465. makefile, target = suite.build(**args)
  466. makefiles.append(makefile)
  467. targets.append(target)
  468. cmd = (['make', '-f', 'Makefile'] +
  469. list(it.chain.from_iterable(['-f', m] for m in makefiles)) +
  470. [target for target in targets])
  471. stdout = []
  472. if args.get('verbose', False):
  473. print(' '.join(shlex.quote(c) for c in cmd))
  474. proc = sp.Popen(cmd,
  475. universal_newlines=True,
  476. bufsize=1,
  477. stdout=sp.PIPE,
  478. stderr=sp.STDOUT)
  479. for line in iter(proc.stdout.readline, ''):
  480. stdout.append(line)
  481. if args.get('verbose', False):
  482. sys.stdout.write(line)
  483. # intercept warnings
  484. m = re.match(
  485. '^{0}([^:]+):(\d+):(?:\d+:)?{0}{1}:{0}(.*)$'
  486. .format('(?:\033\[[\d;]*.| )*', 'warning'),
  487. line)
  488. if m and not args.get('verbose', False):
  489. try:
  490. with open(m.group(1)) as f:
  491. lineno = int(m.group(2))
  492. line = next(it.islice(f, lineno-1, None)).strip('\n')
  493. sys.stdout.write(
  494. "\033[01m{path}:{lineno}:\033[01;35mwarning:\033[m "
  495. "{message}\n{line}\n\n".format(
  496. path=m.group(1), line=line, lineno=lineno,
  497. message=m.group(3)))
  498. except:
  499. pass
  500. proc.wait()
  501. if proc.returncode != 0:
  502. if not args.get('verbose', False):
  503. for line in stdout:
  504. sys.stdout.write(line)
  505. sys.exit(-3)
  506. print('built %d test suites, %d test cases, %d permutations' % (
  507. len(suites),
  508. sum(len(suite.cases) for suite in suites),
  509. sum(len(suite.perms) for suite in suites)))
  510. print('====== testing ======')
  511. try:
  512. for suite in suites:
  513. suite.test(caseno, permno, **args)
  514. except TestFailure:
  515. pass
  516. if args.get('gdb', False):
  517. failure = None
  518. for suite in suites:
  519. for perm in suite.perms:
  520. if getattr(perm, 'result', PASS) != PASS:
  521. failure = perm.result
  522. if failure is not None:
  523. print('======= gdb ======')
  524. # drop into gdb
  525. failure.case.test(failure=failure, **args)
  526. sys.exit(0)
  527. print('====== results ======')
  528. passed = 0
  529. failed = 0
  530. for suite in suites:
  531. for perm in suite.perms:
  532. if not hasattr(perm, 'result'):
  533. continue
  534. if perm.result == PASS:
  535. passed += 1
  536. else:
  537. #sys.stdout.write("--- %s ---\n" % perm)
  538. sys.stdout.write(
  539. "\033[01m{path}:{lineno}:\033[01;31mfailure:\033[m "
  540. "{perm} failed with {returncode}\n".format(
  541. perm=perm, path=perm.suite.path, lineno=perm.lineno-2,
  542. returncode=perm.result.returncode or 0))
  543. if perm.result.stdout:
  544. for line in (perm.result.stdout
  545. if not perm.result.assert_
  546. else perm.result.stdout[:-1]):
  547. sys.stdout.write(line)
  548. if perm.result.assert_:
  549. sys.stdout.write(
  550. "\033[01m{path}:{lineno}:\033[01;31massert:\033[m "
  551. "{message}\n{line}\n".format(
  552. **perm.result.assert_))
  553. else:
  554. for line in perm.result.stdout:
  555. sys.stdout.write(line)
  556. sys.stdout.write('\n')
  557. failed += 1
  558. print('tests passed: %d' % passed)
  559. print('tests failed: %d' % failed)
  560. if __name__ == "__main__":
  561. import argparse
  562. parser = argparse.ArgumentParser(
  563. description="Run parameterized tests in various configurations.")
  564. parser.add_argument('testpath', nargs='?', default=TESTDIR,
  565. help="Description of test(s) to run. By default, this is all tests \
  566. found in the \"{0}\" directory. Here, you can specify a different \
  567. directory of tests, a specific file, a suite by name, and even a \
  568. specific test case by adding brackets. For example \
  569. \"test_dirs[0]\" or \"{0}/test_dirs.toml[0]\".".format(TESTDIR))
  570. parser.add_argument('-D', action='append', default=[],
  571. help="Overriding parameter definitions.")
  572. parser.add_argument('-v', '--verbose', action='store_true',
  573. help="Output everything that is happening.")
  574. parser.add_argument('-k', '--keep-going', action='store_true',
  575. help="Run all tests instead of stopping on first error. Useful for CI.")
  576. parser.add_argument('-p', '--persist', action='store_true',
  577. help="Don't reset the tests disk before each test.")
  578. parser.add_argument('-g', '--gdb', choices=['init', 'start', 'assert'],
  579. nargs='?', const='assert',
  580. help="Drop into gdb on test failure.")
  581. parser.add_argument('--valgrind', action='store_true',
  582. help="Run non-leaky tests under valgrind to check for memory leaks.")
  583. parser.add_argument('--reentrant', action='store_true',
  584. help="Run reentrant tests with simulated power-loss.")
  585. parser.add_argument('-e', '--exec', default=[], type=lambda e: e.split(' '),
  586. help="Run tests with another executable prefixed on the command line.")
  587. main(**vars(parser.parse_args()))