test.py 39 KB

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