Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(292)

Side by Side Diff: tools/rebaseline_imagefiles.py

Issue 21901004: Delete image-based rebaselining tool; we have switched to checksums (Closed) Base URL: http://skia.googlecode.com/svn/trunk/
Patch Set: actually_remove_image_based_code Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 #!/usr/bin/python
2
3 '''
4 Copyright 2013 Google Inc.
5
6 Use of this source code is governed by a BSD-style license that can be
7 found in the LICENSE file.
8 '''
9
10 '''
11 Rebaselines GM test results as individual image files
12 (the "old way", before https://goto.google.com/ChecksumTransitionDetail ).
13
14 Once we have switched our expectations to JSON form for all platforms,
15 we can delete this file.
16
17 There is a lot of code duplicated between here and rebaseline.py, but
18 that's fine because we will delete this file soon.
19
20 TODO(epoger): Fix indentation in this file (2-space indents, not 4-space).
21 '''
22
23 # System-level imports
24 import os
25 import re
26 import subprocess
27 import sys
28 import urllib2
29
30 # Imports from within Skia
31 #
32 # We need to add the 'gm' directory, so that we can import gm_json.py within
33 # that directory. That script allows us to parse the actual-results.json file
34 # written out by the GM tool.
35 # Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end*
36 # so any dirs that are already in the PYTHONPATH will be preferred.
37 #
38 # This assumes that the 'gm' directory has been checked out as a sibling of
39 # the 'tools' directory containing this script, which will be the case if
40 # 'trunk' was checked out as a single unit.
41 GM_DIRECTORY = os.path.realpath(
42 os.path.join(os.path.dirname(os.path.dirname(__file__)), 'gm'))
43 if GM_DIRECTORY not in sys.path:
44 sys.path.append(GM_DIRECTORY)
45 import gm_json
46
47
48 class CommandFailedException(Exception):
49 pass
50
51 class ImageRebaseliner(object):
52
53 # params:
54 # expectations_root: root directory of all expectations
55 # json_base_url: base URL from which to read json_filename
56 # json_filename: filename (under json_base_url) from which to read a
57 # summary of results; typically "actual-results.json"
58 # exception_handler: reference to rebaseline.ExceptionHandler object
59 # tests: list of tests to rebaseline, or None if we should rebaseline
60 # whatever files the JSON results summary file tells us to
61 # configs: which configs to run for each test, or None if we should
62 # rebaseline whatever configs the JSON results summary file tells
63 # us to
64 # dry_run: if True, instead of actually downloading files or adding
65 # files to checkout, display a list of operations that
66 # we would normally perform
67 # add_new: if True, add expectations for tests which don't have any yet
68 # missing_json_is_fatal: whether to halt execution if we cannot read a
69 # JSON actual result summary file
70 def __init__(self, expectations_root, json_base_url, json_filename,
71 exception_handler, tests=None, configs=None, dry_run=False,
72 add_new=False, missing_json_is_fatal=False):
73 self._expectations_root = expectations_root
74 self._tests = tests
75 self._configs = configs
76 self._json_base_url = json_base_url
77 self._json_filename = json_filename
78 self._exception_handler = exception_handler
79 self._dry_run = dry_run
80 self._add_new = add_new
81 self._missing_json_is_fatal = missing_json_is_fatal
82 self._image_filename_re = re.compile(gm_json.IMAGE_FILENAME_PATTERN)
83 self._is_svn_checkout = (
84 os.path.exists(os.path.join(expectations_root, '.svn')) or
85 os.path.exists(os.path.join(expectations_root, os.pardir, '.svn')))
86 self._is_git_checkout = (
87 os.path.exists(os.path.join(expectations_root, '.git')) or
88 os.path.exists(os.path.join(expectations_root, os.pardir, '.git')))
89
90 # If dry_run is False, execute subprocess.call(cmd).
91 # If dry_run is True, print the command we would have otherwise run.
92 # Raises a CommandFailedException if the command fails.
93 def _Call(self, cmd):
94 if self._dry_run:
95 print '%s' % ' '.join(cmd)
96 return
97 if subprocess.call(cmd) != 0:
98 raise CommandFailedException('error running command: ' +
99 ' '.join(cmd))
100
101 # Download a single actual result from GoogleStorage.
102 # Raises an exception if it fails.
103 def _DownloadFromGoogleStorage(self, infilename, outfilename, all_results):
104 test_name = self._image_filename_re.match(infilename).group(1)
105 if not test_name:
106 raise Exception('unable to find test_name for infilename %s' %
107 infilename)
108 try:
109 hash_type, hash_value = all_results[infilename]
110 except KeyError:
111 raise Exception('unable to find filename %s in all_results dict' %
112 infilename)
113 except ValueError as e:
114 raise Exception(
115 'ValueError reading filename %s from all_results dict: %s' % (
116 infilename, e))
117 url = gm_json.CreateGmActualUrl(
118 test_name=test_name, hash_type=hash_type, hash_digest=hash_value)
119 try:
120 self._DownloadFile(source_url=url, dest_filename=outfilename)
121 except CommandFailedException:
122 raise Exception('Couldn\'t fetch gs_url %s as outfile %s' % (
123 url, outfilename))
124
125 # Download a single file, raising a CommandFailedException if it fails.
126 def _DownloadFile(self, source_url, dest_filename):
127 # Download into a temporary file and then rename it afterwards,
128 # so that we don't corrupt the existing file if it fails midway thru.
129 temp_filename = os.path.join(os.path.dirname(dest_filename),
130 '.temp-' + os.path.basename(dest_filename))
131
132 # TODO(epoger): Replace calls to "curl"/"mv" (which will only work on
133 # Unix) with a Python HTTP library (which should work cross-platform)
134 self._Call([ 'curl', '--fail', '--silent', source_url,
135 '--output', temp_filename ])
136 self._Call([ 'mv', temp_filename, dest_filename ])
137
138 # Returns the full contents of a URL, as a single string.
139 #
140 # Unlike standard URL handling, we allow relative "file:" URLs;
141 # for example, "file:one/two" resolves to the file ./one/two
142 # (relative to current working dir)
143 def _GetContentsOfUrl(self, url):
144 file_prefix = 'file:'
145 if url.startswith(file_prefix):
146 filename = url[len(file_prefix):]
147 return open(filename, 'r').read()
148 else:
149 return urllib2.urlopen(url).read()
150
151 # Returns a dictionary of actual results from actual-results.json file.
152 #
153 # The dictionary returned has this format:
154 # {
155 # u'imageblur_565.png': [u'bitmap-64bitMD5', 3359963596899141322],
156 # u'imageblur_8888.png': [u'bitmap-64bitMD5', 4217923806027861152],
157 # u'shadertext3_8888.png': [u'bitmap-64bitMD5', 3713708307125704716]
158 # }
159 #
160 # If the JSON actual result summary file cannot be loaded, the behavior
161 # depends on self._missing_json_is_fatal:
162 # - if true: execution will halt with an exception
163 # - if false: we will log an error message but return an empty dictionary
164 #
165 # params:
166 # json_url: URL pointing to a JSON actual result summary file
167 # sections: a list of section names to include in the results, e.g.
168 # [gm_json.JSONKEY_ACTUALRESULTS_FAILED,
169 # gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON] ;
170 # if None, then include ALL sections.
171 def _GetActualResults(self, json_url, sections=None):
172 try:
173 json_contents = self._GetContentsOfUrl(json_url)
174 except (urllib2.HTTPError, IOError):
175 message = 'unable to load JSON summary URL %s' % json_url
176 if self._missing_json_is_fatal:
177 raise ValueError(message)
178 else:
179 print '# %s' % message
180 return {}
181
182 json_dict = gm_json.LoadFromString(json_contents)
183 results_to_return = {}
184 actual_results = json_dict[gm_json.JSONKEY_ACTUALRESULTS]
185 if not sections:
186 sections = actual_results.keys()
187 for section in sections:
188 section_results = actual_results[section]
189 if section_results:
190 results_to_return.update(section_results)
191 return results_to_return
192
193 # Returns a list of files that require rebaselining.
194 #
195 # Note that this returns a list of FILES, like this:
196 # ['imageblur_565.png', 'xfermodes_pdf.png']
197 # rather than a list of TESTS, like this:
198 # ['imageblur', 'xfermodes']
199 #
200 # params:
201 # json_url: URL pointing to a JSON actual result summary file
202 # add_new: if True, then return files listed in any of these sections:
203 # - JSONKEY_ACTUALRESULTS_FAILED
204 # - JSONKEY_ACTUALRESULTS_NOCOMPARISON
205 # if False, then return files listed in these sections:
206 # - JSONKEY_ACTUALRESULTS_FAILED
207 #
208 def _GetFilesToRebaseline(self, json_url, add_new):
209 if self._dry_run:
210 print ''
211 print '#'
212 print ('# Getting files to rebaseline from JSON summary URL %s ...'
213 % json_url)
214 sections = [gm_json.JSONKEY_ACTUALRESULTS_FAILED]
215 if add_new:
216 sections.append(gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON)
217 results_to_rebaseline = self._GetActualResults(json_url=json_url,
218 sections=sections)
219 files_to_rebaseline = results_to_rebaseline.keys()
220 files_to_rebaseline.sort()
221 print '# ... found files_to_rebaseline %s' % files_to_rebaseline
222 if self._dry_run:
223 print '#'
224 return files_to_rebaseline
225
226 # Rebaseline a single file.
227 def _RebaselineOneFile(self, expectations_subdir, builder_name,
228 infilename, outfilename, all_results):
229 if self._dry_run:
230 print ''
231 print '# ' + infilename
232
233 # Download this result image from Google Storage.
234 # If it fails, an exception will be raised.
235 self._DownloadFromGoogleStorage(infilename=infilename,
236 outfilename=outfilename,
237 all_results=all_results)
238
239 # Add this file to version control (if appropriate).
240 if self._add_new:
241 if self._is_svn_checkout:
242 cmd = [ 'svn', 'add', '--quiet', outfilename ]
243 self._Call(cmd)
244 cmd = [ 'svn', 'propset', '--quiet', 'svn:mime-type',
245 'image/png', outfilename ];
246 self._Call(cmd)
247 elif self._is_git_checkout:
248 cmd = [ 'git', 'add', outfilename ]
249 self._Call(cmd)
250
251 # Rebaseline all tests/types we specified in the constructor,
252 # within this gm-expectations subdir.
253 #
254 # params:
255 # subdir : e.g. 'base-shuttle-win7-intel-float'
256 # builder : e.g. 'Test-Win7-ShuttleA-HD2000-x86-Release'
257 def RebaselineSubdir(self, subdir, builder):
258 if not os.path.isdir(os.path.join(self._expectations_root, subdir)):
259 self._exception_handler.RaiseExceptionOrContinue(Exception((
260 'Could not find "%s" subdir within expectations_root "%s". ' +
261 'Are you sure --expectations-root is pointing at a valid ' +
262 'gm-expected directory?') % (subdir, self._expectations_root)))
263 return
264
265 json_url = '/'.join([self._json_base_url,
266 subdir, builder, subdir,
267 self._json_filename])
268 all_results = self._GetActualResults(json_url=json_url)
269 filenames = self._GetFilesToRebaseline(json_url=json_url,
270 add_new=self._add_new)
271 skipped_files = []
272 for filename in filenames:
273 (test, config) = self._image_filename_re.match(filename).groups()
274 if self._tests:
275 if test not in self._tests:
276 skipped_files.append(filename)
277 continue
278 if self._configs:
279 if config not in self._configs:
280 skipped_files.append(filename)
281 continue
282 outfilename = os.path.join(self._expectations_root, subdir,
283 filename);
284 try:
285 self._RebaselineOneFile(expectations_subdir=subdir,
286 builder_name=builder,
287 infilename=filename,
288 outfilename=outfilename,
289 all_results=all_results)
290 except BaseException as e:
291 self._exception_handler.RaiseExceptionOrContinue(e)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698