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