CmpDriver revision 76715913cd6400feb23950a650cd1777ebe10967
1#!/usr/bin/python
2
3import subprocess
4
5def 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
26def 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
67class 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
83class 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
95class 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
109                ln.startswith('gcc version') or
110                ln.startswith('ccc version')):
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
120def 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
129def main():
130    import os, sys
131
132    args = sys.argv[1:]
133    driverA = os.getenv('DRIVER_A') or 'gcc'
134    driverB = os.getenv('DRIVER_B') or 'xcc'
135
136    infoA = captureDriverInfo(driverA, args)
137    infoB = captureDriverInfo(driverB, args)
138
139    differ = False
140
141    # Compare stdout.
142    if infoA.stdout != infoB.stdout:
143        print '-- STDOUT DIFFERS -'
144        print 'A: ',infoA.stdout
145        print 'B: ',infoB.stdout
146        differ = True
147
148    # Compare stderr.
149    if infoA.stderr != infoB.stderr:
150        print '-- STDERR DIFFERS -'
151        print 'A: ',infoA.stderr
152        print 'B: ',infoB.stderr
153        differ = True
154
155    # Compare commands.
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
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
181            differ = True
182    
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
188        differ = True
189
190    if differ:
191        sys.exit(1)
192
193if __name__ == '__main__':
194   main()
195