blob: b5d6b7658c76b68f4253616e731da8e224f036d5 [file] [log] [blame]
José Fonseca79631cd2011-05-11 14:56:36 +01001#!/usr/bin/env python
2##########################################################################
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
42
43def good():
44 '''Tell git-bisect that this commit is good.'''
45
46 sys.stdout.write('GOOD\n')
47 sys.exit(0)
48
49
50def bad():
51 '''Tell git-bisect that this commit is bad.'''
52
53 sys.stdout.write('BAD\n')
54 sys.exit(1)
55
56
57def skip():
58 '''Tell git-bisect to skip this commit.'''
59
60 sys.stdout.write('SKIP\n')
61 sys.exit(125)
62
63
64def abort():
65 '''Tell git-bisect to abort.'''
66
67 sys.stdout.write('ABORT\n')
68 sys.exit(-1)
69
70
71def which(executable):
72 '''Search for the executable on the PATH.'''
73
74 if platform.system() == 'Windows':
75 exts = ['.exe']
76 else:
77 exts = ['']
78 dirs = os.environ['PATH'].split(os.path.pathsep)
79 for dir in dirs:
80 path = os.path.join(dir, executable)
81 for ext in exts:
82 if os.path.exists(path + ext):
83 return True
84 return False
85
86
87def main():
88 '''Main program.
89
90 It will always invoke sys.exit(), and never return normally.
91 '''
92
93 # Try to guess the build command.
94 if os.path.exists('SConstruct'):
95 default_build = 'scons'
96 elif os.path.exists('Makefile'):
97 default_build = 'make'
98 else:
99 default_build = None
100
101 # Parse command line options
102 optparser = optparse.OptionParser(
103 usage='\n\tgit bisect run %prog [options] -- [glretrace options] <trace>',
104 version='%%prog')
105 optparser.add_option(
106 '-b', '--build', metavar='COMMAND',
107 type='string', dest='build', default=default_build,
108 help='build command [default: %default]')
109 optparser.add_option(
110 '-r', '--retrace', metavar='PROGRAM',
111 type='string', dest='retrace', default='glretrace',
112 help='retrace command [default: %default]')
113 optparser.add_option(
José Fonseca28151902011-05-14 14:56:08 +0100114 '-c', '--compare', metavar='PREFIX',
115 type='string', dest='compare_prefix', default=None,
116 help='snapshot comparison prefix')
117 optparser.add_option(
118 '--precision-threshold', metavar='BITS',
119 type='float', dest='precision_threshold', default='12',
120 help='precision threshold in bits [default: %default]')
121 optparser.add_option(
José Fonseca79631cd2011-05-11 14:56:36 +0100122 '--gl-renderer', metavar='REGEXP',
123 type='string', dest='gl_renderer_re', default='^.*$',
124 help='require a matching GL_RENDERER string [default: %default]')
125
126 (options, args) = optparser.parse_args(sys.argv[1:])
127 if not args:
128 optparser.error("incorrect number of arguments")
129
130 # Build the source
131 if options.build:
132 sys.stdout.write(options.build + '\n')
133 sys.stdout.flush()
134 returncode = subprocess.call(options.build, shell=True)
135 if returncode:
136 skip()
137
138 # TODO: For this to be useful on Windows we'll also need an installation
139 # procedure here.
140
141 # Do some sanity checks. In particular we want to make sure that the GL
142 # implementation is usable, and is the right one (i.e., we didn't fallback
143 # to a different OpenGL implementation due to missing symbols).
144 if platform.system() != 'Windows' and which('glxinfo'):
145 p = subprocess.Popen(['glxinfo'], stdout=subprocess.PIPE)
146 stdout, stderr = p.communicate()
147 if p.returncode:
148 skip()
149
150 # Search for the GL_RENDERER string
151 gl_renderer_header = 'OpenGL renderer string: '
152 gl_renderer = ''
153 for line in stdout.split('\n'):
154 if line.startswith(gl_renderer_header):
155 gl_renderer = line[len(gl_renderer_header):]
José Fonseca28151902011-05-14 14:56:08 +0100156 if line.startswith('direct rendering: No'):
157 sys.stderr.write('Indirect rendering.\n')
158 skip()
José Fonseca79631cd2011-05-11 14:56:36 +0100159
160 # and match it against the regular expression specified in the command
161 # line.
162 if not re.search(options.gl_renderer_re, gl_renderer):
163 sys.stderr.write("GL_RENDERER mismatch: %r !~ /%s/\n" % (gl_renderer, options.gl_renderer_re))
164 skip()
165
166 # Run glretrace
José Fonseca28151902011-05-14 14:56:08 +0100167 command = [options.retrace]
168 if options.compare_prefix:
169 command += ['-c', options.compare_prefix]
170 else:
171 command += ['-b']
172 command += args
173 sys.stdout.write(' '.join(command) + '\n')
José Fonseca79631cd2011-05-11 14:56:36 +0100174 sys.stdout.flush()
José Fonseca28151902011-05-14 14:56:08 +0100175 p = subprocess.Popen(command, stdout=subprocess.PIPE)
176 stdout, stderr = p.communicate()
177 if p.returncode:
178 if not options.compare_prefix:
179 bad()
180 else:
181 skip()
182
183 if options.compare_prefix:
184 failed = False
185 precision_re = re.compile('^Snapshot (\S+) average precision of (\S+) bits$')
186 for line in stdout.split('\n'):
187 mo = precision_re.match(line)
188 if mo:
189 print line
190 call_no = int(mo.group(1))
191 precision = float(mo.group(2))
192 if precision < options.precision_threshold:
193 failed = True
194 if failed:
195 bad()
196
197 # TODO: allow more criterias here, such as, performance threshold
José Fonseca79631cd2011-05-11 14:56:36 +0100198
199 # Success
200 good()
201
202
203# Invoke main program, aborting the bisection on Ctrl+C or any uncaught Python
204# exception.
205if __name__ == '__main__':
206 try:
207 main()
208 except SystemExit:
209 raise
210 except KeyboardInterrupt:
211 abort()
212 except:
213 traceback.print_exc()
214 abort()