Home | History | Annotate | Download | only in utils
      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