test.py 54 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487
  1. #!/usr/bin/env python3
  2. #
  3. # Script to compile and runs tests.
  4. #
  5. # Example:
  6. # ./scripts/test.py runners/test_runner -b
  7. #
  8. # Copyright (c) 2022, The littlefs authors.
  9. # SPDX-License-Identifier: BSD-3-Clause
  10. #
  11. import collections as co
  12. import csv
  13. import errno
  14. import glob
  15. import itertools as it
  16. import math as m
  17. import os
  18. import pty
  19. import re
  20. import shlex
  21. import shutil
  22. import signal
  23. import subprocess as sp
  24. import threading as th
  25. import time
  26. import toml
  27. RUNNER_PATH = './runners/test_runner'
  28. HEADER_PATH = 'runners/test_runner.h'
  29. GDB_PATH = ['gdb']
  30. VALGRIND_PATH = ['valgrind']
  31. PERF_SCRIPT = ['./scripts/perf.py']
  32. def openio(path, mode='r', buffering=-1):
  33. # allow '-' for stdin/stdout
  34. if path == '-':
  35. if mode == 'r':
  36. return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
  37. else:
  38. return os.fdopen(os.dup(sys.stdout.fileno()), mode, buffering)
  39. else:
  40. return open(path, mode, buffering)
  41. class TestCase:
  42. # create a TestCase object from a config
  43. def __init__(self, config, args={}):
  44. self.name = config.pop('name')
  45. self.path = config.pop('path')
  46. self.suite = config.pop('suite')
  47. self.lineno = config.pop('lineno', None)
  48. self.if_ = config.pop('if', None)
  49. if isinstance(self.if_, bool):
  50. self.if_ = 'true' if self.if_ else 'false'
  51. self.code = config.pop('code')
  52. self.code_lineno = config.pop('code_lineno', None)
  53. self.in_ = config.pop('in',
  54. config.pop('suite_in', None))
  55. self.reentrant = config.pop('reentrant',
  56. config.pop('suite_reentrant', False))
  57. # figure out defines and build possible permutations
  58. self.defines = set()
  59. self.permutations = []
  60. # defines can be a dict or a list or dicts
  61. suite_defines = config.pop('suite_defines', {})
  62. if not isinstance(suite_defines, list):
  63. suite_defines = [suite_defines]
  64. defines = config.pop('defines', {})
  65. if not isinstance(defines, list):
  66. defines = [defines]
  67. def csplit(v):
  68. # split commas but only outside of parens
  69. parens = 0
  70. i_ = 0
  71. for i in range(len(v)):
  72. if v[i] == ',' and parens == 0:
  73. yield v[i_:i]
  74. i_ = i+1
  75. elif v[i] in '([{':
  76. parens += 1
  77. elif v[i] in '}])':
  78. parens -= 1
  79. if v[i_:].strip():
  80. yield v[i_:]
  81. def parse_define(v):
  82. # a define entry can be a list
  83. if isinstance(v, list):
  84. for v_ in v:
  85. yield from parse_define(v_)
  86. # or a string
  87. elif isinstance(v, str):
  88. # which can be comma-separated values, with optional
  89. # range statements. This matches the runtime define parser in
  90. # the runner itself.
  91. for v_ in csplit(v):
  92. m = re.search(r'\brange\b\s*\('
  93. r'(?P<start>[^,\s]*)'
  94. r'\s*(?:,\s*(?P<stop>[^,\s]*)'
  95. r'\s*(?:,\s*(?P<step>[^,\s]*)\s*)?)?\)',
  96. v_)
  97. if m:
  98. start = (int(m.group('start'), 0)
  99. if m.group('start') else 0)
  100. stop = (int(m.group('stop'), 0)
  101. if m.group('stop') else None)
  102. step = (int(m.group('step'), 0)
  103. if m.group('step') else 1)
  104. if m.lastindex <= 1:
  105. start, stop = 0, start
  106. for x in range(start, stop, step):
  107. yield from parse_define('%s(%d)%s' % (
  108. v_[:m.start()], x, v_[m.end():]))
  109. else:
  110. yield v_
  111. # or a literal value
  112. elif isinstance(v, bool):
  113. yield 'true' if v else 'false'
  114. else:
  115. yield v
  116. # build possible permutations
  117. for suite_defines_ in suite_defines:
  118. self.defines |= suite_defines_.keys()
  119. for defines_ in defines:
  120. self.defines |= defines_.keys()
  121. self.permutations.extend(dict(perm) for perm in it.product(*(
  122. [(k, v) for v in parse_define(vs)]
  123. for k, vs in sorted((suite_defines_ | defines_).items()))))
  124. for k in config.keys():
  125. print('%swarning:%s in %s, found unused key %r' % (
  126. '\x1b[01;33m' if args['color'] else '',
  127. '\x1b[m' if args['color'] else '',
  128. self.name,
  129. k),
  130. file=sys.stderr)
  131. class TestSuite:
  132. # create a TestSuite object from a toml file
  133. def __init__(self, path, args={}):
  134. self.path = path
  135. self.name = os.path.basename(path)
  136. if self.name.endswith('.toml'):
  137. self.name = self.name[:-len('.toml')]
  138. # load toml file and parse test cases
  139. with open(self.path) as f:
  140. # load tests
  141. config = toml.load(f)
  142. # find line numbers
  143. f.seek(0)
  144. case_linenos = []
  145. code_linenos = []
  146. for i, line in enumerate(f):
  147. match = re.match(
  148. r'(?P<case>\[\s*cases\s*\.\s*(?P<name>\w+)\s*\])'
  149. r'|' r'(?P<code>code\s*=)',
  150. line)
  151. if match and match.group('case'):
  152. case_linenos.append((i+1, match.group('name')))
  153. elif match and match.group('code'):
  154. code_linenos.append(i+2)
  155. # sort in case toml parsing did not retain order
  156. case_linenos.sort()
  157. cases = config.pop('cases')
  158. for (lineno, name), (nlineno, _) in it.zip_longest(
  159. case_linenos, case_linenos[1:],
  160. fillvalue=(float('inf'), None)):
  161. code_lineno = min(
  162. (l for l in code_linenos if l >= lineno and l < nlineno),
  163. default=None)
  164. cases[name]['lineno'] = lineno
  165. cases[name]['code_lineno'] = code_lineno
  166. self.if_ = config.pop('if', None)
  167. if isinstance(self.if_, bool):
  168. self.if_ = 'true' if self.if_ else 'false'
  169. self.code = config.pop('code', None)
  170. self.code_lineno = min(
  171. (l for l in code_linenos
  172. if not case_linenos or l < case_linenos[0][0]),
  173. default=None)
  174. # a couple of these we just forward to all cases
  175. defines = config.pop('defines', {})
  176. in_ = config.pop('in', None)
  177. reentrant = config.pop('reentrant', False)
  178. self.cases = []
  179. for name, case in sorted(cases.items(),
  180. key=lambda c: c[1].get('lineno')):
  181. self.cases.append(TestCase(config={
  182. 'name': name,
  183. 'path': path + (':%d' % case['lineno']
  184. if 'lineno' in case else ''),
  185. 'suite': self.name,
  186. 'suite_defines': defines,
  187. 'suite_in': in_,
  188. 'suite_reentrant': reentrant,
  189. **case},
  190. args=args))
  191. # combine per-case defines
  192. self.defines = set.union(*(
  193. set(case.defines) for case in self.cases))
  194. # combine other per-case things
  195. self.reentrant = any(case.reentrant for case in self.cases)
  196. for k in config.keys():
  197. print('%swarning:%s in %s, found unused key %r' % (
  198. '\x1b[01;33m' if args['color'] else '',
  199. '\x1b[m' if args['color'] else '',
  200. self.name,
  201. k),
  202. file=sys.stderr)
  203. def compile(test_paths, **args):
  204. # find .toml files
  205. paths = []
  206. for path in test_paths:
  207. if os.path.isdir(path):
  208. path = path + '/*.toml'
  209. for path in glob.glob(path):
  210. paths.append(path)
  211. if not paths:
  212. print('no test suites found in %r?' % test_paths)
  213. sys.exit(-1)
  214. # load the suites
  215. suites = [TestSuite(path, args) for path in paths]
  216. suites.sort(key=lambda s: s.name)
  217. # check for name conflicts, these will cause ambiguity problems later
  218. # when running tests
  219. seen = {}
  220. for suite in suites:
  221. if suite.name in seen:
  222. print('%swarning:%s conflicting suite %r, %s and %s' % (
  223. '\x1b[01;33m' if args['color'] else '',
  224. '\x1b[m' if args['color'] else '',
  225. suite.name,
  226. suite.path,
  227. seen[suite.name].path),
  228. file=sys.stderr)
  229. seen[suite.name] = suite
  230. for case in suite.cases:
  231. # only allow conflicts if a case and its suite share a name
  232. if case.name in seen and not (
  233. isinstance(seen[case.name], TestSuite)
  234. and seen[case.name].cases == [case]):
  235. print('%swarning:%s conflicting case %r, %s and %s' % (
  236. '\x1b[01;33m' if args['color'] else '',
  237. '\x1b[m' if args['color'] else '',
  238. case.name,
  239. case.path,
  240. seen[case.name].path),
  241. file=sys.stderr)
  242. seen[case.name] = case
  243. # we can only compile one test suite at a time
  244. if not args.get('source'):
  245. if len(suites) > 1:
  246. print('more than one test suite for compilation? (%r)' % test_paths)
  247. sys.exit(-1)
  248. suite = suites[0]
  249. # write generated test source
  250. if 'output' in args:
  251. with openio(args['output'], 'w') as f:
  252. _write = f.write
  253. def write(s):
  254. f.lineno += s.count('\n')
  255. _write(s)
  256. def writeln(s=''):
  257. f.lineno += s.count('\n') + 1
  258. _write(s)
  259. _write('\n')
  260. f.lineno = 1
  261. f.write = write
  262. f.writeln = writeln
  263. f.writeln("// Generated by %s:" % sys.argv[0])
  264. f.writeln("//")
  265. f.writeln("// %s" % ' '.join(sys.argv))
  266. f.writeln("//")
  267. f.writeln()
  268. # include test_runner.h in every generated file
  269. f.writeln("#include \"%s\"" % args['include'])
  270. f.writeln()
  271. # write out generated functions, this can end up in different
  272. # files depending on the "in" attribute
  273. #
  274. # note it's up to the specific generated file to declare
  275. # the test defines
  276. def write_case_functions(f, suite, case):
  277. # create case define functions
  278. if case.defines:
  279. # deduplicate defines by value to try to reduce the
  280. # number of functions we generate
  281. define_cbs = {}
  282. for i, defines in enumerate(case.permutations):
  283. for k, v in sorted(defines.items()):
  284. if v not in define_cbs:
  285. name = ('__test__%s__%s__%d'
  286. % (case.name, k, i))
  287. define_cbs[v] = name
  288. f.writeln('intmax_t %s('
  289. '__attribute__((unused)) '
  290. 'void *data) {' % name)
  291. f.writeln(4*' '+'return %s;' % v)
  292. f.writeln('}')
  293. f.writeln()
  294. f.writeln('const test_define_t '
  295. '__test__%s__defines[]['
  296. 'TEST_IMPLICIT_DEFINE_COUNT+%d] = {'
  297. % (case.name, len(suite.defines)))
  298. for defines in case.permutations:
  299. f.writeln(4*' '+'{')
  300. for k, v in sorted(defines.items()):
  301. f.writeln(8*' '+'[%-24s] = {%s, NULL},' % (
  302. k+'_i', define_cbs[v]))
  303. f.writeln(4*' '+'},')
  304. f.writeln('};')
  305. f.writeln()
  306. # create case filter function
  307. if suite.if_ is not None or case.if_ is not None:
  308. f.writeln('bool __test__%s__filter(void) {'
  309. % (case.name))
  310. f.writeln(4*' '+'return %s;'
  311. % ' && '.join('(%s)' % if_
  312. for if_ in [suite.if_, case.if_]
  313. if if_ is not None))
  314. f.writeln('}')
  315. f.writeln()
  316. # create case run function
  317. f.writeln('void __test__%s__run('
  318. '__attribute__((unused)) struct lfs_config *cfg) {'
  319. % (case.name))
  320. f.writeln(4*' '+'// test case %s' % case.name)
  321. if case.code_lineno is not None:
  322. f.writeln(4*' '+'#line %d "%s"'
  323. % (case.code_lineno, suite.path))
  324. f.write(case.code)
  325. if case.code_lineno is not None:
  326. f.writeln(4*' '+'#line %d "%s"'
  327. % (f.lineno+1, args['output']))
  328. f.writeln('}')
  329. f.writeln()
  330. if not args.get('source'):
  331. if suite.code is not None:
  332. if suite.code_lineno is not None:
  333. f.writeln('#line %d "%s"'
  334. % (suite.code_lineno, suite.path))
  335. f.write(suite.code)
  336. if suite.code_lineno is not None:
  337. f.writeln('#line %d "%s"'
  338. % (f.lineno+1, args['output']))
  339. f.writeln()
  340. if suite.defines:
  341. for i, define in enumerate(sorted(suite.defines)):
  342. f.writeln('#ifndef %s' % define)
  343. f.writeln('#define %-24s '
  344. 'TEST_IMPLICIT_DEFINE_COUNT+%d' % (define+'_i', i))
  345. f.writeln('#define %-24s '
  346. 'TEST_DEFINE(%s)' % (define, define+'_i'))
  347. f.writeln('#endif')
  348. f.writeln()
  349. # create case functions
  350. for case in suite.cases:
  351. if case.in_ is None:
  352. write_case_functions(f, suite, case)
  353. else:
  354. if case.defines:
  355. f.writeln('extern const test_define_t '
  356. '__test__%s__defines[]['
  357. 'TEST_IMPLICIT_DEFINE_COUNT+%d];'
  358. % (case.name, len(suite.defines)))
  359. if suite.if_ is not None or case.if_ is not None:
  360. f.writeln('extern bool __test__%s__filter('
  361. 'void);'
  362. % (case.name))
  363. f.writeln('extern void __test__%s__run('
  364. 'struct lfs_config *cfg);'
  365. % (case.name))
  366. f.writeln()
  367. # create suite struct
  368. f.writeln('#if defined(__APPLE__)')
  369. f.writeln('__attribute__((section("__DATA,_test_suites")))')
  370. f.writeln('#else')
  371. # note we place this in the custom test_suites section with
  372. # minimum alignment, otherwise GCC ups the alignment to
  373. # 32-bytes for some reason
  374. f.writeln('__attribute__((section("_test_suites"), '
  375. 'aligned(1)))')
  376. f.writeln('#endif')
  377. f.writeln('const struct test_suite __test__%s__suite = {'
  378. % suite.name)
  379. f.writeln(4*' '+'.name = "%s",' % suite.name)
  380. f.writeln(4*' '+'.path = "%s",' % suite.path)
  381. f.writeln(4*' '+'.flags = %s,'
  382. % (' | '.join(filter(None, [
  383. 'TEST_REENTRANT' if suite.reentrant else None]))
  384. or 0))
  385. if suite.defines:
  386. # create suite define names
  387. f.writeln(4*' '+'.define_names = (const char *const['
  388. 'TEST_IMPLICIT_DEFINE_COUNT+%d]){' % (
  389. len(suite.defines)))
  390. for k in sorted(suite.defines):
  391. f.writeln(8*' '+'[%-24s] = "%s",' % (k+'_i', k))
  392. f.writeln(4*' '+'},')
  393. f.writeln(4*' '+'.define_count = '
  394. 'TEST_IMPLICIT_DEFINE_COUNT+%d,' % len(suite.defines))
  395. f.writeln(4*' '+'.cases = (const struct test_case[]){')
  396. for case in suite.cases:
  397. # create case structs
  398. f.writeln(8*' '+'{')
  399. f.writeln(12*' '+'.name = "%s",' % case.name)
  400. f.writeln(12*' '+'.path = "%s",' % case.path)
  401. f.writeln(12*' '+'.flags = %s,'
  402. % (' | '.join(filter(None, [
  403. 'TEST_REENTRANT' if case.reentrant else None]))
  404. or 0))
  405. f.writeln(12*' '+'.permutations = %d,'
  406. % len(case.permutations))
  407. if case.defines:
  408. f.writeln(12*' '+'.defines '
  409. '= (const test_define_t*)__test__%s__defines,'
  410. % (case.name))
  411. if suite.if_ is not None or case.if_ is not None:
  412. f.writeln(12*' '+'.filter = __test__%s__filter,'
  413. % (case.name))
  414. f.writeln(12*' '+'.run = __test__%s__run,'
  415. % (case.name))
  416. f.writeln(8*' '+'},')
  417. f.writeln(4*' '+'},')
  418. f.writeln(4*' '+'.case_count = %d,' % len(suite.cases))
  419. f.writeln('};')
  420. f.writeln()
  421. else:
  422. # copy source
  423. f.writeln('#line 1 "%s"' % args['source'])
  424. with open(args['source']) as sf:
  425. shutil.copyfileobj(sf, f)
  426. f.writeln()
  427. # write any internal tests
  428. for suite in suites:
  429. for case in suite.cases:
  430. if (case.in_ is not None
  431. and os.path.normpath(case.in_)
  432. == os.path.normpath(args['source'])):
  433. # write defines, but note we need to undef any
  434. # new defines since we're in someone else's file
  435. if suite.defines:
  436. for i, define in enumerate(
  437. sorted(suite.defines)):
  438. f.writeln('#ifndef %s' % define)
  439. f.writeln('#define %-24s '
  440. 'TEST_IMPLICIT_DEFINE_COUNT+%d' % (
  441. define+'_i', i))
  442. f.writeln('#define %-24s '
  443. 'TEST_DEFINE(%s)' % (
  444. define, define+'_i'))
  445. f.writeln('#define '
  446. '__TEST__%s__NEEDS_UNDEF' % (
  447. define))
  448. f.writeln('#endif')
  449. f.writeln()
  450. write_case_functions(f, suite, case)
  451. if suite.defines:
  452. for define in sorted(suite.defines):
  453. f.writeln('#ifdef __TEST__%s__NEEDS_UNDEF'
  454. % define)
  455. f.writeln('#undef __TEST__%s__NEEDS_UNDEF'
  456. % define)
  457. f.writeln('#undef %s' % define)
  458. f.writeln('#undef %s' % (define+'_i'))
  459. f.writeln('#endif')
  460. f.writeln()
  461. def find_runner(runner, **args):
  462. cmd = runner.copy()
  463. # run under some external command?
  464. if args.get('exec'):
  465. cmd[:0] = args['exec']
  466. # run under valgrind?
  467. if args.get('valgrind'):
  468. cmd[:0] = args['valgrind_path'] + [
  469. '--leak-check=full',
  470. '--track-origins=yes',
  471. '--error-exitcode=4',
  472. '-q']
  473. # run under perf?
  474. if args.get('perf'):
  475. cmd[:0] = args['perf_script'] + list(filter(None, [
  476. '-R',
  477. '--perf-freq=%s' % args['perf_freq']
  478. if args.get('perf_freq') else None,
  479. '--perf-period=%s' % args['perf_period']
  480. if args.get('perf_period') else None,
  481. '--perf-events=%s' % args['perf_events']
  482. if args.get('perf_events') else None,
  483. '--perf-path=%s' % args['perf_path']
  484. if args.get('perf_path') else None,
  485. '-o%s' % args['perf']]))
  486. # other context
  487. if args.get('geometry'):
  488. cmd.append('-G%s' % args['geometry'])
  489. if args.get('powerloss'):
  490. cmd.append('-P%s' % args['powerloss'])
  491. if args.get('disk'):
  492. cmd.append('-d%s' % args['disk'])
  493. if args.get('trace'):
  494. cmd.append('-t%s' % args['trace'])
  495. if args.get('trace_backtrace'):
  496. cmd.append('--trace-backtrace')
  497. if args.get('trace_period'):
  498. cmd.append('--trace-period=%s' % args['trace_period'])
  499. if args.get('trace_freq'):
  500. cmd.append('--trace-freq=%s' % args['trace_freq'])
  501. if args.get('read_sleep'):
  502. cmd.append('--read-sleep=%s' % args['read_sleep'])
  503. if args.get('prog_sleep'):
  504. cmd.append('--prog-sleep=%s' % args['prog_sleep'])
  505. if args.get('erase_sleep'):
  506. cmd.append('--erase-sleep=%s' % args['erase_sleep'])
  507. # defines?
  508. if args.get('define'):
  509. for define in args.get('define'):
  510. cmd.append('-D%s' % define)
  511. return cmd
  512. def list_(runner, test_ids=[], **args):
  513. cmd = find_runner(runner, **args) + test_ids
  514. if args.get('summary'): cmd.append('--summary')
  515. if args.get('list_suites'): cmd.append('--list-suites')
  516. if args.get('list_cases'): cmd.append('--list-cases')
  517. if args.get('list_suite_paths'): cmd.append('--list-suite-paths')
  518. if args.get('list_case_paths'): cmd.append('--list-case-paths')
  519. if args.get('list_defines'): cmd.append('--list-defines')
  520. if args.get('list_permutation_defines'):
  521. cmd.append('--list-permutation-defines')
  522. if args.get('list_implicit_defines'):
  523. cmd.append('--list-implicit-defines')
  524. if args.get('list_geometries'): cmd.append('--list-geometries')
  525. if args.get('list_powerlosses'): cmd.append('--list-powerlosses')
  526. if args.get('verbose'):
  527. print(' '.join(shlex.quote(c) for c in cmd))
  528. return sp.call(cmd)
  529. def find_perms(runner_, ids=[], **args):
  530. case_suites = {}
  531. expected_case_perms = co.defaultdict(lambda: 0)
  532. expected_perms = 0
  533. total_perms = 0
  534. # query cases from the runner
  535. cmd = runner_ + ['--list-cases'] + ids
  536. if args.get('verbose'):
  537. print(' '.join(shlex.quote(c) for c in cmd))
  538. proc = sp.Popen(cmd,
  539. stdout=sp.PIPE,
  540. stderr=sp.PIPE if not args.get('verbose') else None,
  541. universal_newlines=True,
  542. errors='replace',
  543. close_fds=False)
  544. pattern = re.compile(
  545. r'^(?P<case>[^\s]+)'
  546. r'\s+(?P<flags>[^\s]+)'
  547. r'\s+(?P<filtered>\d+)/(?P<perms>\d+)')
  548. # skip the first line
  549. for line in it.islice(proc.stdout, 1, None):
  550. m = pattern.match(line)
  551. if m:
  552. filtered = int(m.group('filtered'))
  553. perms = int(m.group('perms'))
  554. expected_case_perms[m.group('case')] += filtered
  555. expected_perms += filtered
  556. total_perms += perms
  557. proc.wait()
  558. if proc.returncode != 0:
  559. if not args.get('verbose'):
  560. for line in proc.stderr:
  561. sys.stdout.write(line)
  562. sys.exit(-1)
  563. # get which suite each case belongs to via paths
  564. cmd = runner_ + ['--list-case-paths'] + ids
  565. if args.get('verbose'):
  566. print(' '.join(shlex.quote(c) for c in cmd))
  567. proc = sp.Popen(cmd,
  568. stdout=sp.PIPE,
  569. stderr=sp.PIPE if not args.get('verbose') else None,
  570. universal_newlines=True,
  571. errors='replace',
  572. close_fds=False)
  573. pattern = re.compile(
  574. r'^(?P<case>[^\s]+)'
  575. r'\s+(?P<path>[^:]+):(?P<lineno>\d+)')
  576. # skip the first line
  577. for line in it.islice(proc.stdout, 1, None):
  578. m = pattern.match(line)
  579. if m:
  580. path = m.group('path')
  581. # strip path/suffix here
  582. suite = os.path.basename(path)
  583. if suite.endswith('.toml'):
  584. suite = suite[:-len('.toml')]
  585. case_suites[m.group('case')] = suite
  586. proc.wait()
  587. if proc.returncode != 0:
  588. if not args.get('verbose'):
  589. for line in proc.stderr:
  590. sys.stdout.write(line)
  591. sys.exit(-1)
  592. # figure out expected suite perms
  593. expected_suite_perms = co.defaultdict(lambda: 0)
  594. for case, suite in case_suites.items():
  595. expected_suite_perms[suite] += expected_case_perms[case]
  596. return (
  597. case_suites,
  598. expected_suite_perms,
  599. expected_case_perms,
  600. expected_perms,
  601. total_perms)
  602. def find_path(runner_, id, **args):
  603. path = None
  604. # query from runner
  605. cmd = runner_ + ['--list-case-paths', id]
  606. if args.get('verbose'):
  607. print(' '.join(shlex.quote(c) for c in cmd))
  608. proc = sp.Popen(cmd,
  609. stdout=sp.PIPE,
  610. stderr=sp.PIPE if not args.get('verbose') else None,
  611. universal_newlines=True,
  612. errors='replace',
  613. close_fds=False)
  614. pattern = re.compile(
  615. r'^(?P<case>[^\s]+)'
  616. r'\s+(?P<path>[^:]+):(?P<lineno>\d+)')
  617. # skip the first line
  618. for line in it.islice(proc.stdout, 1, None):
  619. m = pattern.match(line)
  620. if m and path is None:
  621. path_ = m.group('path')
  622. lineno = int(m.group('lineno'))
  623. path = (path_, lineno)
  624. proc.wait()
  625. if proc.returncode != 0:
  626. if not args.get('verbose'):
  627. for line in proc.stderr:
  628. sys.stdout.write(line)
  629. sys.exit(-1)
  630. return path
  631. def find_defines(runner_, id, **args):
  632. # query permutation defines from runner
  633. cmd = runner_ + ['--list-permutation-defines', id]
  634. if args.get('verbose'):
  635. print(' '.join(shlex.quote(c) for c in cmd))
  636. proc = sp.Popen(cmd,
  637. stdout=sp.PIPE,
  638. stderr=sp.PIPE if not args.get('verbose') else None,
  639. universal_newlines=True,
  640. errors='replace',
  641. close_fds=False)
  642. defines = co.OrderedDict()
  643. pattern = re.compile(r'^(?P<define>\w+)=(?P<value>.+)')
  644. for line in proc.stdout:
  645. m = pattern.match(line)
  646. if m:
  647. define = m.group('define')
  648. value = m.group('value')
  649. defines[define] = value
  650. proc.wait()
  651. if proc.returncode != 0:
  652. if not args.get('verbose'):
  653. for line in proc.stderr:
  654. sys.stdout.write(line)
  655. sys.exit(-1)
  656. return defines
  657. # Thread-safe CSV writer
  658. class TestOutput:
  659. def __init__(self, path, head=None, tail=None):
  660. self.f = openio(path, 'w+', 1)
  661. self.lock = th.Lock()
  662. self.head = head or []
  663. self.tail = tail or []
  664. self.writer = csv.DictWriter(self.f, self.head + self.tail)
  665. self.rows = []
  666. def close(self):
  667. self.f.close()
  668. def __enter__(self):
  669. return self
  670. def __exit__(self, *_):
  671. self.f.close()
  672. def writerow(self, row):
  673. with self.lock:
  674. self.rows.append(row)
  675. if all(k in self.head or k in self.tail for k in row.keys()):
  676. # can simply append
  677. self.writer.writerow(row)
  678. else:
  679. # need to rewrite the file
  680. self.head.extend(row.keys() - (self.head + self.tail))
  681. self.f.seek(0)
  682. self.f.truncate()
  683. self.writer = csv.DictWriter(self.f, self.head + self.tail)
  684. self.writer.writeheader()
  685. for row in self.rows:
  686. self.writer.writerow(row)
  687. # A test failure
  688. class TestFailure(Exception):
  689. def __init__(self, id, returncode, stdout, assert_=None):
  690. self.id = id
  691. self.returncode = returncode
  692. self.stdout = stdout
  693. self.assert_ = assert_
  694. def run_stage(name, runner_, ids, stdout_, trace_, output_, **args):
  695. # get expected suite/case/perm counts
  696. (case_suites,
  697. expected_suite_perms,
  698. expected_case_perms,
  699. expected_perms,
  700. total_perms) = find_perms(runner_, ids, **args)
  701. passed_suite_perms = co.defaultdict(lambda: 0)
  702. passed_case_perms = co.defaultdict(lambda: 0)
  703. passed_perms = 0
  704. powerlosses = 0
  705. failures = []
  706. killed = False
  707. pattern = re.compile(r'^(?:'
  708. r'(?P<op>running|finished|skipped|powerloss) '
  709. r'(?P<id>(?P<case>[^:]+)[^\s]*)'
  710. r'|' r'(?P<path>[^:]+):(?P<lineno>\d+):(?P<op_>assert):'
  711. r' *(?P<message>.*)'
  712. r')$')
  713. locals = th.local()
  714. children = set()
  715. def run_runner(runner_, ids=[]):
  716. nonlocal passed_suite_perms
  717. nonlocal passed_case_perms
  718. nonlocal passed_perms
  719. nonlocal powerlosses
  720. nonlocal locals
  721. # run the tests!
  722. cmd = runner_ + ids
  723. if args.get('verbose'):
  724. print(' '.join(shlex.quote(c) for c in cmd))
  725. mpty, spty = pty.openpty()
  726. proc = sp.Popen(cmd, stdout=spty, stderr=spty, close_fds=False)
  727. os.close(spty)
  728. children.add(proc)
  729. mpty = os.fdopen(mpty, 'r', 1)
  730. last_id = None
  731. last_stdout = co.deque(maxlen=args.get('context', 5) + 1)
  732. last_assert = None
  733. try:
  734. while True:
  735. # parse a line for state changes
  736. try:
  737. line = mpty.readline()
  738. except OSError as e:
  739. if e.errno != errno.EIO:
  740. raise
  741. break
  742. if not line:
  743. break
  744. last_stdout.append(line)
  745. if stdout_:
  746. try:
  747. stdout_.write(line)
  748. stdout_.flush()
  749. except BrokenPipeError:
  750. pass
  751. m = pattern.match(line)
  752. if m:
  753. op = m.group('op') or m.group('op_')
  754. if op == 'running':
  755. locals.seen_perms += 1
  756. last_id = m.group('id')
  757. last_stdout.clear()
  758. last_assert = None
  759. elif op == 'powerloss':
  760. last_id = m.group('id')
  761. powerlosses += 1
  762. elif op == 'finished':
  763. case = m.group('case')
  764. suite = case_suites[case]
  765. passed_suite_perms[suite] += 1
  766. passed_case_perms[case] += 1
  767. passed_perms += 1
  768. if output_:
  769. # get defines and write to csv
  770. defines = find_defines(
  771. runner_, m.group('id'), **args)
  772. output_.writerow({
  773. 'suite': suite,
  774. 'case': case,
  775. 'test_passed': '1/1',
  776. **defines})
  777. elif op == 'skipped':
  778. locals.seen_perms += 1
  779. elif op == 'assert':
  780. last_assert = (
  781. m.group('path'),
  782. int(m.group('lineno')),
  783. m.group('message'))
  784. # go ahead and kill the process, aborting takes a while
  785. if args.get('keep_going'):
  786. proc.kill()
  787. except KeyboardInterrupt:
  788. raise TestFailure(last_id, 1, list(last_stdout))
  789. finally:
  790. children.remove(proc)
  791. mpty.close()
  792. proc.wait()
  793. if proc.returncode != 0:
  794. raise TestFailure(
  795. last_id,
  796. proc.returncode,
  797. list(last_stdout),
  798. last_assert)
  799. def run_job(runner_, ids=[], start=None, step=None):
  800. nonlocal failures
  801. nonlocal killed
  802. nonlocal locals
  803. start = start or 0
  804. step = step or 1
  805. while start < total_perms:
  806. job_runner = runner_.copy()
  807. if args.get('isolate') or args.get('valgrind'):
  808. job_runner.append('-s%s,%s,%s' % (start, start+step, step))
  809. else:
  810. job_runner.append('-s%s,,%s' % (start, step))
  811. try:
  812. # run the tests
  813. locals.seen_perms = 0
  814. run_runner(job_runner, ids)
  815. assert locals.seen_perms > 0
  816. start += locals.seen_perms*step
  817. except TestFailure as failure:
  818. # keep track of failures
  819. if output_:
  820. case, _ = failure.id.split(':', 1)
  821. suite = case_suites[case]
  822. # get defines and write to csv
  823. defines = find_defines(runner_, failure.id, **args)
  824. output_.writerow({
  825. 'suite': suite,
  826. 'case': case,
  827. 'test_passed': '0/1',
  828. **defines})
  829. # race condition for multiple failures?
  830. if failures and not args.get('keep_going'):
  831. break
  832. failures.append(failure)
  833. if args.get('keep_going') and not killed:
  834. # resume after failed test
  835. assert locals.seen_perms > 0
  836. start += locals.seen_perms*step
  837. continue
  838. else:
  839. # stop other tests
  840. killed = True
  841. for child in children.copy():
  842. child.kill()
  843. break
  844. # parallel jobs?
  845. runners = []
  846. if 'jobs' in args:
  847. for job in range(args['jobs']):
  848. runners.append(th.Thread(
  849. target=run_job, args=(runner_, ids, job, args['jobs']),
  850. daemon=True))
  851. else:
  852. runners.append(th.Thread(
  853. target=run_job, args=(runner_, ids, None, None),
  854. daemon=True))
  855. def print_update(done):
  856. if not args.get('verbose') and (args['color'] or done):
  857. sys.stdout.write('%s%srunning %s%s:%s %s%s' % (
  858. '\r\x1b[K' if args['color'] else '',
  859. '\x1b[?7l' if not done else '',
  860. ('\x1b[32m' if not failures else '\x1b[31m')
  861. if args['color'] else '',
  862. name,
  863. '\x1b[m' if args['color'] else '',
  864. ', '.join(filter(None, [
  865. '%d/%d suites' % (
  866. sum(passed_suite_perms[k] == v
  867. for k, v in expected_suite_perms.items()),
  868. len(expected_suite_perms))
  869. if (not args.get('by_suites')
  870. and not args.get('by_cases')) else None,
  871. '%d/%d cases' % (
  872. sum(passed_case_perms[k] == v
  873. for k, v in expected_case_perms.items()),
  874. len(expected_case_perms))
  875. if not args.get('by_cases') else None,
  876. '%d/%d perms' % (passed_perms, expected_perms),
  877. '%dpls!' % powerlosses
  878. if powerlosses else None,
  879. '%s%d/%d failures%s' % (
  880. '\x1b[31m' if args['color'] else '',
  881. len(failures),
  882. expected_perms,
  883. '\x1b[m' if args['color'] else '')
  884. if failures else None])),
  885. '\x1b[?7h' if not done else '\n'))
  886. sys.stdout.flush()
  887. for r in runners:
  888. r.start()
  889. try:
  890. while any(r.is_alive() for r in runners):
  891. time.sleep(0.01)
  892. print_update(False)
  893. except KeyboardInterrupt:
  894. # this is handled by the runner threads, we just
  895. # need to not abort here
  896. killed = True
  897. finally:
  898. print_update(True)
  899. for r in runners:
  900. r.join()
  901. return (
  902. expected_perms,
  903. passed_perms,
  904. powerlosses,
  905. failures,
  906. killed)
  907. def run(runner, test_ids=[], **args):
  908. # query runner for tests
  909. runner_ = find_runner(runner, **args)
  910. print('using runner: %s' % ' '.join(shlex.quote(c) for c in runner_))
  911. (_,
  912. expected_suite_perms,
  913. expected_case_perms,
  914. expected_perms,
  915. total_perms) = find_perms(runner_, test_ids, **args)
  916. print('found %d suites, %d cases, %d/%d permutations' % (
  917. len(expected_suite_perms),
  918. len(expected_case_perms),
  919. expected_perms,
  920. total_perms))
  921. print()
  922. # automatic job detection?
  923. if args.get('jobs') == 0:
  924. args['jobs'] = len(os.sched_getaffinity(0))
  925. # truncate and open logs here so they aren't disconnected between tests
  926. stdout = None
  927. if args.get('stdout'):
  928. stdout = openio(args['stdout'], 'w', 1)
  929. trace = None
  930. if args.get('trace'):
  931. trace = openio(args['trace'], 'w', 1)
  932. output = None
  933. if args.get('output'):
  934. output = TestOutput(args['output'],
  935. ['suite', 'case'],
  936. ['test_passed'])
  937. # measure runtime
  938. start = time.time()
  939. # spawn runners
  940. expected = 0
  941. passed = 0
  942. powerlosses = 0
  943. failures = []
  944. for by in (test_ids if test_ids
  945. else expected_case_perms.keys() if args.get('by_cases')
  946. else expected_suite_perms.keys() if args.get('by_suites')
  947. else [None]):
  948. # spawn jobs for stage
  949. (expected_,
  950. passed_,
  951. powerlosses_,
  952. failures_,
  953. killed) = run_stage(
  954. by or 'tests',
  955. runner_,
  956. [by] if by is not None else [],
  957. stdout,
  958. trace,
  959. output,
  960. **args)
  961. # collect passes/failures
  962. expected += expected_
  963. passed += passed_
  964. powerlosses += powerlosses_
  965. failures.extend(failures_)
  966. if (failures and not args.get('keep_going')) or killed:
  967. break
  968. stop = time.time()
  969. if stdout:
  970. try:
  971. stdout.close()
  972. except BrokenPipeError:
  973. pass
  974. if trace:
  975. try:
  976. trace.close()
  977. except BrokenPipeError:
  978. pass
  979. if output:
  980. output.close()
  981. # show summary
  982. print()
  983. print('%sdone:%s %s' % (
  984. ('\x1b[32m' if not failures else '\x1b[31m')
  985. if args['color'] else '',
  986. '\x1b[m' if args['color'] else '',
  987. ', '.join(filter(None, [
  988. '%d/%d passed' % (passed, expected),
  989. '%d/%d failed' % (len(failures), expected),
  990. '%dpls!' % powerlosses if powerlosses else None,
  991. 'in %.2fs' % (stop-start)]))))
  992. print()
  993. # print each failure
  994. for failure in failures:
  995. assert failure.id is not None, '%s broken? %r' % (
  996. ' '.join(shlex.quote(c) for c in runner_),
  997. failure)
  998. # get some extra info from runner
  999. path, lineno = find_path(runner_, failure.id, **args)
  1000. defines = find_defines(runner_, failure.id, **args)
  1001. # show summary of failure
  1002. print('%s%s:%d:%sfailure:%s %s%s failed' % (
  1003. '\x1b[01m' if args['color'] else '',
  1004. path, lineno,
  1005. '\x1b[01;31m' if args['color'] else '',
  1006. '\x1b[m' if args['color'] else '',
  1007. failure.id,
  1008. ' (%s)' % ', '.join('%s=%s' % (k,v) for k,v in defines.items())
  1009. if defines else ''))
  1010. if failure.stdout:
  1011. stdout = failure.stdout
  1012. if failure.assert_ is not None:
  1013. stdout = stdout[:-1]
  1014. for line in stdout[-args.get('context', 5):]:
  1015. sys.stdout.write(line)
  1016. if failure.assert_ is not None:
  1017. path, lineno, message = failure.assert_
  1018. print('%s%s:%d:%sassert:%s %s' % (
  1019. '\x1b[01m' if args['color'] else '',
  1020. path, lineno,
  1021. '\x1b[01;31m' if args['color'] else '',
  1022. '\x1b[m' if args['color'] else '',
  1023. message))
  1024. with open(path) as f:
  1025. line = next(it.islice(f, lineno-1, None)).strip('\n')
  1026. print(line)
  1027. print()
  1028. # drop into gdb?
  1029. if failures and (args.get('gdb')
  1030. or args.get('gdb_case')
  1031. or args.get('gdb_main')
  1032. or args.get('gdb_pl') is not None
  1033. or args.get('gdb_pl_before')
  1034. or args.get('gdb_pl_after')):
  1035. failure = failures[0]
  1036. cmd = runner_ + [failure.id]
  1037. if args.get('gdb_main'):
  1038. # we don't really need the case breakpoint here, but it
  1039. # can be helpful
  1040. path, lineno = find_path(runner_, failure.id, **args)
  1041. cmd[:0] = args['gdb_path'] + [
  1042. '-ex', 'break main',
  1043. '-ex', 'break %s:%d' % (path, lineno),
  1044. '-ex', 'run',
  1045. '--args']
  1046. elif args.get('gdb_case'):
  1047. path, lineno = find_path(runner_, failure.id, **args)
  1048. cmd[:0] = args['gdb_path'] + [
  1049. '-ex', 'break %s:%d' % (path, lineno),
  1050. '-ex', 'run',
  1051. '--args']
  1052. elif args.get('gdb_pl') is not None:
  1053. path, lineno = find_path(runner_, failure.id, **args)
  1054. cmd[:0] = args['gdb_path'] + [
  1055. '-ex', 'break %s:%d' % (path, lineno),
  1056. '-ex', 'ignore 1 %d' % args['gdb_pl'],
  1057. '-ex', 'run',
  1058. '--args']
  1059. elif args.get('gdb_pl_before'):
  1060. # figure out how many powerlosses there were
  1061. powerlosses = (
  1062. sum(1 for _ in re.finditer('[0-9a-f]',
  1063. failure.id.split(':', 2)[-1]))
  1064. if failure.id.count(':') >= 2 else 0)
  1065. path, lineno = find_path(runner_, failure.id, **args)
  1066. cmd[:0] = args['gdb_path'] + [
  1067. '-ex', 'break %s:%d' % (path, lineno),
  1068. '-ex', 'ignore 1 %d' % max(powerlosses-1, 0),
  1069. '-ex', 'run',
  1070. '--args']
  1071. elif args.get('gdb_pl_after'):
  1072. # figure out how many powerlosses there were
  1073. powerlosses = (
  1074. sum(1 for _ in re.finditer('[0-9a-f]',
  1075. failure.id.split(':', 2)[-1]))
  1076. if failure.id.count(':') >= 2 else 0)
  1077. path, lineno = find_path(runner_, failure.id, **args)
  1078. cmd[:0] = args['gdb_path'] + [
  1079. '-ex', 'break %s:%d' % (path, lineno),
  1080. '-ex', 'ignore 1 %d' % powerlosses,
  1081. '-ex', 'run',
  1082. '--args']
  1083. elif failure.assert_ is not None:
  1084. cmd[:0] = args['gdb_path'] + [
  1085. '-ex', 'run',
  1086. '-ex', 'frame function raise',
  1087. '-ex', 'up 2',
  1088. '--args']
  1089. else:
  1090. cmd[:0] = args['gdb_path'] + [
  1091. '-ex', 'run',
  1092. '--args']
  1093. # exec gdb interactively
  1094. if args.get('verbose'):
  1095. print(' '.join(shlex.quote(c) for c in cmd))
  1096. os.execvp(cmd[0], cmd)
  1097. return 1 if failures else 0
  1098. def main(**args):
  1099. # figure out what color should be
  1100. if args.get('color') == 'auto':
  1101. args['color'] = sys.stdout.isatty()
  1102. elif args.get('color') == 'always':
  1103. args['color'] = True
  1104. else:
  1105. args['color'] = False
  1106. if args.get('compile'):
  1107. return compile(**args)
  1108. elif (args.get('summary')
  1109. or args.get('list_suites')
  1110. or args.get('list_cases')
  1111. or args.get('list_suite_paths')
  1112. or args.get('list_case_paths')
  1113. or args.get('list_defines')
  1114. or args.get('list_permutation_defines')
  1115. or args.get('list_implicit_defines')
  1116. or args.get('list_geometries')
  1117. or args.get('list_powerlosses')):
  1118. return list_(**args)
  1119. else:
  1120. return run(**args)
  1121. if __name__ == "__main__":
  1122. import argparse
  1123. import sys
  1124. argparse.ArgumentParser._handle_conflict_ignore = lambda *_: None
  1125. argparse._ArgumentGroup._handle_conflict_ignore = lambda *_: None
  1126. parser = argparse.ArgumentParser(
  1127. description="Build and run tests.",
  1128. allow_abbrev=False,
  1129. conflict_handler='ignore')
  1130. parser.add_argument(
  1131. '-v', '--verbose',
  1132. action='store_true',
  1133. help="Output commands that run behind the scenes.")
  1134. parser.add_argument(
  1135. '--color',
  1136. choices=['never', 'always', 'auto'],
  1137. default='auto',
  1138. help="When to use terminal colors. Defaults to 'auto'.")
  1139. # test flags
  1140. test_parser = parser.add_argument_group('test options')
  1141. test_parser.add_argument(
  1142. 'runner',
  1143. nargs='?',
  1144. type=lambda x: x.split(),
  1145. help="Test runner to use for testing. Defaults to %r." % RUNNER_PATH)
  1146. test_parser.add_argument(
  1147. 'test_ids',
  1148. nargs='*',
  1149. help="Description of tests to run.")
  1150. test_parser.add_argument(
  1151. '-Y', '--summary',
  1152. action='store_true',
  1153. help="Show quick summary.")
  1154. test_parser.add_argument(
  1155. '-l', '--list-suites',
  1156. action='store_true',
  1157. help="List test suites.")
  1158. test_parser.add_argument(
  1159. '-L', '--list-cases',
  1160. action='store_true',
  1161. help="List test cases.")
  1162. test_parser.add_argument(
  1163. '--list-suite-paths',
  1164. action='store_true',
  1165. help="List the path for each test suite.")
  1166. test_parser.add_argument(
  1167. '--list-case-paths',
  1168. action='store_true',
  1169. help="List the path and line number for each test case.")
  1170. test_parser.add_argument(
  1171. '--list-defines',
  1172. action='store_true',
  1173. help="List all defines in this test-runner.")
  1174. test_parser.add_argument(
  1175. '--list-permutation-defines',
  1176. action='store_true',
  1177. help="List explicit defines in this test-runner.")
  1178. test_parser.add_argument(
  1179. '--list-implicit-defines',
  1180. action='store_true',
  1181. help="List implicit defines in this test-runner.")
  1182. test_parser.add_argument(
  1183. '--list-geometries',
  1184. action='store_true',
  1185. help="List the available disk geometries.")
  1186. test_parser.add_argument(
  1187. '--list-powerlosses',
  1188. action='store_true',
  1189. help="List the available power-loss scenarios.")
  1190. test_parser.add_argument(
  1191. '-D', '--define',
  1192. action='append',
  1193. help="Override a test define.")
  1194. test_parser.add_argument(
  1195. '-G', '--geometry',
  1196. help="Comma-separated list of disk geometries to test.")
  1197. test_parser.add_argument(
  1198. '-P', '--powerloss',
  1199. help="Comma-separated list of power-loss scenarios to test.")
  1200. test_parser.add_argument(
  1201. '-d', '--disk',
  1202. help="Direct block device operations to this file.")
  1203. test_parser.add_argument(
  1204. '-t', '--trace',
  1205. help="Direct trace output to this file.")
  1206. test_parser.add_argument(
  1207. '--trace-backtrace',
  1208. action='store_true',
  1209. help="Include a backtrace with every trace statement.")
  1210. test_parser.add_argument(
  1211. '--trace-period',
  1212. help="Sample trace output at this period in cycles.")
  1213. test_parser.add_argument(
  1214. '--trace-freq',
  1215. help="Sample trace output at this frequency in hz.")
  1216. test_parser.add_argument(
  1217. '-O', '--stdout',
  1218. help="Direct stdout to this file. Note stderr is already merged here.")
  1219. test_parser.add_argument(
  1220. '-o', '--output',
  1221. help="CSV file to store results.")
  1222. test_parser.add_argument(
  1223. '--read-sleep',
  1224. help="Artificial read delay in seconds.")
  1225. test_parser.add_argument(
  1226. '--prog-sleep',
  1227. help="Artificial prog delay in seconds.")
  1228. test_parser.add_argument(
  1229. '--erase-sleep',
  1230. help="Artificial erase delay in seconds.")
  1231. test_parser.add_argument(
  1232. '-j', '--jobs',
  1233. nargs='?',
  1234. type=lambda x: int(x, 0),
  1235. const=0,
  1236. help="Number of parallel runners to run. 0 runs one runner per core.")
  1237. test_parser.add_argument(
  1238. '-k', '--keep-going',
  1239. action='store_true',
  1240. help="Don't stop on first error.")
  1241. test_parser.add_argument(
  1242. '-i', '--isolate',
  1243. action='store_true',
  1244. help="Run each test permutation in a separate process.")
  1245. test_parser.add_argument(
  1246. '-b', '--by-suites',
  1247. action='store_true',
  1248. help="Step through tests by suite.")
  1249. test_parser.add_argument(
  1250. '-B', '--by-cases',
  1251. action='store_true',
  1252. help="Step through tests by case.")
  1253. test_parser.add_argument(
  1254. '--context',
  1255. type=lambda x: int(x, 0),
  1256. default=5,
  1257. help="Show this many lines of stdout on test failure. "
  1258. "Defaults to 5.")
  1259. test_parser.add_argument(
  1260. '--gdb',
  1261. action='store_true',
  1262. help="Drop into gdb on test failure.")
  1263. test_parser.add_argument(
  1264. '--gdb-case',
  1265. action='store_true',
  1266. help="Drop into gdb on test failure but stop at the beginning "
  1267. "of the failing test case.")
  1268. test_parser.add_argument(
  1269. '--gdb-main',
  1270. action='store_true',
  1271. help="Drop into gdb on test failure but stop at the beginning "
  1272. "of main.")
  1273. test_parser.add_argument(
  1274. '--gdb-pl',
  1275. type=lambda x: int(x, 0),
  1276. help="Drop into gdb on this specific powerloss.")
  1277. test_parser.add_argument(
  1278. '--gdb-pl-before',
  1279. action='store_true',
  1280. help="Drop into gdb before the powerloss that caused the failure.")
  1281. test_parser.add_argument(
  1282. '--gdb-pl-after',
  1283. action='store_true',
  1284. help="Drop into gdb after the powerloss that caused the failure.")
  1285. test_parser.add_argument(
  1286. '--gdb-path',
  1287. type=lambda x: x.split(),
  1288. default=GDB_PATH,
  1289. help="Path to the gdb executable, may include flags. "
  1290. "Defaults to %r." % GDB_PATH)
  1291. test_parser.add_argument(
  1292. '--exec',
  1293. type=lambda e: e.split(),
  1294. help="Run under another executable.")
  1295. test_parser.add_argument(
  1296. '--valgrind',
  1297. action='store_true',
  1298. help="Run under Valgrind to find memory errors. Implicitly sets "
  1299. "--isolate.")
  1300. test_parser.add_argument(
  1301. '--valgrind-path',
  1302. type=lambda x: x.split(),
  1303. default=VALGRIND_PATH,
  1304. help="Path to the Valgrind executable, may include flags. "
  1305. "Defaults to %r." % VALGRIND_PATH)
  1306. test_parser.add_argument(
  1307. '-p', '--perf',
  1308. help="Run under Linux's perf to sample performance counters, writing "
  1309. "samples to this file.")
  1310. test_parser.add_argument(
  1311. '--perf-freq',
  1312. help="perf sampling frequency. This is passed directly to the perf "
  1313. "script.")
  1314. test_parser.add_argument(
  1315. '--perf-period',
  1316. help="perf sampling period. This is passed directly to the perf "
  1317. "script.")
  1318. test_parser.add_argument(
  1319. '--perf-events',
  1320. help="perf events to record. This is passed directly to the perf "
  1321. "script.")
  1322. test_parser.add_argument(
  1323. '--perf-script',
  1324. type=lambda x: x.split(),
  1325. default=PERF_SCRIPT,
  1326. help="Path to the perf script to use. Defaults to %r." % PERF_SCRIPT)
  1327. test_parser.add_argument(
  1328. '--perf-path',
  1329. type=lambda x: x.split(),
  1330. help="Path to the perf executable, may include flags. This is passed "
  1331. "directly to the perf script")
  1332. # compilation flags
  1333. comp_parser = parser.add_argument_group('compilation options')
  1334. comp_parser.add_argument(
  1335. 'test_paths',
  1336. nargs='*',
  1337. help="Description of *.toml files to compile. May be a directory "
  1338. "or a list of paths.")
  1339. comp_parser.add_argument(
  1340. '-c', '--compile',
  1341. action='store_true',
  1342. help="Compile a test suite or source file.")
  1343. comp_parser.add_argument(
  1344. '-s', '--source',
  1345. help="Source file to compile, possibly injecting internal tests.")
  1346. comp_parser.add_argument(
  1347. '--include',
  1348. default=HEADER_PATH,
  1349. help="Inject this header file into every compiled test file. "
  1350. "Defaults to %r." % HEADER_PATH)
  1351. comp_parser.add_argument(
  1352. '-o', '--output',
  1353. help="Output file.")
  1354. # runner/test_paths overlap, so need to do some munging here
  1355. args = parser.parse_intermixed_args()
  1356. args.test_paths = [' '.join(args.runner or [])] + args.test_ids
  1357. args.runner = args.runner or [RUNNER_PATH]
  1358. sys.exit(main(**{k: v
  1359. for k, v in vars(args).items()
  1360. if v is not None}))