test_.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. #!/usr/bin/env python3
  2. #
  3. # Script to compile and runs tests.
  4. #
  5. import glob
  6. import itertools as it
  7. import math as m
  8. import os
  9. import re
  10. import shutil
  11. import toml
  12. TEST_PATHS = ['tests_']
  13. SUITE_PROLOGUE = """
  14. //////// AUTOGENERATED ////////
  15. #include "runners/test_runner.h"
  16. #include <stdio.h>
  17. """
  18. CASE_PROLOGUE = """
  19. lfs_t lfs;
  20. """
  21. CASE_EPILOGUE = """
  22. """
  23. PRE_DEFINES = [
  24. 'READ_SIZE',
  25. 'PROG_SIZE',
  26. 'BLOCK_SIZE',
  27. 'BLOCK_COUNT',
  28. 'BLOCK_CYCLES',
  29. 'CACHE_SIZE',
  30. 'LOOKAHEAD_SIZE',
  31. 'ERASE_VALUE',
  32. 'ERASE_CYCLES',
  33. 'BADBLOCK_BEHAVIOR',
  34. ]
  35. # TODO
  36. # def testpath(path):
  37. # def testcase(path):
  38. # def testperm(path):
  39. def testsuite(path):
  40. name = os.path.basename(path)
  41. if name.endswith('.toml'):
  42. name = name[:-len('.toml')]
  43. return name
  44. # TODO move this out in other files
  45. def openio(path, mode='r'):
  46. if path == '-':
  47. if 'r' in mode:
  48. return os.fdopen(os.dup(sys.stdin.fileno()), 'r')
  49. else:
  50. return os.fdopen(os.dup(sys.stdout.fileno()), 'w')
  51. else:
  52. return open(path, mode)
  53. class TestCase:
  54. # create a TestCase object from a config
  55. def __init__(self, config, args={}):
  56. self.name = config.pop('name')
  57. self.path = config.pop('path')
  58. self.suite = config.pop('suite')
  59. self.lineno = config.pop('lineno', None)
  60. self.code = config.pop('code')
  61. self.code_lineno = config.pop('code_lineno', None)
  62. # figure out defines and the number of resulting permutations
  63. self.defines = {}
  64. for k, v in config.pop('defines', {}).items():
  65. try:
  66. v = eval(v)
  67. except:
  68. v = v
  69. if not isinstance(v, str):
  70. try:
  71. v = list(v)
  72. except:
  73. v = [v]
  74. else:
  75. v = [v]
  76. self.defines[k] = v
  77. self.permutations = m.prod(len(v) for v in self.defines.values())
  78. for k in config.keys():
  79. print('warning: in %s, found unused key %r' % (self.id(), k),
  80. file=sys.stderr)
  81. def id(self):
  82. return '%s#%s' % (self.suite, self.name)
  83. class TestSuite:
  84. # create a TestSuite object from a toml file
  85. def __init__(self, path, args={}):
  86. self.name = testsuite(path)
  87. self.path = path
  88. # load toml file and parse test cases
  89. with open(self.path) as f:
  90. # load tests
  91. config = toml.load(f)
  92. # find line numbers
  93. f.seek(0)
  94. case_linenos = []
  95. code_linenos = []
  96. for i, line in enumerate(f):
  97. match = re.match(
  98. '(?P<case>\[\s*cases\s*\.\s*(?P<name>\w+)\s*\])' +
  99. '|(?P<code>code\s*=\s*(?:\'\'\'|"""))',
  100. line)
  101. if match and match.group('case'):
  102. case_linenos.append((i+1, match.group('name')))
  103. elif match and match.group('code'):
  104. code_linenos.append(i+2)
  105. # sort in case toml parsing did not retain order
  106. case_linenos.sort()
  107. cases = config.pop('cases', [])
  108. for (lineno, name), (nlineno, _) in it.zip_longest(
  109. case_linenos, case_linenos[1:],
  110. fillvalue=(float('inf'), None)):
  111. code_lineno = min(
  112. (l for l in code_linenos if l >= lineno and l < nlineno),
  113. default=None)
  114. cases[name]['lineno'] = lineno
  115. cases[name]['code_lineno'] = code_lineno
  116. self.code = config.pop('code', None)
  117. self.code_lineno = min(
  118. (l for l in code_linenos
  119. if not case_linenos or l < case_linenos[0][0]),
  120. default=None)
  121. self.cases = []
  122. for name, case in sorted(cases.items(),
  123. key=lambda c: c[1].get('lineno')):
  124. self.cases.append(TestCase(config={
  125. 'name': name,
  126. 'path': path + (':%d' % case['lineno']
  127. if 'lineno' in case else ''),
  128. 'suite': self.name,
  129. **case}))
  130. # combine pre-defines and per-case defines
  131. self.defines = PRE_DEFINES + sorted(
  132. set.union(*(set(case.defines) for case in self.cases)))
  133. for k in config.keys():
  134. print('warning: in %s, found unused key %r' % (self.id(), k),
  135. file=sys.stderr)
  136. def id(self):
  137. return self.name
  138. def compile(**args):
  139. # find .toml files
  140. paths = []
  141. for path in args['test_paths']:
  142. if os.path.isdir(path):
  143. path = path + '/*.toml'
  144. for path in glob.glob(path):
  145. paths.append(path)
  146. if not paths:
  147. print('no test suites found in %r?' % args['test_paths'])
  148. sys.exit(-1)
  149. if not args.get('source'):
  150. if len(paths) > 1:
  151. print('more than one test suite for compilation? (%r)'
  152. % args['test_paths'])
  153. sys.exit(-1)
  154. # write out a test suite
  155. suite = TestSuite(paths[0])
  156. if 'output' in args:
  157. with openio(args['output'], 'w') as f:
  158. f.write(SUITE_PROLOGUE)
  159. f.write('\n')
  160. if suite.code is not None:
  161. if suite.code_lineno is not None:
  162. f.write('#line %d "%s"\n'
  163. % (suite.code_lineno, suite.path))
  164. f.write(suite.code)
  165. f.write('\n')
  166. for i, define in it.islice(
  167. enumerate(suite.defines),
  168. len(PRE_DEFINES), None):
  169. f.write('#define %-24s test_define(%d)\n' % (define, i))
  170. f.write('\n')
  171. for case in suite.cases:
  172. # create case defines
  173. if case.defines:
  174. for perm, defines in enumerate(
  175. it.product(*(
  176. [(k, v) for v in vs]
  177. for k, vs in case.defines.items()))):
  178. f.write('const uintmax_t '
  179. '__test__%s__%s__%d__defines[] = {\n'
  180. % (suite.name, case.name, perm))
  181. for k, v in sorted(defines):
  182. f.write(4*' '+'[%d] = %s,\n'
  183. % (suite.defines.index(k), v))
  184. f.write('};\n')
  185. f.write('\n')
  186. f.write('const uintmax_t *const '
  187. '__test__%s__%s__defines[] = {\n'
  188. % (suite.name, case.name))
  189. for perm in range(case.permutations):
  190. f.write(4*' '+'__test__%s__%s__%d__defines,\n'
  191. % (suite.name, case.name, perm))
  192. f.write('};\n')
  193. f.write('\n')
  194. f.write('const bool '
  195. '__test__%s__%s__define_mask[] = {\n'
  196. % (suite.name, case.name))
  197. for i, k in enumerate(suite.defines):
  198. f.write(4*' '+'%s,\n'
  199. % ('true' if k in case.defines else 'false'))
  200. f.write('};\n')
  201. f.write('\n')
  202. # create case filter function
  203. f.write('bool __test__%s__%s__filter('
  204. '__attribute__((unused)) struct lfs_config *cfg, '
  205. '__attribute__((unused)) uint32_t perm) {\n'
  206. % (suite.name, case.name))
  207. f.write(4*' '+'return true;\n')
  208. f.write('}\n')
  209. f.write('\n')
  210. # create case run function
  211. f.write('void __test__%s__%s__run('
  212. '__attribute__((unused)) struct lfs_config *cfg, '
  213. '__attribute__((unused)) uint32_t perm) {\n'
  214. % (suite.name, case.name))
  215. f.write(4*' '+'%s\n'
  216. % CASE_PROLOGUE.strip().replace('\n', '\n'+4*' '))
  217. f.write('\n')
  218. f.write(4*' '+'// test case %s\n' % case.id())
  219. if case.code_lineno is not None:
  220. f.write(4*' '+'#line %d "%s"\n'
  221. % (case.code_lineno, suite.path))
  222. f.write(case.code)
  223. f.write('\n')
  224. f.write(4*' '+'%s\n'
  225. % CASE_EPILOGUE.strip().replace('\n', '\n'+4*' '))
  226. f.write('}\n')
  227. f.write('\n')
  228. # create case struct
  229. f.write('const struct test_case __test__%s__%s__case = {\n'
  230. % (suite.name, case.name))
  231. f.write(4*' '+'.id = "%s",\n' % case.id())
  232. f.write(4*' '+'.name = "%s",\n' % case.name)
  233. f.write(4*' '+'.path = "%s",\n' % case.path)
  234. f.write(4*' '+'.types = TEST_NORMAL,\n')
  235. f.write(4*' '+'.permutations = %d,\n' % case.permutations)
  236. if case.defines:
  237. f.write(4*' '+'.defines = __test__%s__%s__defines,\n'
  238. % (suite.name, case.name))
  239. f.write(4*' '+'.define_mask = '
  240. '__test__%s__%s__define_mask,\n'
  241. % (suite.name, case.name))
  242. f.write(4*' '+'.filter = __test__%s__%s__filter,\n'
  243. % (suite.name, case.name))
  244. f.write(4*' '+'.run = __test__%s__%s__run,\n'
  245. % (suite.name, case.name))
  246. f.write('};\n')
  247. f.write('\n')
  248. # create suite define names
  249. f.write('const char *const __test__%s__define_names[] = {\n'
  250. % suite.name)
  251. for k in suite.defines:
  252. f.write(4*' '+'"%s",\n' % k)
  253. f.write('};\n')
  254. f.write('\n')
  255. # create suite struct
  256. f.write('const struct test_suite __test__%s__suite = {\n'
  257. % suite.name)
  258. f.write(4*' '+'.id = "%s",\n' % suite.id())
  259. f.write(4*' '+'.name = "%s",\n' % suite.name)
  260. f.write(4*' '+'.path = "%s",\n' % suite.path)
  261. f.write(4*' '+'.define_names = __test__%s__define_names,\n'
  262. % suite.name)
  263. f.write(4*' '+'.define_count = %d,\n' % len(suite.defines))
  264. f.write(4*' '+'.cases = (const struct test_case *const []){\n')
  265. for case in suite.cases:
  266. f.write(8*' '+'&__test__%s__%s__case,\n'
  267. % (suite.name, case.name))
  268. f.write(4*' '+'},\n')
  269. f.write(4*' '+'.case_count = %d,\n' % len(suite.cases))
  270. f.write('};\n')
  271. f.write('\n')
  272. else:
  273. # load all suites
  274. suites = [TestSuite(path) for path in paths]
  275. suites.sort(key=lambda s: s.name)
  276. # write out a test source
  277. if 'output' in args:
  278. with openio(args['output'], 'w') as f:
  279. f.write('#line 1 "%s"\n' % args['source'])
  280. with open(args['source']) as sf:
  281. shutil.copyfileobj(sf, f)
  282. f.write('\n')
  283. f.write(SUITE_PROLOGUE)
  284. f.write('\n')
  285. # add suite info to test_runner.c
  286. if args['source'] == 'runners/test_runner.c':
  287. f.write('\n')
  288. for suite in suites:
  289. f.write('extern const struct test_suite '
  290. '__test__%s__suite;\n' % suite.name)
  291. f.write('const struct test_suite *test_suites[] = {\n')
  292. for suite in suites:
  293. f.write(4*' '+'&__test__%s__suite,\n' % suite.name)
  294. f.write('};\n')
  295. f.write('const size_t test_suite_count = %d;\n'
  296. % len(suites))
  297. def run(**args):
  298. pass
  299. def main(**args):
  300. if args.get('compile'):
  301. compile(**args)
  302. else:
  303. run(**args)
  304. if __name__ == "__main__":
  305. import argparse
  306. import sys
  307. parser = argparse.ArgumentParser(
  308. description="Build and run tests.")
  309. # TODO document test case/perm specifier
  310. parser.add_argument('test_paths', nargs='*', default=TEST_PATHS,
  311. help="Description of test(s) to run. May be a directory, a path, or \
  312. test identifier. Defaults to all tests in %r." % TEST_PATHS)
  313. # test flags
  314. test_parser = parser.add_argument_group('test options')
  315. # compilation flags
  316. comp_parser = parser.add_argument_group('compilation options')
  317. comp_parser.add_argument('-c', '--compile', action='store_true',
  318. help="Compile a test suite or source file.")
  319. comp_parser.add_argument('-s', '--source',
  320. help="Source file to compile, possibly injecting internal tests.")
  321. comp_parser.add_argument('-o', '--output',
  322. help="Output file.")
  323. # TODO apply this to other scripts?
  324. sys.exit(main(**{k: v
  325. for k, v in vars(parser.parse_args()).items()
  326. if v is not None}))