1837f574eSEli Friedman#!/usr/bin/env python 2c8385688SDaniel Dunbar 3*77364929SAlp Toker""" 4*77364929SAlp TokerA simple utility that compares tool invocations and exit codes issued by 5*77364929SAlp Tokercompiler drivers that support -### (e.g. gcc and clang). 6*77364929SAlp Toker""" 7*77364929SAlp Toker 8c8385688SDaniel Dunbarimport subprocess 9c8385688SDaniel Dunbar 10c8385688SDaniel Dunbardef splitArgs(s): 11c8385688SDaniel Dunbar it = iter(s) 12c8385688SDaniel Dunbar current = '' 13c8385688SDaniel Dunbar inQuote = False 14c8385688SDaniel Dunbar for c in it: 15c8385688SDaniel Dunbar if c == '"': 16c8385688SDaniel Dunbar if inQuote: 17c8385688SDaniel Dunbar inQuote = False 18c8385688SDaniel Dunbar yield current + '"' 19c8385688SDaniel Dunbar else: 20c8385688SDaniel Dunbar inQuote = True 21c8385688SDaniel Dunbar current = '"' 22c8385688SDaniel Dunbar elif inQuote: 23c8385688SDaniel Dunbar if c == '\\': 24c8385688SDaniel Dunbar current += c 25c8385688SDaniel Dunbar current += it.next() 26c8385688SDaniel Dunbar else: 27c8385688SDaniel Dunbar current += c 28c8385688SDaniel Dunbar elif not c.isspace(): 29c8385688SDaniel Dunbar yield c 30c8385688SDaniel Dunbar 31c8385688SDaniel Dunbardef insertMinimumPadding(a, b, dist): 32c8385688SDaniel Dunbar """insertMinimumPadding(a,b) -> (a',b') 33c8385688SDaniel Dunbar 34c8385688SDaniel Dunbar Return two lists of equal length, where some number of Nones have 35c8385688SDaniel Dunbar been inserted into the shorter list such that sum(map(dist, a', 36c8385688SDaniel Dunbar b')) is minimized. 37c8385688SDaniel Dunbar 38c8385688SDaniel Dunbar Assumes dist(X, Y) -> int and non-negative. 39c8385688SDaniel Dunbar """ 40c8385688SDaniel Dunbar 41156fa7ddSDaniel Dunbar def cost(a, b): 42156fa7ddSDaniel Dunbar return sum(map(dist, a + [None] * (len(b) - len(a)), b)) 43c8385688SDaniel Dunbar 44156fa7ddSDaniel Dunbar # Normalize so a is shortest. 45156fa7ddSDaniel Dunbar if len(b) < len(a): 46156fa7ddSDaniel Dunbar b, a = insertMinimumPadding(b, a, dist) 47156fa7ddSDaniel Dunbar return a,b 48c8385688SDaniel Dunbar 49156fa7ddSDaniel Dunbar # For each None we have to insert... 50156fa7ddSDaniel Dunbar for i in range(len(b) - len(a)): 51156fa7ddSDaniel Dunbar # For each position we could insert it... 52156fa7ddSDaniel Dunbar current = cost(a, b) 53156fa7ddSDaniel Dunbar best = None 54156fa7ddSDaniel Dunbar for j in range(len(a) + 1): 55156fa7ddSDaniel Dunbar a_0 = a[:j] + [None] + a[j:] 56156fa7ddSDaniel Dunbar candidate = cost(a_0, b) 57156fa7ddSDaniel Dunbar if best is None or candidate < best[0]: 58156fa7ddSDaniel Dunbar best = (candidate, a_0, j) 59156fa7ddSDaniel Dunbar a = best[1] 60156fa7ddSDaniel Dunbar return a,b 61c8385688SDaniel Dunbar 62c8385688SDaniel Dunbarclass ZipperDiff(object): 6357540c5bSChris Lattner """ZipperDiff - Simple (slow) diff only accommodating inserts.""" 64c8385688SDaniel Dunbar 65c8385688SDaniel Dunbar def __init__(self, a, b): 66c8385688SDaniel Dunbar self.a = a 67c8385688SDaniel Dunbar self.b = b 68c8385688SDaniel Dunbar 69c8385688SDaniel Dunbar def dist(self, a, b): 70c8385688SDaniel Dunbar return a != b 71c8385688SDaniel Dunbar 72c8385688SDaniel Dunbar def getDiffs(self): 73c8385688SDaniel Dunbar a,b = insertMinimumPadding(self.a, self.b, self.dist) 74c8385688SDaniel Dunbar for aElt,bElt in zip(a,b): 75c8385688SDaniel Dunbar if self.dist(aElt, bElt): 76c8385688SDaniel Dunbar yield aElt,bElt 77c8385688SDaniel Dunbar 78c8385688SDaniel Dunbarclass DriverZipperDiff(ZipperDiff): 79c8385688SDaniel Dunbar def isTempFile(self, filename): 80c8385688SDaniel Dunbar if filename[0] != '"' or filename[-1] != '"': 81c8385688SDaniel Dunbar return False 82c8385688SDaniel Dunbar return (filename.startswith('/tmp/', 1) or 83c8385688SDaniel Dunbar filename.startswith('/var/', 1)) 84c8385688SDaniel Dunbar 85c8385688SDaniel Dunbar def dist(self, a, b): 86c8385688SDaniel Dunbar if a and b and self.isTempFile(a) and self.isTempFile(b): 87c8385688SDaniel Dunbar return 0 88c8385688SDaniel Dunbar return super(DriverZipperDiff, self).dist(a,b) 89c8385688SDaniel Dunbar 90c8385688SDaniel Dunbarclass CompileInfo: 91c8385688SDaniel Dunbar def __init__(self, out, err, res): 92c8385688SDaniel Dunbar self.commands = [] 93c8385688SDaniel Dunbar 94c8385688SDaniel Dunbar # Standard out isn't used for much. 95c8385688SDaniel Dunbar self.stdout = out 96c8385688SDaniel Dunbar self.stderr = '' 97c8385688SDaniel Dunbar 98c8385688SDaniel Dunbar # FIXME: Compare error messages as well. 99c8385688SDaniel Dunbar for ln in err.split('\n'): 100c8385688SDaniel Dunbar if (ln == 'Using built-in specs.' or 101c8385688SDaniel Dunbar ln.startswith('Target: ') or 102c8385688SDaniel Dunbar ln.startswith('Configured with: ') or 103c8385688SDaniel Dunbar ln.startswith('Thread model: ') or 10436ccb30aSDaniel Dunbar ln.startswith('gcc version') or 10556b534d2SDaniel Dunbar ln.startswith('clang version')): 106c8385688SDaniel Dunbar pass 107c8385688SDaniel Dunbar elif ln.strip().startswith('"'): 108c8385688SDaniel Dunbar self.commands.append(list(splitArgs(ln))) 109c8385688SDaniel Dunbar else: 110c8385688SDaniel Dunbar self.stderr += ln + '\n' 111c8385688SDaniel Dunbar 112c8385688SDaniel Dunbar self.stderr = self.stderr.strip() 113c8385688SDaniel Dunbar self.exitCode = res 114c8385688SDaniel Dunbar 115c8385688SDaniel Dunbardef captureDriverInfo(cmd, args): 116c8385688SDaniel Dunbar p = subprocess.Popen([cmd,'-###'] + args, 117c8385688SDaniel Dunbar stdin=None, 118c8385688SDaniel Dunbar stdout=subprocess.PIPE, 119c8385688SDaniel Dunbar stderr=subprocess.PIPE) 120c8385688SDaniel Dunbar out,err = p.communicate() 121c8385688SDaniel Dunbar res = p.wait() 122c8385688SDaniel Dunbar return CompileInfo(out,err,res) 123c8385688SDaniel Dunbar 124c8385688SDaniel Dunbardef main(): 12535926e10SDaniel Dunbar import os, sys 126c8385688SDaniel Dunbar 127c8385688SDaniel Dunbar args = sys.argv[1:] 12835926e10SDaniel Dunbar driverA = os.getenv('DRIVER_A') or 'gcc' 129156fa7ddSDaniel Dunbar driverB = os.getenv('DRIVER_B') or 'clang' 130c8385688SDaniel Dunbar 131c8385688SDaniel Dunbar infoA = captureDriverInfo(driverA, args) 132c8385688SDaniel Dunbar infoB = captureDriverInfo(driverB, args) 133c8385688SDaniel Dunbar 134563dc04fSDaniel Dunbar differ = False 135563dc04fSDaniel Dunbar 136c8385688SDaniel Dunbar # Compare stdout. 137c8385688SDaniel Dunbar if infoA.stdout != infoB.stdout: 138c8385688SDaniel Dunbar print '-- STDOUT DIFFERS -' 13956b534d2SDaniel Dunbar print 'A OUTPUT: ',infoA.stdout 14056b534d2SDaniel Dunbar print 'B OUTPUT: ',infoB.stdout 14156b534d2SDaniel Dunbar print 14256b534d2SDaniel Dunbar 14356b534d2SDaniel Dunbar diff = ZipperDiff(infoA.stdout.split('\n'), 14456b534d2SDaniel Dunbar infoB.stdout.split('\n')) 14556b534d2SDaniel Dunbar for i,(aElt,bElt) in enumerate(diff.getDiffs()): 14656b534d2SDaniel Dunbar if aElt is None: 14756b534d2SDaniel Dunbar print 'A missing: %s' % bElt 14856b534d2SDaniel Dunbar elif bElt is None: 14956b534d2SDaniel Dunbar print 'B missing: %s' % aElt 15056b534d2SDaniel Dunbar else: 15156b534d2SDaniel Dunbar print 'mismatch: A: %s' % aElt 15256b534d2SDaniel Dunbar print ' B: %s' % bElt 15356b534d2SDaniel Dunbar 154563dc04fSDaniel Dunbar differ = True 155c8385688SDaniel Dunbar 156c8385688SDaniel Dunbar # Compare stderr. 157c8385688SDaniel Dunbar if infoA.stderr != infoB.stderr: 158c8385688SDaniel Dunbar print '-- STDERR DIFFERS -' 15956b534d2SDaniel Dunbar print 'A STDERR: ',infoA.stderr 16056b534d2SDaniel Dunbar print 'B STDERR: ',infoB.stderr 16156b534d2SDaniel Dunbar print 16256b534d2SDaniel Dunbar 16356b534d2SDaniel Dunbar diff = ZipperDiff(infoA.stderr.split('\n'), 16456b534d2SDaniel Dunbar infoB.stderr.split('\n')) 16556b534d2SDaniel Dunbar for i,(aElt,bElt) in enumerate(diff.getDiffs()): 16656b534d2SDaniel Dunbar if aElt is None: 16756b534d2SDaniel Dunbar print 'A missing: %s' % bElt 16856b534d2SDaniel Dunbar elif bElt is None: 16956b534d2SDaniel Dunbar print 'B missing: %s' % aElt 17056b534d2SDaniel Dunbar else: 17156b534d2SDaniel Dunbar print 'mismatch: A: %s' % aElt 17256b534d2SDaniel Dunbar print ' B: %s' % bElt 17356b534d2SDaniel Dunbar 174563dc04fSDaniel Dunbar differ = True 175c8385688SDaniel Dunbar 176c8385688SDaniel Dunbar # Compare commands. 17770e06bdbSDaniel Dunbar for i,(a,b) in enumerate(map(None, infoA.commands, infoB.commands)): 17870e06bdbSDaniel Dunbar if a is None: 17970e06bdbSDaniel Dunbar print 'A MISSING:',' '.join(b) 18070e06bdbSDaniel Dunbar differ = True 18170e06bdbSDaniel Dunbar continue 18270e06bdbSDaniel Dunbar elif b is None: 18370e06bdbSDaniel Dunbar print 'B MISSING:',' '.join(a) 18470e06bdbSDaniel Dunbar differ = True 18570e06bdbSDaniel Dunbar continue 18670e06bdbSDaniel Dunbar 187c8385688SDaniel Dunbar diff = DriverZipperDiff(a,b) 188c8385688SDaniel Dunbar diffs = list(diff.getDiffs()) 189c8385688SDaniel Dunbar if diffs: 190c8385688SDaniel Dunbar print '-- COMMAND %d DIFFERS -' % i 191c8385688SDaniel Dunbar print 'A COMMAND:',' '.join(a) 192c8385688SDaniel Dunbar print 'B COMMAND:',' '.join(b) 193c8385688SDaniel Dunbar print 194c8385688SDaniel Dunbar for i,(aElt,bElt) in enumerate(diffs): 195c8385688SDaniel Dunbar if aElt is None: 196c8385688SDaniel Dunbar print 'A missing: %s' % bElt 197c8385688SDaniel Dunbar elif bElt is None: 198c8385688SDaniel Dunbar print 'B missing: %s' % aElt 199c8385688SDaniel Dunbar else: 200c8385688SDaniel Dunbar print 'mismatch: A: %s' % aElt 201c8385688SDaniel Dunbar print ' B: %s' % bElt 202563dc04fSDaniel Dunbar differ = True 203c8385688SDaniel Dunbar 204c8385688SDaniel Dunbar # Compare result codes. 205c8385688SDaniel Dunbar if infoA.exitCode != infoB.exitCode: 206c8385688SDaniel Dunbar print '-- EXIT CODES DIFFER -' 207c8385688SDaniel Dunbar print 'A: ',infoA.exitCode 208c8385688SDaniel Dunbar print 'B: ',infoB.exitCode 209563dc04fSDaniel Dunbar differ = True 210563dc04fSDaniel Dunbar 211563dc04fSDaniel Dunbar if differ: 212563dc04fSDaniel Dunbar sys.exit(1) 213c8385688SDaniel Dunbar 214c8385688SDaniel Dunbarif __name__ == '__main__': 215c8385688SDaniel Dunbar main() 216