]> git.cworth.org Git - apitrace/blob - scripts/retracediff.py
Script to run glretrace in parallel, comparing generated snapshots.
[apitrace] / scripts / retracediff.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 '''Run two retrace instances in parallel, comparing generated snapshots.
28 '''
29
30
31 import optparse
32 import os.path
33 import re
34 import shutil
35 import subprocess
36 import platform
37 import sys
38 import tempfile
39
40 from snapdiff import Comparer
41 import jsondiff
42
43
44 # Null file, to use when we're not interested in subprocesses output
45 if platform.system() == 'Windows':
46     NULL = open('NUL:', 'wt')
47 else:
48     NULL = open('/dev/null', 'wt')
49
50
51 class Setup:
52
53     def __init__(self, args, env=None):
54         self.args = args
55         self.env = env
56
57     def retrace(self, snapshot_dir):
58         cmd = [
59             options.retrace,
60             '-s', snapshot_dir + os.path.sep,
61             '-S', options.snapshot_frequency,
62         ] + self.args
63         p = subprocess.Popen(cmd, env=self.env, stdout=subprocess.PIPE, stderr=NULL)
64         return p
65
66     def dump_state(self, call_no):
67         '''Get the state dump at the specified call no.'''
68
69         cmd = [
70             options.retrace,
71             '-D', str(call_no),
72         ] + self.args
73         p = subprocess.Popen(cmd, env=self.env, stdout=subprocess.PIPE, stderr=NULL)
74         state = jsondiff.load(p.stdout)
75         p.wait()
76         return state
77
78
79 def diff_state(setup, ref_call_no, src_call_no):
80     ref_state = setup.dump_state(ref_call_no)
81     src_state = setup.dump_state(src_call_no)
82     sys.stdout.flush()
83     differ = jsondiff.Differ(sys.stdout)
84     differ.visit(ref_state, src_state)
85     sys.stdout.write('\n')
86
87
88 def parse_env(optparser, entries):
89     '''Translate a list of NAME=VALUE entries into an environment dictionary.'''
90
91     env = os.environ.copy()
92     for entry in entries:
93         try:
94             name, var = entry.split('=', 1)
95         except Exception:
96             optparser.error('invalid environment entry %r' % entry)
97         env[name] = var
98     return env
99
100
101 def main():
102     '''Main program.
103     '''
104
105     global options
106
107     # Parse command line options
108     optparser = optparse.OptionParser(
109         usage='\n\t%prog [options] -- [glretrace options] <trace>',
110         version='%%prog')
111     optparser.add_option(
112         '-r', '--retrace', metavar='PROGRAM',
113         type='string', dest='retrace', default='glretrace',
114         help='retrace command [default: %default]')
115     optparser.add_option(
116         '--ref-env', metavar='NAME=VALUE',
117         type='string', action='append', dest='ref_env', default=[],
118         help='reference environment variable')
119     optparser.add_option(
120         '--src-env', metavar='NAME=VALUE',
121         type='string', action='append', dest='src_env', default=[],
122         help='reference environment variable')
123     optparser.add_option(
124         '--diff-prefix', metavar='PATH',
125         type='string', dest='diff_prefix', default='.',
126         help='reference environment variable')
127     optparser.add_option(
128         '-t', '--threshold', metavar='BITS',
129         type="float", dest="threshold", default=12.0,
130         help="threshold precision  [default: %default]")
131     optparser.add_option(
132         '-S', '--snapshot-frequency', metavar='FREQUENCY',
133         type="string", dest="snapshot_frequency", default='draw',
134         help="snapshot frequency  [default: %default]")
135
136     (options, args) = optparser.parse_args(sys.argv[1:])
137     ref_env = parse_env(optparser, options.ref_env)
138     src_env = parse_env(optparser, options.src_env)
139     if not args:
140         optparser.error("incorrect number of arguments")
141
142     ref_setup = Setup(args, ref_env)
143     src_setup = Setup(args, src_env)
144
145     image_re = re.compile('^Wrote (.*\.png)$')
146
147     last_good = -1
148     last_bad = -1
149     ref_snapshot_dir = tempfile.mkdtemp()
150     try:
151         src_snapshot_dir = tempfile.mkdtemp()
152         try:
153             ref_proc = ref_setup.retrace(ref_snapshot_dir)
154             try:
155                 src_proc = src_setup.retrace(src_snapshot_dir)
156                 try:
157                     for ref_line in ref_proc.stdout:
158                         # Get the reference image
159                         ref_line = ref_line.rstrip()
160                         mo = image_re.match(ref_line)
161                         if mo:
162                             ref_image = mo.group(1)
163                             for src_line in src_proc.stdout:
164                                 # Get the source image
165                                 src_line = src_line.rstrip()
166                                 mo = image_re.match(src_line)
167                                 if mo:
168                                     src_image = mo.group(1)
169                                     
170                                     root, ext = os.path.splitext(os.path.basename(src_image))
171                                     call_no = int(root)
172
173                                     # Compare the two images
174                                     comparer = Comparer(ref_image, src_image)
175                                     precision = comparer.precision()
176
177                                     sys.stdout.write('%u %f\n' % (call_no, precision))
178                                     
179                                     if precision < options.threshold:
180                                         if options.diff_prefix:
181                                             comparer.write_diff(os.path.join(options.diff_prefix, root + '.diff.png'))
182                                         if last_bad < last_good:
183                                             diff_state(src_setup, last_good, call_no)
184                                         last_bad = call_no
185                                     else:
186                                         last_good = call_no
187
188                                     sys.stdout.flush()
189                                    
190                                     os.unlink(src_image)
191                                     break
192                             os.unlink(ref_image)
193                 finally:
194                     src_proc.terminate()
195             finally:
196                 ref_proc.terminate()
197         finally:
198             shutil.rmtree(ref_snapshot_dir)
199     finally:
200         shutil.rmtree(src_snapshot_dir)
201
202
203 if __name__ == '__main__':
204     main()