Home | History | Annotate | Download | only in utils
      1 #!/usr/bin/env python
      2 
      3 """
      4 A simple utility that compares tool invocations and exit codes issued by
      5 compiler drivers that support -### (e.g. gcc and clang).
      6 """
      7 
      8 import subprocess
      9 
     10 def splitArgs(s):
     11     it = iter(s)
     12     current = ''
     13     inQuote = False
     14     for c in it:
     15         if c == '"':
     16             if inQuote:
     17                 inQuote = False
     18                 yield current + '"'
     19             else:
     20                 inQuote = True
     21                 current = '"'
     22         elif inQuote:
     23             if c == '\\':
     24                 current += c
     25                 current += it.next()
     26             else:
     27                 current += c
     28         elif not c.isspace():
     29             yield c
     30 
     31 def insertMinimumPadding(a, b, dist):
     32     """insertMinimumPadding(a,b) -> (a',b')
     33 
     34     Return two lists of equal length, where some number of Nones have
     35     been inserted into the shorter list such that sum(map(dist, a',
     36     b')) is minimized.
     37 
     38     Assumes dist(X, Y) -> int and non-negative.
     39     """
     40     
     41     def cost(a, b):
     42         return sum(map(dist, a + [None] * (len(b) - len(a)), b))
     43 
     44     # Normalize so a is shortest.
     45     if len(b) < len(a):
     46         b, a = insertMinimumPadding(b, a, dist)
     47         return a,b
     48 
     49     # For each None we have to insert...
     50     for i in range(len(b) - len(a)):
     51         # For each position we could insert it...
     52         current = cost(a, b)
     53         best = None
     54         for j in range(len(a) + 1):
     55             a_0 = a[:j] + [None] + a[j:]
     56             candidate = cost(a_0, b)
     57             if best is None or candidate < best[0]:
     58                 best = (candidate, a_0, j)
     59         a = best[1]
     60     return a,b
     61 
     62 class ZipperDiff(object):
     63     """ZipperDiff - Simple (slow) diff only accommodating inserts."""
     64     
     65     def __init__(self, a, b):
     66         self.a = a
     67         self.b = b
     68 
     69     def dist(self, a, b):
     70         return a != b
     71 
     72     def getDiffs(self):
     73         a,b =  insertMinimumPadding(self.a, self.b, self.dist)
     74         for aElt,bElt in zip(a,b):
     75             if self.dist(aElt, bElt):
     76                 yield aElt,bElt
     77 
     78 class DriverZipperDiff(ZipperDiff):
     79     def isTempFile(self, filename):
     80         if filename[0] != '"' or filename[-1] != '"':
     81             return False
     82         return (filename.startswith('/tmp/', 1) or
     83                 filename.startswith('/var/', 1))
     84 
     85     def dist(self, a, b):
     86         if a and b and self.isTempFile(a) and self.isTempFile(b):
     87             return 0
     88         return super(DriverZipperDiff, self).dist(a,b)        
     89 
     90 class CompileInfo:
     91     def __init__(self, out, err, res):
     92         self.commands = []
     93         
     94         # Standard out isn't used for much.
     95         self.stdout = out
     96         self.stderr = ''
     97 
     98         # FIXME: Compare error messages as well.
     99         for ln in err.split('\n'):
    100             if (ln == 'Using built-in specs.' or
    101                 ln.startswith('Target: ') or
    102                 ln.startswith('Configured with: ') or
    103                 ln.startswith('Thread model: ') or
    104                 ln.startswith('gcc version') or
    105                 ln.startswith('clang version')):
    106                 pass
    107             elif ln.strip().startswith('"'):
    108                 self.commands.append(list(splitArgs(ln)))
    109             else:
    110                 self.stderr += ln + '\n'
    111         
    112         self.stderr = self.stderr.strip()
    113         self.exitCode = res
    114 
    115 def captureDriverInfo(cmd, args):
    116     p = subprocess.Popen([cmd,'-###'] + args,
    117                          stdin=None,
    118                          stdout=subprocess.PIPE,
    119                          stderr=subprocess.PIPE)
    120     out,err = p.communicate()
    121     res = p.wait()
    122     return CompileInfo(out,err,res)
    123 
    124 def main():
    125     import os, sys
    126 
    127     args = sys.argv[1:]
    128     driverA = os.getenv('DRIVER_A') or 'gcc'
    129     driverB = os.getenv('DRIVER_B') or 'clang'
    130 
    131     infoA = captureDriverInfo(driverA, args)
    132     infoB = captureDriverInfo(driverB, args)
    133 
    134     differ = False
    135 
    136     # Compare stdout.
    137     if infoA.stdout != infoB.stdout:
    138         print '-- STDOUT DIFFERS -'
    139         print 'A OUTPUT: ',infoA.stdout
    140         print 'B OUTPUT: ',infoB.stdout
    141         print
    142 
    143         diff = ZipperDiff(infoA.stdout.split('\n'),
    144                           infoB.stdout.split('\n'))
    145         for i,(aElt,bElt) in enumerate(diff.getDiffs()):
    146             if aElt is None:
    147                 print 'A missing: %s' % bElt
    148             elif bElt is None:
    149                 print 'B missing: %s' % aElt
    150             else:
    151                 print 'mismatch: A: %s' % aElt
    152                 print '          B: %s' % bElt
    153 
    154         differ = True
    155 
    156     # Compare stderr.
    157     if infoA.stderr != infoB.stderr:
    158         print '-- STDERR DIFFERS -'
    159         print 'A STDERR: ',infoA.stderr
    160         print 'B STDERR: ',infoB.stderr
    161         print
    162 
    163         diff = ZipperDiff(infoA.stderr.split('\n'),
    164                           infoB.stderr.split('\n'))
    165         for i,(aElt,bElt) in enumerate(diff.getDiffs()):
    166             if aElt is None:
    167                 print 'A missing: %s' % bElt
    168             elif bElt is None:
    169                 print 'B missing: %s' % aElt
    170             else:
    171                 print 'mismatch: A: %s' % aElt
    172                 print '          B: %s' % bElt
    173 
    174         differ = True
    175 
    176     # Compare commands.
    177     for i,(a,b) in enumerate(map(None, infoA.commands, infoB.commands)):
    178         if a is None:
    179             print 'A MISSING:',' '.join(b)
    180             differ = True
    181             continue
    182         elif b is None:
    183             print 'B MISSING:',' '.join(a)
    184             differ = True
    185             continue
    186 
    187         diff = DriverZipperDiff(a,b)
    188         diffs = list(diff.getDiffs())
    189         if diffs:
    190             print '-- COMMAND %d DIFFERS -' % i
    191             print 'A COMMAND:',' '.join(a)
    192             print 'B COMMAND:',' '.join(b)
    193             print
    194             for i,(aElt,bElt) in enumerate(diffs):
    195                 if aElt is None:
    196                     print 'A missing: %s' % bElt
    197                 elif bElt is None:
    198                     print 'B missing: %s' % aElt
    199                 else:
    200                     print 'mismatch: A: %s' % aElt
    201                     print '          B: %s' % bElt
    202             differ = True
    203     
    204     # Compare result codes.
    205     if infoA.exitCode != infoB.exitCode:
    206         print '-- EXIT CODES DIFFER -'
    207         print 'A: ',infoA.exitCode
    208         print 'B: ',infoB.exitCode
    209         differ = True
    210 
    211     if differ:
    212         sys.exit(1)
    213 
    214 if __name__ == '__main__':
    215     main()
    216