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

Side by Side Diff: gm/rebaseline_server/results.py

Issue 208243003: rebaseline_server: rename results.py to compare_to_expectations.py (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: rebase Created 6 years, 9 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
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 2
3 """ 3 """
4 Copyright 2013 Google Inc. 4 Copyright 2013 Google Inc.
5 5
6 Use of this source code is governed by a BSD-style license that can be 6 Use of this source code is governed by a BSD-style license that can be
7 found in the LICENSE file. 7 found in the LICENSE file.
8 8
9 Repackage expected/actual GM results as needed by our HTML rebaseline viewer. 9 Repackage expected/actual GM results as needed by our HTML rebaseline viewer.
10 """ 10 """
11 11
12 # System-level imports 12 # System-level imports
13 import argparse
14 import fnmatch
15 import json
16 import logging
17 import os 13 import os
18 import re 14 import re
19 import sys 15 import sys
20 import time
21 16
22 # Imports from within Skia 17 # Imports from within Skia
23 # 18 #
24 # TODO(epoger): Once we move the create_filepath_url() function out of
25 # download_actuals into a shared utility module, we won't need to import
26 # download_actuals anymore.
27 #
28 # We need to add the 'gm' directory, so that we can import gm_json.py within 19 # We need to add the 'gm' directory, so that we can import gm_json.py within
29 # that directory. That script allows us to parse the actual-results.json file 20 # that directory. That script allows us to parse the actual-results.json file
30 # written out by the GM tool. 21 # written out by the GM tool.
31 # Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end* 22 # Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end*
32 # so any dirs that are already in the PYTHONPATH will be preferred. 23 # so any dirs that are already in the PYTHONPATH will be preferred.
33 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) 24 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
34 GM_DIRECTORY = os.path.dirname(PARENT_DIRECTORY) 25 GM_DIRECTORY = os.path.dirname(PARENT_DIRECTORY)
35 TRUNK_DIRECTORY = os.path.dirname(GM_DIRECTORY)
36 if GM_DIRECTORY not in sys.path: 26 if GM_DIRECTORY not in sys.path:
37 sys.path.append(GM_DIRECTORY) 27 sys.path.append(GM_DIRECTORY)
38 import download_actuals
39 import gm_json 28 import gm_json
40 import imagediffdb
41 import imagepair
42 import imagepairset
43 29
44 # Keys used to link an image to a particular GM test. 30 # Keys used to link an image to a particular GM test.
45 # NOTE: Keep these in sync with static/constants.js 31 # NOTE: Keep these in sync with static/constants.js
46 REBASELINE_SERVER_SCHEMA_VERSION_NUMBER = 2 32 REBASELINE_SERVER_SCHEMA_VERSION_NUMBER = 2
47 KEY__EXPECTATIONS__BUGS = gm_json.JSONKEY_EXPECTEDRESULTS_BUGS 33 KEY__EXPECTATIONS__BUGS = gm_json.JSONKEY_EXPECTEDRESULTS_BUGS
48 KEY__EXPECTATIONS__IGNOREFAILURE = gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE 34 KEY__EXPECTATIONS__IGNOREFAILURE = gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE
49 KEY__EXPECTATIONS__REVIEWED = gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED 35 KEY__EXPECTATIONS__REVIEWED = gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED
50 KEY__EXTRACOLUMN__BUILDER = 'builder' 36 KEY__EXTRACOLUMN__BUILDER = 'builder'
51 KEY__EXTRACOLUMN__CONFIG = 'config' 37 KEY__EXTRACOLUMN__CONFIG = 'config'
52 KEY__EXTRACOLUMN__RESULT_TYPE = 'resultType' 38 KEY__EXTRACOLUMN__RESULT_TYPE = 'resultType'
53 KEY__EXTRACOLUMN__TEST = 'test' 39 KEY__EXTRACOLUMN__TEST = 'test'
54 KEY__HEADER = 'header' 40 KEY__HEADER = 'header'
55 KEY__HEADER__DATAHASH = 'dataHash' 41 KEY__HEADER__DATAHASH = 'dataHash'
56 KEY__HEADER__IS_EDITABLE = 'isEditable' 42 KEY__HEADER__IS_EDITABLE = 'isEditable'
57 KEY__HEADER__IS_EXPORTED = 'isExported' 43 KEY__HEADER__IS_EXPORTED = 'isExported'
58 KEY__HEADER__IS_STILL_LOADING = 'resultsStillLoading' 44 KEY__HEADER__IS_STILL_LOADING = 'resultsStillLoading'
59 KEY__HEADER__RESULTS_ALL = 'all' 45 KEY__HEADER__RESULTS_ALL = 'all'
60 KEY__HEADER__RESULTS_FAILURES = 'failures' 46 KEY__HEADER__RESULTS_FAILURES = 'failures'
61 KEY__HEADER__SCHEMA_VERSION = 'schemaVersion' 47 KEY__HEADER__SCHEMA_VERSION = 'schemaVersion'
62 KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE = 'timeNextUpdateAvailable' 48 KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE = 'timeNextUpdateAvailable'
63 KEY__HEADER__TIME_UPDATED = 'timeUpdated' 49 KEY__HEADER__TIME_UPDATED = 'timeUpdated'
64 KEY__HEADER__TYPE = 'type' 50 KEY__HEADER__TYPE = 'type'
65 KEY__NEW_IMAGE_URL = 'newImageUrl' 51 KEY__NEW_IMAGE_URL = 'newImageUrl'
66 KEY__RESULT_TYPE__FAILED = gm_json.JSONKEY_ACTUALRESULTS_FAILED 52 KEY__RESULT_TYPE__FAILED = gm_json.JSONKEY_ACTUALRESULTS_FAILED
67 KEY__RESULT_TYPE__FAILUREIGNORED = gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED 53 KEY__RESULT_TYPE__FAILUREIGNORED = gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED
68 KEY__RESULT_TYPE__NOCOMPARISON = gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON 54 KEY__RESULT_TYPE__NOCOMPARISON = gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON
69 KEY__RESULT_TYPE__SUCCEEDED = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED 55 KEY__RESULT_TYPE__SUCCEEDED = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED
70 56
71 EXPECTATION_FIELDS_PASSED_THRU_VERBATIM = [
72 KEY__EXPECTATIONS__BUGS,
73 KEY__EXPECTATIONS__IGNOREFAILURE,
74 KEY__EXPECTATIONS__REVIEWED,
75 ]
76
77 IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN) 57 IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN)
78 IMAGE_FILENAME_FORMATTER = '%s_%s.png' # pass in (testname, config) 58 IMAGE_FILENAME_FORMATTER = '%s_%s.png' # pass in (testname, config)
79
80 IMAGEPAIR_SET_DESCRIPTIONS = ('expected image', 'actual image')
81
82 DEFAULT_ACTUALS_DIR = '.gm-actuals'
83 DEFAULT_EXPECTATIONS_DIR = os.path.join(TRUNK_DIRECTORY, 'expectations', 'gm')
84 DEFAULT_GENERATED_IMAGES_ROOT = os.path.join(
85 PARENT_DIRECTORY, '.generated-images')
86
87
88 class Results(object):
89 """ Loads actual and expected GM results into an ImagePairSet.
90
91 Loads actual and expected results from all builders, except for those skipped
92 by _ignore_builder().
93
94 Once this object has been constructed, the results (in self._results[])
95 are immutable. If you want to update the results based on updated JSON
96 file contents, you will need to create a new Results object."""
97
98 def __init__(self, actuals_root=DEFAULT_ACTUALS_DIR,
99 expected_root=DEFAULT_EXPECTATIONS_DIR,
100 generated_images_root=DEFAULT_GENERATED_IMAGES_ROOT,
101 diff_base_url=None):
102 """
103 Args:
104 actuals_root: root directory containing all actual-results.json files
105 expected_root: root directory containing all expected-results.json files
106 generated_images_root: directory within which to create all pixel diffs;
107 if this directory does not yet exist, it will be created
108 diff_base_url: base URL within which the client should look for diff
109 images; if not specified, defaults to a "file:///" URL representation
110 of generated_images_root
111 """
112 time_start = int(time.time())
113 self._image_diff_db = imagediffdb.ImageDiffDB(generated_images_root)
114 self._diff_base_url = (
115 diff_base_url or
116 download_actuals.create_filepath_url(generated_images_root))
117 self._actuals_root = actuals_root
118 self._expected_root = expected_root
119 self._load_actual_and_expected()
120 self._timestamp = int(time.time())
121 logging.info('Results complete; took %d seconds.' %
122 (self._timestamp - time_start))
123
124 def get_timestamp(self):
125 """Return the time at which this object was created, in seconds past epoch
126 (UTC).
127 """
128 return self._timestamp
129
130 def edit_expectations(self, modifications):
131 """Edit the expectations stored within this object and write them back
132 to disk.
133
134 Note that this will NOT update the results stored in self._results[] ;
135 in order to see those updates, you must instantiate a new Results object
136 based on the (now updated) files on disk.
137
138 Args:
139 modifications: a list of dictionaries, one for each expectation to update:
140
141 [
142 {
143 imagepair.KEY__EXPECTATIONS_DATA: {
144 KEY__EXPECTATIONS__BUGS: [123, 456],
145 KEY__EXPECTATIONS__IGNOREFAILURE: false,
146 KEY__EXPECTATIONS__REVIEWED: true,
147 },
148 imagepair.KEY__EXTRA_COLUMN_VALUES: {
149 KEY__EXTRACOLUMN__BUILDER: 'Test-Mac10.6-MacMini4.1-GeForce320M-x 86-Debug',
150 KEY__EXTRACOLUMN__CONFIG: '8888',
151 KEY__EXTRACOLUMN__TEST: 'bigmatrix',
152 },
153 KEY__NEW_IMAGE_URL: 'bitmap-64bitMD5/bigmatrix/10894408024079689926 .png',
154 },
155 ...
156 ]
157
158 """
159 expected_builder_dicts = Results._read_dicts_from_root(self._expected_root)
160 for mod in modifications:
161 image_name = IMAGE_FILENAME_FORMATTER % (
162 mod[imagepair.KEY__EXTRA_COLUMN_VALUES][KEY__EXTRACOLUMN__TEST],
163 mod[imagepair.KEY__EXTRA_COLUMN_VALUES][KEY__EXTRACOLUMN__CONFIG])
164 _, hash_type, hash_digest = gm_json.SplitGmRelativeUrl(
165 mod[KEY__NEW_IMAGE_URL])
166 allowed_digests = [[hash_type, int(hash_digest)]]
167 new_expectations = {
168 gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS: allowed_digests,
169 }
170 for field in EXPECTATION_FIELDS_PASSED_THRU_VERBATIM:
171 value = mod[imagepair.KEY__EXPECTATIONS_DATA].get(field)
172 if value is not None:
173 new_expectations[field] = value
174 builder_dict = expected_builder_dicts[
175 mod[imagepair.KEY__EXTRA_COLUMN_VALUES][KEY__EXTRACOLUMN__BUILDER]]
176 builder_expectations = builder_dict.get(gm_json.JSONKEY_EXPECTEDRESULTS)
177 if not builder_expectations:
178 builder_expectations = {}
179 builder_dict[gm_json.JSONKEY_EXPECTEDRESULTS] = builder_expectations
180 builder_expectations[image_name] = new_expectations
181 Results._write_dicts_to_root(expected_builder_dicts, self._expected_root)
182
183 def get_results_of_type(self, results_type):
184 """Return results of some/all tests (depending on 'results_type' parameter).
185
186 Args:
187 results_type: string describing which types of results to include; must
188 be one of the RESULTS_* constants
189
190 Results are returned in a dictionary as output by ImagePairSet.as_dict().
191 """
192 return self._results[results_type]
193
194 def get_packaged_results_of_type(self, results_type, reload_seconds=None,
195 is_editable=False, is_exported=True):
196 """ Package the results of some/all tests as a complete response_dict.
197
198 Args:
199 results_type: string indicating which set of results to return;
200 must be one of the RESULTS_* constants
201 reload_seconds: if specified, note that new results may be available once
202 these results are reload_seconds old
203 is_editable: whether clients are allowed to submit new baselines
204 is_exported: whether these results are being made available to other
205 network hosts
206 """
207 response_dict = self._results[results_type]
208 time_updated = self.get_timestamp()
209 response_dict[KEY__HEADER] = {
210 KEY__HEADER__SCHEMA_VERSION: REBASELINE_SERVER_SCHEMA_VERSION_NUMBER,
211
212 # Timestamps:
213 # 1. when this data was last updated
214 # 2. when the caller should check back for new data (if ever)
215 KEY__HEADER__TIME_UPDATED: time_updated,
216 KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: (
217 (time_updated+reload_seconds) if reload_seconds else None),
218
219 # The type we passed to get_results_of_type()
220 KEY__HEADER__TYPE: results_type,
221
222 # Hash of dataset, which the client must return with any edits--
223 # this ensures that the edits were made to a particular dataset.
224 KEY__HEADER__DATAHASH: str(hash(repr(
225 response_dict[imagepairset.KEY__IMAGEPAIRS]))),
226
227 # Whether the server will accept edits back.
228 KEY__HEADER__IS_EDITABLE: is_editable,
229
230 # Whether the service is accessible from other hosts.
231 KEY__HEADER__IS_EXPORTED: is_exported,
232 }
233 return response_dict
234
235 @staticmethod
236 def _ignore_builder(builder):
237 """Returns True if we should ignore expectations and actuals for a builder.
238
239 This allows us to ignore builders for which we don't maintain expectations
240 (trybots, Valgrind, ASAN, TSAN), and avoid problems like
241 https://code.google.com/p/skia/issues/detail?id=2036 ('rebaseline_server
242 produces error when trying to add baselines for ASAN/TSAN builders')
243
244 Args:
245 builder: name of this builder, as a string
246
247 Returns:
248 True if we should ignore expectations and actuals for this builder.
249 """
250 return (builder.endswith('-Trybot') or
251 ('Valgrind' in builder) or
252 ('TSAN' in builder) or
253 ('ASAN' in builder))
254
255 @staticmethod
256 def _read_dicts_from_root(root, pattern='*.json'):
257 """Read all JSON dictionaries within a directory tree.
258
259 Args:
260 root: path to root of directory tree
261 pattern: which files to read within root (fnmatch-style pattern)
262
263 Returns:
264 A meta-dictionary containing all the JSON dictionaries found within
265 the directory tree, keyed by the builder name of each dictionary.
266
267 Raises:
268 IOError if root does not refer to an existing directory
269 """
270 if not os.path.isdir(root):
271 raise IOError('no directory found at path %s' % root)
272 meta_dict = {}
273 for dirpath, dirnames, filenames in os.walk(root):
274 for matching_filename in fnmatch.filter(filenames, pattern):
275 builder = os.path.basename(dirpath)
276 if Results._ignore_builder(builder):
277 continue
278 fullpath = os.path.join(dirpath, matching_filename)
279 meta_dict[builder] = gm_json.LoadFromFile(fullpath)
280 return meta_dict
281
282 @staticmethod
283 def _create_relative_url(hashtype_and_digest, test_name):
284 """Returns the URL for this image, relative to GM_ACTUALS_ROOT_HTTP_URL.
285
286 If we don't have a record of this image, returns None.
287
288 Args:
289 hashtype_and_digest: (hash_type, hash_digest) tuple, or None if we
290 don't have a record of this image
291 test_name: string; name of the GM test that created this image
292 """
293 if not hashtype_and_digest:
294 return None
295 return gm_json.CreateGmRelativeUrl(
296 test_name=test_name,
297 hash_type=hashtype_and_digest[0],
298 hash_digest=hashtype_and_digest[1])
299
300 @staticmethod
301 def _write_dicts_to_root(meta_dict, root, pattern='*.json'):
302 """Write all per-builder dictionaries within meta_dict to files under
303 the root path.
304
305 Security note: this will only write to files that already exist within
306 the root path (as found by os.walk() within root), so we don't need to
307 worry about malformed content writing to disk outside of root.
308 However, the data written to those files is not double-checked, so it
309 could contain poisonous data.
310
311 Args:
312 meta_dict: a builder-keyed meta-dictionary containing all the JSON
313 dictionaries we want to write out
314 root: path to root of directory tree within which to write files
315 pattern: which files to write within root (fnmatch-style pattern)
316
317 Raises:
318 IOError if root does not refer to an existing directory
319 KeyError if the set of per-builder dictionaries written out was
320 different than expected
321 """
322 if not os.path.isdir(root):
323 raise IOError('no directory found at path %s' % root)
324 actual_builders_written = []
325 for dirpath, dirnames, filenames in os.walk(root):
326 for matching_filename in fnmatch.filter(filenames, pattern):
327 builder = os.path.basename(dirpath)
328 if Results._ignore_builder(builder):
329 continue
330 per_builder_dict = meta_dict.get(builder)
331 if per_builder_dict is not None:
332 fullpath = os.path.join(dirpath, matching_filename)
333 gm_json.WriteToFile(per_builder_dict, fullpath)
334 actual_builders_written.append(builder)
335
336 # Check: did we write out the set of per-builder dictionaries we
337 # expected to?
338 expected_builders_written = sorted(meta_dict.keys())
339 actual_builders_written.sort()
340 if expected_builders_written != actual_builders_written:
341 raise KeyError(
342 'expected to write dicts for builders %s, but actually wrote them '
343 'for builders %s' % (
344 expected_builders_written, actual_builders_written))
345
346 def _load_actual_and_expected(self):
347 """Loads the results of all tests, across all builders (based on the
348 files within self._actuals_root and self._expected_root),
349 and stores them in self._results.
350 """
351 logging.info('Reading actual-results JSON files from %s...' %
352 self._actuals_root)
353 actual_builder_dicts = Results._read_dicts_from_root(self._actuals_root)
354 logging.info('Reading expected-results JSON files from %s...' %
355 self._expected_root)
356 expected_builder_dicts = Results._read_dicts_from_root(self._expected_root)
357
358 all_image_pairs = imagepairset.ImagePairSet(
359 descriptions=IMAGEPAIR_SET_DESCRIPTIONS,
360 diff_base_url=self._diff_base_url)
361 failing_image_pairs = imagepairset.ImagePairSet(
362 descriptions=IMAGEPAIR_SET_DESCRIPTIONS,
363 diff_base_url=self._diff_base_url)
364
365 all_image_pairs.ensure_extra_column_values_in_summary(
366 column_id=KEY__EXTRACOLUMN__RESULT_TYPE, values=[
367 KEY__RESULT_TYPE__FAILED,
368 KEY__RESULT_TYPE__FAILUREIGNORED,
369 KEY__RESULT_TYPE__NOCOMPARISON,
370 KEY__RESULT_TYPE__SUCCEEDED,
371 ])
372 failing_image_pairs.ensure_extra_column_values_in_summary(
373 column_id=KEY__EXTRACOLUMN__RESULT_TYPE, values=[
374 KEY__RESULT_TYPE__FAILED,
375 KEY__RESULT_TYPE__FAILUREIGNORED,
376 KEY__RESULT_TYPE__NOCOMPARISON,
377 ])
378
379 builders = sorted(actual_builder_dicts.keys())
380 num_builders = len(builders)
381 builder_num = 0
382 for builder in builders:
383 builder_num += 1
384 logging.info('Generating pixel diffs for builder #%d of %d, "%s"...' %
385 (builder_num, num_builders, builder))
386 actual_results_for_this_builder = (
387 actual_builder_dicts[builder][gm_json.JSONKEY_ACTUALRESULTS])
388 for result_type in sorted(actual_results_for_this_builder.keys()):
389 results_of_this_type = actual_results_for_this_builder[result_type]
390 if not results_of_this_type:
391 continue
392 for image_name in sorted(results_of_this_type.keys()):
393 (test, config) = IMAGE_FILENAME_RE.match(image_name).groups()
394 actual_image_relative_url = Results._create_relative_url(
395 hashtype_and_digest=results_of_this_type[image_name],
396 test_name=test)
397
398 # Default empty expectations; overwrite these if we find any real ones
399 expectations_per_test = None
400 expected_image_relative_url = None
401 expectations_dict = None
402 try:
403 expectations_per_test = (
404 expected_builder_dicts
405 [builder][gm_json.JSONKEY_EXPECTEDRESULTS][image_name])
406 # TODO(epoger): assumes a single allowed digest per test, which is
407 # fine; see https://code.google.com/p/skia/issues/detail?id=1787
408 expected_image_hashtype_and_digest = (
409 expectations_per_test
410 [gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS][0])
411 expected_image_relative_url = Results._create_relative_url(
412 hashtype_and_digest=expected_image_hashtype_and_digest,
413 test_name=test)
414 expectations_dict = {}
415 for field in EXPECTATION_FIELDS_PASSED_THRU_VERBATIM:
416 expectations_dict[field] = expectations_per_test.get(field)
417 except (KeyError, TypeError):
418 # There are several cases in which we would expect to find
419 # no expectations for a given test:
420 #
421 # 1. result_type == NOCOMPARISON
422 # There are no expectations for this test yet!
423 #
424 # 2. alternate rendering mode failures (e.g. serialized)
425 # In cases like
426 # https://code.google.com/p/skia/issues/detail?id=1684
427 # ('tileimagefilter GM test failing in serialized render mode'),
428 # the gm-actuals will list a failure for the alternate
429 # rendering mode even though we don't have explicit expectations
430 # for the test (the implicit expectation is that it must
431 # render the same in all rendering modes).
432 #
433 # Don't log type 1, because it is common.
434 # Log other types, because they are rare and we should know about
435 # them, but don't throw an exception, because we need to keep our
436 # tools working in the meanwhile!
437 if result_type != KEY__RESULT_TYPE__NOCOMPARISON:
438 logging.warning('No expectations found for test: %s' % {
439 KEY__EXTRACOLUMN__BUILDER: builder,
440 KEY__EXTRACOLUMN__RESULT_TYPE: result_type,
441 'image_name': image_name,
442 })
443
444 # If this test was recently rebaselined, it will remain in
445 # the 'failed' set of actuals until all the bots have
446 # cycled (although the expectations have indeed been set
447 # from the most recent actuals). Treat these as successes
448 # instead of failures.
449 #
450 # TODO(epoger): Do we need to do something similar in
451 # other cases, such as when we have recently marked a test
452 # as ignoreFailure but it still shows up in the 'failed'
453 # category? Maybe we should not rely on the result_type
454 # categories recorded within the gm_actuals AT ALL, and
455 # instead evaluate the result_type ourselves based on what
456 # we see in expectations vs actual checksum?
457 if expected_image_relative_url == actual_image_relative_url:
458 updated_result_type = KEY__RESULT_TYPE__SUCCEEDED
459 else:
460 updated_result_type = result_type
461 extra_columns_dict = {
462 KEY__EXTRACOLUMN__RESULT_TYPE: updated_result_type,
463 KEY__EXTRACOLUMN__BUILDER: builder,
464 KEY__EXTRACOLUMN__TEST: test,
465 KEY__EXTRACOLUMN__CONFIG: config,
466 }
467 try:
468 image_pair = imagepair.ImagePair(
469 image_diff_db=self._image_diff_db,
470 base_url=gm_json.GM_ACTUALS_ROOT_HTTP_URL,
471 imageA_relative_url=expected_image_relative_url,
472 imageB_relative_url=actual_image_relative_url,
473 expectations=expectations_dict,
474 extra_columns=extra_columns_dict)
475 all_image_pairs.add_image_pair(image_pair)
476 if updated_result_type != KEY__RESULT_TYPE__SUCCEEDED:
477 failing_image_pairs.add_image_pair(image_pair)
478 except Exception:
479 logging.exception('got exception while creating new ImagePair')
480
481 self._results = {
482 KEY__HEADER__RESULTS_ALL: all_image_pairs.as_dict(),
483 KEY__HEADER__RESULTS_FAILURES: failing_image_pairs.as_dict(),
484 }
485
486
487 def main():
488 logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
489 datefmt='%m/%d/%Y %H:%M:%S',
490 level=logging.INFO)
491 parser = argparse.ArgumentParser()
492 parser.add_argument(
493 '--actuals', default=DEFAULT_ACTUALS_DIR,
494 help='Directory containing all actual-result JSON files')
495 parser.add_argument(
496 '--expectations', default=DEFAULT_EXPECTATIONS_DIR,
497 help='Directory containing all expected-result JSON files; defaults to '
498 '\'%(default)s\' .')
499 parser.add_argument(
500 '--outfile', required=True,
501 help='File to write result summary into, in JSON format.')
502 parser.add_argument(
503 '--results', default=KEY__HEADER__RESULTS_FAILURES,
504 help='Which result types to include. Defaults to \'%(default)s\'; '
505 'must be one of ' +
506 str([KEY__HEADER__RESULTS_FAILURES, KEY__HEADER__RESULTS_ALL]))
507 parser.add_argument(
508 '--workdir', default=DEFAULT_GENERATED_IMAGES_ROOT,
509 help='Directory within which to download images and generate diffs; '
510 'defaults to \'%(default)s\' .')
511 args = parser.parse_args()
512 results = Results(actuals_root=args.actuals,
513 expected_root=args.expectations,
514 generated_images_root=args.workdir)
515 gm_json.WriteToFile(
516 results.get_packaged_results_of_type(results_type=args.results),
517 args.outfile)
518
519
520 if __name__ == '__main__':
521 main()
OLDNEW
« no previous file with comments | « gm/rebaseline_server/compare_to_expectations_test.py ('k') | gm/rebaseline_server/results_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698