test.py 42 KB

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