177a1fe945b3d0f1efa22e9d36a8ea7e272dd93c2Eli Friedman#!/usr/bin/env python
2ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
3ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbarimport subprocess
4ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
5ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbardef splitArgs(s):
6ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    it = iter(s)
7ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    current = ''
8ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    inQuote = False
9ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    for c in it:
10ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        if c == '"':
11ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar            if inQuote:
12ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                inQuote = False
13ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                yield current + '"'
14ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar            else:
15ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                inQuote = True
16ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                current = '"'
17ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        elif inQuote:
18ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar            if c == '\\':
19ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                current += c
20ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                current += it.next()
21ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar            else:
22ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                current += c
23ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        elif not c.isspace():
24ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar            yield c
25ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
26ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbardef insertMinimumPadding(a, b, dist):
27ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    """insertMinimumPadding(a,b) -> (a',b')
28ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
29ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    Return two lists of equal length, where some number of Nones have
30ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    been inserted into the shorter list such that sum(map(dist, a',
31ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    b')) is minimized.
32ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
33ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    Assumes dist(X, Y) -> int and non-negative.
34ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    """
35ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    
368f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar    def cost(a, b):
378f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar        return sum(map(dist, a + [None] * (len(b) - len(a)), b))
388f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar
398f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar    # Normalize so a is shortest.
408f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar    if len(b) < len(a):
418f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar        b, a = insertMinimumPadding(b, a, dist)
428f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar        return a,b
438f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar
448f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar    # For each None we have to insert...
458f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar    for i in range(len(b) - len(a)):
468f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar        # For each position we could insert it...
478f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar        current = cost(a, b)
488f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar        best = None
498f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar        for j in range(len(a) + 1):
508f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar            a_0 = a[:j] + [None] + a[j:]
518f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar            candidate = cost(a_0, b)
528f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar            if best is None or candidate < best[0]:
538f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar                best = (candidate, a_0, j)
548f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar        a = best[1]
558f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar    return a,b
56ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
57ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbarclass ZipperDiff(object):
58fc8f0e14ad142ed811e90fbd9a30e419e301c717Chris Lattner    """ZipperDiff - Simple (slow) diff only accommodating inserts."""
59ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    
60ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    def __init__(self, a, b):
61ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        self.a = a
62ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        self.b = b
63ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
64ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    def dist(self, a, b):
65ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        return a != b
66ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
67ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    def getDiffs(self):
68ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        a,b =  insertMinimumPadding(self.a, self.b, self.dist)
69ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        for aElt,bElt in zip(a,b):
70ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar            if self.dist(aElt, bElt):
71ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                yield aElt,bElt
72ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
73ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbarclass DriverZipperDiff(ZipperDiff):
74ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    def isTempFile(self, filename):
75ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        if filename[0] != '"' or filename[-1] != '"':
76ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar            return False
77ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        return (filename.startswith('/tmp/', 1) or
78ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                filename.startswith('/var/', 1))
79ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
80ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    def dist(self, a, b):
81ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        if a and b and self.isTempFile(a) and self.isTempFile(b):
82ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar            return 0
83ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        return super(DriverZipperDiff, self).dist(a,b)        
84ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
85ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbarclass CompileInfo:
86ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    def __init__(self, out, err, res):
87ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        self.commands = []
88ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        
89ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        # Standard out isn't used for much.
90ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        self.stdout = out
91ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        self.stderr = ''
92ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
93ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        # FIXME: Compare error messages as well.
94ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        for ln in err.split('\n'):
95ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar            if (ln == 'Using built-in specs.' or
96ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                ln.startswith('Target: ') or
97ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                ln.startswith('Configured with: ') or
98ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                ln.startswith('Thread model: ') or
9976715913cd6400feb23950a650cd1777ebe10967Daniel Dunbar                ln.startswith('gcc version') or
100ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar                ln.startswith('clang version')):
101ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                pass
102ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar            elif ln.strip().startswith('"'):
103ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                self.commands.append(list(splitArgs(ln)))
104ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar            else:
105ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                self.stderr += ln + '\n'
106ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        
107ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        self.stderr = self.stderr.strip()
108ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        self.exitCode = res
109ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
110ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbardef captureDriverInfo(cmd, args):
111ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    p = subprocess.Popen([cmd,'-###'] + args,
112ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                         stdin=None,
113ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                         stdout=subprocess.PIPE,
114ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                         stderr=subprocess.PIPE)
115ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    out,err = p.communicate()
116ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    res = p.wait()
117ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    return CompileInfo(out,err,res)
118ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
119ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbardef main():
1202253dc4705e7816a6cafcf95498da7080041a3e2Daniel Dunbar    import os, sys
121ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
122ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    args = sys.argv[1:]
1232253dc4705e7816a6cafcf95498da7080041a3e2Daniel Dunbar    driverA = os.getenv('DRIVER_A') or 'gcc'
1248f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar    driverB = os.getenv('DRIVER_B') or 'clang'
125ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
126ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    infoA = captureDriverInfo(driverA, args)
127ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    infoB = captureDriverInfo(driverB, args)
128ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
129c97c05f849e051afcd1f4a9d4dfa65c88a642b53Daniel Dunbar    differ = False
130c97c05f849e051afcd1f4a9d4dfa65c88a642b53Daniel Dunbar
131ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    # Compare stdout.
132ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    if infoA.stdout != infoB.stdout:
133ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        print '-- STDOUT DIFFERS -'
134ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar        print 'A OUTPUT: ',infoA.stdout
135ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar        print 'B OUTPUT: ',infoB.stdout
136ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar        print
137ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar
138ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar        diff = ZipperDiff(infoA.stdout.split('\n'),
139ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar                          infoB.stdout.split('\n'))
140ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar        for i,(aElt,bElt) in enumerate(diff.getDiffs()):
141ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar            if aElt is None:
142ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar                print 'A missing: %s' % bElt
143ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar            elif bElt is None:
144ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar                print 'B missing: %s' % aElt
145ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar            else:
146ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar                print 'mismatch: A: %s' % aElt
147ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar                print '          B: %s' % bElt
148ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar
149c97c05f849e051afcd1f4a9d4dfa65c88a642b53Daniel Dunbar        differ = True
150ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
151ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    # Compare stderr.
152ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    if infoA.stderr != infoB.stderr:
153ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        print '-- STDERR DIFFERS -'
154ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar        print 'A STDERR: ',infoA.stderr
155ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar        print 'B STDERR: ',infoB.stderr
156ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar        print
157ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar
158ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar        diff = ZipperDiff(infoA.stderr.split('\n'),
159ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar                          infoB.stderr.split('\n'))
160ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar        for i,(aElt,bElt) in enumerate(diff.getDiffs()):
161ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar            if aElt is None:
162ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar                print 'A missing: %s' % bElt
163ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar            elif bElt is None:
164ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar                print 'B missing: %s' % aElt
165ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar            else:
166ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar                print 'mismatch: A: %s' % aElt
167ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar                print '          B: %s' % bElt
168ad8958cb22f860a148e22d8f78511caf763482cfDaniel Dunbar
169c97c05f849e051afcd1f4a9d4dfa65c88a642b53Daniel Dunbar        differ = True
170ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
171ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    # Compare commands.
172163f31a0194fcff8ccea6e1c2a495f58deb286aaDaniel Dunbar    for i,(a,b) in enumerate(map(None, infoA.commands, infoB.commands)):
173163f31a0194fcff8ccea6e1c2a495f58deb286aaDaniel Dunbar        if a is None:
174163f31a0194fcff8ccea6e1c2a495f58deb286aaDaniel Dunbar            print 'A MISSING:',' '.join(b)
175163f31a0194fcff8ccea6e1c2a495f58deb286aaDaniel Dunbar            differ = True
176163f31a0194fcff8ccea6e1c2a495f58deb286aaDaniel Dunbar            continue
177163f31a0194fcff8ccea6e1c2a495f58deb286aaDaniel Dunbar        elif b is None:
178163f31a0194fcff8ccea6e1c2a495f58deb286aaDaniel Dunbar            print 'B MISSING:',' '.join(a)
179163f31a0194fcff8ccea6e1c2a495f58deb286aaDaniel Dunbar            differ = True
180163f31a0194fcff8ccea6e1c2a495f58deb286aaDaniel Dunbar            continue
181163f31a0194fcff8ccea6e1c2a495f58deb286aaDaniel Dunbar
182ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        diff = DriverZipperDiff(a,b)
183ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        diffs = list(diff.getDiffs())
184ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        if diffs:
185ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar            print '-- COMMAND %d DIFFERS -' % i
186ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar            print 'A COMMAND:',' '.join(a)
187ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar            print 'B COMMAND:',' '.join(b)
188ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar            print
189ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar            for i,(aElt,bElt) in enumerate(diffs):
190ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                if aElt is None:
191ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                    print 'A missing: %s' % bElt
192ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                elif bElt is None:
193ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                    print 'B missing: %s' % aElt
194ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                else:
195ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                    print 'mismatch: A: %s' % aElt
196ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar                    print '          B: %s' % bElt
19706172d680766ffb96a22c48c390ec3e0fd41625bDaniel Dunbar            differ = True
198163f31a0194fcff8ccea6e1c2a495f58deb286aaDaniel Dunbar    
199ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    # Compare result codes.
200ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar    if infoA.exitCode != infoB.exitCode:
201ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        print '-- EXIT CODES DIFFER -'
202ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        print 'A: ',infoA.exitCode
203ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar        print 'B: ',infoB.exitCode
204c97c05f849e051afcd1f4a9d4dfa65c88a642b53Daniel Dunbar        differ = True
205c97c05f849e051afcd1f4a9d4dfa65c88a642b53Daniel Dunbar
206c97c05f849e051afcd1f4a9d4dfa65c88a642b53Daniel Dunbar    if differ:
207c97c05f849e051afcd1f4a9d4dfa65c88a642b53Daniel Dunbar        sys.exit(1)
208ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbar
209ab13061fe7bb8dcd8ef6bdecd08c5728eda9359eDaniel Dunbarif __name__ == '__main__':
2108f5fdbefcde5543cb92f541de2db1d463bc36838Daniel Dunbar    main()
211