test_.py 25 KB

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