test.py 42 KB

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