#!/usr/bin/env python """ Run the test(s) listed on the command line. If a directory is listed, the script will recursively walk the directory for files named .mk and run each. 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'. Each test is run in an empty directory. The test file may contain lines at the beginning to alter the default behavior. These are all evaluated as python: #T commandline: ['extra', 'params', 'here'] #T returncode: 2 #T returncode-on: {'win32': 2} #T environment: {'VAR': 'VALUE} #T grep-for: "text" """ from subprocess import Popen, PIPE, STDOUT from optparse import OptionParser import os, re, sys, shutil, glob class ParentDict(dict): def __init__(self, parent, **kwargs): self.d = dict(kwargs) self.parent = parent def __setitem__(self, k, v): self.d[k] = v def __getitem__(self, k): if k in self.d: return self.d[k] return self.parent[k] thisdir = os.path.dirname(os.path.abspath(__file__)) pymake = [sys.executable, os.path.join(os.path.dirname(thisdir), 'make.py')] manifest = os.path.join(thisdir, 'tests.manifest') o = OptionParser() o.add_option('-g', '--gmake', dest="gmake", default="gmake") o.add_option('-d', '--tempdir', dest="tempdir", default="_mktests") opts, args = o.parse_args() if len(args) == 0: args = [thisdir] makefiles = [] for a in args: if os.path.isfile(a): makefiles.append(a) elif os.path.isdir(a): makefiles.extend(sorted(glob.glob(os.path.join(a, '*.mk')))) def runTest(makefile, make, logfile, options): """ Given a makefile path, test it with a given `make` and return (pass, message). """ if os.path.exists(opts.tempdir): shutil.rmtree(opts.tempdir) os.mkdir(opts.tempdir, 0755) logfd = open(logfile, 'w') p = Popen(make + options['commandline'], stdout=logfd, stderr=STDOUT, env=options['env']) logfd.close() retcode = p.wait() if retcode != options['returncode']: return False, "FAIL (returncode=%i)" % retcode logfd = open(logfile) stdout = logfd.read() logfd.close() if stdout.find('TEST-FAIL') != -1: print stdout return False, "FAIL (TEST-FAIL printed)" if options['grepfor'] and stdout.find(options['grepfor']) == -1: print stdout return False, "FAIL (%s not in output)" % options['grepfor'] if options['returncode'] == 0 and stdout.find('TEST-PASS') == -1: print stdout return False, 'FAIL (No TEST-PASS printed)' if options['returncode'] != 0: return True, 'PASS (retcode=%s)' % retcode return True, 'PASS' print "%-30s%-28s%-28s" % ("Test:", "gmake:", "pymake:") gmakefails = 0 pymakefails = 0 tre = re.compile('^#T (gmake |pymake )?([a-z-]+)(?:: (.*))?$') for makefile in makefiles: # For some reason, MAKEFILE_LIST uses native paths in GNU make on Windows # (even in MSYS!) so we pass both TESTPATH and NATIVE_TESTPATH cline = ['-C', opts.tempdir, '-f', os.path.abspath(makefile), 'TESTPATH=%s' % thisdir.replace('\\','/'), 'NATIVE_TESTPATH=%s' % thisdir] if sys.platform == 'win32': #XXX: hack so we can specialize the separator character on windows. # we really shouldn't need this, but y'know cline += ['__WIN32__=1'] options = { 'returncode': 0, 'grepfor': None, 'env': dict(os.environ), 'commandline': cline, 'pass': True, 'skip': False, } gmakeoptions = ParentDict(options) pymakeoptions = ParentDict(options) dmap = {None: options, 'gmake ': gmakeoptions, 'pymake ': pymakeoptions} mdata = open(makefile) for line in mdata: line = line.strip() m = tre.search(line) if m is None: break make, key, data = m.group(1, 2, 3) d = dmap[make] if data is not None: data = eval(data) if key == 'commandline': assert make is None d['commandline'].extend(data) elif key == 'returncode': d['returncode'] = data elif key == 'returncode-on': if sys.platform in data: d['returncode'] = data[sys.platform] elif key == 'environment': for k, v in data.iteritems(): d['env'][k] = v elif key == 'grep-for': d['grepfor'] = data elif key == 'fail': d['pass'] = False elif key == 'skip': d['skip'] = True else: print >>sys.stderr, "%s: Unexpected #T key: %s" % (makefile, key) sys.exit(1) mdata.close() if gmakeoptions['skip']: gmakepass, gmakemsg = True, '' else: gmakepass, gmakemsg = runTest(makefile, [opts.gmake], makefile + '.gmakelog', gmakeoptions) if gmakeoptions['pass']: if not gmakepass: gmakefails += 1 else: if gmakepass: gmakefails += 1 gmakemsg = "UNEXPECTED PASS" else: gmakemsg = "KNOWN FAIL" if pymakeoptions['skip']: pymakepass, pymakemsg = True, '' else: pymakepass, pymakemsg = runTest(makefile, pymake, makefile + '.pymakelog', pymakeoptions) if pymakeoptions['pass']: if not pymakepass: pymakefails += 1 else: if pymakepass: pymakefails += 1 pymakemsg = "UNEXPECTED PASS" else: pymakemsg = "OK (known fail)" print "%-30.30s%-28.28s%-28.28s" % (os.path.basename(makefile), gmakemsg, pymakemsg) print print "Summary:" print "%-30s%-28s%-28s" % ("", "gmake:", "pymake:") if gmakefails == 0: gmakemsg = 'PASS' else: gmakemsg = 'FAIL (%i failures)' % gmakefails if pymakefails == 0: pymakemsg = 'PASS' else: pymakemsg = 'FAIL (%i failures)' % pymakefails print "%-30.30s%-28.28s%-28.28s" % ('', gmakemsg, pymakemsg) shutil.rmtree(opts.tempdir) if gmakefails or pymakefails: sys.exit(1)