runtests.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. #!/usr/bin/env python
  2. """
  3. Run the test(s) listed on the command line. If a directory is listed, the script will recursively
  4. walk the directory for files named .mk and run each.
  5. For each test, we run gmake -f test.mk. By default, make must exit with an exit code of 0, and must print 'TEST-PASS'.
  6. Each test is run in an empty directory.
  7. The test file may contain lines at the beginning to alter the default behavior. These are all evaluated as python:
  8. #T commandline: ['extra', 'params', 'here']
  9. #T returncode: 2
  10. #T returncode-on: {'win32': 2}
  11. #T environment: {'VAR': 'VALUE}
  12. #T grep-for: "text"
  13. """
  14. from subprocess import Popen, PIPE, STDOUT
  15. from optparse import OptionParser
  16. import os, re, sys, shutil, glob
  17. class ParentDict(dict):
  18. def __init__(self, parent, **kwargs):
  19. self.d = dict(kwargs)
  20. self.parent = parent
  21. def __setitem__(self, k, v):
  22. self.d[k] = v
  23. def __getitem__(self, k):
  24. if k in self.d:
  25. return self.d[k]
  26. return self.parent[k]
  27. thisdir = os.path.dirname(os.path.abspath(__file__))
  28. pymake = [sys.executable, os.path.join(os.path.dirname(thisdir), 'make.py')]
  29. manifest = os.path.join(thisdir, 'tests.manifest')
  30. o = OptionParser()
  31. o.add_option('-g', '--gmake',
  32. dest="gmake", default="gmake")
  33. o.add_option('-d', '--tempdir',
  34. dest="tempdir", default="_mktests")
  35. opts, args = o.parse_args()
  36. if len(args) == 0:
  37. args = [thisdir]
  38. makefiles = []
  39. for a in args:
  40. if os.path.isfile(a):
  41. makefiles.append(a)
  42. elif os.path.isdir(a):
  43. makefiles.extend(sorted(glob.glob(os.path.join(a, '*.mk'))))
  44. def runTest(makefile, make, logfile, options):
  45. """
  46. Given a makefile path, test it with a given `make` and return
  47. (pass, message).
  48. """
  49. if os.path.exists(opts.tempdir): shutil.rmtree(opts.tempdir)
  50. os.mkdir(opts.tempdir, 0755)
  51. logfd = open(logfile, 'w')
  52. p = Popen(make + options['commandline'], stdout=logfd, stderr=STDOUT, env=options['env'])
  53. logfd.close()
  54. retcode = p.wait()
  55. if retcode != options['returncode']:
  56. return False, "FAIL (returncode=%i)" % retcode
  57. logfd = open(logfile)
  58. stdout = logfd.read()
  59. logfd.close()
  60. if stdout.find('TEST-FAIL') != -1:
  61. print stdout
  62. return False, "FAIL (TEST-FAIL printed)"
  63. if options['grepfor'] and stdout.find(options['grepfor']) == -1:
  64. print stdout
  65. return False, "FAIL (%s not in output)" % options['grepfor']
  66. if options['returncode'] == 0 and stdout.find('TEST-PASS') == -1:
  67. print stdout
  68. return False, 'FAIL (No TEST-PASS printed)'
  69. if options['returncode'] != 0:
  70. return True, 'PASS (retcode=%s)' % retcode
  71. return True, 'PASS'
  72. print "%-30s%-28s%-28s" % ("Test:", "gmake:", "pymake:")
  73. gmakefails = 0
  74. pymakefails = 0
  75. tre = re.compile('^#T (gmake |pymake )?([a-z-]+)(?:: (.*))?$')
  76. for makefile in makefiles:
  77. # For some reason, MAKEFILE_LIST uses native paths in GNU make on Windows
  78. # (even in MSYS!) so we pass both TESTPATH and NATIVE_TESTPATH
  79. cline = ['-C', opts.tempdir, '-f', os.path.abspath(makefile), 'TESTPATH=%s' % thisdir.replace('\\','/'), 'NATIVE_TESTPATH=%s' % thisdir]
  80. if sys.platform == 'win32':
  81. #XXX: hack so we can specialize the separator character on windows.
  82. # we really shouldn't need this, but y'know
  83. cline += ['__WIN32__=1']
  84. options = {
  85. 'returncode': 0,
  86. 'grepfor': None,
  87. 'env': dict(os.environ),
  88. 'commandline': cline,
  89. 'pass': True,
  90. 'skip': False,
  91. }
  92. gmakeoptions = ParentDict(options)
  93. pymakeoptions = ParentDict(options)
  94. dmap = {None: options, 'gmake ': gmakeoptions, 'pymake ': pymakeoptions}
  95. mdata = open(makefile)
  96. for line in mdata:
  97. line = line.strip()
  98. m = tre.search(line)
  99. if m is None:
  100. break
  101. make, key, data = m.group(1, 2, 3)
  102. d = dmap[make]
  103. if data is not None:
  104. data = eval(data)
  105. if key == 'commandline':
  106. assert make is None
  107. d['commandline'].extend(data)
  108. elif key == 'returncode':
  109. d['returncode'] = data
  110. elif key == 'returncode-on':
  111. if sys.platform in data:
  112. d['returncode'] = data[sys.platform]
  113. elif key == 'environment':
  114. for k, v in data.iteritems():
  115. d['env'][k] = v
  116. elif key == 'grep-for':
  117. d['grepfor'] = data
  118. elif key == 'fail':
  119. d['pass'] = False
  120. elif key == 'skip':
  121. d['skip'] = True
  122. else:
  123. print >>sys.stderr, "%s: Unexpected #T key: %s" % (makefile, key)
  124. sys.exit(1)
  125. mdata.close()
  126. if gmakeoptions['skip']:
  127. gmakepass, gmakemsg = True, ''
  128. else:
  129. gmakepass, gmakemsg = runTest(makefile, [opts.gmake],
  130. makefile + '.gmakelog', gmakeoptions)
  131. if gmakeoptions['pass']:
  132. if not gmakepass:
  133. gmakefails += 1
  134. else:
  135. if gmakepass:
  136. gmakefails += 1
  137. gmakemsg = "UNEXPECTED PASS"
  138. else:
  139. gmakemsg = "KNOWN FAIL"
  140. if pymakeoptions['skip']:
  141. pymakepass, pymakemsg = True, ''
  142. else:
  143. pymakepass, pymakemsg = runTest(makefile, pymake,
  144. makefile + '.pymakelog', pymakeoptions)
  145. if pymakeoptions['pass']:
  146. if not pymakepass:
  147. pymakefails += 1
  148. else:
  149. if pymakepass:
  150. pymakefails += 1
  151. pymakemsg = "UNEXPECTED PASS"
  152. else:
  153. pymakemsg = "OK (known fail)"
  154. print "%-30.30s%-28.28s%-28.28s" % (os.path.basename(makefile),
  155. gmakemsg, pymakemsg)
  156. print
  157. print "Summary:"
  158. print "%-30s%-28s%-28s" % ("", "gmake:", "pymake:")
  159. if gmakefails == 0:
  160. gmakemsg = 'PASS'
  161. else:
  162. gmakemsg = 'FAIL (%i failures)' % gmakefails
  163. if pymakefails == 0:
  164. pymakemsg = 'PASS'
  165. else:
  166. pymakemsg = 'FAIL (%i failures)' % pymakefails
  167. print "%-30.30s%-28.28s%-28.28s" % ('', gmakemsg, pymakemsg)
  168. shutil.rmtree(opts.tempdir)
  169. if gmakefails or pymakefails:
  170. sys.exit(1)