2 ##########################################################################
4 # Copyright 2011 Jose Fonseca
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:
14 # The above copyright notice and this permission notice shall be included in
15 # all copies or substantial portions of the Software.
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
25 ##########################################################################/
28 '''Apitrace test suite based on Mesa demos.'''
41 ansi_re = re.compile('\x1b\[[0-9]{1,2}(;[0-9]{1,2}){0,2}m')
45 # http://www.theeggeadventure.com/wikimedia/index.php/Linux_Tips#Use_sed_to_remove_ANSI_colors
46 return ansi_re.sub('', s)
49 def popen(command, *args, **kwargs):
51 sys.stdout.write('cd %s && ' % kwargs['cwd'])
53 for name, value in kwargs['env'].iteritems():
54 if value != os.environ.get(name, None):
55 sys.stdout.write('%s=%s ' % (name, value))
56 sys.stdout.write(' '.join(command) + '\n')
58 return subprocess.Popen(command, *args, **kwargs)
61 ignored_function_names = set([
63 'glXGetCurrentDisplay',
66 'glXGetProcAddressARB',
68 'glXGetVisualFromFBConfig',
70 'glXCreateNewContext',
71 'glXMakeContextCurrent',
77 def image_tag(html, image):
78 url = os.path.relpath(image, options.results)
79 html.write(' <td><a href="%s"><img src="%s"/></a></td>\n' % (url, url))
82 def runtest(html, demo):
84 app = os.path.join(options.mesa_demos, 'src', demo)
86 dirname, basename = os.path.split(app)
88 trace = os.path.abspath(os.path.join(options.results, demo.replace('/', '-') + '.trace'))
90 env = os.environ.copy()
91 env['LD_PRELOAD'] = os.path.abspath(os.path.join(options.build, 'glxtrace.so'))
92 env['TRACE_FILE'] = trace
94 args = [os.path.join('.', basename)]
97 p = popen(args, cwd=dirname)
100 if p.poll() is not None:
102 if subprocess.call(['xwininfo', '-name', window_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0:
104 if p.returncode is None:
107 sys.stdout.write('SKIP (app)\n')
110 ref_image = os.path.join(options.results, demo.replace('/', '-') + '.ref.png')
111 p = popen(args, env=env, cwd=dirname)
115 if p.poll() is not None:
117 if subprocess.call(['xwininfo', '-name', window_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0:
120 if p.returncode is None:
121 subprocess.call("xwd -name '%s' | xwdtopnm | pnmtopng > '%s'" % (args[0], ref_image), shell=True)
123 if p.returncode is None:
128 sys.stdout.write('FAIL (trace)\n')
131 p = popen([os.path.join(options.build, 'tracedump'), trace], stdout=subprocess.PIPE)
132 stdout, _ = p.communicate()
133 if p.returncode != 0:
134 sys.stdout.write('FAIL (tracedump)\n')
137 call_re = re.compile('^([0-9]+) (\w+)\(')
140 for orig_line in stdout.split('\n'):
141 line = ansi_strip(orig_line)
142 mo = call_re.match(line)
144 call_no = int(mo.group(1))
145 function_name = mo.group(2)
146 if function_name in ignored_function_names:
148 if function_name == 'glXSwapBuffers':
150 if function_name in ('glFlush', 'glFinish'):
154 args = [os.path.join(options.build, 'glretrace')]
161 if os.path.exists(ref_image) and frames < 10:
162 snapshot_prefix = os.path.join(options.results, demo.replace('/', '-') + '.')
163 args += ['-s', snapshot_prefix]
165 snapshot_prefix = None
167 p = popen(args, stdout=subprocess.PIPE)
168 stdout, _ = p.communicate()
169 if p.returncode != 0:
170 sys.stdout.write('FAIL (glretrace)\n')
173 image_re = re.compile('^Wrote (.*\.png)$')
175 for line in stdout.split('\n'):
176 mo = image_re.match(line)
181 delta_image = os.path.join(options.results, demo.replace('/', '-') + '.diff.png')
187 '-dissimilarity-threshold', '1',
188 ref_image, image, delta_image
189 ], stderr = subprocess.PIPE)
190 _, stderr = p.communicate()
198 html.write(' <tr>\n')
199 image_tag(html, ref_image)
200 image_tag(html, image)
201 image_tag(html, delta_image)
202 html.write(' </tr>\n')
205 sys.stdout.write('FAIL (snapshot)\n')
208 sys.stdout.write('PASS\n')
212 'trivial/clear-color',
214 'trivial/clear-fbo-scissor',
215 'trivial/clear-fbo-tex',
216 'trivial/clear-random',
217 'trivial/clear-repeat',
218 'trivial/clear-scissor',
219 'trivial/clear-undefined',
221 'trivial/dlist-begin-call-end',
222 'trivial/dlist-dangling',
223 'trivial/dlist-degenerate',
224 'trivial/dlist-edgeflag',
225 'trivial/dlist-edgeflag-dangling',
226 'trivial/dlist-flat-tri',
227 'trivial/dlist-mat-tri',
228 'trivial/dlist-recursive-call',
229 'trivial/dlist-tri-flat-tri',
230 'trivial/dlist-tri-mat-tri',
231 'trivial/draw2arrays',
232 'trivial/drawarrays',
233 'trivial/drawelements',
234 'trivial/drawelements-large',
242 'trivial/line-smooth',
243 'trivial/line-stipple-wide',
244 'trivial/line-userclip',
245 'trivial/line-userclip-clip',
246 'trivial/line-userclip-nop',
247 'trivial/line-userclip-nop-clip',
251 'trivial/lineloop-clip',
252 'trivial/lineloop-elts',
254 'trivial/linestrip-clip',
255 'trivial/linestrip-flat-stipple',
256 'trivial/linestrip-stipple',
257 'trivial/linestrip-stipple-wide',
258 'trivial/long-fixed-func',
261 'trivial/point-clip',
262 'trivial/point-param',
263 'trivial/point-sprite',
264 'trivial/point-wide',
265 'trivial/point-wide-smooth',
268 'trivial/poly-flat-clip',
269 'trivial/poly-flat-unfilled-clip',
270 'trivial/poly-unfilled',
273 'trivial/quad-clip-all-vertices',
274 'trivial/quad-clip-nearplane',
275 'trivial/quad-degenerate',
277 'trivial/quad-offset-factor',
278 'trivial/quad-offset-unfilled',
279 'trivial/quad-offset-units',
280 'trivial/quad-tex-2d',
281 'trivial/quad-tex-3d',
282 'trivial/quad-tex-alpha',
283 'trivial/quad-tex-pbo',
284 'trivial/quad-tex-sub',
285 'trivial/quad-unfilled',
286 'trivial/quad-unfilled-clip',
287 'trivial/quad-unfilled-stipple',
290 'trivial/quadstrip-clip',
291 'trivial/quadstrip-cont',
292 'trivial/quadstrip-flat',
293 'trivial/readpixels',
298 'trivial/tri-alpha-tex',
299 'trivial/tri-array-interleaved',
301 'trivial/tri-blend-color',
302 'trivial/tri-blend-max',
303 'trivial/tri-blend-min',
304 'trivial/tri-blend-revsub',
305 'trivial/tri-blend-sub',
309 'trivial/tri-cull-both',
311 'trivial/tri-edgeflag',
312 'trivial/tri-edgeflag-array',
314 'trivial/tri-fbo-tex',
315 'trivial/tri-fbo-tex-mip',
317 'trivial/tri-flat-clip',
320 'trivial/tri-fp-const-imm',
322 'trivial/tri-lit-material',
323 'trivial/tri-logicop-none',
324 'trivial/tri-logicop-xor',
325 'trivial/tri-mask-tri',
326 'trivial/tri-multitex-vbo',
328 'trivial/tri-point-line-clipped',
330 'trivial/tri-repeat',
331 'trivial/tri-scissor-tri',
332 'trivial/tri-square',
333 'trivial/tri-stencil',
334 'trivial/tri-stipple',
336 'trivial/tri-tex-1d',
337 'trivial/tri-tex-3d',
339 'trivial/tri-unfilled',
340 'trivial/tri-unfilled-clip',
341 'trivial/tri-unfilled-edgeflag',
342 'trivial/tri-unfilled-fog',
343 'trivial/tri-unfilled-point',
344 'trivial/tri-unfilled-smooth',
345 'trivial/tri-unfilled-tri',
346 'trivial/tri-unfilled-tri-lit',
347 'trivial/tri-unfilled-userclip',
348 'trivial/tri-unfilled-userclip-stip',
349 'trivial/tri-userclip',
350 'trivial/tri-viewport',
355 'trivial/trifan-flat',
356 'trivial/trifan-flat-clip',
357 'trivial/trifan-flat-unfilled-clip',
358 'trivial/trifan-unfilled',
360 'trivial/tristrip-clip',
361 'trivial/tristrip-flat',
362 'trivial/vbo-drawarrays',
363 'trivial/vbo-drawelements',
364 'trivial/vbo-drawrange',
365 'trivial/vbo-noninterleaved',
368 'trivial/vp-array-hf',
369 'trivial/vp-array-int',
371 'trivial/vp-line-clip',
374 'trivial/vp-tri-cb-pos',
375 'trivial/vp-tri-cb-tex',
376 'trivial/vp-tri-imm',
377 'trivial/vp-tri-invariant',
378 'trivial/vp-tri-swap',
379 'trivial/vp-tri-tex',
380 'trivial/vp-unfilled',
394 'demos/fbo_firecube',
421 'demos/singlebuffer',
437 #'fp/fp-tri', # XXX: parameterized
442 'fp/tri-depthwrite2',
458 'glsl/geom-stipple-lines',
459 'glsl/geom-wide-lines',
470 'glsl/shadow_sampler',
478 'glsl/vert-or-frag-only',
485 #'perf/drawoverhead',
509 'redbook/convolution',
537 'redbook/picksquare',
545 'redbook/sccolorlight',
555 'redbook/surfpoints',
556 'redbook/teaambient',
565 'redbook/texturesurf',
611 'tests/arbfptexture',
615 'tests/arbnpot-mipmap',
619 'tests/arbvpwarpmesh',
620 'tests/arraytexture',
628 'tests/bug_texstore_i8',
630 'tests/calibrate_rast',
632 #'tests/copypixrate', # XXX: benchmark
637 'tests/drawbuffers2',
640 'tests/ext422square',
644 #'tests/fillrate', # XXX: benchmark
660 'tests/mipmap_comp_tests',
661 'tests/mipmap_limits',
662 'tests/mipmap_tunnel',
665 'tests/multitexarray',
669 'tests/packedpixels',
673 'tests/prog_parameter',
676 #'tests/readrate', # XXX: benchmark
679 'tests/scissor-viewport',
681 'tests/shader-interp',
683 'tests/shadow-sample',
685 'tests/stencilreaddraw',
688 'tests/streaming_rect',
690 #'tests/subtexrate', # XXX: benchmark
693 'tests/texcompress2',
703 'tests/unfilledclip',
723 #'xdemos/glthreads', # XXX: multithreaded
724 'xdemos/glxcontexts',
727 'xdemos/glxgears_fbconfig',
728 'xdemos/glxgears_pixmap',
734 'xdemos/glxswapcontrol',
740 'xdemos/opencloseopen',
746 #'xdemos/sharedtex_mt', # XXX: multithreaded
747 'xdemos/texture_from_pixmap',
750 'xdemos/xrotfontdemo',
751 'xdemos/yuvrect_client',
764 # Parse command line options
765 optparser = optparse.OptionParser(
766 usage='\n\t%prog [options] [demo] ...',
768 optparser.add_option(
769 '--build', metavar='PATH',
770 type='string', dest='build', default='.',
771 help='path to apitrace build [default=%default]')
772 optparser.add_option(
773 '--results', metavar='PATH',
774 type='string', dest='results', default='results',
775 help='results directory [default=%default]')
776 optparser.add_option(
777 '--mesa-demos', metavar='PATH',
778 type='string', dest='mesa_demos', default=os.environ.get('MESA_DEMOS'),
779 help='path to Mesa demos [default=%default]')
781 (options, args) = optparser.parse_args(sys.argv[1:])
783 if not options.mesa_demos:
784 optparser.error('path to Mesa demos not specified')
785 if not os.path.exists(options.results):
786 os.makedirs(options.results)
791 if arg.endswith('/'):
793 if test.startswith(arg):
794 testlist.append(test)
800 html = open(os.path.join(options.results, 'index.html'), 'wt')
801 html.write('<html>\n')
802 html.write(' <body>\n')
803 html.write(' <table border="1">\n')
804 html.write(' <tr><th>Ref</th><th>Src</th><th>Δ</th></tr>\n')
805 for test in testlist:
807 html.write(' </table>\n')
808 html.write(' </body>\n')
809 html.write('</html>\n')
812 if __name__ == '__main__':