1#!/usr/bin/env 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    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
57class 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
73class 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
85class 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
110def 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
119def 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
209if __name__ == '__main__':
210    main()
211