test_.py 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027
  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 signal
  16. import subprocess as sp
  17. import threading as th
  18. import time
  19. import toml
  20. TEST_PATHS = ['tests_']
  21. RUNNER_PATH = './runners/test_runner'
  22. SUITE_PROLOGUE = """
  23. #include "runners/test_runner.h"
  24. #include "bd/lfs_testbd.h"
  25. #include <stdio.h>
  26. """
  27. CASE_PROLOGUE = """
  28. """
  29. CASE_EPILOGUE = """
  30. """
  31. def testpath(path):
  32. path, *_ = path.split('#', 1)
  33. return path
  34. def testsuite(path):
  35. suite = testpath(path)
  36. suite = os.path.basename(suite)
  37. if suite.endswith('.toml'):
  38. suite = suite[:-len('.toml')]
  39. return suite
  40. def testcase(path):
  41. _, case, *_ = path.split('#', 2)
  42. return '%s#%s' % (testsuite(path), case)
  43. # TODO move this out in other files
  44. def openio(path, mode='r'):
  45. if path == '-':
  46. if 'r' in mode:
  47. return os.fdopen(os.dup(sys.stdin.fileno()), 'r')
  48. else:
  49. return os.fdopen(os.dup(sys.stdout.fileno()), 'w')
  50. else:
  51. return open(path, mode)
  52. class TestCase:
  53. # create a TestCase object from a config
  54. def __init__(self, config, args={}):
  55. self.name = config.pop('name')
  56. self.path = config.pop('path')
  57. self.suite = config.pop('suite')
  58. self.lineno = config.pop('lineno', None)
  59. self.if_ = config.pop('if', None)
  60. if isinstance(self.if_, bool):
  61. self.if_ = 'true' if self.if_ else 'false'
  62. self.code = config.pop('code')
  63. self.code_lineno = config.pop('code_lineno', None)
  64. self.in_ = config.pop('in',
  65. config.pop('suite_in', None))
  66. self.normal = config.pop('normal',
  67. config.pop('suite_normal', True))
  68. self.reentrant = config.pop('reentrant',
  69. config.pop('suite_reentrant', False))
  70. # figure out defines and build possible permutations
  71. self.defines = set()
  72. self.permutations = []
  73. suite_defines = config.pop('suite_defines', {})
  74. if not isinstance(suite_defines, list):
  75. suite_defines = [suite_defines]
  76. defines = config.pop('defines', {})
  77. if not isinstance(defines, list):
  78. defines = [defines]
  79. # build possible permutations
  80. for suite_defines_ in suite_defines:
  81. self.defines |= suite_defines_.keys()
  82. for defines_ in defines:
  83. self.defines |= defines_.keys()
  84. self.permutations.extend(map(dict, it.product(*(
  85. [(k, v) for v in (vs if isinstance(vs, list) else [vs])]
  86. for k, vs in sorted(
  87. (suite_defines_ | defines_).items())))))
  88. for k in config.keys():
  89. print('\x1b[01;33mwarning:\x1b[m in %s, found unused key %r'
  90. % (self.id(), k),
  91. file=sys.stderr)
  92. def id(self):
  93. return '%s#%s' % (self.suite, self.name)
  94. class TestSuite:
  95. # create a TestSuite object from a toml file
  96. def __init__(self, path, args={}):
  97. self.name = testsuite(path)
  98. self.path = testpath(path)
  99. # load toml file and parse test cases
  100. with open(self.path) as f:
  101. # load tests
  102. config = toml.load(f)
  103. # find line numbers
  104. f.seek(0)
  105. case_linenos = []
  106. code_linenos = []
  107. for i, line in enumerate(f):
  108. match = re.match(
  109. '(?P<case>\[\s*cases\s*\.\s*(?P<name>\w+)\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('code'):
  115. code_linenos.append(i+2)
  116. # sort in case toml parsing did not retain order
  117. case_linenos.sort()
  118. cases = config.pop('cases')
  119. for (lineno, name), (nlineno, _) in it.zip_longest(
  120. case_linenos, case_linenos[1:],
  121. fillvalue=(float('inf'), None)):
  122. code_lineno = min(
  123. (l for l in code_linenos if l >= lineno and l < nlineno),
  124. default=None)
  125. cases[name]['lineno'] = lineno
  126. cases[name]['code_lineno'] = code_lineno
  127. self.if_ = config.pop('if', None)
  128. if isinstance(self.if_, bool):
  129. self.if_ = 'true' if self.if_ else 'false'
  130. self.code = config.pop('code', None)
  131. self.code_lineno = min(
  132. (l for l in code_linenos
  133. if not case_linenos or l < case_linenos[0][0]),
  134. default=None)
  135. # a couple of these we just forward to all cases
  136. defines = config.pop('defines', {})
  137. in_ = config.pop('in', None)
  138. normal = config.pop('normal', True)
  139. reentrant = config.pop('reentrant', False)
  140. self.cases = []
  141. for name, case in sorted(cases.items(),
  142. key=lambda c: c[1].get('lineno')):
  143. self.cases.append(TestCase(config={
  144. 'name': name,
  145. 'path': path + (':%d' % case['lineno']
  146. if 'lineno' in case else ''),
  147. 'suite': self.name,
  148. 'suite_defines': defines,
  149. 'suite_in': in_,
  150. 'suite_normal': normal,
  151. 'suite_reentrant': reentrant,
  152. **case}))
  153. # combine per-case defines
  154. self.defines = set.union(*(
  155. set(case.defines) for case in self.cases))
  156. # combine other per-case things
  157. self.normal = any(case.normal for case in self.cases)
  158. self.reentrant = any(case.reentrant for case in self.cases)
  159. for k in config.keys():
  160. print('\x1b[01;33mwarning:\x1b[m in %s, found unused key %r'
  161. % (self.id(), k),
  162. file=sys.stderr)
  163. def id(self):
  164. return self.name
  165. def compile(**args):
  166. # find .toml files
  167. paths = []
  168. for path in args.get('test_ids', TEST_PATHS):
  169. if os.path.isdir(path):
  170. path = path + '/*.toml'
  171. for path in glob.glob(path):
  172. paths.append(path)
  173. if not paths:
  174. print('no test suites found in %r?' % args['test_ids'])
  175. sys.exit(-1)
  176. if not args.get('source'):
  177. if len(paths) > 1:
  178. print('more than one test suite for compilation? (%r)'
  179. % args['test_ids'])
  180. sys.exit(-1)
  181. # load our suite
  182. suite = TestSuite(paths[0])
  183. else:
  184. # load all suites
  185. suites = [TestSuite(path) for path in paths]
  186. suites.sort(key=lambda s: s.name)
  187. # write generated test source
  188. if 'output' in args:
  189. with openio(args['output'], 'w') as f:
  190. _write = f.write
  191. def write(s):
  192. f.lineno += s.count('\n')
  193. _write(s)
  194. def writeln(s=''):
  195. f.lineno += s.count('\n') + 1
  196. _write(s)
  197. _write('\n')
  198. f.lineno = 1
  199. f.write = write
  200. f.writeln = writeln
  201. # redirect littlefs tracing
  202. f.writeln('#define LFS_TRACE_(fmt, ...) do { \\')
  203. f.writeln(8*' '+'extern FILE *test_trace; \\')
  204. f.writeln(8*' '+'if (test_trace) { \\')
  205. f.writeln(12*' '+'fprintf(test_trace, '
  206. '"%s:%d:trace: " fmt "%s\\n", \\')
  207. f.writeln(20*' '+'__FILE__, __LINE__, __VA_ARGS__); \\')
  208. f.writeln(8*' '+'} \\')
  209. f.writeln(4*' '+'} while (0)')
  210. f.writeln('#define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "")')
  211. f.writeln('#define LFS_TESTBD_TRACE(...) '
  212. 'LFS_TRACE_(__VA_ARGS__, "")')
  213. f.writeln()
  214. # write out generated functions, this can end up in different
  215. # files depending on the "in" attribute
  216. #
  217. # note it's up to the specific generated file to declare
  218. # the test defines
  219. def write_case_functions(f, suite, case):
  220. # create case define functions
  221. if case.defines:
  222. # deduplicate defines by value to try to reduce the
  223. # number of functions we generate
  224. define_cbs = {}
  225. for i, defines in enumerate(case.permutations):
  226. for k, v in sorted(defines.items()):
  227. if v not in define_cbs:
  228. name = ('__test__%s__%s__%s__%d'
  229. % (suite.name, case.name, k, i))
  230. define_cbs[v] = name
  231. f.writeln('uintmax_t %s(void) {' % name)
  232. f.writeln(4*' '+'return %s;' % v)
  233. f.writeln('}')
  234. f.writeln()
  235. f.writeln('uintmax_t (*const *const '
  236. '__test__%s__%s__defines[])(void) = {'
  237. % (suite.name, case.name))
  238. for defines in case.permutations:
  239. f.writeln(4*' '+'(uintmax_t (*const[])(void)){')
  240. for define in sorted(suite.defines):
  241. f.writeln(8*' '+'%s,' % (
  242. define_cbs[defines[define]]
  243. if define in defines
  244. else 'NULL'))
  245. f.writeln(4*' '+'},')
  246. f.writeln('};')
  247. f.writeln()
  248. # create case filter function
  249. if suite.if_ is not None or case.if_ is not None:
  250. f.writeln('bool __test__%s__%s__filter(void) {'
  251. % (suite.name, case.name))
  252. f.writeln(4*' '+'return %s;'
  253. % ' && '.join('(%s)' % if_
  254. for if_ in [suite.if_, case.if_]
  255. if if_ is not None))
  256. f.writeln('}')
  257. f.writeln()
  258. # create case run function
  259. f.writeln('void __test__%s__%s__run('
  260. '__attribute__((unused)) struct lfs_config *cfg) {'
  261. % (suite.name, case.name))
  262. if CASE_PROLOGUE.strip():
  263. f.writeln(4*' '+'%s'
  264. % CASE_PROLOGUE.strip().replace('\n', '\n'+4*' '))
  265. f.writeln()
  266. f.writeln(4*' '+'// test case %s' % case.id())
  267. if case.code_lineno is not None:
  268. f.writeln(4*' '+'#line %d "%s"'
  269. % (case.code_lineno, suite.path))
  270. f.write(case.code)
  271. if case.code_lineno is not None:
  272. f.writeln(4*' '+'#line %d "%s"'
  273. % (f.lineno+1, args['output']))
  274. if CASE_EPILOGUE.strip():
  275. f.writeln()
  276. f.writeln(4*' '+'%s'
  277. % CASE_EPILOGUE.strip().replace('\n', '\n'+4*' '))
  278. f.writeln('}')
  279. f.writeln()
  280. if not args.get('source'):
  281. # write test suite prologue
  282. f.writeln('%s' % SUITE_PROLOGUE.strip())
  283. f.writeln()
  284. if suite.code is not None:
  285. if suite.code_lineno is not None:
  286. f.writeln('#line %d "%s"'
  287. % (suite.code_lineno, suite.path))
  288. f.write(suite.code)
  289. if suite.code_lineno is not None:
  290. f.writeln('#line %d "%s"'
  291. % (f.lineno+1, args['output']))
  292. f.writeln()
  293. if suite.defines:
  294. for i, define in enumerate(sorted(suite.defines)):
  295. f.writeln('#ifndef %s' % define)
  296. f.writeln('#define %-24s test_define(%d)'
  297. % (define, i))
  298. f.writeln('#endif')
  299. f.writeln()
  300. for case in suite.cases:
  301. # create case functions
  302. if case.in_ is None:
  303. write_case_functions(f, suite, case)
  304. else:
  305. if case.defines:
  306. f.writeln('extern uintmax_t (*const *const '
  307. '__test__%s__%s__defines[])(void);'
  308. % (suite.name, case.name))
  309. if suite.if_ is not None or case.if_ is not None:
  310. f.writeln('extern bool __test__%s__%s__filter('
  311. 'void);'
  312. % (suite.name, case.name))
  313. f.writeln('extern void __test__%s__%s__run('
  314. 'struct lfs_config *cfg);'
  315. % (suite.name, case.name))
  316. f.writeln()
  317. # create case struct
  318. f.writeln('const struct test_case __test__%s__%s__case = {'
  319. % (suite.name, case.name))
  320. f.writeln(4*' '+'.id = "%s",' % case.id())
  321. f.writeln(4*' '+'.name = "%s",' % case.name)
  322. f.writeln(4*' '+'.path = "%s",' % case.path)
  323. f.writeln(4*' '+'.types = %s,'
  324. % ' | '.join(filter(None, [
  325. 'TEST_NORMAL' if case.normal else None,
  326. 'TEST_REENTRANT' if case.reentrant else None])))
  327. f.writeln(4*' '+'.permutations = %d,'
  328. % len(case.permutations))
  329. if case.defines:
  330. f.writeln(4*' '+'.defines = __test__%s__%s__defines,'
  331. % (suite.name, case.name))
  332. if suite.if_ is not None or case.if_ is not None:
  333. f.writeln(4*' '+'.filter = __test__%s__%s__filter,'
  334. % (suite.name, case.name))
  335. f.writeln(4*' '+'.run = __test__%s__%s__run,'
  336. % (suite.name, case.name))
  337. f.writeln('};')
  338. f.writeln()
  339. # create suite define names
  340. if suite.defines:
  341. f.writeln('const char *const __test__%s__define_names[] = {'
  342. % suite.name)
  343. for k in sorted(suite.defines):
  344. f.writeln(4*' '+'"%s",' % k)
  345. f.writeln('};')
  346. f.writeln()
  347. # create suite struct
  348. f.writeln('const struct test_suite __test__%s__suite = {'
  349. % suite.name)
  350. f.writeln(4*' '+'.id = "%s",' % suite.id())
  351. f.writeln(4*' '+'.name = "%s",' % suite.name)
  352. f.writeln(4*' '+'.path = "%s",' % suite.path)
  353. f.writeln(4*' '+'.types = %s,'
  354. % ' | '.join(filter(None, [
  355. 'TEST_NORMAL' if suite.normal else None,
  356. 'TEST_REENTRANT' if suite.reentrant else None])))
  357. if suite.defines:
  358. f.writeln(4*' '+'.define_names = __test__%s__define_names,'
  359. % suite.name)
  360. f.writeln(4*' '+'.define_count = %d,' % len(suite.defines))
  361. f.writeln(4*' '+'.cases = (const struct test_case *const []){')
  362. for case in suite.cases:
  363. f.writeln(8*' '+'&__test__%s__%s__case,'
  364. % (suite.name, case.name))
  365. f.writeln(4*' '+'},')
  366. f.writeln(4*' '+'.case_count = %d,' % len(suite.cases))
  367. f.writeln('};')
  368. f.writeln()
  369. else:
  370. # copy source
  371. f.writeln('#line 1 "%s"' % args['source'])
  372. with open(args['source']) as sf:
  373. shutil.copyfileobj(sf, f)
  374. f.writeln()
  375. f.write(SUITE_PROLOGUE)
  376. f.writeln()
  377. # write any internal tests
  378. for suite in suites:
  379. for case in suite.cases:
  380. if (case.in_ is not None
  381. and os.path.normpath(case.in_)
  382. == os.path.normpath(args['source'])):
  383. # write defines, but note we need to undef any
  384. # new defines since we're in someone else's file
  385. if suite.defines:
  386. for i, define in enumerate(
  387. sorted(suite.defines)):
  388. f.writeln('#ifndef %s' % define)
  389. f.writeln('#define %-24s test_define(%d)'
  390. % (define, i))
  391. f.writeln('#define __TEST__%s__NEEDS_UNDEF'
  392. % define)
  393. f.writeln('#endif')
  394. f.writeln()
  395. write_case_functions(f, suite, case)
  396. if suite.defines:
  397. for define in sorted(suite.defines):
  398. f.writeln('#ifdef __TEST__%s__NEEDS_UNDEF'
  399. % define)
  400. f.writeln('#undef __TEST__%s__NEEDS_UNDEF'
  401. % define)
  402. f.writeln('#undef %s' % define)
  403. f.writeln('#endif')
  404. f.writeln()
  405. # add suite info to test_runner.c
  406. if args['source'] == 'runners/test_runner.c':
  407. f.writeln()
  408. for suite in suites:
  409. f.writeln('extern const struct test_suite '
  410. '__test__%s__suite;' % suite.name)
  411. f.writeln('const struct test_suite *test_suites[] = {')
  412. for suite in suites:
  413. f.writeln(4*' '+'&__test__%s__suite,' % suite.name)
  414. f.writeln('};')
  415. f.writeln('const size_t test_suite_count = %d;'
  416. % len(suites))
  417. def runner(**args):
  418. cmd = args['runner'].copy()
  419. cmd.extend(args.get('test_ids'))
  420. # run under some external command?
  421. cmd[:0] = args.get('exec', [])
  422. # run under valgrind?
  423. if args.get('valgrind'):
  424. cmd[:0] = filter(None, [
  425. 'valgrind',
  426. '--leak-check=full',
  427. '--track-origins=yes',
  428. '--error-exitcode=4',
  429. '-q'])
  430. # filter tests?
  431. if args.get('normal'): cmd.append('-n')
  432. if args.get('reentrant'): cmd.append('-r')
  433. if args.get('geometry'):
  434. cmd.append('-G%s' % args.get('geometry'))
  435. # defines?
  436. if args.get('define'):
  437. for define in args.get('define'):
  438. cmd.append('-D%s' % define)
  439. return cmd
  440. def list_(**args):
  441. cmd = runner(**args)
  442. if args.get('summary'): cmd.append('--summary')
  443. if args.get('list_suites'): cmd.append('--list-suites')
  444. if args.get('list_cases'): cmd.append('--list-cases')
  445. if args.get('list_paths'): cmd.append('--list-paths')
  446. if args.get('list_defines'): cmd.append('--list-defines')
  447. if args.get('list_geometries'): cmd.append('--list-geometries')
  448. if args.get('verbose'):
  449. print(' '.join(shlex.quote(c) for c in cmd))
  450. sys.exit(sp.call(cmd))
  451. def find_cases(runner_, **args):
  452. # query from runner
  453. cmd = runner_ + ['--list-cases']
  454. if args.get('verbose'):
  455. print(' '.join(shlex.quote(c) for c in cmd))
  456. proc = sp.Popen(cmd,
  457. stdout=sp.PIPE,
  458. stderr=sp.PIPE if not args.get('verbose') else None,
  459. universal_newlines=True,
  460. errors='replace')
  461. expected_suite_perms = co.defaultdict(lambda: 0)
  462. expected_case_perms = co.defaultdict(lambda: 0)
  463. expected_perms = 0
  464. total_perms = 0
  465. pattern = re.compile(
  466. '^(?P<id>(?P<case>(?P<suite>[^#]+)#[^\s#]+)[^\s]*)\s+'
  467. '[^\s]+\s+(?P<filtered>\d+)/(?P<perms>\d+)')
  468. # skip the first line
  469. for line in it.islice(proc.stdout, 1, None):
  470. m = pattern.match(line)
  471. if m:
  472. filtered = int(m.group('filtered'))
  473. expected_suite_perms[m.group('suite')] += filtered
  474. expected_case_perms[m.group('id')] += filtered
  475. expected_perms += filtered
  476. total_perms += int(m.group('perms'))
  477. proc.wait()
  478. if proc.returncode != 0:
  479. if not args.get('verbose'):
  480. for line in proc.stderr:
  481. sys.stdout.write(line)
  482. sys.exit(-1)
  483. return (
  484. expected_suite_perms,
  485. expected_case_perms,
  486. expected_perms,
  487. total_perms)
  488. def find_paths(runner_, **args):
  489. # query from runner
  490. cmd = runner_ + ['--list-paths']
  491. if args.get('verbose'):
  492. print(' '.join(shlex.quote(c) for c in cmd))
  493. proc = sp.Popen(cmd,
  494. stdout=sp.PIPE,
  495. stderr=sp.PIPE if not args.get('verbose') else None,
  496. universal_newlines=True,
  497. errors='replace')
  498. paths = co.OrderedDict()
  499. pattern = re.compile(
  500. '^(?P<id>(?P<case>(?P<suite>[^#]+)#[^\s#]+)[^\s]*)\s+'
  501. '(?P<path>[^:]+):(?P<lineno>\d+)')
  502. for line in proc.stdout:
  503. m = pattern.match(line)
  504. if m:
  505. paths[m.group('id')] = (m.group('path'), int(m.group('lineno')))
  506. proc.wait()
  507. if proc.returncode != 0:
  508. if not args.get('verbose'):
  509. for line in proc.stderr:
  510. sys.stdout.write(line)
  511. sys.exit(-1)
  512. return paths
  513. def find_defines(runner_, **args):
  514. # query from runner
  515. cmd = runner_ + ['--list-defines']
  516. if args.get('verbose'):
  517. print(' '.join(shlex.quote(c) for c in cmd))
  518. proc = sp.Popen(cmd,
  519. stdout=sp.PIPE,
  520. stderr=sp.PIPE if not args.get('verbose') else None,
  521. universal_newlines=True,
  522. errors='replace')
  523. defines = co.OrderedDict()
  524. pattern = re.compile(
  525. '^(?P<id>(?P<case>(?P<suite>[^#]+)#[^\s#]+)[^\s]*)\s+'
  526. '(?P<defines>(?:\w+=\w+\s*)+)')
  527. for line in proc.stdout:
  528. m = pattern.match(line)
  529. if m:
  530. defines[m.group('id')] = {k: v
  531. for k, v in re.findall('(\w+)=(\w+)', m.group('defines'))}
  532. proc.wait()
  533. if proc.returncode != 0:
  534. if not args.get('verbose'):
  535. for line in proc.stderr:
  536. sys.stdout.write(line)
  537. sys.exit(-1)
  538. return defines
  539. class TestFailure(Exception):
  540. def __init__(self, id, returncode, output, assert_=None):
  541. self.id = id
  542. self.returncode = returncode
  543. self.output = output
  544. self.assert_ = assert_
  545. def run_stage(name, runner_, **args):
  546. # get expected suite/case/perm counts
  547. expected_suite_perms, expected_case_perms, expected_perms, total_perms = (
  548. find_cases(runner_, **args))
  549. passed_suite_perms = co.defaultdict(lambda: 0)
  550. passed_case_perms = co.defaultdict(lambda: 0)
  551. passed_perms = 0
  552. failures = []
  553. killed = False
  554. pattern = re.compile('^(?:'
  555. '(?P<op>running|finished|skipped) '
  556. '(?P<id>(?P<case>(?P<suite>[^#]+)#[^\s#]+)[^\s]*)'
  557. '|' '(?P<path>[^:]+):(?P<lineno>\d+):(?P<op_>assert):'
  558. ' *(?P<message>.*)' ')$')
  559. locals = th.local()
  560. children = set()
  561. def run_runner(runner_):
  562. nonlocal passed_suite_perms
  563. nonlocal passed_case_perms
  564. nonlocal passed_perms
  565. nonlocal locals
  566. # run the tests!
  567. cmd = runner_.copy()
  568. if args.get('disk'):
  569. cmd.append('--disk=%s' % args['disk'])
  570. if args.get('trace'):
  571. cmd.append('--trace=%s' % args['trace'])
  572. if args.get('verbose'):
  573. print(' '.join(shlex.quote(c) for c in cmd))
  574. mpty, spty = pty.openpty()
  575. proc = sp.Popen(cmd, stdout=spty, stderr=spty)
  576. os.close(spty)
  577. children.add(proc)
  578. mpty = os.fdopen(mpty, 'r', 1)
  579. if args.get('output'):
  580. output = openio(args['output'], 'w')
  581. last_id = None
  582. last_output = []
  583. last_assert = None
  584. try:
  585. while True:
  586. # parse a line for state changes
  587. try:
  588. line = mpty.readline()
  589. except OSError as e:
  590. if e.errno == errno.EIO:
  591. break
  592. raise
  593. if not line:
  594. break
  595. last_output.append(line)
  596. if args.get('output'):
  597. output.write(line)
  598. elif args.get('verbose'):
  599. sys.stdout.write(line)
  600. m = pattern.match(line)
  601. if m:
  602. op = m.group('op') or m.group('op_')
  603. if op == 'running':
  604. locals.seen_perms += 1
  605. last_id = m.group('id')
  606. last_output = []
  607. last_assert = None
  608. elif op == 'finished':
  609. passed_suite_perms[m.group('suite')] += 1
  610. passed_case_perms[m.group('case')] += 1
  611. passed_perms += 1
  612. elif op == 'skipped':
  613. locals.seen_perms += 1
  614. elif op == 'assert':
  615. last_assert = (
  616. m.group('path'),
  617. int(m.group('lineno')),
  618. m.group('message'))
  619. # go ahead and kill the process, aborting takes a while
  620. if args.get('keep_going'):
  621. proc.kill()
  622. except KeyboardInterrupt:
  623. raise TestFailure(last_id, 1, last_output)
  624. finally:
  625. children.remove(proc)
  626. mpty.close()
  627. if args.get('output'):
  628. output.close()
  629. proc.wait()
  630. if proc.returncode != 0:
  631. raise TestFailure(
  632. last_id,
  633. proc.returncode,
  634. last_output,
  635. last_assert)
  636. def run_job(runner, start=None, step=None):
  637. nonlocal failures
  638. nonlocal locals
  639. start = start or 0
  640. step = step or 1
  641. while start < total_perms:
  642. runner_ = runner.copy()
  643. if start is not None:
  644. runner_.append('--start=%d' % start)
  645. if step is not None:
  646. runner_.append('--step=%d' % step)
  647. if args.get('isolate') or args.get('valgrind'):
  648. runner_.append('--stop=%d' % (start+step))
  649. try:
  650. # run the tests
  651. locals.seen_perms = 0
  652. run_runner(runner_)
  653. assert locals.seen_perms > 0
  654. start += locals.seen_perms*step
  655. except TestFailure as failure:
  656. # race condition for multiple failures?
  657. if failures and not args.get('keep_going'):
  658. break
  659. failures.append(failure)
  660. if args.get('keep_going') and not killed:
  661. # resume after failed test
  662. assert locals.seen_perms > 0
  663. start += locals.seen_perms*step
  664. continue
  665. else:
  666. # stop other tests
  667. for child in children.copy():
  668. child.kill()
  669. # parallel jobs?
  670. runners = []
  671. if 'jobs' in args:
  672. for job in range(args['jobs']):
  673. runners.append(th.Thread(
  674. target=run_job, args=(runner_, job, args['jobs'])))
  675. else:
  676. runners.append(th.Thread(
  677. target=run_job, args=(runner_, None, None)))
  678. for r in runners:
  679. r.start()
  680. needs_newline = False
  681. try:
  682. while any(r.is_alive() for r in runners):
  683. time.sleep(0.01)
  684. if not args.get('verbose'):
  685. sys.stdout.write('\r\x1b[K'
  686. 'running \x1b[%dm%s:\x1b[m %s '
  687. % (32 if not failures else 31,
  688. name,
  689. ', '.join(filter(None, [
  690. '%d/%d suites' % (
  691. sum(passed_suite_perms[k] == v
  692. for k, v in expected_suite_perms.items()),
  693. len(expected_suite_perms))
  694. if (not args.get('by_suites')
  695. and not args.get('by_cases')) else None,
  696. '%d/%d cases' % (
  697. sum(passed_case_perms[k] == v
  698. for k, v in expected_case_perms.items()),
  699. len(expected_case_perms))
  700. if not args.get('by_cases') else None,
  701. '%d/%d perms' % (passed_perms, expected_perms),
  702. '\x1b[31m%d/%d failures\x1b[m'
  703. % (len(failures), expected_perms)
  704. if failures else None]))))
  705. sys.stdout.flush()
  706. needs_newline = True
  707. except KeyboardInterrupt:
  708. # this is handled by the runner threads, we just
  709. # need to not abort here
  710. killed = True
  711. finally:
  712. if needs_newline:
  713. print()
  714. for r in runners:
  715. r.join()
  716. return (
  717. expected_perms,
  718. passed_perms,
  719. failures,
  720. killed)
  721. def run(**args):
  722. start = time.time()
  723. runner_ = runner(**args)
  724. print('using runner: %s'
  725. % ' '.join(shlex.quote(c) for c in runner_))
  726. expected_suite_perms, expected_case_perms, expected_perms, total_perms = (
  727. find_cases(runner_, **args))
  728. print('found %d suites, %d cases, %d/%d permutations'
  729. % (len(expected_suite_perms),
  730. len(expected_case_perms),
  731. expected_perms,
  732. total_perms))
  733. print()
  734. expected = 0
  735. passed = 0
  736. failures = []
  737. for type, by in it.product(
  738. ['normal', 'reentrant'],
  739. expected_case_perms.keys() if args.get('by_cases')
  740. else expected_suite_perms.keys() if args.get('by_suites')
  741. else [None]):
  742. # rebuild runner for each stage to override test identifier if needed
  743. stage_runner = runner(**args | {
  744. 'test_ids': [by] if by is not None else args.get('test_ids', []),
  745. 'normal': type == 'normal',
  746. 'reentrant': type == 'reentrant'})
  747. # spawn jobs for stage
  748. expected_, passed_, failures_, killed = run_stage(
  749. '%s %s' % (type, by or 'tests'), stage_runner, **args)
  750. expected += expected_
  751. passed += passed_
  752. failures.extend(failures_)
  753. if (failures and not args.get('keep_going')) or killed:
  754. break
  755. # show summary
  756. print()
  757. print('\x1b[%dmdone:\x1b[m %d/%d passed, %d/%d failed, in %.2fs'
  758. % (32 if not failures else 31,
  759. passed, expected, len(failures), expected,
  760. time.time()-start))
  761. print()
  762. # print each failure
  763. if failures:
  764. # get some extra info from runner
  765. runner_paths = find_paths(runner_, **args)
  766. runner_defines = find_defines(runner_, **args)
  767. for failure in failures:
  768. # show summary of failure
  769. path, lineno = runner_paths[testcase(failure.id)]
  770. defines = runner_defines[failure.id]
  771. print('\x1b[01m%s:%d:\x1b[01;31mfailure:\x1b[m %s%s failed'
  772. % (path, lineno, failure.id,
  773. ' (%s)' % ', '.join(
  774. '%s=%s' % (k, v) for k, v in defines.items())
  775. if defines else ''))
  776. if failure.output:
  777. output = failure.output
  778. if failure.assert_ is not None:
  779. output = output[:-1]
  780. for line in output[-5:]:
  781. sys.stdout.write(line)
  782. if failure.assert_ is not None:
  783. path, lineno, message = failure.assert_
  784. print('\x1b[01m%s:%d:\x1b[01;31massert:\x1b[m %s'
  785. % (path, lineno, message))
  786. with open(path) as f:
  787. line = next(it.islice(f, lineno-1, None)).strip('\n')
  788. print(line)
  789. print()
  790. # drop into gdb?
  791. if failures and (args.get('gdb')
  792. or args.get('gdb_case')
  793. or args.get('gdb_main')):
  794. failure = failures[0]
  795. runner_ = runner(**args | {'test_ids': [failure.id]})
  796. if args.get('gdb_main'):
  797. cmd = ['gdb',
  798. '-ex', 'break main',
  799. '-ex', 'run',
  800. '--args'] + runner_
  801. elif args.get('gdb_case'):
  802. path, lineno = runner_paths[testcase(failure.id)]
  803. cmd = ['gdb',
  804. '-ex', 'break %s:%d' % (path, lineno),
  805. '-ex', 'run',
  806. '--args'] + runner_
  807. elif failure.assert_ is not None:
  808. cmd = ['gdb',
  809. '-ex', 'run',
  810. '-ex', 'frame function raise',
  811. '-ex', 'up 2',
  812. '--args'] + runner_
  813. else:
  814. cmd = ['gdb',
  815. '-ex', 'run',
  816. '--args'] + runner_
  817. # exec gdb interactively
  818. if args.get('verbose'):
  819. print(' '.join(shlex.quote(c) for c in cmd))
  820. os.execvp(cmd[0], cmd)
  821. return 1 if failures else 0
  822. def main(**args):
  823. if args.get('compile'):
  824. compile(**args)
  825. elif (args.get('summary')
  826. or args.get('list_suites')
  827. or args.get('list_cases')
  828. or args.get('list_paths')
  829. or args.get('list_defines')
  830. or args.get('list_geometries')
  831. or args.get('list_defaults')):
  832. list_(**args)
  833. else:
  834. run(**args)
  835. if __name__ == "__main__":
  836. import argparse
  837. import sys
  838. parser = argparse.ArgumentParser(
  839. description="Build and run tests.",
  840. conflict_handler='resolve')
  841. parser.add_argument('test_ids', nargs='*',
  842. help="Description of testis to run. May be a directory, path, or \
  843. test identifier. Test identifiers are of the form \
  844. <suite_name>#<case_name>#<permutation>, but suffixes can be \
  845. dropped to run any matching tests. Defaults to %r." % TEST_PATHS)
  846. parser.add_argument('-v', '--verbose', action='store_true',
  847. help="Output commands that run behind the scenes.")
  848. # test flags
  849. test_parser = parser.add_argument_group('test options')
  850. test_parser.add_argument('-Y', '--summary', action='store_true',
  851. help="Show quick summary.")
  852. test_parser.add_argument('-l', '--list-suites', action='store_true',
  853. help="List test suites.")
  854. test_parser.add_argument('-L', '--list-cases', action='store_true',
  855. help="List test cases.")
  856. test_parser.add_argument('--list-paths', action='store_true',
  857. help="List the path for each test case.")
  858. test_parser.add_argument('--list-defines', action='store_true',
  859. help="List the defines for each test permutation.")
  860. test_parser.add_argument('--list-geometries', action='store_true',
  861. help="List the disk geometries used for testing.")
  862. test_parser.add_argument('--list-defaults', action='store_true',
  863. help="List the default defines in this test-runner.")
  864. test_parser.add_argument('-D', '--define', action='append',
  865. help="Override a test define.")
  866. test_parser.add_argument('-G', '--geometry',
  867. help="Filter by geometry.")
  868. test_parser.add_argument('-n', '--normal', action='store_true',
  869. help="Filter for normal tests. Can be combined.")
  870. test_parser.add_argument('-r', '--reentrant', action='store_true',
  871. help="Filter for reentrant tests. Can be combined.")
  872. test_parser.add_argument('-d', '--disk',
  873. help="Use this file as the disk.")
  874. test_parser.add_argument('-t', '--trace',
  875. help="Redirect trace output to this file.")
  876. test_parser.add_argument('-o', '--output',
  877. help="Redirect stdout and stderr to this file.")
  878. test_parser.add_argument('--runner', default=[RUNNER_PATH],
  879. type=lambda x: x.split(),
  880. help="Path to runner, defaults to %r" % RUNNER_PATH)
  881. test_parser.add_argument('-j', '--jobs', nargs='?', type=int,
  882. const=len(os.sched_getaffinity(0)),
  883. help="Number of parallel runners to run.")
  884. test_parser.add_argument('-k', '--keep-going', action='store_true',
  885. help="Don't stop on first error.")
  886. test_parser.add_argument('-i', '--isolate', action='store_true',
  887. help="Run each test permutation in a separate process.")
  888. test_parser.add_argument('-b', '--by-suites', action='store_true',
  889. help="Step through tests by suite.")
  890. test_parser.add_argument('-B', '--by-cases', action='store_true',
  891. help="Step through tests by case.")
  892. test_parser.add_argument('--gdb', action='store_true',
  893. help="Drop into gdb on test failure.")
  894. test_parser.add_argument('--gdb-case', action='store_true',
  895. help="Drop into gdb on test failure but stop at the beginning \
  896. of the failing test case.")
  897. test_parser.add_argument('--gdb-main', action='store_true',
  898. help="Drop into gdb on test failure but stop at the beginning \
  899. of main.")
  900. test_parser.add_argument('--valgrind', action='store_true',
  901. help="Run under Valgrind to find memory errors. Implicitly sets \
  902. --isolate.")
  903. test_parser.add_argument('--exec', default=[], type=lambda e: e.split(),
  904. help="Run under another executable.")
  905. # compilation flags
  906. comp_parser = parser.add_argument_group('compilation options')
  907. comp_parser.add_argument('-c', '--compile', action='store_true',
  908. help="Compile a test suite or source file.")
  909. comp_parser.add_argument('-s', '--source',
  910. help="Source file to compile, possibly injecting internal tests.")
  911. comp_parser.add_argument('-o', '--output',
  912. help="Output file.")
  913. # TODO apply this to other scripts?
  914. sys.exit(main(**{k: v
  915. for k, v in vars(parser.parse_args()).items()
  916. if v is not None}))