main.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. #! /usr/bin/env python
  2. # If Psyco is installed, fire it up so that the test suite will run more
  3. # quickly
  4. try:
  5. import psyco
  6. psyco.full()
  7. except ImportError:
  8. pass
  9. import sys
  10. import os
  11. import unittest
  12. from optparse import OptionParser
  13. from ConfigParser import DEFAULTSECT
  14. # Add the system_tests directory to our python path
  15. mypath = os.path.dirname(sys.argv[0])
  16. sys.path.append(os.path.join(mypath, "system_tests"))
  17. import testutils
  18. def all(iterable):
  19. """
  20. Return True if all elements of the iterable are true
  21. (or if the iterable is empty).
  22. iterable - any object with an iterable context.
  23. """
  24. for i in iterable:
  25. if not i:
  26. return False
  27. return True
  28. def exist(d, iterable):
  29. """
  30. Return generator object consisting of Boolean values.
  31. d - dictionary
  32. iterable - any object with an iterable context.
  33. At present a list or single item.
  34. """
  35. for i in iterable:
  36. yield i in d
  37. def uniq(iterable):
  38. """
  39. Return generator object consisting
  40. from the uniq items from the 'iterable'.
  41. 'iterable' should be presorted.
  42. iterable - any object with iterable context
  43. """
  44. last = None
  45. for i in iterable:
  46. if i != last:
  47. last = i
  48. yield i
  49. class TestSet(object):
  50. """
  51. Describe an hierarchy of aggregate test names/test collections
  52. """
  53. def __init__(self, key, desc):
  54. self.key = key
  55. self.desc = desc
  56. self.children = None
  57. self.suite = None
  58. def clean_pyc_files(dir):
  59. """
  60. Remove files ended on 'pyc' or 'pyo' from directory 'dir'
  61. dir - directory name
  62. """
  63. def path_visitor(a, d, f):
  64. for i in f:
  65. fpath = os.path.join(d, i)
  66. if fpath.lower().endswith(".pyc") or fpath.lower().endswith(".pyo"):
  67. os.unlink(fpath)
  68. del f[:]
  69. os.path.walk(dir, path_visitor, None)
  70. def find_all_tests(path, name='', create_all=False):
  71. """
  72. Return a dictionary of TestSet objects.
  73. Searches recursively the supplied directory tree
  74. for the names/descriptions of the tests on the directory level.
  75. path - path to the testsuite location
  76. name - a string in extracting components of the file path name
  77. create_all - flag
  78. """
  79. locals = dict()
  80. sys.path.append(path)
  81. clean_pyc_files(path)
  82. execfile(os.path.join(path, "test_info.py"), globals(), locals)
  83. basename = os.path.basename(path)
  84. tests = {}
  85. individual = []
  86. if 'tests' in locals:
  87. for k, v in locals["tests"].items():
  88. if k in locals:
  89. t = TestSet(k, v)
  90. t.suite = locals[k]()
  91. tests[k] = t
  92. individual.append(t)
  93. else:
  94. print 'Ignoring test "%s", as no corresponding test object was found.' % k
  95. if 'collections' in locals:
  96. for k, v in locals["collections"].items():
  97. subtests = v[1]
  98. desc = v[0]
  99. if all(exist(tests, subtests)):
  100. t = TestSet(k, desc)
  101. t.children = {}
  102. for i in subtests:
  103. t.children[i] = None
  104. tests[k] = t
  105. else:
  106. print 'Ignoring test "%s", as one or more subtests were missing.' % k
  107. if 'subdirs' in locals:
  108. for k, v in locals["subdirs"].items():
  109. subname = os.path.join(name, k)
  110. subtests = find_all_tests(os.path.join(path, k), subname, True)
  111. if 'all' in subtests:
  112. t = subtests['all']
  113. del subtests['all']
  114. t.key = subname
  115. t.desc = v
  116. t.children = subtests
  117. tests[subname] = t
  118. individual.append(t)
  119. if create_all and len(individual) != 0:
  120. t = TestSet("all", "All tests")
  121. tests["all"] = t
  122. return tests
  123. def print_tests(t, indent=' '):
  124. """
  125. Print out the dictionary of tests names/descriptions
  126. on the directory level (aggregated).
  127. Used when running with option '-list or -l'
  128. t - dictionary of test name: TestSet object pairs
  129. indent - string used for formatted output
  130. """
  131. for n, v in t.items():
  132. if v is None:
  133. print indent + "- (" + n + ")"
  134. else:
  135. print indent + "- " + n + " : " + v.desc
  136. if v.children is not None:
  137. print_tests(v.children, indent + ' ')
  138. def include_test(all_tests, rt, inc):
  139. """
  140. Added test names specified in 'inc'
  141. to the list of tests names collection 'rt'
  142. all_tests - dictionary of test name: TestSet object pairs
  143. rt - list of aggregate test names
  144. inc - name of the aggregate test directory or test collection
  145. """
  146. path = inc.split('/')
  147. if path[0] not in all_tests:
  148. return
  149. rt.append(inc)
  150. def append_expanded(t, rt, prefix):
  151. """
  152. Recursively adds names of the test collections held in the TestSet.children
  153. to the list of test collections names 'rt'
  154. it - TestSet object
  155. rt - list of tests names (they may be directory names)
  156. prefix - part of the path name used for extracting test
  157. collection names on the different level of granularity
  158. """
  159. new_prefix = os.path.join(prefix, t.key)
  160. if t.suite is not None:
  161. rt.append(new_prefix)
  162. if t.children is not None:
  163. for i, child in t.children.items():
  164. if child is None:
  165. rt.append(os.path.join(prefix, i))
  166. else:
  167. append_expanded(child, rt, new_prefix)
  168. def expand_test(all_tests, rt, exp, prefix=''):
  169. """
  170. Recursively expands the list 'rt' to include
  171. test collections on the levels below the names/paths
  172. as specified in 'rt'
  173. all_tests - dictionary of test name: TestSet object pairs
  174. rt - list of aggregate test names
  175. exp - name of the aggregate test
  176. prefix - string used to extract parts of the file path name
  177. """
  178. full = os.path.join(prefix, exp)
  179. try:
  180. while True:
  181. rt.remove(full)
  182. except:
  183. pass
  184. s = exp.split('/')
  185. if len(s) > 1 and s[0] in all_tests:
  186. t = all_tests[s[0]].children
  187. expand_test(t, rt, str.join('/', s[1:]), os.path.join(prefix, s[0]))
  188. elif exp in all_tests:
  189. append_expanded(all_tests[exp], rt, prefix)
  190. def exclude_test(all_tests, rt, exc):
  191. """
  192. Remove the aggregate test name specified in 'exc'
  193. from the list of aggregate test names 'rt'
  194. all_tests - dictionary of test name: TestSet object pairs
  195. rt - list of aggregate test names
  196. exc - list of aggregate test names to remove from 'rt'
  197. """
  198. path = exc.split('/')
  199. if path[0] not in all_tests:
  200. return
  201. names = [exc]
  202. expand_test(all_tests, names, exc)
  203. for i in names:
  204. try:
  205. while True:
  206. rt.remove(i)
  207. except:
  208. pass
  209. def generate_run_tests(all_tests, rt, inc, exc):
  210. """
  211. Create list of aggregate test names/test collections
  212. all_tests - dictionary of test name: TestSet object pairs
  213. rt - list of aggregate test names
  214. inc - list of aggregate test names to include into 'rt'
  215. exc - list of aggregate test names to remove from 'rt'
  216. """
  217. if inc is not None:
  218. for i in inc:
  219. include_test(all_tests, rt, i)
  220. ort = list(rt)
  221. for r in ort:
  222. expand_test(all_tests, rt, r)
  223. if exc is not None:
  224. for e in exc:
  225. exclude_test(all_tests, rt, e)
  226. def add_to_test_suite(suite, all_tests, r):
  227. """
  228. Recursively add to the test suite aggregate
  229. tests described in the path name 'r'
  230. suite - object of unittest.TestSuite()
  231. all_tests - dictionary of test name: TestSet object pairs
  232. r - name of the aggregate test to add to the suite
  233. """
  234. path = r.split(os.sep)
  235. while len(path) > 1:
  236. all_tests = all_tests[path[0]].children
  237. del path[0]
  238. suite.addTest(all_tests[path[0]].suite)
  239. def build_test_suite(all_tests, run_tests):
  240. """
  241. Create a TestSuite() suite
  242. all_tests - dictionary of test name: TestSet object pairs
  243. run_tests - list of aggregate test names
  244. """
  245. suite = unittest.TestSuite()
  246. for r in run_tests:
  247. add_to_test_suite(suite, all_tests, r)
  248. return suite
  249. op = OptionParser()
  250. op.add_option("-c", "--config", dest="config", default="./test.cfg", help="load configuration from CONFIG")
  251. op.add_option("-T", "--testpath", dest="testpath", default="../mdl/testsuite", help="run tests in MDL files located under TESTPATH")
  252. op.add_option("-l", "--list", dest="list", action="store_true", help="display a list of all tests found under the test directory")
  253. op.add_option("-i", "--include", dest="include", action="append", help="comma-separated list of tests to include (default: all tests)")
  254. op.add_option("-e", "--exclude", dest="exclude", action="append", help="comma-separated list of tests to exclude (default: none)")
  255. op.add_option("-v", "--verbose", dest="verbosity", action="append", help="increase the verbosity of the test suite")
  256. op.add_option("-r", "--results", dest="results", default="./test_results", help="run all MCell tests under the directory RESULTS (WARNING: directory will be wiped clean first!)")
  257. (options, args) = op.parse_args()
  258. all_tests = find_all_tests(options.testpath)
  259. # List all tests, if requested
  260. if options.list:
  261. print "Found tests:"
  262. print_tests(all_tests)
  263. sys.exit(0)
  264. # Load our configuration.
  265. test_conf = testutils.test_config(options.config)
  266. testutils.McellTest.config = test_conf
  267. # Check our configuration for which tests to run
  268. try:
  269. run_tests = [x.strip() for x in test_conf.get("main", "run_tests").split(',')]
  270. except:
  271. if options.include is None:
  272. run_tests = all_tests.keys()
  273. else:
  274. run_tests = []
  275. # Check validity of the mcell executable filepath
  276. # Currently we place path/to/mcell under DEFAULT section
  277. # in the configuration file. Since user may place path/to/mcell
  278. # under differently named sections we also check validity
  279. # of path/to/mcell inside class McellTest.
  280. mcell = test_conf.config.get(DEFAULTSECT, "mcellpath")
  281. try:
  282. os.stat(mcell)
  283. except:
  284. print "ERROR: path to mcell executable '%s' in configuration file is invalid" % test_conf.filepath
  285. sys.exit(0)
  286. # Parse include options
  287. include = None
  288. if options.include and len(options.include) != 0:
  289. include = []
  290. [include.extend(x) for x in [y.split(',') for y in options.include]]
  291. # Parse exclude options
  292. exclude = None
  293. if options.exclude and len(options.exclude) != 0:
  294. exclude = []
  295. [exclude.extend(x) for x in [y.split(',') for y in options.exclude]]
  296. # Generate the final list of tests to run
  297. generate_run_tests(all_tests, run_tests, include, exclude)
  298. run_tests.sort()
  299. run_tests = list(uniq(run_tests))
  300. # Check that run_tests is not empty...
  301. if len(run_tests) == 0:
  302. print "No tests to run."
  303. sys.exit(0)
  304. # Report on which tests are being run
  305. print "Running tests:"
  306. for i in run_tests:
  307. print " - " + i
  308. # Assign verbosity level (0, 1, or 2)
  309. if options.verbosity:
  310. verbosity = int(options.verbosity[0])
  311. else:
  312. verbosity = 0
  313. # Build a top-level suite containing all relevant tests
  314. suite = build_test_suite(all_tests, run_tests)
  315. testutils.cleandir(options.results)
  316. unittest.TextTestRunner(verbosity=verbosity).run(suite)