]> git.cworth.org Git - apitrace-tests/blob - test.py
ec25fabad1e1d4b9eef4c8ac79eede225b8c2b0b
[apitrace-tests] / test.py
1 #!/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 '''Common test suite code.'''
28
29
30 import os.path
31 import optparse
32 import sys
33 import subprocess
34 import time
35 import re
36 import signal
37
38
39 ansi_re = re.compile('\x1b\[[0-9]{1,2}(;[0-9]{1,2}){0,2}m')
40
41
42 def ansi_strip(s):
43     # http://www.theeggeadventure.com/wikimedia/index.php/Linux_Tips#Use_sed_to_remove_ANSI_colors
44     return ansi_re.sub('', s)
45
46
47 def popen(command, *args, **kwargs):
48     if 'cwd' in kwargs:
49         sys.stdout.write('cd %s && ' % kwargs['cwd'])
50     if 'env' in kwargs:
51         for name, value in kwargs['env'].iteritems():
52             if value != os.environ.get(name, None):
53                 sys.stdout.write('%s=%s ' % (name, value))
54     sys.stdout.write(' '.join(command) + '\n')
55     sys.stdout.flush()
56     return subprocess.Popen(command, *args, **kwargs)
57
58
59 ignored_function_names = set([
60     'glGetString',
61     'glXGetCurrentDisplay',
62     'glXGetClientString',
63     'glXGetProcAddress',
64     'glXGetProcAddressARB',
65     'glXQueryVersion',
66     'glXGetVisualFromFBConfig',
67     'glXChooseFBConfig',
68     'glXCreateNewContext',
69     'glXMakeContextCurrent',
70     'glXQueryExtension',
71     'glXIsDirect',
72 ])
73
74
75 class Report:
76
77     def __init__(self, basedir):
78         self.basedir = basedir
79         if not os.path.exists(basedir):
80             os.makedirs(basedir)
81         self.html = open(os.path.join(basedir, 'index.html'), 'wt')
82         self._header()
83
84     def _header(self):
85         self.html.write('<html>\n')
86         self.html.write('  <body>\n')
87         self.html.write('    <table border="1">\n')
88         self.html.write('      <tr><th>Ref</th><th>Src</th><th>&Delta;</th></tr>\n')
89
90     def _image_tag(self, image):
91         url = os.path.relpath(image, self.basedir)
92         self.html.write('        <td><a href="%s"><img src="%s"/></a></td>\n' % (url, url))
93
94     def add_snapshot(self, ref_image, src_image, diff_image):
95         self.html.write('      <tr>\n')
96         self._image_tag(ref_image)
97         self._image_tag(src_image)
98         self._image_tag(diff_image)
99         self.html.write('      </tr>\n')
100         self.html.flush()
101
102     def _footer(self):
103         self.html.write('    </table>\n')
104         self.html.write('  </body>\n')
105         self.html.write('</html>\n')
106
107     def __del__(self):
108         self._footer()
109         self.html.close()
110
111
112 class TestCase:
113
114     def __init__(self, name, args, cwd=None, build=None, results = '.'):
115         self.name = name
116         self.args = args
117         self.cwd = cwd
118         self.build = build
119         self.results = results
120
121         if not os.path.exists(results):
122             os.makedirs(results)
123
124
125     def _get_build_path(self, path):
126         if self.build is not None:
127             path = os.path.abspath(os.path.join(self.build, path))
128             if not os.path.exists(path):
129                 sys.stderr.write('error: %s does not exist\n' % path)
130                 sys.exit(1)
131         return path
132
133     def run(self, report):
134
135         trace = os.path.abspath(os.path.join(self.results, self.name + '.trace'))
136
137         ld_preload = self._get_build_path('glxtrace.so')
138
139         env = os.environ.copy()
140         env['LD_PRELOAD'] = ld_preload
141         env['TRACE_FILE'] = trace
142
143         window_name = self.args[0]
144
145         p = popen(self.args, cwd=self.cwd)
146         for i in range(3):
147             time.sleep(1)
148             if p.poll() is not None:
149                 break
150             if subprocess.call(['xwininfo', '-name', window_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0:
151                 break
152         if p.returncode is None:
153             p.terminate()
154         elif p.returncode:
155             sys.stdout.write('SKIP (app)\n')
156             return
157
158         ref_image = os.path.join(self.results, self.name + '.ref.png')
159         p = popen(self.args, env=env, cwd=self.cwd)
160         try:
161             for i in range(5):
162                 time.sleep(1)
163                 if p.poll() is not None:
164                     break
165                 if subprocess.call(['xwininfo', '-name', window_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0:
166                     break
167
168             if p.returncode is None:
169                 subprocess.call("xwd -name '%s' | xwdtopnm | pnmtopng > '%s'" % (window_name, ref_image), shell=True)
170         finally:
171             if p.returncode is None:
172                 p.terminate()
173                 p.wait()
174             elif p.returncode:
175                 sys.stdout.write('FAIL (trace)\n')
176                 return
177
178         p = popen([self._get_build_path('tracedump'), trace], stdout=subprocess.PIPE)
179         call_re = re.compile('^([0-9]+) (\w+)\(')
180         swapbuffers = 0
181         flushes = 0
182         for orig_line in p.stdout:
183             orig_line = orig_line.rstrip()
184             line = ansi_strip(orig_line)
185             mo = call_re.match(line)
186             if mo:
187                 call_no = int(mo.group(1))
188                 function_name = mo.group(2)
189                 if function_name in ignored_function_names:
190                     continue
191                 if function_name == 'glXSwapBuffers':
192                     swapbuffers += 1
193                 if function_name in ('glFlush', 'glFinish'):
194                     flushes += 1
195         p.wait()
196         if p.returncode != 0:
197             sys.stdout.write('FAIL (tracedump)\n')
198             return
199
200         args = [self._get_build_path('glretrace')]
201         if swapbuffers:
202             args += ['-db']
203             frames = swapbuffers
204         else:
205             args += ['-sb']
206             frames = flushes
207         if os.path.exists(ref_image) and frames < 10:
208             snapshot_prefix = os.path.join(self.results, self.name + '.')
209             args += ['-s', snapshot_prefix]
210         else:
211             snapshot_prefix = None
212         args += [trace]
213         p = popen(args, stdout=subprocess.PIPE)
214         image_re = re.compile('^Wrote (.*\.png)$')
215         image = None
216         for line in p.stdout:
217             line = line.rstrip()
218             mo = image_re.match(line)
219             if mo:
220                 image = mo.group(1)
221         p.wait()
222         if p.returncode != 0:
223             sys.stdout.write('FAIL (glretrace)\n')
224             return
225
226         if image:
227             delta_image = os.path.join(self.results, self.name + '.diff.png')
228             p = popen([
229                 'compare',
230                 '-alpha', 'opaque',
231                 '-metric', 'AE',
232                 '-fuzz', '5%',
233                 '-dissimilarity-threshold', '1',
234                 ref_image, image, delta_image
235             ], stderr = subprocess.PIPE)
236             _, stderr = p.communicate()
237
238             try:
239                 ae = int(stderr)
240             except ValueError:
241                 ae = 9999
242
243             if ae:
244                 report.add_snapshot(ref_image, image, delta_image)
245                 sys.stdout.write('FAIL (snapshot)\n')
246                 return
247
248         sys.stdout.write('PASS\n')
249
250
251 def main():
252     # Parse command line options
253     optparser = optparse.OptionParser(
254         usage='\n\t%prog [options] -- program [args] ...',
255         version='%%prog')
256     optparser.add_option(
257         '-B', '--build', metavar='PATH',
258         type='string', dest='build', default=None,
259         help='path to apitrace build')
260     optparser.add_option(
261         '-C', '--directory', metavar='PATH',
262         type='string', dest='cwd', default=None,
263         help='change to directory')
264     optparser.add_option(
265         '-R', '--results', metavar='PATH',
266         type='string', dest='results', default='results',
267         help='results directory [default=%default]')
268
269     (options, args) = optparser.parse_args(sys.argv[1:])
270     if not args:
271         optparser.error('program must be specified')
272
273     test = TestCase(
274         name = os.path.basename(args[0]), 
275         args = args,
276         cwd = options.cwd,
277         build = options.build,
278         results = options.results,
279     )
280
281     report = Report(options.results)
282     test.run(report)
283
284
285 if __name__ == '__main__':
286     main()