1 #!/usr/bin/env python 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 def cost(a, b): 37 return sum(map(dist, a + [None] * (len(b) - len(a)), b)) 38 39 # Normalize so a is shortest. 40 if len(b) < len(a): 41 b, a = insertMinimumPadding(b, a, dist) 42 return a,b 43 44 # For each None we have to insert... 45 for i in range(len(b) - len(a)): 46 # For each position we could insert it... 47 current = cost(a, b) 48 best = None 49 for j in range(len(a) + 1): 50 a_0 = a[:j] + [None] + a[j:] 51 candidate = cost(a_0, b) 52 if best is None or candidate < best[0]: 53 best = (candidate, a_0, j) 54 a = best[1] 55 return a,b 56 57 class ZipperDiff(object): 58 """ZipperDiff - Simple (slow) diff only accommodating inserts.""" 59 60 def __init__(self, a, b): 61 self.a = a 62 self.b = b 63 64 def dist(self, a, b): 65 return a != b 66 67 def getDiffs(self): 68 a,b = insertMinimumPadding(self.a, self.b, self.dist) 69 for aElt,bElt in zip(a,b): 70 if self.dist(aElt, bElt): 71 yield aElt,bElt 72 73 class DriverZipperDiff(ZipperDiff): 74 def isTempFile(self, filename): 75 if filename[0] != '"' or filename[-1] != '"': 76 return False 77 return (filename.startswith('/tmp/', 1) or 78 filename.startswith('/var/', 1)) 79 80 def dist(self, a, b): 81 if a and b and self.isTempFile(a) and self.isTempFile(b): 82 return 0 83 return super(DriverZipperDiff, self).dist(a,b) 84 85 class CompileInfo: 86 def __init__(self, out, err, res): 87 self.commands = [] 88 89 # Standard out isn't used for much. 90 self.stdout = out 91 self.stderr = '' 92 93 # FIXME: Compare error messages as well. 94 for ln in err.split('\n'): 95 if (ln == 'Using built-in specs.' or 96 ln.startswith('Target: ') or 97 ln.startswith('Configured with: ') or 98 ln.startswith('Thread model: ') or 99 ln.startswith('gcc version') or 100 ln.startswith('clang version')): 101 pass 102 elif ln.strip().startswith('"'): 103 self.commands.append(list(splitArgs(ln))) 104 else: 105 self.stderr += ln + '\n' 106 107 self.stderr = self.stderr.strip() 108 self.exitCode = res 109 110 def captureDriverInfo(cmd, args): 111 p = subprocess.Popen([cmd,'-###'] + args, 112 stdin=None, 113 stdout=subprocess.PIPE, 114 stderr=subprocess.PIPE) 115 out,err = p.communicate() 116 res = p.wait() 117 return CompileInfo(out,err,res) 118 119 def main(): 120 import os, sys 121 122 args = sys.argv[1:] 123 driverA = os.getenv('DRIVER_A') or 'gcc' 124 driverB = os.getenv('DRIVER_B') or 'clang' 125 126 infoA = captureDriverInfo(driverA, args) 127 infoB = captureDriverInfo(driverB, args) 128 129 differ = False 130 131 # Compare stdout. 132 if infoA.stdout != infoB.stdout: 133 print '-- STDOUT DIFFERS -' 134 print 'A OUTPUT: ',infoA.stdout 135 print 'B OUTPUT: ',infoB.stdout 136 print 137 138 diff = ZipperDiff(infoA.stdout.split('\n'), 139 infoB.stdout.split('\n')) 140 for i,(aElt,bElt) in enumerate(diff.getDiffs()): 141 if aElt is None: 142 print 'A missing: %s' % bElt 143 elif bElt is None: 144 print 'B missing: %s' % aElt 145 else: 146 print 'mismatch: A: %s' % aElt 147 print ' B: %s' % bElt 148 149 differ = True 150 151 # Compare stderr. 152 if infoA.stderr != infoB.stderr: 153 print '-- STDERR DIFFERS -' 154 print 'A STDERR: ',infoA.stderr 155 print 'B STDERR: ',infoB.stderr 156 print 157 158 diff = ZipperDiff(infoA.stderr.split('\n'), 159 infoB.stderr.split('\n')) 160 for i,(aElt,bElt) in enumerate(diff.getDiffs()): 161 if aElt is None: 162 print 'A missing: %s' % bElt 163 elif bElt is None: 164 print 'B missing: %s' % aElt 165 else: 166 print 'mismatch: A: %s' % aElt 167 print ' B: %s' % bElt 168 169 differ = True 170 171 # Compare commands. 172 for i,(a,b) in enumerate(map(None, infoA.commands, infoB.commands)): 173 if a is None: 174 print 'A MISSING:',' '.join(b) 175 differ = True 176 continue 177 elif b is None: 178 print 'B MISSING:',' '.join(a) 179 differ = True 180 continue 181 182 diff = DriverZipperDiff(a,b) 183 diffs = list(diff.getDiffs()) 184 if diffs: 185 print '-- COMMAND %d DIFFERS -' % i 186 print 'A COMMAND:',' '.join(a) 187 print 'B COMMAND:',' '.join(b) 188 print 189 for i,(aElt,bElt) in enumerate(diffs): 190 if aElt is None: 191 print 'A missing: %s' % bElt 192 elif bElt is None: 193 print 'B missing: %s' % aElt 194 else: 195 print 'mismatch: A: %s' % aElt 196 print ' B: %s' % bElt 197 differ = True 198 199 # Compare result codes. 200 if infoA.exitCode != infoB.exitCode: 201 print '-- EXIT CODES DIFFER -' 202 print 'A: ',infoA.exitCode 203 print 'B: ',infoB.exitCode 204 differ = True 205 206 if differ: 207 sys.exit(1) 208 209 if __name__ == '__main__': 210 main() 211