blob: 38167e898b96ad66ab8121ff880f7cab116af508 [file] [log] [blame]
Jose Fonseca247e1fa2019-04-28 14:14:44 +01001#!/usr/bin/env python3
José Fonseca79631cd2011-05-11 14:56:36 +01002##########################################################################
3#
4# Copyright 2011 VMware, Inc.
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'''Check a trace replays successfully or not.
28
29It is meant to be used with git bisect. See git bisect manpage for more
30details.
31'''
32
33
34import optparse
35import os.path
36import platform
37import re
38import subprocess
39import sys
40import traceback
41
José Fonsecacad91cb2013-05-25 12:14:29 +010042import snapdiff
43import retracediff
44
José Fonseca79631cd2011-05-11 14:56:36 +010045
46def good():
47 '''Tell git-bisect that this commit is good.'''
48
49 sys.stdout.write('GOOD\n')
50 sys.exit(0)
51
52
53def bad():
54 '''Tell git-bisect that this commit is bad.'''
55
56 sys.stdout.write('BAD\n')
57 sys.exit(1)
58
59
60def skip():
61 '''Tell git-bisect to skip this commit.'''
62
63 sys.stdout.write('SKIP\n')
64 sys.exit(125)
65
66
67def abort():
68 '''Tell git-bisect to abort.'''
69
70 sys.stdout.write('ABORT\n')
71 sys.exit(-1)
72
73
74def which(executable):
75 '''Search for the executable on the PATH.'''
76
77 if platform.system() == 'Windows':
78 exts = ['.exe']
79 else:
80 exts = ['']
81 dirs = os.environ['PATH'].split(os.path.pathsep)
82 for dir in dirs:
83 path = os.path.join(dir, executable)
84 for ext in exts:
85 if os.path.exists(path + ext):
86 return True
87 return False
88
89
90def main():
91 '''Main program.
92
93 It will always invoke sys.exit(), and never return normally.
94 '''
95
96 # Try to guess the build command.
97 if os.path.exists('SConstruct'):
98 default_build = 'scons'
99 elif os.path.exists('Makefile'):
100 default_build = 'make'
101 else:
102 default_build = None
103
104 # Parse command line options
105 optparser = optparse.OptionParser(
106 usage='\n\tgit bisect run %prog [options] -- [glretrace options] <trace>',
107 version='%%prog')
108 optparser.add_option(
109 '-b', '--build', metavar='COMMAND',
110 type='string', dest='build', default=default_build,
111 help='build command [default: %default]')
112 optparser.add_option(
113 '-r', '--retrace', metavar='PROGRAM',
114 type='string', dest='retrace', default='glretrace',
115 help='retrace command [default: %default]')
116 optparser.add_option(
José Fonseca28151902011-05-14 14:56:08 +0100117 '-c', '--compare', metavar='PREFIX',
118 type='string', dest='compare_prefix', default=None,
119 help='snapshot comparison prefix')
120 optparser.add_option(
121 '--precision-threshold', metavar='BITS',
José Fonseca1accd6a2011-06-05 01:44:42 +0100122 type='float', dest='precision_threshold', default=8.0,
José Fonseca28151902011-05-14 14:56:08 +0100123 help='precision threshold in bits [default: %default]')
124 optparser.add_option(
José Fonseca79631cd2011-05-11 14:56:36 +0100125 '--gl-renderer', metavar='REGEXP',
126 type='string', dest='gl_renderer_re', default='^.*$',
127 help='require a matching GL_RENDERER string [default: %default]')
128
129 (options, args) = optparser.parse_args(sys.argv[1:])
130 if not args:
131 optparser.error("incorrect number of arguments")
132
133 # Build the source
134 if options.build:
135 sys.stdout.write(options.build + '\n')
136 sys.stdout.flush()
137 returncode = subprocess.call(options.build, shell=True)
138 if returncode:
139 skip()
140
141 # TODO: For this to be useful on Windows we'll also need an installation
142 # procedure here.
143
144 # Do some sanity checks. In particular we want to make sure that the GL
145 # implementation is usable, and is the right one (i.e., we didn't fallback
146 # to a different OpenGL implementation due to missing symbols).
147 if platform.system() != 'Windows' and which('glxinfo'):
José Fonsecacad91cb2013-05-25 12:14:29 +0100148 glxinfo = subprocess.Popen(['glxinfo'], stdout=subprocess.PIPE)
149 stdout, stderr = glxinfo.communicate()
150 if glxinfo.returncode:
José Fonseca79631cd2011-05-11 14:56:36 +0100151 skip()
152
153 # Search for the GL_RENDERER string
154 gl_renderer_header = 'OpenGL renderer string: '
155 gl_renderer = ''
156 for line in stdout.split('\n'):
157 if line.startswith(gl_renderer_header):
158 gl_renderer = line[len(gl_renderer_header):]
José Fonseca28151902011-05-14 14:56:08 +0100159 if line.startswith('direct rendering: No'):
160 sys.stderr.write('Indirect rendering.\n')
161 skip()
José Fonseca79631cd2011-05-11 14:56:36 +0100162
163 # and match it against the regular expression specified in the command
164 # line.
165 if not re.search(options.gl_renderer_re, gl_renderer):
166 sys.stderr.write("GL_RENDERER mismatch: %r !~ /%s/\n" % (gl_renderer, options.gl_renderer_re))
167 skip()
168
169 # Run glretrace
José Fonsecacad91cb2013-05-25 12:14:29 +0100170
171 retracer = retracediff.Retracer(options.retrace, args)
José Fonseca28151902011-05-14 14:56:08 +0100172
173 if options.compare_prefix:
José Fonsecacad91cb2013-05-25 12:14:29 +0100174 refImages = {}
175 callNos = []
176
177 images = snapdiff.find_images(options.compare_prefix)
178 images.sort()
179 for image in images:
180 imageName, ext = os.path.splitext(image)
181 try:
182 callNo = int(imageName)
183 except ValueError:
184 continue
185 refImages[callNo] = options.compare_prefix + image
186 callNos.append(callNo)
187
188 run = retracer.snapshot(','.join(map(str, callNos)))
189 while True:
190 srcImage, callNo = run.nextSnapshot()
191 if srcImage is None:
192 break
193
194 refImage = refImages[callNo]
195
196 # Compare the two images
197 comparer = snapdiff.Comparer(refImage, srcImage)
198 precision = comparer.precision()
199
200 mismatch = precision < options.precision_threshold
201 if mismatch:
202 bad()
203 run.process.wait()
204 if run.process.returncode:
205 skip()
206 else:
207 returncode = retracer.retrace('-b')
208 if returncode:
José Fonseca28151902011-05-14 14:56:08 +0100209 bad()
210
211 # TODO: allow more criterias here, such as, performance threshold
José Fonseca79631cd2011-05-11 14:56:36 +0100212
213 # Success
214 good()
215
216
217# Invoke main program, aborting the bisection on Ctrl+C or any uncaught Python
218# exception.
219if __name__ == '__main__':
220 try:
221 main()
222 except SystemExit:
223 raise
224 except KeyboardInterrupt:
225 abort()
226 except:
227 traceback.print_exc()
228 abort()