| Eli Friedman | 77a1fe9 | 2009-07-10 20:15:12 +0000 | [diff] [blame^] | 1 | #!/usr/bin/env python | 
| Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 2 |  | 
 | 3 | import subprocess | 
 | 4 |  | 
 | 5 | def splitArgs(s): | 
 | 6 |     it = iter(s) | 
 | 7 |     current = '' | 
 | 8 |     inQuote = False | 
 | 9 |     for c in it: | 
 | 10 |         if c == '"': | 
 | 11 |             if inQuote: | 
 | 12 |                 inQuote = False | 
 | 13 |                 yield current + '"' | 
 | 14 |             else: | 
 | 15 |                 inQuote = True | 
 | 16 |                 current = '"' | 
 | 17 |         elif inQuote: | 
 | 18 |             if c == '\\': | 
 | 19 |                 current += c | 
 | 20 |                 current += it.next() | 
 | 21 |             else: | 
 | 22 |                 current += c | 
 | 23 |         elif not c.isspace(): | 
 | 24 |             yield c | 
 | 25 |  | 
 | 26 | def insertMinimumPadding(a, b, dist): | 
 | 27 |     """insertMinimumPadding(a,b) -> (a',b') | 
 | 28 |  | 
 | 29 |     Return two lists of equal length, where some number of Nones have | 
 | 30 |     been inserted into the shorter list such that sum(map(dist, a', | 
 | 31 |     b')) is minimized. | 
 | 32 |  | 
 | 33 |     Assumes dist(X, Y) -> int and non-negative. | 
 | 34 |     """ | 
 | 35 |      | 
 | 36 |     # Yay for simplicity over complexity. | 
 | 37 |  | 
 | 38 |     def extend(aElt, bElt, solution): | 
 | 39 |         d0,(a0,b0) = solution | 
 | 40 |         return d0 + dist(aElt,bElt), (([aElt]+a0),([bElt]+b0)) | 
 | 41 |  | 
 | 42 |     def f(a, b): | 
 | 43 |         if len(a) == len(b): | 
 | 44 |             return (sum(map(dist, a, b)), (a, b)) | 
 | 45 |          | 
 | 46 |         if not a or not b: | 
 | 47 |             if not a: | 
 | 48 |                 a += [None] * len(b) | 
 | 49 |             else: | 
 | 50 |                 b += [None] * len(a) | 
 | 51 |             return (sum(map(dist, a, b)), (a, b)) | 
 | 52 |  | 
 | 53 |         if int(dist(a[0], b[0])) == 0: | 
 | 54 |             # Non-negative condition implies maximum is satisfied | 
 | 55 |             # taking this. | 
 | 56 |             return extend(a[0], b[0], f(a[1:], b[1:])) | 
 | 57 |  | 
 | 58 |         if len(a) < len(b): | 
 | 59 |             return min(f([None] + a, b), | 
 | 60 |                        extend(a[0], b[0], f(a[1:], b[1:]))) | 
 | 61 |         else: | 
 | 62 |             return min(f(a, [None] + b), | 
 | 63 |                        extend(a[0], b[0], f(a[1:], b[1:]))) | 
 | 64 |              | 
 | 65 |     return f(a, b)[1] | 
 | 66 |  | 
 | 67 | class ZipperDiff(object): | 
 | 68 |     """ZipperDiff - Simple (slow) diff only accomodating inserts.""" | 
 | 69 |      | 
 | 70 |     def __init__(self, a, b): | 
 | 71 |         self.a = a | 
 | 72 |         self.b = b | 
 | 73 |  | 
 | 74 |     def dist(self, a, b): | 
 | 75 |         return a != b | 
 | 76 |  | 
 | 77 |     def getDiffs(self): | 
 | 78 |         a,b =  insertMinimumPadding(self.a, self.b, self.dist) | 
 | 79 |         for aElt,bElt in zip(a,b): | 
 | 80 |             if self.dist(aElt, bElt): | 
 | 81 |                 yield aElt,bElt | 
 | 82 |  | 
 | 83 | class DriverZipperDiff(ZipperDiff): | 
 | 84 |     def isTempFile(self, filename): | 
 | 85 |         if filename[0] != '"' or filename[-1] != '"': | 
 | 86 |             return False | 
 | 87 |         return (filename.startswith('/tmp/', 1) or | 
 | 88 |                 filename.startswith('/var/', 1)) | 
 | 89 |  | 
 | 90 |     def dist(self, a, b): | 
 | 91 |         if a and b and self.isTempFile(a) and self.isTempFile(b): | 
 | 92 |             return 0 | 
 | 93 |         return super(DriverZipperDiff, self).dist(a,b)         | 
 | 94 |  | 
 | 95 | class CompileInfo: | 
 | 96 |     def __init__(self, out, err, res): | 
 | 97 |         self.commands = [] | 
 | 98 |          | 
 | 99 |         # Standard out isn't used for much. | 
 | 100 |         self.stdout = out | 
 | 101 |         self.stderr = '' | 
 | 102 |  | 
 | 103 |         # FIXME: Compare error messages as well. | 
 | 104 |         for ln in err.split('\n'): | 
 | 105 |             if (ln == 'Using built-in specs.' or | 
 | 106 |                 ln.startswith('Target: ') or | 
 | 107 |                 ln.startswith('Configured with: ') or | 
 | 108 |                 ln.startswith('Thread model: ') or | 
| Daniel Dunbar | 7671591 | 2009-01-28 19:26:20 +0000 | [diff] [blame] | 109 |                 ln.startswith('gcc version') or | 
 | 110 |                 ln.startswith('ccc version')): | 
| Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 111 |                 pass | 
 | 112 |             elif ln.strip().startswith('"'): | 
 | 113 |                 self.commands.append(list(splitArgs(ln))) | 
 | 114 |             else: | 
 | 115 |                 self.stderr += ln + '\n' | 
 | 116 |          | 
 | 117 |         self.stderr = self.stderr.strip() | 
 | 118 |         self.exitCode = res | 
 | 119 |  | 
 | 120 | def captureDriverInfo(cmd, args): | 
 | 121 |     p = subprocess.Popen([cmd,'-###'] + args, | 
 | 122 |                          stdin=None, | 
 | 123 |                          stdout=subprocess.PIPE, | 
 | 124 |                          stderr=subprocess.PIPE) | 
 | 125 |     out,err = p.communicate() | 
 | 126 |     res = p.wait() | 
 | 127 |     return CompileInfo(out,err,res) | 
 | 128 |  | 
 | 129 | def main(): | 
| Daniel Dunbar | 2253dc4 | 2009-01-14 20:06:04 +0000 | [diff] [blame] | 130 |     import os, sys | 
| Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 131 |  | 
 | 132 |     args = sys.argv[1:] | 
| Daniel Dunbar | 2253dc4 | 2009-01-14 20:06:04 +0000 | [diff] [blame] | 133 |     driverA = os.getenv('DRIVER_A') or 'gcc' | 
 | 134 |     driverB = os.getenv('DRIVER_B') or 'xcc' | 
| Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 135 |  | 
 | 136 |     infoA = captureDriverInfo(driverA, args) | 
 | 137 |     infoB = captureDriverInfo(driverB, args) | 
 | 138 |  | 
| Daniel Dunbar | c97c05f | 2009-01-17 00:50:45 +0000 | [diff] [blame] | 139 |     differ = False | 
 | 140 |  | 
| Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 141 |     # Compare stdout. | 
 | 142 |     if infoA.stdout != infoB.stdout: | 
 | 143 |         print '-- STDOUT DIFFERS -' | 
 | 144 |         print 'A: ',infoA.stdout | 
 | 145 |         print 'B: ',infoB.stdout | 
| Daniel Dunbar | c97c05f | 2009-01-17 00:50:45 +0000 | [diff] [blame] | 146 |         differ = True | 
| Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 147 |  | 
 | 148 |     # Compare stderr. | 
 | 149 |     if infoA.stderr != infoB.stderr: | 
 | 150 |         print '-- STDERR DIFFERS -' | 
 | 151 |         print 'A: ',infoA.stderr | 
 | 152 |         print 'B: ',infoB.stderr | 
| Daniel Dunbar | c97c05f | 2009-01-17 00:50:45 +0000 | [diff] [blame] | 153 |         differ = True | 
| Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 154 |  | 
 | 155 |     # Compare commands. | 
| Daniel Dunbar | 163f31a | 2009-01-21 23:34:23 +0000 | [diff] [blame] | 156 |     for i,(a,b) in enumerate(map(None, infoA.commands, infoB.commands)): | 
 | 157 |         if a is None: | 
 | 158 |             print 'A MISSING:',' '.join(b) | 
 | 159 |             differ = True | 
 | 160 |             continue | 
 | 161 |         elif b is None: | 
 | 162 |             print 'B MISSING:',' '.join(a) | 
 | 163 |             differ = True | 
 | 164 |             continue | 
 | 165 |  | 
| Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 166 |         diff = DriverZipperDiff(a,b) | 
 | 167 |         diffs = list(diff.getDiffs()) | 
 | 168 |         if diffs: | 
 | 169 |             print '-- COMMAND %d DIFFERS -' % i | 
 | 170 |             print 'A COMMAND:',' '.join(a) | 
 | 171 |             print 'B COMMAND:',' '.join(b) | 
 | 172 |             print | 
 | 173 |             for i,(aElt,bElt) in enumerate(diffs): | 
 | 174 |                 if aElt is None: | 
 | 175 |                     print 'A missing: %s' % bElt | 
 | 176 |                 elif bElt is None: | 
 | 177 |                     print 'B missing: %s' % aElt | 
 | 178 |                 else: | 
 | 179 |                     print 'mismatch: A: %s' % aElt | 
 | 180 |                     print '          B: %s' % bElt | 
| Daniel Dunbar | 06172d6 | 2009-01-20 00:47:24 +0000 | [diff] [blame] | 181 |             differ = True | 
| Daniel Dunbar | 163f31a | 2009-01-21 23:34:23 +0000 | [diff] [blame] | 182 |      | 
| Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 183 |     # Compare result codes. | 
 | 184 |     if infoA.exitCode != infoB.exitCode: | 
 | 185 |         print '-- EXIT CODES DIFFER -' | 
 | 186 |         print 'A: ',infoA.exitCode | 
 | 187 |         print 'B: ',infoB.exitCode | 
| Daniel Dunbar | c97c05f | 2009-01-17 00:50:45 +0000 | [diff] [blame] | 188 |         differ = True | 
 | 189 |  | 
 | 190 |     if differ: | 
 | 191 |         sys.exit(1) | 
| Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 192 |  | 
 | 193 | if __name__ == '__main__': | 
 | 194 |    main() |