blob: f111322a526f2159901082377cd65b71594010de [file] [log] [blame]
José Fonsecaa65795f2012-02-18 18:15:18 +00001#!/usr/bin/env python
2##########################################################################
3#
4# Copyright 2011 Jose Fonseca
5# All Rights Reserved.
6#
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:
13#
14# The above copyright notice and this permission notice shall be included in
15# all copies or substantial portions of the Software.
16#
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
23# THE SOFTWARE.
24#
25##########################################################################/
26
27
José Fonseca8f9116e2012-11-16 22:19:10 +000028import difflib
29import itertools
José Fonsecaa65795f2012-02-18 18:15:18 +000030import optparse
José Fonseca3cf1e9a2012-03-16 15:46:26 +000031import os.path
José Fonseca8f9116e2012-11-16 22:19:10 +000032import platform
José Fonsecaa65795f2012-02-18 18:15:18 +000033import shutil
34import subprocess
35import sys
36import tempfile
37
38
José Fonseca8f9116e2012-11-16 22:19:10 +000039##########################################################################/
40#
41# Abstract interface
42#
José Fonseca3cf1e9a2012-03-16 15:46:26 +000043
José Fonseca8f9116e2012-11-16 22:19:10 +000044
45class Differ:
46
47 def __init__(self, apitrace):
48 self.apitrace = apitrace
49 self.isatty = sys.stdout.isatty()
50
Jose Fonsecad5878852015-06-24 11:07:28 +010051 def setRefTrace(self, refTrace, ref_calls):
José Fonseca8f9116e2012-11-16 22:19:10 +000052 raise NotImplementedError
53
Jose Fonsecad5878852015-06-24 11:07:28 +010054 def setSrcTrace(self, srcTrace, src_calls):
José Fonseca8f9116e2012-11-16 22:19:10 +000055 raise NotImplementedError
56
57 def diff(self):
58 raise NotImplementedError
59
60
61##########################################################################/
62#
63# External diff tool
64#
65
66
67class AsciiDumper:
68
José Fonseca095fb952014-02-04 14:27:05 +000069 def __init__(self, apitrace, trace, calls, callNos):
José Fonseca3cf1e9a2012-03-16 15:46:26 +000070 self.output = tempfile.NamedTemporaryFile()
71
72 dump_args = [
José Fonseca8f9116e2012-11-16 22:19:10 +000073 apitrace,
José Fonsecaa65795f2012-02-18 18:15:18 +000074 'dump',
75 '--color=never',
José Fonseca095fb952014-02-04 14:27:05 +000076 '--call-nos=' + ('yes' if callNos else 'no'),
José Fonsecaa65795f2012-02-18 18:15:18 +000077 '--arg-names=no',
José Fonseca3cf1e9a2012-03-16 15:46:26 +000078 '--calls=' + calls,
José Fonsecaa65795f2012-02-18 18:15:18 +000079 trace
José Fonseca3cf1e9a2012-03-16 15:46:26 +000080 ]
José Fonsecaa65795f2012-02-18 18:15:18 +000081
José Fonseca3cf1e9a2012-03-16 15:46:26 +000082 self.dump = subprocess.Popen(
83 args = dump_args,
84 stdout = self.output,
85 universal_newlines = True,
86 )
José Fonsecadf1a1812012-03-14 11:06:44 +000087
José Fonsecaa65795f2012-02-18 18:15:18 +000088
José Fonseca8f9116e2012-11-16 22:19:10 +000089class ExternalDiffer(Differ):
José Fonsecac6998962012-03-16 09:56:25 +000090
José Fonseca8f9116e2012-11-16 22:19:10 +000091 if platform.system() == 'Windows':
92 start_delete = ''
93 end_delete = ''
94 start_insert = ''
95 end_insert = ''
96 else:
97 start_delete = '\33[9m\33[31m'
98 end_delete = '\33[0m'
99 start_insert = '\33[32m'
100 end_insert = '\33[0m'
José Fonsecac6998962012-03-16 09:56:25 +0000101
Jose Fonsecad5878852015-06-24 11:07:28 +0100102 def __init__(self, apitrace, options):
José Fonseca8f9116e2012-11-16 22:19:10 +0000103 Differ.__init__(self, apitrace)
Jose Fonsecad5878852015-06-24 11:07:28 +0100104 tool = options.tool
105 callNos = options.callNos
106
José Fonseca8f9116e2012-11-16 22:19:10 +0000107 self.diff_args = [tool]
108 if tool == 'diff':
109 self.diff_args += [
José Fonseca3cf1e9a2012-03-16 15:46:26 +0000110 '--speed-large-files',
Carl Worthf45f1e22012-08-13 13:24:03 -0700111 ]
José Fonseca8f9116e2012-11-16 22:19:10 +0000112 if self.isatty:
Jose Fonsecad5878852015-06-24 11:07:28 +0100113 if options.suppressCommonLines:
114 self.diff_args += ['--unchanged-line-format=']
115 else:
116 self.diff_args += ['--unchanged-line-format=%l\n']
José Fonseca8f9116e2012-11-16 22:19:10 +0000117 self.diff_args += [
118 '--old-line-format=' + self.start_delete + '%l' + self.end_delete + '\n',
119 '--new-line-format=' + self.start_insert + '%l' + self.end_insert + '\n',
120 ]
Jose Fonsecad5878852015-06-24 11:07:28 +0100121 else:
122 if options.suppressCommonLines:
123 self.diff_args += ['--unchanged-line-format=']
124 else:
125 self.diff_args += ['--unchanged-line-format= %l\n']
126 self.diff_args += [
127 '--old-line-format=- %l\n',
128 '--new-line-format=+ %l\n',
129 ]
José Fonseca8f9116e2012-11-16 22:19:10 +0000130 elif tool == 'sdiff':
Vladimír Vondrušc6cace52015-09-05 17:46:34 +0100131 if options.width is None:
José Fonseca8f9116e2012-11-16 22:19:10 +0000132 import curses
133 curses.setupterm()
Vladimír Vondrušc6cace52015-09-05 17:46:34 +0100134 options.width = curses.tigetnum('cols')
José Fonseca8f9116e2012-11-16 22:19:10 +0000135 self.diff_args += [
Vladimír Vondrušc6cace52015-09-05 17:46:34 +0100136 '--width=%u' % options.width,
José Fonseca3cf1e9a2012-03-16 15:46:26 +0000137 '--speed-large-files',
138 ]
José Fonseca8f9116e2012-11-16 22:19:10 +0000139 elif tool == 'wdiff':
140 self.diff_args += [
José Fonseca3cf1e9a2012-03-16 15:46:26 +0000141 #'--terminal',
142 '--avoid-wraps',
Carl Worthf45f1e22012-08-13 13:24:03 -0700143 ]
José Fonseca8f9116e2012-11-16 22:19:10 +0000144 if self.isatty:
145 self.diff_args += [
146 '--start-delete=' + self.start_delete,
147 '--end-delete=' + self.end_delete,
148 '--start-insert=' + self.start_insert,
149 '--end-insert=' + self.end_insert,
150 ]
151 else:
152 assert False
José Fonseca095fb952014-02-04 14:27:05 +0000153 self.callNos = callNos
José Fonseca3cf1e9a2012-03-16 15:46:26 +0000154
Jose Fonsecad5878852015-06-24 11:07:28 +0100155 def setRefTrace(self, refTrace, ref_calls):
156 self.ref_dumper = AsciiDumper(self.apitrace, refTrace, ref_calls, self.callNos)
José Fonseca3cf1e9a2012-03-16 15:46:26 +0000157
Jose Fonsecad5878852015-06-24 11:07:28 +0100158 def setSrcTrace(self, srcTrace, src_calls):
159 self.src_dumper = AsciiDumper(self.apitrace, srcTrace, src_calls, self.callNos)
José Fonseca8f9116e2012-11-16 22:19:10 +0000160
161 def diff(self):
162 diff_args = self.diff_args + [
163 self.ref_dumper.output.name,
164 self.src_dumper.output.name,
165 ]
166
167 self.ref_dumper.dump.wait()
168 self.src_dumper.dump.wait()
169
170 less = None
José Fonsecac6ae8732014-05-02 22:34:05 +0100171 diff_stdout = None
José Fonseca8f9116e2012-11-16 22:19:10 +0000172 if self.isatty:
José Fonsecac6ae8732014-05-02 22:34:05 +0100173 try:
174 less = subprocess.Popen(
175 args = ['less', '-FRXn'],
176 stdin = subprocess.PIPE
177 )
178 except OSError:
179 pass
180 else:
181 diff_stdout = less.stdin
José Fonseca8f9116e2012-11-16 22:19:10 +0000182
183 diff = subprocess.Popen(
184 args = diff_args,
185 stdout = diff_stdout,
186 universal_newlines = True,
José Fonsecaa65795f2012-02-18 18:15:18 +0000187 )
188
José Fonseca8f9116e2012-11-16 22:19:10 +0000189 diff.wait()
José Fonsecaa65795f2012-02-18 18:15:18 +0000190
José Fonseca8f9116e2012-11-16 22:19:10 +0000191 if less is not None:
192 less.stdin.close()
193 less.wait()
José Fonseca3cf1e9a2012-03-16 15:46:26 +0000194
José Fonseca3cf1e9a2012-03-16 15:46:26 +0000195
José Fonseca8f9116e2012-11-16 22:19:10 +0000196##########################################################################/
197#
198# Python diff
199#
200
201from unpickle import Unpickler, Dumper, Rebuilder
José Fonseca975f1242012-12-06 07:11:29 +0000202from highlight import PlainHighlighter, LessHighlighter
José Fonseca8f9116e2012-11-16 22:19:10 +0000203
204
205ignoredFunctionNames = set([
206 'glGetString',
207 'glXGetClientString',
208 'glXGetCurrentDisplay',
209 'glXGetCurrentContext',
210 'glXGetProcAddress',
211 'glXGetProcAddressARB',
212 'wglGetProcAddress',
213])
214
215
216class Blob:
217 '''Data-less proxy for bytearrays, to save memory.'''
218
219 def __init__(self, size, hash):
220 self.size = size
221 self.hash = hash
222
223 def __repr__(self):
224 return 'blob(%u)' % self.size
225
226 def __eq__(self, other):
227 return isinstance(other, Blob) and self.size == other.size and self.hash == other.hash
228
229 def __hash__(self):
230 return self.hash
231
232
233class BlobReplacer(Rebuilder):
234 '''Replace blobs with proxys.'''
235
236 def visitByteArray(self, obj):
237 return Blob(len(obj), hash(str(obj)))
238
239 def visitCall(self, call):
240 call.args = map(self.visit, call.args)
241 call.ret = self.visit(call.ret)
242
243
244class Loader(Unpickler):
245
246 def __init__(self, stream):
247 Unpickler.__init__(self, stream)
248 self.calls = []
249 self.rebuilder = BlobReplacer()
250
251 def handleCall(self, call):
252 if call.functionName not in ignoredFunctionNames:
253 self.rebuilder.visitCall(call)
254 self.calls.append(call)
255
256
257class PythonDiffer(Differ):
258
Jose Fonsecad5878852015-06-24 11:07:28 +0100259 def __init__(self, apitrace, options):
José Fonseca8f9116e2012-11-16 22:19:10 +0000260 Differ.__init__(self, apitrace)
261 self.a = None
262 self.b = None
263 if self.isatty:
264 self.highlighter = LessHighlighter()
265 else:
José Fonseca975f1242012-12-06 07:11:29 +0000266 self.highlighter = PlainHighlighter()
José Fonseca8f9116e2012-11-16 22:19:10 +0000267 self.delete_color = self.highlighter.red
268 self.insert_color = self.highlighter.green
Jose Fonsecad5878852015-06-24 11:07:28 +0100269 self.callNos = options.callNos
270 self.suppressCommonLines = options.suppressCommonLines
José Fonseca8f9116e2012-11-16 22:19:10 +0000271 self.aSpace = 0
272 self.bSpace = 0
273 self.dumper = Dumper()
274
Jose Fonsecad5878852015-06-24 11:07:28 +0100275 def setRefTrace(self, refTrace, ref_calls):
276 self.a = self.readTrace(refTrace, ref_calls)
José Fonseca8f9116e2012-11-16 22:19:10 +0000277
Jose Fonsecad5878852015-06-24 11:07:28 +0100278 def setSrcTrace(self, srcTrace, src_calls):
279 self.b = self.readTrace(srcTrace, src_calls)
José Fonseca8f9116e2012-11-16 22:19:10 +0000280
281 def readTrace(self, trace, calls):
282 p = subprocess.Popen(
283 args = [
284 self.apitrace,
285 'pickle',
286 '--symbolic',
287 '--calls=' + calls,
288 trace
289 ],
290 stdout = subprocess.PIPE,
291 )
292
293 parser = Loader(p.stdout)
294 parser.parse()
295 return parser.calls
296
297 def diff(self):
298 try:
299 self._diff()
300 except IOError:
301 pass
302
303 def _diff(self):
304 matcher = difflib.SequenceMatcher(self.isjunk, self.a, self.b)
305 for tag, alo, ahi, blo, bhi in matcher.get_opcodes():
306 if tag == 'replace':
307 self.replace(alo, ahi, blo, bhi)
308 elif tag == 'delete':
309 self.delete(alo, ahi, blo, bhi)
310 elif tag == 'insert':
311 self.insert(alo, ahi, blo, bhi)
312 elif tag == 'equal':
313 self.equal(alo, ahi, blo, bhi)
314 else:
315 raise ValueError, 'unknown tag %s' % (tag,)
316
317 def isjunk(self, call):
318 return call.functionName == 'glGetError' and call.ret in ('GL_NO_ERROR', 0)
319
320 def replace(self, alo, ahi, blo, bhi):
321 assert alo < ahi and blo < bhi
322
323 a_names = [call.functionName for call in self.a[alo:ahi]]
324 b_names = [call.functionName for call in self.b[blo:bhi]]
325
326 matcher = difflib.SequenceMatcher(None, a_names, b_names)
327 for tag, _alo, _ahi, _blo, _bhi in matcher.get_opcodes():
328 _alo += alo
329 _ahi += alo
330 _blo += blo
331 _bhi += blo
332 if tag == 'replace':
333 self.replace_dissimilar(_alo, _ahi, _blo, _bhi)
334 elif tag == 'delete':
335 self.delete(_alo, _ahi, _blo, _bhi)
336 elif tag == 'insert':
337 self.insert(_alo, _ahi, _blo, _bhi)
338 elif tag == 'equal':
339 self.replace_similar(_alo, _ahi, _blo, _bhi)
340 else:
341 raise ValueError, 'unknown tag %s' % (tag,)
342
343 def replace_similar(self, alo, ahi, blo, bhi):
344 assert alo < ahi and blo < bhi
345 assert ahi - alo == bhi - blo
346 for i in xrange(0, bhi - blo):
347 self.highlighter.write('| ')
348 a_call = self.a[alo + i]
349 b_call = self.b[blo + i]
350 assert a_call.functionName == b_call.functionName
351 self.dumpCallNos(a_call.no, b_call.no)
352 self.highlighter.bold(True)
353 self.highlighter.write(b_call.functionName)
354 self.highlighter.bold(False)
355 self.highlighter.write('(')
356 sep = ''
357 numArgs = max(len(a_call.args), len(b_call.args))
358 for j in xrange(numArgs):
359 self.highlighter.write(sep)
360 try:
José Fonseca87c34802014-06-20 14:13:13 +0100361 a_argName, a_argVal = a_call.args[j]
José Fonseca8f9116e2012-11-16 22:19:10 +0000362 except IndexError:
363 pass
364 try:
José Fonseca87c34802014-06-20 14:13:13 +0100365 b_argName, b_argVal = b_call.args[j]
José Fonseca8f9116e2012-11-16 22:19:10 +0000366 except IndexError:
367 pass
José Fonseca87c34802014-06-20 14:13:13 +0100368 self.replace_value(a_argName, b_argName)
369 self.highlighter.write(' = ')
370 self.replace_value(a_argVal, b_argVal)
José Fonseca8f9116e2012-11-16 22:19:10 +0000371 sep = ', '
372 self.highlighter.write(')')
373 if a_call.ret is not None or b_call.ret is not None:
374 self.highlighter.write(' = ')
375 self.replace_value(a_call.ret, b_call.ret)
376 self.highlighter.write('\n')
377
378 def replace_dissimilar(self, alo, ahi, blo, bhi):
379 assert alo < ahi and blo < bhi
380 if bhi - blo < ahi - alo:
381 self.insert(alo, alo, blo, bhi)
382 self.delete(alo, ahi, bhi, bhi)
383 else:
384 self.delete(alo, ahi, blo, blo)
385 self.insert(ahi, ahi, blo, bhi)
386
387 def replace_value(self, a, b):
388 if b == a:
389 self.highlighter.write(self.dumper.visit(b))
390 else:
391 self.highlighter.strike()
392 self.highlighter.color(self.delete_color)
393 self.highlighter.write(self.dumper.visit(a))
394 self.highlighter.normal()
Jose Fonsecad5878852015-06-24 11:07:28 +0100395 self.highlighter.write(" -> ")
José Fonseca8f9116e2012-11-16 22:19:10 +0000396 self.highlighter.color(self.insert_color)
397 self.highlighter.write(self.dumper.visit(b))
398 self.highlighter.normal()
399
400 escape = "\33["
401
402 def delete(self, alo, ahi, blo, bhi):
403 assert alo < ahi
404 assert blo == bhi
405 for i in xrange(alo, ahi):
406 call = self.a[i]
407 self.highlighter.write('- ')
408 self.dumpCallNos(call.no, None)
409 self.highlighter.strike()
410 self.highlighter.color(self.delete_color)
411 self.dumpCall(call)
412
413 def insert(self, alo, ahi, blo, bhi):
414 assert alo == ahi
415 assert blo < bhi
416 for i in xrange(blo, bhi):
417 call = self.b[i]
418 self.highlighter.write('+ ')
419 self.dumpCallNos(None, call.no)
420 self.highlighter.color(self.insert_color)
421 self.dumpCall(call)
422
423 def equal(self, alo, ahi, blo, bhi):
Jose Fonsecad5878852015-06-24 11:07:28 +0100424 if self.suppressCommonLines:
425 return
José Fonseca8f9116e2012-11-16 22:19:10 +0000426 assert alo < ahi and blo < bhi
427 assert ahi - alo == bhi - blo
428 for i in xrange(0, bhi - blo):
429 self.highlighter.write(' ')
430 a_call = self.a[alo + i]
431 b_call = self.b[blo + i]
432 assert a_call.functionName == b_call.functionName
433 assert len(a_call.args) == len(b_call.args)
434 self.dumpCallNos(a_call.no, b_call.no)
435 self.dumpCall(b_call)
436
437 def dumpCallNos(self, aNo, bNo):
438 if not self.callNos:
439 return
440
José Fonsecafcf044b2014-03-27 17:39:04 +0000441 if aNo is not None and bNo is not None and aNo == bNo:
442 aNoStr = str(aNo)
443 self.highlighter.write(aNoStr)
444 self.aSpace = len(aNoStr)
445 self.bSpace = self.aSpace
446 self.highlighter.write(' ')
447 return
448
José Fonseca8f9116e2012-11-16 22:19:10 +0000449 if aNo is None:
450 self.highlighter.write(' '*self.aSpace)
451 else:
452 aNoStr = str(aNo)
453 self.highlighter.strike()
454 self.highlighter.color(self.delete_color)
455 self.highlighter.write(aNoStr)
456 self.highlighter.normal()
457 self.aSpace = len(aNoStr)
458 self.highlighter.write(' ')
459 if bNo is None:
460 self.highlighter.write(' '*self.bSpace)
461 else:
462 bNoStr = str(bNo)
463 self.highlighter.color(self.insert_color)
464 self.highlighter.write(bNoStr)
465 self.highlighter.normal()
466 self.bSpace = len(bNoStr)
467 self.highlighter.write(' ')
468
469 def dumpCall(self, call):
470 self.highlighter.bold(True)
471 self.highlighter.write(call.functionName)
472 self.highlighter.bold(False)
José Fonseca87c34802014-06-20 14:13:13 +0100473 self.highlighter.write('(' + self.dumper.visitItems(call.args) + ')')
José Fonseca8f9116e2012-11-16 22:19:10 +0000474 if call.ret is not None:
475 self.highlighter.write(' = ' + self.dumper.visit(call.ret))
476 self.highlighter.normal()
477 self.highlighter.write('\n')
478
479
480
481##########################################################################/
482#
483# Main program
484#
José Fonsecaa65795f2012-02-18 18:15:18 +0000485
486
José Fonsecac6998962012-03-16 09:56:25 +0000487def which(executable):
488 '''Search for the executable on the PATH.'''
489
490 if platform.system() == 'Windows':
491 exts = ['.exe']
492 else:
493 exts = ['']
494 dirs = os.environ['PATH'].split(os.path.pathsep)
495 for dir in dirs:
496 path = os.path.join(dir, executable)
497 for ext in exts:
498 if os.path.exists(path + ext):
499 return True
500 return False
501
502
José Fonsecaa65795f2012-02-18 18:15:18 +0000503def main():
504 '''Main program.
505 '''
506
José Fonsecaa65795f2012-02-18 18:15:18 +0000507 # Parse command line options
508 optparser = optparse.OptionParser(
José Fonseca8f9116e2012-11-16 22:19:10 +0000509 usage='\n\t%prog [options] TRACE TRACE',
José Fonsecaa65795f2012-02-18 18:15:18 +0000510 version='%%prog')
511 optparser.add_option(
512 '-a', '--apitrace', metavar='PROGRAM',
513 type='string', dest='apitrace', default='apitrace',
514 help='apitrace command [default: %default]')
515 optparser.add_option(
José Fonseca155380f2013-10-18 17:15:17 -0700516 '-t', '--tool', metavar='TOOL',
José Fonseca8f9116e2012-11-16 22:19:10 +0000517 type="choice", choices=('diff', 'sdiff', 'wdiff', 'python'),
José Fonseca155380f2013-10-18 17:15:17 -0700518 dest="tool", default=None,
519 help="diff tool: diff, sdiff, wdiff, or python [default: auto]")
José Fonsecac6998962012-03-16 09:56:25 +0000520 optparser.add_option(
José Fonsecaa65795f2012-02-18 18:15:18 +0000521 '-c', '--calls', metavar='CALLSET',
José Fonseca8f9116e2012-11-16 22:19:10 +0000522 type="string", dest="calls", default='0-10000',
José Fonsecaa65795f2012-02-18 18:15:18 +0000523 help="calls to compare [default: %default]")
524 optparser.add_option(
José Fonseca3cf1e9a2012-03-16 15:46:26 +0000525 '--ref-calls', metavar='CALLSET',
Jose Fonsecad5878852015-06-24 11:07:28 +0100526 type="string", dest="refCalls", default=None,
José Fonseca3cf1e9a2012-03-16 15:46:26 +0000527 help="calls to compare from reference trace")
528 optparser.add_option(
529 '--src-calls', metavar='CALLSET',
Jose Fonsecad5878852015-06-24 11:07:28 +0100530 type="string", dest="srcCalls", default=None,
José Fonseca3cf1e9a2012-03-16 15:46:26 +0000531 help="calls to compare from source trace")
532 optparser.add_option(
José Fonseca8f9116e2012-11-16 22:19:10 +0000533 '--call-nos',
534 action="store_true",
Jose Fonsecad5878852015-06-24 11:07:28 +0100535 dest="callNos", default=False,
José Fonseca8f9116e2012-11-16 22:19:10 +0000536 help="dump call numbers")
537 optparser.add_option(
Jose Fonsecad5878852015-06-24 11:07:28 +0100538 '--suppress-common-lines',
539 action="store_true",
540 dest="suppressCommonLines", default=False,
541 help="do not output common lines")
542 optparser.add_option(
José Fonsecaa65795f2012-02-18 18:15:18 +0000543 '-w', '--width', metavar='NUM',
José Fonseca8f9116e2012-11-16 22:19:10 +0000544 type="int", dest="width",
545 help="columns [default: auto]")
José Fonsecaa65795f2012-02-18 18:15:18 +0000546
José Fonsecaa65795f2012-02-18 18:15:18 +0000547 (options, args) = optparser.parse_args(sys.argv[1:])
548 if len(args) != 2:
549 optparser.error("incorrect number of arguments")
550
José Fonseca155380f2013-10-18 17:15:17 -0700551 if options.tool is None:
José Fonseca8f9116e2012-11-16 22:19:10 +0000552 if platform.system() == 'Windows':
José Fonseca155380f2013-10-18 17:15:17 -0700553 options.tool = 'python'
José Fonseca3cf1e9a2012-03-16 15:46:26 +0000554 else:
José Fonseca8f9116e2012-11-16 22:19:10 +0000555 if which('wdiff'):
José Fonseca155380f2013-10-18 17:15:17 -0700556 options.tool = 'wdiff'
José Fonseca3cf1e9a2012-03-16 15:46:26 +0000557 else:
José Fonseca8f9116e2012-11-16 22:19:10 +0000558 sys.stderr.write('warning: wdiff not found\n')
559 if which('sdiff'):
José Fonseca155380f2013-10-18 17:15:17 -0700560 options.tool = 'sdiff'
José Fonseca8f9116e2012-11-16 22:19:10 +0000561 else:
562 sys.stderr.write('warning: sdiff not found\n')
José Fonseca155380f2013-10-18 17:15:17 -0700563 options.tool = 'diff'
José Fonseca3cf1e9a2012-03-16 15:46:26 +0000564
Jose Fonsecad5878852015-06-24 11:07:28 +0100565 if options.refCalls is None:
566 options.refCalls = options.calls
567 if options.srcCalls is None:
568 options.srcCalls = options.calls
José Fonseca3cf1e9a2012-03-16 15:46:26 +0000569
Jose Fonsecad5878852015-06-24 11:07:28 +0100570 refTrace, srcTrace = args
José Fonseca8f9116e2012-11-16 22:19:10 +0000571
José Fonseca155380f2013-10-18 17:15:17 -0700572 if options.tool == 'python':
Jose Fonsecad5878852015-06-24 11:07:28 +0100573 factory = PythonDiffer
José Fonseca8f9116e2012-11-16 22:19:10 +0000574 else:
Jose Fonsecad5878852015-06-24 11:07:28 +0100575 factory = ExternalDiffer
576 differ = factory(options.apitrace, options)
577 differ.setRefTrace(refTrace, options.refCalls)
578 differ.setSrcTrace(srcTrace, options.srcCalls)
José Fonseca8f9116e2012-11-16 22:19:10 +0000579 differ.diff()
José Fonsecaa65795f2012-02-18 18:15:18 +0000580
581
582if __name__ == '__main__':
583 main()