+ignoredFunctionNames = set([
+ 'glGetString',
+ 'glXGetClientString',
+ 'glXGetCurrentDisplay',
+ 'glXGetCurrentContext',
+ 'glXGetProcAddress',
+ 'glXGetProcAddressARB',
+ 'wglGetProcAddress',
+])
+
+
+class Blob:
+ '''Data-less proxy for bytearrays, to save memory.'''
+
+ def __init__(self, size, hash):
+ self.size = size
+ self.hash = hash
+
+ def __repr__(self):
+ return 'blob(%u)' % self.size
+
+ def __eq__(self, other):
+ return isinstance(other, Blob) and self.size == other.size and self.hash == other.hash
+
+ def __hash__(self):
+ return self.hash
+
+
+class BlobReplacer(Rebuilder):
+ '''Replace blobs with proxys.'''
+
+ def visitByteArray(self, obj):
+ return Blob(len(obj), hash(str(obj)))
+
+ def visitCall(self, call):
+ call.args = map(self.visit, call.args)
+ call.ret = self.visit(call.ret)
+
+
+class Loader(Unpickler):
+
+ def __init__(self, stream):
+ Unpickler.__init__(self, stream)
+ self.calls = []
+ self.rebuilder = BlobReplacer()
+
+ def handleCall(self, call):
+ if call.functionName not in ignoredFunctionNames:
+ self.rebuilder.visitCall(call)
+ self.calls.append(call)
+
+
+class PythonDiffer(Differ):
+
+ def __init__(self, apitrace, callNos = False):
+ Differ.__init__(self, apitrace)
+ self.a = None
+ self.b = None
+ if self.isatty:
+ self.highlighter = LessHighlighter()
+ else:
+ self.highlighter = PlainHighlighter()
+ self.delete_color = self.highlighter.red
+ self.insert_color = self.highlighter.green
+ self.callNos = callNos
+ self.aSpace = 0
+ self.bSpace = 0
+ self.dumper = Dumper()
+
+ def setRefTrace(self, ref_trace, ref_calls):
+ self.a = self.readTrace(ref_trace, ref_calls)
+
+ def setSrcTrace(self, src_trace, src_calls):
+ self.b = self.readTrace(src_trace, src_calls)
+
+ def readTrace(self, trace, calls):
+ p = subprocess.Popen(
+ args = [
+ self.apitrace,
+ 'pickle',
+ '--symbolic',
+ '--calls=' + calls,
+ trace
+ ],
+ stdout = subprocess.PIPE,
+ )
+
+ parser = Loader(p.stdout)
+ parser.parse()
+ return parser.calls
+
+ def diff(self):
+ try:
+ self._diff()
+ except IOError:
+ pass
+
+ def _diff(self):
+ matcher = difflib.SequenceMatcher(self.isjunk, self.a, self.b)
+ for tag, alo, ahi, blo, bhi in matcher.get_opcodes():
+ if tag == 'replace':
+ self.replace(alo, ahi, blo, bhi)
+ elif tag == 'delete':
+ self.delete(alo, ahi, blo, bhi)
+ elif tag == 'insert':
+ self.insert(alo, ahi, blo, bhi)
+ elif tag == 'equal':
+ self.equal(alo, ahi, blo, bhi)
+ else:
+ raise ValueError, 'unknown tag %s' % (tag,)
+
+ def isjunk(self, call):
+ return call.functionName == 'glGetError' and call.ret in ('GL_NO_ERROR', 0)
+
+ def replace(self, alo, ahi, blo, bhi):
+ assert alo < ahi and blo < bhi
+
+ a_names = [call.functionName for call in self.a[alo:ahi]]
+ b_names = [call.functionName for call in self.b[blo:bhi]]
+
+ matcher = difflib.SequenceMatcher(None, a_names, b_names)
+ for tag, _alo, _ahi, _blo, _bhi in matcher.get_opcodes():
+ _alo += alo
+ _ahi += alo
+ _blo += blo
+ _bhi += blo
+ if tag == 'replace':
+ self.replace_dissimilar(_alo, _ahi, _blo, _bhi)
+ elif tag == 'delete':
+ self.delete(_alo, _ahi, _blo, _bhi)
+ elif tag == 'insert':
+ self.insert(_alo, _ahi, _blo, _bhi)
+ elif tag == 'equal':
+ self.replace_similar(_alo, _ahi, _blo, _bhi)
+ else:
+ raise ValueError, 'unknown tag %s' % (tag,)
+
+ def replace_similar(self, alo, ahi, blo, bhi):
+ assert alo < ahi and blo < bhi
+ assert ahi - alo == bhi - blo
+ for i in xrange(0, bhi - blo):
+ self.highlighter.write('| ')
+ a_call = self.a[alo + i]
+ b_call = self.b[blo + i]
+ assert a_call.functionName == b_call.functionName
+ self.dumpCallNos(a_call.no, b_call.no)
+ self.highlighter.bold(True)
+ self.highlighter.write(b_call.functionName)
+ self.highlighter.bold(False)
+ self.highlighter.write('(')
+ sep = ''
+ numArgs = max(len(a_call.args), len(b_call.args))
+ for j in xrange(numArgs):
+ self.highlighter.write(sep)
+ try:
+ a_arg = a_call.args[j]
+ except IndexError:
+ pass
+ try:
+ b_arg = b_call.args[j]
+ except IndexError:
+ pass
+ self.replace_value(a_arg, b_arg)
+ sep = ', '
+ self.highlighter.write(')')
+ if a_call.ret is not None or b_call.ret is not None:
+ self.highlighter.write(' = ')
+ self.replace_value(a_call.ret, b_call.ret)
+ self.highlighter.write('\n')
+
+ def replace_dissimilar(self, alo, ahi, blo, bhi):
+ assert alo < ahi and blo < bhi
+ if bhi - blo < ahi - alo:
+ self.insert(alo, alo, blo, bhi)
+ self.delete(alo, ahi, bhi, bhi)
+ else:
+ self.delete(alo, ahi, blo, blo)
+ self.insert(ahi, ahi, blo, bhi)
+
+ def replace_value(self, a, b):
+ if b == a:
+ self.highlighter.write(self.dumper.visit(b))
+ else:
+ self.highlighter.strike()
+ self.highlighter.color(self.delete_color)
+ self.highlighter.write(self.dumper.visit(a))
+ self.highlighter.normal()
+ self.highlighter.write(" ")
+ self.highlighter.color(self.insert_color)
+ self.highlighter.write(self.dumper.visit(b))
+ self.highlighter.normal()
+
+ escape = "\33["
+
+ def delete(self, alo, ahi, blo, bhi):
+ assert alo < ahi
+ assert blo == bhi
+ for i in xrange(alo, ahi):
+ call = self.a[i]
+ self.highlighter.write('- ')
+ self.dumpCallNos(call.no, None)
+ self.highlighter.strike()
+ self.highlighter.color(self.delete_color)
+ self.dumpCall(call)
+
+ def insert(self, alo, ahi, blo, bhi):
+ assert alo == ahi
+ assert blo < bhi
+ for i in xrange(blo, bhi):
+ call = self.b[i]
+ self.highlighter.write('+ ')
+ self.dumpCallNos(None, call.no)
+ self.highlighter.color(self.insert_color)
+ self.dumpCall(call)
+
+ def equal(self, alo, ahi, blo, bhi):
+ assert alo < ahi and blo < bhi
+ assert ahi - alo == bhi - blo
+ for i in xrange(0, bhi - blo):
+ self.highlighter.write(' ')
+ a_call = self.a[alo + i]
+ b_call = self.b[blo + i]
+ assert a_call.functionName == b_call.functionName
+ assert len(a_call.args) == len(b_call.args)
+ self.dumpCallNos(a_call.no, b_call.no)
+ self.dumpCall(b_call)
+
+ def dumpCallNos(self, aNo, bNo):
+ if not self.callNos:
+ return
+
+ if aNo is None:
+ self.highlighter.write(' '*self.aSpace)
+ else:
+ aNoStr = str(aNo)
+ self.highlighter.strike()
+ self.highlighter.color(self.delete_color)
+ self.highlighter.write(aNoStr)
+ self.highlighter.normal()
+ self.aSpace = len(aNoStr)
+ self.highlighter.write(' ')
+ if bNo is None:
+ self.highlighter.write(' '*self.bSpace)
+ else:
+ bNoStr = str(bNo)
+ self.highlighter.color(self.insert_color)
+ self.highlighter.write(bNoStr)
+ self.highlighter.normal()
+ self.bSpace = len(bNoStr)
+ self.highlighter.write(' ')
+
+ def dumpCall(self, call):
+ self.highlighter.bold(True)
+ self.highlighter.write(call.functionName)
+ self.highlighter.bold(False)
+ self.highlighter.write('(' + ', '.join(itertools.imap(self.dumper.visit, call.args)) + ')')
+ if call.ret is not None:
+ self.highlighter.write(' = ' + self.dumper.visit(call.ret))
+ self.highlighter.normal()
+ self.highlighter.write('\n')
+
+
+
+##########################################################################/
+#
+# Main program
+#