2 ##########################################################################
4 # Copyright 2011 Jose Fonseca
7 # Permission is hereby granted, free of charge, to any person obtaining a copy
8 # of this software and associated documentation files (the "Software"), to deal
9 # in the Software without restriction, including without limitation the rights
10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 # copies of the Software, and to permit persons to whom the Software is
12 # furnished to do so, subject to the following conditions:
14 # The above copyright notice and this permission notice shall be included in
15 # all copies or substantial portions of the Software.
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 ##########################################################################/
34 from unpickle import Unpickler
35 from highlight import Highlighter
38 ignoredFunctionNames = set([
41 'glXGetCurrentDisplay',
43 'glXGetProcAddressARB',
48 class Loader(Unpickler):
50 def __init__(self, stream):
51 Unpickler.__init__(self, stream)
54 def handleCall(self, call):
57 if call.functionName not in ignoredFunctionNames:
58 self.calls.append(call)
66 '--calls=' + options.calls,
69 stdout = subprocess.PIPE,
73 parser = Loader(p.stdout)
80 def __init__(self, a, b, highlighter):
83 self.highlighter = highlighter
84 self.delete_color = highlighter.red
85 self.insert_color = highlighter.green
88 matcher = difflib.SequenceMatcher(None, self.a, self.b)
89 for tag, alo, ahi, blo, bhi in matcher.get_opcodes():
91 self.replace(alo, ahi, blo, bhi)
99 raise ValueError, 'unknown tag %s' % (tag,)
101 def replace(self, alo, ahi, blo, bhi):
102 assert alo < ahi and blo < bhi
104 a_names = [call.functionName for call in self.a[alo:ahi]]
105 b_names = [call.functionName for call in self.b[blo:bhi]]
107 matcher = difflib.SequenceMatcher(None, a_names, b_names)
108 for tag, _alo, _ahi, _blo, _bhi in matcher.get_opcodes():
114 self.replace_dissimilar(_alo, _ahi, _blo, _bhi)
115 elif tag == 'delete':
116 self.delete(_alo, _ahi)
117 elif tag == 'insert':
118 self.insert(_blo, _bhi)
120 self.replace_similar(_alo, _ahi, _blo, _bhi)
122 raise ValueError, 'unknown tag %s' % (tag,)
124 def replace_similar(self, alo, ahi, blo, bhi):
125 assert alo < ahi and blo < bhi
126 assert ahi - alo == bhi - blo
127 for i in xrange(0, bhi - blo):
128 a_call = self.a[alo + i]
129 b_call = self.b[blo + i]
130 assert a_call.functionName == b_call.functionName
131 assert len(a_call.args) == len(b_call.args)
133 self.highlighter.bold(True)
134 self.highlighter.write(b_call.functionName)
135 self.highlighter.bold(False)
137 for j in xrange(len(b_call.args)):
138 self.highlighter.write(sep)
139 self.replace_value(a_call.args[j], b_call.args[j])
141 self.highlighter.write(')')
142 if a_call.ret is not None or b_call.ret is not None:
143 self.highlighter.write(' = ')
144 self.replace_value(a_call.ret, b_call.ret)
145 self.highlighter.write('\n')
147 def replace_dissimilar(self, alo, ahi, blo, bhi):
148 assert alo < ahi and blo < bhi
149 if bhi - blo < ahi - alo:
150 first = self.insert(blo, bhi)
151 second = self.delete(alo, ahi)
153 first = self.delete(alo, ahi)
154 second = self.insert(blo, bhi)
156 for g in first, second:
160 def replace_value(self, a, b):
162 self.highlighter.write(str(b))
164 self.highlighter.color(self.delete_color)
165 self.highlighter.write(str(b))
166 self.highlighter.normal()
167 self.highlighter.write(" -> ")
168 self.highlighter.color(self.insert_color)
169 self.highlighter.write(str(b))
170 self.highlighter.normal()
174 def delete(self, alo, ahi):
175 self.dump(self.delete_prefix, self.a, alo, ahi, self.normal_suffix)
177 def insert(self, blo, bhi):
178 self.dump(self.insert_prefix, self.b, blo, bhi, self.normal_suffix)
180 def equal(self, alo, ahi):
181 self.dump(self.equal_prefix, self.a, alo, ahi, self.normal_suffix)
183 def dump(self, prefix, x, lo, hi, suffix):
184 for i in xrange(lo, hi):
187 self.highlighter.bold(True)
188 self.highlighter.write(call.functionName)
189 self.highlighter.bold(False)
190 self.highlighter.write('(' + ', '.join(map(repr, call.args)) + ')')
191 if call.ret is not None:
192 self.highlighter.write(' = ' + repr(call.ret))
194 self.highlighter.write('\n')
196 def delete_prefix(self):
197 self.highlighter.write('- ')
198 self.highlighter.color(self.delete_color)
200 def insert_prefix(self):
201 self.highlighter.write('+ ')
202 self.highlighter.color(self.insert_color)
204 def equal_prefix(self):
205 self.highlighter.write(' ')
207 def normal_suffix(self):
208 self.highlighter.normal()
215 # Parse command line options
216 optparser = optparse.OptionParser(
217 usage='\n\t%prog <trace> <trace>',
219 optparser.add_option(
220 '-a', '--apitrace', metavar='PROGRAM',
221 type='string', dest='apitrace', default='apitrace',
222 help='apitrace command [default: %default]')
223 optparser.add_option(
224 '-c', '--calls', metavar='CALLSET',
225 type="string", dest="calls", default='1-10000',
226 help="calls to compare [default: %default]")
229 (options, args) = optparser.parse_args(sys.argv[1:])
231 optparser.error("incorrect number of arguments")
233 ref_calls = readtrace(args[0])
234 src_calls = readtrace(args[1])
236 if sys.stdout.isatty():
237 less = subprocess.Popen(
238 args = ['less', '-FRXn'],
239 stdin = subprocess.PIPE
241 highlighter = Highlighter(less.stdin, True)
243 highlighter = Highlighter(sys.stdout)
245 differ = SDiffer(ref_calls, src_calls, highlighter)
252 if __name__ == '__main__':