test_.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856
  1. #!/usr/bin/env python3
  2. #
  3. # Script to compile and runs tests.
  4. #
  5. import collections as co
  6. import errno
  7. import glob
  8. import itertools as it
  9. import math as m
  10. import os
  11. import pty
  12. import re
  13. import shlex
  14. import shutil
  15. import subprocess as sp
  16. import threading as th
  17. import time
  18. import toml
  19. TEST_PATHS = ['tests_']
  20. RUNNER_PATH = './runners/test_runner'
  21. SUITE_PROLOGUE = """
  22. #include "runners/test_runner.h"
  23. #include <stdio.h>
  24. """
  25. CASE_PROLOGUE = """
  26. """
  27. CASE_EPILOGUE = """
  28. """
  29. TEST_PREDEFINES = [
  30. 'READ_SIZE',
  31. 'PROG_SIZE',
  32. 'BLOCK_SIZE',
  33. 'BLOCK_COUNT',
  34. 'BLOCK_CYCLES',
  35. 'CACHE_SIZE',
  36. 'LOOKAHEAD_SIZE',
  37. 'ERASE_VALUE',
  38. 'ERASE_CYCLES',
  39. 'BADBLOCK_BEHAVIOR',
  40. ]
  41. # TODO
  42. # def testpath(path):
  43. # def testcase(path):
  44. # def testperm(path):
  45. def testsuite(path):
  46. name = os.path.basename(path)
  47. if name.endswith('.toml'):
  48. name = name[:-len('.toml')]
  49. return name
  50. # TODO move this out in other files
  51. def openio(path, mode='r'):
  52. if path == '-':
  53. if 'r' in mode:
  54. return os.fdopen(os.dup(sys.stdin.fileno()), 'r')
  55. else:
  56. return os.fdopen(os.dup(sys.stdout.fileno()), 'w')
  57. else:
  58. return open(path, mode)
  59. class TestCase:
  60. # create a TestCase object from a config
  61. def __init__(self, config, args={}):
  62. self.name = config.pop('name')
  63. self.path = config.pop('path')
  64. self.suite = config.pop('suite')
  65. self.lineno = config.pop('lineno', None)
  66. self.if_ = config.pop('if', None)
  67. if isinstance(self.if_, bool):
  68. self.if_ = 'true' if self.if_ else 'false'
  69. self.if_lineno = config.pop('if_lineno', None)
  70. self.code = config.pop('code')
  71. self.code_lineno = config.pop('code_lineno', None)
  72. self.normal = config.pop('normal',
  73. config.pop('suite_normal', True))
  74. self.reentrant = config.pop('reentrant',
  75. config.pop('suite_reentrant', False))
  76. self.valgrind = config.pop('valgrind',
  77. config.pop('suite_valgrind', True))
  78. # figure out defines and the number of resulting permutations
  79. self.defines = {}
  80. for k, v in (
  81. config.pop('suite_defines', {})
  82. | config.pop('defines', {})).items():
  83. if not isinstance(v, list):
  84. v = [v]
  85. self.defines[k] = v
  86. self.permutations = m.prod(len(v) for v in self.defines.values())
  87. for k in config.keys():
  88. print('warning: in %s, found unused key %r' % (self.id(), k),
  89. file=sys.stderr)
  90. def id(self):
  91. return '%s#%s' % (self.suite, self.name)
  92. class TestSuite:
  93. # create a TestSuite object from a toml file
  94. def __init__(self, path, args={}):
  95. self.name = testsuite(path)
  96. self.path = path
  97. # load toml file and parse test cases
  98. with open(self.path) as f:
  99. # load tests
  100. config = toml.load(f)
  101. # find line numbers
  102. f.seek(0)
  103. case_linenos = []
  104. if_linenos = []
  105. code_linenos = []
  106. for i, line in enumerate(f):
  107. match = re.match(
  108. '(?P<case>\[\s*cases\s*\.\s*(?P<name>\w+)\s*\])' +
  109. '|(?P<if>if\s*=)'
  110. '|(?P<code>code\s*=)',
  111. line)
  112. if match and match.group('case'):
  113. case_linenos.append((i+1, match.group('name')))
  114. elif match and match.group('if'):
  115. if_linenos.append(i+1)
  116. elif match and match.group('code'):
  117. code_linenos.append(i+2)
  118. # sort in case toml parsing did not retain order
  119. case_linenos.sort()
  120. cases = config.pop('cases', [])
  121. for (lineno, name), (nlineno, _) in it.zip_longest(
  122. case_linenos, case_linenos[1:],
  123. fillvalue=(float('inf'), None)):
  124. if_lineno = min(
  125. (l for l in if_linenos if l >= lineno and l < nlineno),
  126. default=None)
  127. code_lineno = min(
  128. (l for l in code_linenos if l >= lineno and l < nlineno),
  129. default=None)
  130. cases[name]['lineno'] = lineno
  131. cases[name]['if_lineno'] = if_lineno
  132. cases[name]['code_lineno'] = code_lineno
  133. self.if_ = config.pop('if', None)
  134. if isinstance(self.if_, bool):
  135. self.if_ = 'true' if self.if_ else 'false'
  136. self.if_lineno = min(
  137. (l for l in if_linenos
  138. if not case_linenos or l < case_linenos[0][0]),
  139. default=None)
  140. self.code = config.pop('code', None)
  141. self.code_lineno = min(
  142. (l for l in code_linenos
  143. if not case_linenos or l < case_linenos[0][0]),
  144. default=None)
  145. # a couple of these we just forward to all cases
  146. defines = config.pop('defines', {})
  147. normal = config.pop('normal', True)
  148. reentrant = config.pop('reentrant', False)
  149. valgrind = config.pop('valgrind', True)
  150. self.cases = []
  151. for name, case in sorted(cases.items(),
  152. key=lambda c: c[1].get('lineno')):
  153. self.cases.append(TestCase(config={
  154. 'name': name,
  155. 'path': path + (':%d' % case['lineno']
  156. if 'lineno' in case else ''),
  157. 'suite': self.name,
  158. 'suite_defines': defines,
  159. 'suite_normal': normal,
  160. 'suite_reentrant': reentrant,
  161. 'suite_valgrind': valgrind,
  162. **case}))
  163. # combine pre-defines and per-case defines
  164. self.defines = TEST_PREDEFINES + sorted(
  165. set.union(*(set(case.defines) for case in self.cases)))
  166. # combine other per-case things
  167. self.normal = any(case.normal for case in self.cases)
  168. self.reentrant = any(case.reentrant for case in self.cases)
  169. self.valgrind = any(case.valgrind for case in self.cases)
  170. for k in config.keys():
  171. print('warning: in %s, found unused key %r' % (self.id(), k),
  172. file=sys.stderr)
  173. def id(self):
  174. return self.name
  175. def compile(**args):
  176. # find .toml files
  177. paths = []
  178. for path in args.get('test_paths', TEST_PATHS):
  179. if os.path.isdir(path):
  180. path = path + '/*.toml'
  181. for path in glob.glob(path):
  182. paths.append(path)
  183. if not paths:
  184. print('no test suites found in %r?' % args['test_paths'])
  185. sys.exit(-1)
  186. if not args.get('source'):
  187. if len(paths) > 1:
  188. print('more than one test suite for compilation? (%r)'
  189. % args['test_paths'])
  190. sys.exit(-1)
  191. # write out a test suite
  192. suite = TestSuite(paths[0])
  193. if 'output' in args:
  194. with openio(args['output'], 'w') as f:
  195. # redirect littlefs tracing
  196. f.write('#define LFS_TRACE_(fmt, ...) do { \\\n')
  197. f.write(8*' '+'extern FILE *test_trace; \\\n')
  198. f.write(8*' '+'if (test_trace) { \\\n')
  199. f.write(12*' '+'fprintf(test_trace, '
  200. '"%s:%d:trace: " fmt "%s\\n", \\\n')
  201. f.write(20*' '+'__FILE__, __LINE__, __VA_ARGS__); \\\n')
  202. f.write(8*' '+'} \\\n')
  203. f.write(4*' '+'} while (0)\n')
  204. f.write('#define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "")\n')
  205. f.write('#define LFS_TESTBD_TRACE(...) '
  206. 'LFS_TRACE_(__VA_ARGS__, "")\n')
  207. f.write('\n')
  208. f.write('%s\n' % SUITE_PROLOGUE.strip())
  209. f.write('\n')
  210. if suite.code is not None:
  211. if suite.code_lineno is not None:
  212. f.write('#line %d "%s"\n'
  213. % (suite.code_lineno, suite.path))
  214. f.write(suite.code)
  215. f.write('\n')
  216. for i, define in it.islice(
  217. enumerate(suite.defines),
  218. len(TEST_PREDEFINES), None):
  219. f.write('#define %-24s test_define(%d)\n' % (define, i))
  220. f.write('\n')
  221. for case in suite.cases:
  222. # create case defines
  223. if case.defines:
  224. sorted_defines = sorted(case.defines.items())
  225. for perm, defines in enumerate(
  226. it.product(*(
  227. [(k, v) for v in vs]
  228. for k, vs in sorted_defines))):
  229. f.write('const test_define_t '
  230. '__test__%s__%s__%d__defines[] = {\n'
  231. % (suite.name, case.name, perm))
  232. for k, v in defines:
  233. f.write(4*' '+'%s,\n' % v)
  234. f.write('};\n')
  235. f.write('\n')
  236. f.write('const test_define_t *const '
  237. '__test__%s__%s__defines[] = {\n'
  238. % (suite.name, case.name))
  239. for perm in range(case.permutations):
  240. f.write(4*' '+'__test__%s__%s__%d__defines,\n'
  241. % (suite.name, case.name, perm))
  242. f.write('};\n')
  243. f.write('\n')
  244. f.write('const uint8_t '
  245. '__test__%s__%s__define_map[] = {\n'
  246. % (suite.name, case.name))
  247. for k in suite.defines:
  248. f.write(4*' '+'%s,\n'
  249. % ([k for k, _ in sorted_defines].index(k)
  250. if k in case.defines else '0xff'))
  251. f.write('};\n')
  252. f.write('\n')
  253. # create case filter function
  254. if suite.if_ is not None or case.if_ is not None:
  255. f.write('bool __test__%s__%s__filter('
  256. '__attribute__((unused)) uint32_t perm) {\n'
  257. % (suite.name, case.name))
  258. if suite.if_ is not None:
  259. f.write(4*' '+'#line %d "%s"\n'
  260. % (suite.if_lineno, suite.path))
  261. f.write(4*' '+'if (!(%s)) {\n' % suite.if_)
  262. f.write(8*' '+'return false;\n')
  263. f.write(4*' '+'}\n')
  264. f.write('\n')
  265. if case.if_ is not None:
  266. f.write(4*' '+'#line %d "%s"\n'
  267. % (case.if_lineno, suite.path))
  268. f.write(4*' '+'if (!(%s)) {\n' % case.if_)
  269. f.write(8*' '+'return false;\n')
  270. f.write(4*' '+'}\n')
  271. f.write('\n')
  272. f.write(4*' '+'return true;\n')
  273. f.write('}\n')
  274. f.write('\n')
  275. # create case run function
  276. f.write('void __test__%s__%s__run('
  277. '__attribute__((unused)) struct lfs_config *cfg, '
  278. '__attribute__((unused)) uint32_t perm) {\n'
  279. % (suite.name, case.name))
  280. f.write(4*' '+'%s\n'
  281. % CASE_PROLOGUE.strip().replace('\n', '\n'+4*' '))
  282. f.write('\n')
  283. f.write(4*' '+'// test case %s\n' % case.id())
  284. if case.code_lineno is not None:
  285. f.write(4*' '+'#line %d "%s"\n'
  286. % (case.code_lineno, suite.path))
  287. f.write(case.code)
  288. f.write('\n')
  289. f.write(4*' '+'%s\n'
  290. % CASE_EPILOGUE.strip().replace('\n', '\n'+4*' '))
  291. f.write('}\n')
  292. f.write('\n')
  293. # create case struct
  294. f.write('const struct test_case __test__%s__%s__case = {\n'
  295. % (suite.name, case.name))
  296. f.write(4*' '+'.id = "%s",\n' % case.id())
  297. f.write(4*' '+'.name = "%s",\n' % case.name)
  298. f.write(4*' '+'.path = "%s",\n' % case.path)
  299. f.write(4*' '+'.types = %s,\n'
  300. % ' | '.join(filter(None, [
  301. 'TEST_NORMAL' if case.normal else None,
  302. 'TEST_REENTRANT' if case.reentrant else None,
  303. 'TEST_VALGRIND' if case.valgrind else None])))
  304. f.write(4*' '+'.permutations = %d,\n' % case.permutations)
  305. if case.defines:
  306. f.write(4*' '+'.defines = __test__%s__%s__defines,\n'
  307. % (suite.name, case.name))
  308. f.write(4*' '+'.define_map = '
  309. '__test__%s__%s__define_map,\n'
  310. % (suite.name, case.name))
  311. if suite.if_ is not None or case.if_ is not None:
  312. f.write(4*' '+'.filter = __test__%s__%s__filter,\n'
  313. % (suite.name, case.name))
  314. f.write(4*' '+'.run = __test__%s__%s__run,\n'
  315. % (suite.name, case.name))
  316. f.write('};\n')
  317. f.write('\n')
  318. # create suite define names
  319. f.write('const char *const __test__%s__define_names[] = {\n'
  320. % suite.name)
  321. for k in suite.defines:
  322. f.write(4*' '+'"%s",\n' % k)
  323. f.write('};\n')
  324. f.write('\n')
  325. # create suite struct
  326. f.write('const struct test_suite __test__%s__suite = {\n'
  327. % suite.name)
  328. f.write(4*' '+'.id = "%s",\n' % suite.id())
  329. f.write(4*' '+'.name = "%s",\n' % suite.name)
  330. f.write(4*' '+'.path = "%s",\n' % suite.path)
  331. f.write(4*' '+'.types = %s,\n'
  332. % ' | '.join(filter(None, [
  333. 'TEST_NORMAL' if suite.normal else None,
  334. 'TEST_REENTRANT' if suite.reentrant else None,
  335. 'TEST_VALGRIND' if suite.valgrind else None])))
  336. f.write(4*' '+'.define_names = __test__%s__define_names,\n'
  337. % suite.name)
  338. f.write(4*' '+'.define_count = %d,\n' % len(suite.defines))
  339. f.write(4*' '+'.cases = (const struct test_case *const []){\n')
  340. for case in suite.cases:
  341. f.write(8*' '+'&__test__%s__%s__case,\n'
  342. % (suite.name, case.name))
  343. f.write(4*' '+'},\n')
  344. f.write(4*' '+'.case_count = %d,\n' % len(suite.cases))
  345. f.write('};\n')
  346. f.write('\n')
  347. else:
  348. # load all suites
  349. suites = [TestSuite(path) for path in paths]
  350. suites.sort(key=lambda s: s.name)
  351. # write out a test source
  352. if 'output' in args:
  353. with openio(args['output'], 'w') as f:
  354. # redirect littlefs tracing
  355. f.write('#define LFS_TRACE_(fmt, ...) do { \\\n')
  356. f.write(8*' '+'extern FILE *test_trace; \\\n')
  357. f.write(8*' '+'if (test_trace) { \\\n')
  358. f.write(12*' '+'fprintf(test_trace, '
  359. '"%s:%d:trace: " fmt "%s\\n", \\\n')
  360. f.write(20*' '+'__FILE__, __LINE__, __VA_ARGS__); \\\n')
  361. f.write(8*' '+'} \\\n')
  362. f.write(4*' '+'} while (0)\n')
  363. f.write('#define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "")\n')
  364. f.write('#define LFS_TESTBD_TRACE(...) '
  365. 'LFS_TRACE_(__VA_ARGS__, "")\n')
  366. f.write('\n')
  367. # copy source
  368. f.write('#line 1 "%s"\n' % args['source'])
  369. with open(args['source']) as sf:
  370. shutil.copyfileobj(sf, f)
  371. f.write('\n')
  372. f.write(SUITE_PROLOGUE)
  373. f.write('\n')
  374. # add suite info to test_runner.c
  375. if args['source'] == 'runners/test_runner.c':
  376. f.write('\n')
  377. for suite in suites:
  378. f.write('extern const struct test_suite '
  379. '__test__%s__suite;\n' % suite.name)
  380. f.write('const struct test_suite *test_suites[] = {\n')
  381. for suite in suites:
  382. f.write(4*' '+'&__test__%s__suite,\n' % suite.name)
  383. f.write('};\n')
  384. f.write('const size_t test_suite_count = %d;\n'
  385. % len(suites))
  386. def runner(**args):
  387. cmd = args['runner'].copy()
  388. # TODO multiple paths?
  389. if 'test_paths' in args:
  390. cmd.extend(args.get('test_paths'))
  391. if args.get('normal'): cmd.append('-n')
  392. if args.get('reentrant'): cmd.append('-r')
  393. if args.get('valgrind'): cmd.append('-V')
  394. if args.get('geometry'):
  395. cmd.append('-G%s' % args.get('geometry'))
  396. if args.get('define'):
  397. for define in args.get('define'):
  398. cmd.append('-D%s' % define)
  399. return cmd
  400. def list_(**args):
  401. cmd = runner(**args)
  402. if args.get('summary'): cmd.append('--summary')
  403. if args.get('list_suites'): cmd.append('--list-suites')
  404. if args.get('list_cases'): cmd.append('--list-cases')
  405. if args.get('list_paths'): cmd.append('--list-paths')
  406. if args.get('list_defines'): cmd.append('--list-defines')
  407. if args.get('list_geometries'): cmd.append('--list-geometries')
  408. if args.get('verbose'):
  409. print(' '.join(shlex.quote(c) for c in cmd))
  410. sys.exit(sp.call(cmd))
  411. def find_cases(runner_, **args):
  412. # first get suite/case/perm counts
  413. cmd = runner_ + ['--list-cases']
  414. if args.get('verbose'):
  415. print(' '.join(shlex.quote(c) for c in cmd))
  416. proc = sp.Popen(cmd,
  417. stdout=sp.PIPE,
  418. stderr=sp.PIPE if not args.get('verbose') else None,
  419. universal_newlines=True,
  420. errors='replace')
  421. expected_suite_perms = co.defaultdict(lambda: 0)
  422. expected_case_perms = co.defaultdict(lambda: 0)
  423. expected_perms = 0
  424. total_perms = 0
  425. pattern = re.compile(
  426. '^(?P<id>(?P<suite>[^#]+)#[^ #]+) +'
  427. '[^ ]+ +[^ ]+ +[^ ]+ +'
  428. '(?P<filtered>[0-9]+)/(?P<perms>[0-9]+)$')
  429. # skip the first line
  430. next(proc.stdout)
  431. for line in proc.stdout:
  432. m = pattern.match(line)
  433. if m:
  434. filtered = int(m.group('filtered'))
  435. expected_suite_perms[m.group('suite')] += filtered
  436. expected_case_perms[m.group('id')] += filtered
  437. expected_perms += filtered
  438. total_perms += int(m.group('perms'))
  439. proc.wait()
  440. if proc.returncode != 0:
  441. if not args.get('verbose'):
  442. for line in proc.stderr:
  443. sys.stdout.write(line)
  444. sys.exit(-1)
  445. return (
  446. expected_suite_perms,
  447. expected_case_perms,
  448. expected_perms,
  449. total_perms)
  450. class TestFailure(Exception):
  451. def __init__(self, id, returncode, stdout, assert_=None):
  452. self.id = id
  453. self.returncode = returncode
  454. self.stdout = stdout
  455. self.assert_ = assert_
  456. def run_step(name, runner_, **args):
  457. # get expected suite/case/perm counts
  458. expected_suite_perms, expected_case_perms, expected_perms, total_perms = (
  459. find_cases(runner_, **args))
  460. # TODO persist/trace
  461. # TODO valgrind/gdb/exec
  462. passed_suite_perms = co.defaultdict(lambda: 0)
  463. passed_case_perms = co.defaultdict(lambda: 0)
  464. passed_perms = 0
  465. failures = []
  466. pattern = re.compile('^(?:'
  467. '(?P<op>running|finished|skipped) '
  468. '(?P<id>(?P<case>(?P<suite>[^#]+)#[^\s#]+)[^\s]*)'
  469. '|' '(?P<path>[^:]+):(?P<lineno>[0-9]+):(?P<op_>assert):'
  470. ' *(?P<message>.*)' ')$')
  471. locals = th.local()
  472. # TODO use process group instead of this set?
  473. children = set()
  474. def run_runner(runner_):
  475. nonlocal passed_suite_perms
  476. nonlocal passed_case_perms
  477. nonlocal passed_perms
  478. nonlocal locals
  479. # run the tests!
  480. cmd = runner_
  481. if args.get('verbose'):
  482. print(' '.join(shlex.quote(c) for c in cmd))
  483. mpty, spty = pty.openpty()
  484. proc = sp.Popen(cmd, stdout=spty, stderr=spty)
  485. os.close(spty)
  486. mpty = os.fdopen(mpty, 'r', 1)
  487. children.add(proc)
  488. last_id = None
  489. last_stdout = []
  490. last_assert = None
  491. try:
  492. while True:
  493. # parse a line for state changes
  494. try:
  495. line = mpty.readline()
  496. except OSError as e:
  497. if e.errno == errno.EIO:
  498. break
  499. raise
  500. if not line:
  501. break
  502. last_stdout.append(line)
  503. if args.get('verbose'):
  504. sys.stdout.write(line)
  505. m = pattern.match(line)
  506. if m:
  507. op = m.group('op') or m.group('op_')
  508. if op == 'running':
  509. locals.seen_perms += 1
  510. last_id = m.group('id')
  511. last_stdout = []
  512. last_assert = None
  513. elif op == 'finished':
  514. passed_suite_perms[m.group('suite')] += 1
  515. passed_case_perms[m.group('case')] += 1
  516. passed_perms += 1
  517. elif op == 'skipped':
  518. locals.seen_perms += 1
  519. elif op == 'assert':
  520. last_assert = (
  521. m.group('path'),
  522. int(m.group('lineno')),
  523. m.group('message'))
  524. # TODO why is kill _so_ much faster than terminate?
  525. proc.kill()
  526. except KeyboardInterrupt:
  527. raise TestFailure(last_id, 1, last_stdout)
  528. finally:
  529. children.remove(proc)
  530. mpty.close()
  531. proc.wait()
  532. if proc.returncode != 0:
  533. raise TestFailure(
  534. last_id,
  535. proc.returncode,
  536. last_stdout,
  537. last_assert)
  538. def run_job(runner, skip=None, every=None):
  539. nonlocal failures
  540. nonlocal locals
  541. while (skip or 0) < total_perms:
  542. runner_ = runner.copy()
  543. if skip is not None:
  544. runner_.append('--skip=%d' % skip)
  545. if every is not None:
  546. runner_.append('--every=%d' % every)
  547. try:
  548. # run the tests
  549. locals.seen_perms = 0
  550. run_runner(runner_)
  551. except TestFailure as failure:
  552. # race condition for multiple failures?
  553. if failures and not args.get('keep_going'):
  554. break
  555. failures.append(failure)
  556. if args.get('keep_going'):
  557. # resume after failed test
  558. skip = (skip or 0) + locals.seen_perms*(every or 1)
  559. continue
  560. else:
  561. # stop other tests
  562. for child in children:
  563. # TODO why is kill _so_ much faster than terminate?
  564. child.kill()
  565. break
  566. # parallel jobs?
  567. runners = []
  568. if 'jobs' in args:
  569. for job in range(args['jobs']):
  570. runners.append(th.Thread(
  571. target=run_job, args=(runner_, job, args['jobs'])))
  572. else:
  573. runners.append(th.Thread(
  574. target=run_job, args=(runner_, None, None)))
  575. for r in runners:
  576. r.start()
  577. needs_newline = False
  578. try:
  579. while any(r.is_alive() for r in runners):
  580. time.sleep(0.01)
  581. if not args.get('verbose'):
  582. sys.stdout.write('\r\x1b[K'
  583. 'running \x1b[%dm%s\x1b[m: '
  584. '%d/%d suites, %d/%d cases, %d/%d perms%s '
  585. % (32 if not failures else 31,
  586. name,
  587. sum(passed_suite_perms[k] == v
  588. for k, v in expected_suite_perms.items()),
  589. len(expected_suite_perms),
  590. sum(passed_case_perms[k] == v
  591. for k, v in expected_case_perms.items()),
  592. len(expected_case_perms),
  593. passed_perms,
  594. expected_perms,
  595. ', \x1b[31m%d/%d failures\x1b[m'
  596. % (len(failures), expected_perms)
  597. if failures else ''))
  598. sys.stdout.flush()
  599. needs_newline = True
  600. finally:
  601. if needs_newline:
  602. print()
  603. for r in runners:
  604. r.join()
  605. return (
  606. expected_perms,
  607. passed_perms,
  608. failures)
  609. def run(**args):
  610. start = time.time()
  611. runner_ = runner(**args)
  612. print('using runner `%s`'
  613. % ' '.join(shlex.quote(c) for c in runner_))
  614. expected_suite_perms, expected_case_perms, expected_perms, total_perms = (
  615. find_cases(runner_, **args))
  616. print('found %d suites, %d cases, %d/%d permutations'
  617. % (len(expected_suite_perms),
  618. len(expected_case_perms),
  619. expected_perms,
  620. total_perms))
  621. print()
  622. expected = 0
  623. passed = 0
  624. failures = []
  625. if args.get('by_suites'):
  626. for type in ['normal', 'reentrant', 'valgrind']:
  627. for suite in expected_suite_perms.keys():
  628. expected_, passed_, failures_ = run_step(
  629. '%s %s' % (type, suite),
  630. runner_ + ['--%s' % type, suite],
  631. **args)
  632. expected += expected_
  633. passed += passed_
  634. failures.extend(failures_)
  635. if failures and not args.get('keep_going'):
  636. break
  637. if failures and not args.get('keep_going'):
  638. break
  639. elif args.get('by_cases'):
  640. for type in ['normal', 'reentrant', 'valgrind']:
  641. for case in expected_case_perms.keys():
  642. expected_, passed_, failures_ = run_step(
  643. '%s %s' % (type, case),
  644. runner_ + ['--%s' % type, case],
  645. **args)
  646. expected += expected_
  647. passed += passed_
  648. failures.extend(failures_)
  649. if failures and not args.get('keep_going'):
  650. break
  651. if failures and not args.get('keep_going'):
  652. break
  653. else:
  654. for type in ['normal', 'reentrant', 'valgrind']:
  655. expected_, passed_, failures_ = run_step(
  656. '%s tests' % type,
  657. runner_ + ['--%s' % type],
  658. **args)
  659. expected += expected_
  660. passed += passed_
  661. failures.extend(failures_)
  662. if failures and not args.get('keep_going'):
  663. break
  664. # show summary
  665. print()
  666. print('\x1b[%dmdone\x1b[m: %d/%d passed, %d/%d failed, in %.2fs'
  667. % (32 if not failures else 31,
  668. passed, expected, len(failures), expected,
  669. time.time()-start))
  670. print()
  671. # print each failure
  672. # TODO get line, defines, path
  673. for failure in failures:
  674. # print('\x1b[01m%s:%d:\x1b[01;31mfailure:\x1b[m %s failed'
  675. # # TODO this should be the suite path and lineno
  676. # % (failure.assert
  677. if failure.assert_ is not None:
  678. path, lineno, message = failure.assert_
  679. print('\x1b[01m%s:%d:\x1b[01;31massert:\x1b[m %s'
  680. % (path, lineno, message))
  681. with open(path) as f:
  682. line = next(it.islice(f, lineno-1, None)).strip('\n')
  683. print(line)
  684. print()
  685. return 1 if failures else 0
  686. def main(**args):
  687. if args.get('compile'):
  688. compile(**args)
  689. elif (args.get('summary')
  690. or args.get('list_suites')
  691. or args.get('list_cases')
  692. or args.get('list_paths')
  693. or args.get('list_defines')
  694. or args.get('list_geometries')):
  695. list_(**args)
  696. else:
  697. run(**args)
  698. if __name__ == "__main__":
  699. import argparse
  700. import sys
  701. parser = argparse.ArgumentParser(
  702. description="Build and run tests.")
  703. # TODO document test case/perm specifier
  704. parser.add_argument('test_paths', nargs='*',
  705. help="Description of testis to run. May be a directory, path, or \
  706. test identifier. Defaults to %r." % TEST_PATHS)
  707. parser.add_argument('-v', '--verbose', action='store_true',
  708. help="Output commands that run behind the scenes.")
  709. # test flags
  710. test_parser = parser.add_argument_group('test options')
  711. test_parser.add_argument('-Y', '--summary', action='store_true',
  712. help="Show quick summary.")
  713. test_parser.add_argument('-l', '--list-suites', action='store_true',
  714. help="List test suites.")
  715. test_parser.add_argument('-L', '--list-cases', action='store_true',
  716. help="List test cases.")
  717. test_parser.add_argument('--list-paths', action='store_true',
  718. help="List the path for each test case.")
  719. test_parser.add_argument('--list-defines', action='store_true',
  720. help="List the defines for each test permutation.")
  721. test_parser.add_argument('--list-geometries', action='store_true',
  722. help="List the disk geometries used for testing.")
  723. test_parser.add_argument('-D', '--define', action='append',
  724. help="Override a test define.")
  725. test_parser.add_argument('-G', '--geometry',
  726. help="Filter by geometry.")
  727. test_parser.add_argument('-n', '--normal', action='store_true',
  728. help="Filter for normal tests. Can be combined.")
  729. test_parser.add_argument('-r', '--reentrant', action='store_true',
  730. help="Filter for reentrant tests. Can be combined.")
  731. test_parser.add_argument('-V', '--valgrind', action='store_true',
  732. help="Filter for Valgrind tests. Can be combined.")
  733. test_parser.add_argument('-p', '--persist',
  734. help="Persist the disk to this file.")
  735. test_parser.add_argument('-t', '--trace',
  736. help="Redirect trace output to this file.")
  737. test_parser.add_argument('--runner', default=[RUNNER_PATH],
  738. type=lambda x: x.split(),
  739. help="Path to runner, defaults to %r" % RUNNER_PATH)
  740. test_parser.add_argument('-j', '--jobs', nargs='?', type=int,
  741. const=len(os.sched_getaffinity(0)),
  742. help="Number of parallel runners to run.")
  743. test_parser.add_argument('-k', '--keep-going', action='store_true',
  744. help="Don't stop on first error.")
  745. test_parser.add_argument('-b', '--by-suites', action='store_true',
  746. help="Step through tests by suite.")
  747. test_parser.add_argument('-B', '--by-cases', action='store_true',
  748. help="Step through tests by case.")
  749. # compilation flags
  750. comp_parser = parser.add_argument_group('compilation options')
  751. comp_parser.add_argument('-c', '--compile', action='store_true',
  752. help="Compile a test suite or source file.")
  753. comp_parser.add_argument('-s', '--source',
  754. help="Source file to compile, possibly injecting internal tests.")
  755. comp_parser.add_argument('-o', '--output',
  756. help="Output file.")
  757. # TODO apply this to other scripts?
  758. sys.exit(main(**{k: v
  759. for k, v in vars(parser.parse_args()).items()
  760. if v is not None}))