CmpDriver 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. #!/usr/bin/env python
  2. """
  3. A simple utility that compares tool invocations and exit codes issued by
  4. compiler drivers that support -### (e.g. gcc and clang).
  5. """
  6. import subprocess
  7. def splitArgs(s):
  8. it = iter(s)
  9. current = ''
  10. inQuote = False
  11. for c in it:
  12. if c == '"':
  13. if inQuote:
  14. inQuote = False
  15. yield current + '"'
  16. else:
  17. inQuote = True
  18. current = '"'
  19. elif inQuote:
  20. if c == '\\':
  21. current += c
  22. current += it.next()
  23. else:
  24. current += c
  25. elif not c.isspace():
  26. yield c
  27. def insertMinimumPadding(a, b, dist):
  28. """insertMinimumPadding(a,b) -> (a',b')
  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. Assumes dist(X, Y) -> int and non-negative.
  33. """
  34. def cost(a, b):
  35. return sum(map(dist, a + [None] * (len(b) - len(a)), b))
  36. # Normalize so a is shortest.
  37. if len(b) < len(a):
  38. b, a = insertMinimumPadding(b, a, dist)
  39. return a,b
  40. # For each None we have to insert...
  41. for i in range(len(b) - len(a)):
  42. # For each position we could insert it...
  43. current = cost(a, b)
  44. best = None
  45. for j in range(len(a) + 1):
  46. a_0 = a[:j] + [None] + a[j:]
  47. candidate = cost(a_0, b)
  48. if best is None or candidate < best[0]:
  49. best = (candidate, a_0, j)
  50. a = best[1]
  51. return a,b
  52. class ZipperDiff(object):
  53. """ZipperDiff - Simple (slow) diff only accommodating inserts."""
  54. def __init__(self, a, b):
  55. self.a = a
  56. self.b = b
  57. def dist(self, a, b):
  58. return a != b
  59. def getDiffs(self):
  60. a,b = insertMinimumPadding(self.a, self.b, self.dist)
  61. for aElt,bElt in zip(a,b):
  62. if self.dist(aElt, bElt):
  63. yield aElt,bElt
  64. class DriverZipperDiff(ZipperDiff):
  65. def isTempFile(self, filename):
  66. if filename[0] != '"' or filename[-1] != '"':
  67. return False
  68. return (filename.startswith('/tmp/', 1) or
  69. filename.startswith('/var/', 1))
  70. def dist(self, a, b):
  71. if a and b and self.isTempFile(a) and self.isTempFile(b):
  72. return 0
  73. return super(DriverZipperDiff, self).dist(a,b)
  74. class CompileInfo:
  75. def __init__(self, out, err, res):
  76. self.commands = []
  77. # Standard out isn't used for much.
  78. self.stdout = out
  79. self.stderr = ''
  80. # FIXME: Compare error messages as well.
  81. for ln in err.split('\n'):
  82. if (ln == 'Using built-in specs.' or
  83. ln.startswith('Target: ') or
  84. ln.startswith('Configured with: ') or
  85. ln.startswith('Thread model: ') or
  86. ln.startswith('gcc version') or
  87. ln.startswith('clang version')):
  88. pass
  89. elif ln.strip().startswith('"'):
  90. self.commands.append(list(splitArgs(ln)))
  91. else:
  92. self.stderr += ln + '\n'
  93. self.stderr = self.stderr.strip()
  94. self.exitCode = res
  95. def captureDriverInfo(cmd, args):
  96. p = subprocess.Popen([cmd,'-###'] + args,
  97. stdin=None,
  98. stdout=subprocess.PIPE,
  99. stderr=subprocess.PIPE)
  100. out,err = p.communicate()
  101. res = p.wait()
  102. return CompileInfo(out,err,res)
  103. def main():
  104. import os, sys
  105. args = sys.argv[1:]
  106. driverA = os.getenv('DRIVER_A') or 'gcc'
  107. driverB = os.getenv('DRIVER_B') or 'clang'
  108. infoA = captureDriverInfo(driverA, args)
  109. infoB = captureDriverInfo(driverB, args)
  110. differ = False
  111. # Compare stdout.
  112. if infoA.stdout != infoB.stdout:
  113. print '-- STDOUT DIFFERS -'
  114. print 'A OUTPUT: ',infoA.stdout
  115. print 'B OUTPUT: ',infoB.stdout
  116. print
  117. diff = ZipperDiff(infoA.stdout.split('\n'),
  118. infoB.stdout.split('\n'))
  119. for i,(aElt,bElt) in enumerate(diff.getDiffs()):
  120. if aElt is None:
  121. print 'A missing: %s' % bElt
  122. elif bElt is None:
  123. print 'B missing: %s' % aElt
  124. else:
  125. print 'mismatch: A: %s' % aElt
  126. print ' B: %s' % bElt
  127. differ = True
  128. # Compare stderr.
  129. if infoA.stderr != infoB.stderr:
  130. print '-- STDERR DIFFERS -'
  131. print 'A STDERR: ',infoA.stderr
  132. print 'B STDERR: ',infoB.stderr
  133. print
  134. diff = ZipperDiff(infoA.stderr.split('\n'),
  135. infoB.stderr.split('\n'))
  136. for i,(aElt,bElt) in enumerate(diff.getDiffs()):
  137. if aElt is None:
  138. print 'A missing: %s' % bElt
  139. elif bElt is None:
  140. print 'B missing: %s' % aElt
  141. else:
  142. print 'mismatch: A: %s' % aElt
  143. print ' B: %s' % bElt
  144. differ = True
  145. # Compare commands.
  146. for i,(a,b) in enumerate(map(None, infoA.commands, infoB.commands)):
  147. if a is None:
  148. print 'A MISSING:',' '.join(b)
  149. differ = True
  150. continue
  151. elif b is None:
  152. print 'B MISSING:',' '.join(a)
  153. differ = True
  154. continue
  155. diff = DriverZipperDiff(a,b)
  156. diffs = list(diff.getDiffs())
  157. if diffs:
  158. print '-- COMMAND %d DIFFERS -' % i
  159. print 'A COMMAND:',' '.join(a)
  160. print 'B COMMAND:',' '.join(b)
  161. print
  162. for i,(aElt,bElt) in enumerate(diffs):
  163. if aElt is None:
  164. print 'A missing: %s' % bElt
  165. elif bElt is None:
  166. print 'B missing: %s' % aElt
  167. else:
  168. print 'mismatch: A: %s' % aElt
  169. print ' B: %s' % bElt
  170. differ = True
  171. # Compare result codes.
  172. if infoA.exitCode != infoB.exitCode:
  173. print '-- EXIT CODES DIFFER -'
  174. print 'A: ',infoA.exitCode
  175. print 'B: ',infoB.exitCode
  176. differ = True
  177. if differ:
  178. sys.exit(1)
  179. if __name__ == '__main__':
  180. main()