blob: c7915f261df9708adb95ce4cf63cfbe0a9d704df [file] [log] [blame]
epoger@google.comf9d134d2013-09-27 15:02:44 +00001#!/usr/bin/python
2
epoger@google.com9fb6c8a2013-10-09 18:05:58 +00003"""
epoger@google.comf9d134d2013-09-27 15:02:44 +00004Copyright 2013 Google Inc.
5
6Use of this source code is governed by a BSD-style license that can be
7found in the LICENSE file.
epoger@google.comf9d134d2013-09-27 15:02:44 +00008
epoger@google.comf9d134d2013-09-27 15:02:44 +00009Repackage expected/actual GM results as needed by our HTML rebaseline viewer.
epoger@google.com9fb6c8a2013-10-09 18:05:58 +000010"""
epoger@google.comf9d134d2013-09-27 15:02:44 +000011
12# System-level imports
commit-bot@chromium.org7b06c8e2013-12-23 22:47:15 +000013import argparse
epoger@google.comf9d134d2013-09-27 15:02:44 +000014import fnmatch
15import json
epoger@google.comdcb4e652013-10-11 18:45:33 +000016import logging
epoger@google.comf9d134d2013-09-27 15:02:44 +000017import os
18import re
19import sys
epoger@google.com542b65f2013-10-15 20:10:33 +000020import time
epoger@google.comf9d134d2013-09-27 15:02:44 +000021
22# Imports from within Skia
23#
24# We need to add the 'gm' directory, so that we can import gm_json.py within
25# that directory. That script allows us to parse the actual-results.json file
26# written out by the GM tool.
27# Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end*
28# so any dirs that are already in the PYTHONPATH will be preferred.
commit-bot@chromium.org7b06c8e2013-12-23 22:47:15 +000029PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
30GM_DIRECTORY = os.path.dirname(PARENT_DIRECTORY)
epoger@google.comf9d134d2013-09-27 15:02:44 +000031if GM_DIRECTORY not in sys.path:
32 sys.path.append(GM_DIRECTORY)
33import gm_json
epoger@google.com9dddf6f2013-11-08 16:25:25 +000034import imagediffdb
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000035import imagepair
36import imagepairset
37
38# Keys used to link an image to a particular GM test.
39# NOTE: Keep these in sync with static/constants.js
40KEY__EXPECTATIONS__BUGS = gm_json.JSONKEY_EXPECTEDRESULTS_BUGS
41KEY__EXPECTATIONS__IGNOREFAILURE = gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE
42KEY__EXPECTATIONS__REVIEWED = gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED
43KEY__EXTRACOLUMN__BUILDER = 'builder'
44KEY__EXTRACOLUMN__CONFIG = 'config'
45KEY__EXTRACOLUMN__RESULT_TYPE = 'resultType'
46KEY__EXTRACOLUMN__TEST = 'test'
47KEY__HEADER__RESULTS_ALL = 'all'
48KEY__HEADER__RESULTS_FAILURES = 'failures'
49KEY__NEW_IMAGE_URL = 'newImageUrl'
50KEY__RESULT_TYPE__FAILED = gm_json.JSONKEY_ACTUALRESULTS_FAILED
51KEY__RESULT_TYPE__FAILUREIGNORED = gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED
52KEY__RESULT_TYPE__NOCOMPARISON = gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON
53KEY__RESULT_TYPE__SUCCEEDED = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED
54
55EXPECTATION_FIELDS_PASSED_THRU_VERBATIM = [
56 KEY__EXPECTATIONS__BUGS,
57 KEY__EXPECTATIONS__IGNOREFAILURE,
58 KEY__EXPECTATIONS__REVIEWED,
59]
epoger@google.comf9d134d2013-09-27 15:02:44 +000060
61IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN)
epoger@google.comeb832592013-10-23 15:07:26 +000062IMAGE_FILENAME_FORMATTER = '%s_%s.png' # pass in (testname, config)
63
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000064IMAGEPAIR_SET_DESCRIPTIONS = ('expected image', 'actual image')
epoger@google.com055e3b52013-10-26 14:31:11 +000065
epoger@google.comf9d134d2013-09-27 15:02:44 +000066
67class Results(object):
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000068 """ Loads actual and expected GM results into an ImagePairSet.
69
70 Loads actual and expected results from all builders, except for those skipped
71 by _ignore_builder().
epoger@google.comdcb4e652013-10-11 18:45:33 +000072
epoger@google.comeb832592013-10-23 15:07:26 +000073 Once this object has been constructed, the results (in self._results[])
74 are immutable. If you want to update the results based on updated JSON
75 file contents, you will need to create a new Results object."""
epoger@google.comf9d134d2013-09-27 15:02:44 +000076
epoger@google.com9dddf6f2013-11-08 16:25:25 +000077 def __init__(self, actuals_root, expected_root, generated_images_root):
epoger@google.comf9d134d2013-09-27 15:02:44 +000078 """
epoger@google.com9fb6c8a2013-10-09 18:05:58 +000079 Args:
epoger@google.comf9d134d2013-09-27 15:02:44 +000080 actuals_root: root directory containing all actual-results.json files
81 expected_root: root directory containing all expected-results.json files
epoger@google.com214a0242013-11-22 19:26:18 +000082 generated_images_root: directory within which to create all pixel diffs;
epoger@google.com9dddf6f2013-11-08 16:25:25 +000083 if this directory does not yet exist, it will be created
epoger@google.comf9d134d2013-09-27 15:02:44 +000084 """
commit-bot@chromium.orga6ecbb82013-12-19 19:08:31 +000085 time_start = int(time.time())
epoger@google.com9dddf6f2013-11-08 16:25:25 +000086 self._image_diff_db = imagediffdb.ImageDiffDB(generated_images_root)
epoger@google.comeb832592013-10-23 15:07:26 +000087 self._actuals_root = actuals_root
88 self._expected_root = expected_root
89 self._load_actual_and_expected()
epoger@google.com542b65f2013-10-15 20:10:33 +000090 self._timestamp = int(time.time())
commit-bot@chromium.orga6ecbb82013-12-19 19:08:31 +000091 logging.info('Results complete; took %d seconds.' %
92 (self._timestamp - time_start))
epoger@google.com542b65f2013-10-15 20:10:33 +000093
94 def get_timestamp(self):
95 """Return the time at which this object was created, in seconds past epoch
96 (UTC).
97 """
98 return self._timestamp
epoger@google.comf9d134d2013-09-27 15:02:44 +000099
epoger@google.comeb832592013-10-23 15:07:26 +0000100 def edit_expectations(self, modifications):
101 """Edit the expectations stored within this object and write them back
102 to disk.
103
104 Note that this will NOT update the results stored in self._results[] ;
105 in order to see those updates, you must instantiate a new Results object
106 based on the (now updated) files on disk.
107
108 Args:
109 modifications: a list of dictionaries, one for each expectation to update:
110
111 [
112 {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000113 imagepair.KEY__EXPECTATIONS_DATA: {
114 KEY__EXPECTATIONS__BUGS: [123, 456],
115 KEY__EXPECTATIONS__IGNOREFAILURE: false,
116 KEY__EXPECTATIONS__REVIEWED: true,
117 },
118 imagepair.KEY__EXTRA_COLUMN_VALUES: {
119 KEY__EXTRACOLUMN__BUILDER: 'Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug',
120 KEY__EXTRACOLUMN__CONFIG: '8888',
121 KEY__EXTRACOLUMN__TEST: 'bigmatrix',
122 },
123 KEY__NEW_IMAGE_URL: 'bitmap-64bitMD5/bigmatrix/10894408024079689926.png',
epoger@google.comeb832592013-10-23 15:07:26 +0000124 },
125 ...
126 ]
127
epoger@google.comeb832592013-10-23 15:07:26 +0000128 """
129 expected_builder_dicts = Results._read_dicts_from_root(self._expected_root)
130 for mod in modifications:
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000131 image_name = IMAGE_FILENAME_FORMATTER % (
132 mod[imagepair.KEY__EXTRA_COLUMN_VALUES][KEY__EXTRACOLUMN__TEST],
133 mod[imagepair.KEY__EXTRA_COLUMN_VALUES][KEY__EXTRACOLUMN__CONFIG])
134 _, hash_type, hash_digest = gm_json.SplitGmRelativeUrl(
135 mod[KEY__NEW_IMAGE_URL])
136 allowed_digests = [[hash_type, int(hash_digest)]]
epoger@google.comeb832592013-10-23 15:07:26 +0000137 new_expectations = {
138 gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS: allowed_digests,
epoger@google.comeb832592013-10-23 15:07:26 +0000139 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000140 for field in EXPECTATION_FIELDS_PASSED_THRU_VERBATIM:
141 value = mod[imagepair.KEY__EXPECTATIONS_DATA].get(field)
epoger@google.com055e3b52013-10-26 14:31:11 +0000142 if value is not None:
143 new_expectations[field] = value
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000144 builder_dict = expected_builder_dicts[
145 mod[imagepair.KEY__EXTRA_COLUMN_VALUES][KEY__EXTRACOLUMN__BUILDER]]
epoger@google.comeb832592013-10-23 15:07:26 +0000146 builder_expectations = builder_dict.get(gm_json.JSONKEY_EXPECTEDRESULTS)
147 if not builder_expectations:
148 builder_expectations = {}
149 builder_dict[gm_json.JSONKEY_EXPECTEDRESULTS] = builder_expectations
150 builder_expectations[image_name] = new_expectations
151 Results._write_dicts_to_root(expected_builder_dicts, self._expected_root)
152
epoger@google.comdcb4e652013-10-11 18:45:33 +0000153 def get_results_of_type(self, type):
154 """Return results of some/all tests (depending on 'type' parameter).
155
156 Args:
157 type: string describing which types of results to include; must be one
158 of the RESULTS_* constants
159
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000160 Results are returned in a dictionary as output by ImagePairSet.as_dict().
epoger@google.comf9d134d2013-09-27 15:02:44 +0000161 """
epoger@google.comdcb4e652013-10-11 18:45:33 +0000162 return self._results[type]
epoger@google.comf9d134d2013-09-27 15:02:44 +0000163
164 @staticmethod
commit-bot@chromium.org04747a52014-01-15 19:16:09 +0000165 def _ignore_builder(builder):
166 """Returns True if we should ignore expectations and actuals for a builder.
167
168 This allows us to ignore builders for which we don't maintain expectations
169 (trybots, Valgrind, ASAN, TSAN), and avoid problems like
170 https://code.google.com/p/skia/issues/detail?id=2036 ('rebaseline_server
171 produces error when trying to add baselines for ASAN/TSAN builders')
172
173 Args:
174 builder: name of this builder, as a string
175
176 Returns:
177 True if we should ignore expectations and actuals for this builder.
178 """
179 return (builder.endswith('-Trybot') or
180 ('Valgrind' in builder) or
181 ('TSAN' in builder) or
182 ('ASAN' in builder))
183
184 @staticmethod
epoger@google.comeb832592013-10-23 15:07:26 +0000185 def _read_dicts_from_root(root, pattern='*.json'):
epoger@google.com9fb6c8a2013-10-09 18:05:58 +0000186 """Read all JSON dictionaries within a directory tree.
epoger@google.comf9d134d2013-09-27 15:02:44 +0000187
epoger@google.com9fb6c8a2013-10-09 18:05:58 +0000188 Args:
epoger@google.comf9d134d2013-09-27 15:02:44 +0000189 root: path to root of directory tree
190 pattern: which files to read within root (fnmatch-style pattern)
epoger@google.com9fb6c8a2013-10-09 18:05:58 +0000191
192 Returns:
193 A meta-dictionary containing all the JSON dictionaries found within
194 the directory tree, keyed by the builder name of each dictionary.
epoger@google.com542b65f2013-10-15 20:10:33 +0000195
196 Raises:
197 IOError if root does not refer to an existing directory
epoger@google.comf9d134d2013-09-27 15:02:44 +0000198 """
epoger@google.com542b65f2013-10-15 20:10:33 +0000199 if not os.path.isdir(root):
200 raise IOError('no directory found at path %s' % root)
epoger@google.comf9d134d2013-09-27 15:02:44 +0000201 meta_dict = {}
202 for dirpath, dirnames, filenames in os.walk(root):
203 for matching_filename in fnmatch.filter(filenames, pattern):
204 builder = os.path.basename(dirpath)
commit-bot@chromium.org04747a52014-01-15 19:16:09 +0000205 if Results._ignore_builder(builder):
epoger@google.comf9d134d2013-09-27 15:02:44 +0000206 continue
207 fullpath = os.path.join(dirpath, matching_filename)
208 meta_dict[builder] = gm_json.LoadFromFile(fullpath)
209 return meta_dict
210
epoger@google.comeb832592013-10-23 15:07:26 +0000211 @staticmethod
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000212 def _create_relative_url(hashtype_and_digest, test_name):
213 """Returns the URL for this image, relative to GM_ACTUALS_ROOT_HTTP_URL.
214
215 If we don't have a record of this image, returns None.
216
217 Args:
218 hashtype_and_digest: (hash_type, hash_digest) tuple, or None if we
219 don't have a record of this image
220 test_name: string; name of the GM test that created this image
221 """
222 if not hashtype_and_digest:
223 return None
224 return gm_json.CreateGmRelativeUrl(
225 test_name=test_name,
226 hash_type=hashtype_and_digest[0],
227 hash_digest=hashtype_and_digest[1])
228
229 @staticmethod
epoger@google.comeb832592013-10-23 15:07:26 +0000230 def _write_dicts_to_root(meta_dict, root, pattern='*.json'):
231 """Write all per-builder dictionaries within meta_dict to files under
232 the root path.
233
234 Security note: this will only write to files that already exist within
235 the root path (as found by os.walk() within root), so we don't need to
236 worry about malformed content writing to disk outside of root.
237 However, the data written to those files is not double-checked, so it
238 could contain poisonous data.
239
240 Args:
241 meta_dict: a builder-keyed meta-dictionary containing all the JSON
242 dictionaries we want to write out
243 root: path to root of directory tree within which to write files
244 pattern: which files to write within root (fnmatch-style pattern)
245
246 Raises:
247 IOError if root does not refer to an existing directory
248 KeyError if the set of per-builder dictionaries written out was
249 different than expected
250 """
251 if not os.path.isdir(root):
252 raise IOError('no directory found at path %s' % root)
253 actual_builders_written = []
254 for dirpath, dirnames, filenames in os.walk(root):
255 for matching_filename in fnmatch.filter(filenames, pattern):
256 builder = os.path.basename(dirpath)
commit-bot@chromium.org04747a52014-01-15 19:16:09 +0000257 if Results._ignore_builder(builder):
epoger@google.comeb832592013-10-23 15:07:26 +0000258 continue
259 per_builder_dict = meta_dict.get(builder)
commit-bot@chromium.org7dd5d6e2013-12-11 20:19:42 +0000260 if per_builder_dict is not None:
epoger@google.comeb832592013-10-23 15:07:26 +0000261 fullpath = os.path.join(dirpath, matching_filename)
262 gm_json.WriteToFile(per_builder_dict, fullpath)
263 actual_builders_written.append(builder)
264
265 # Check: did we write out the set of per-builder dictionaries we
266 # expected to?
267 expected_builders_written = sorted(meta_dict.keys())
268 actual_builders_written.sort()
269 if expected_builders_written != actual_builders_written:
270 raise KeyError(
271 'expected to write dicts for builders %s, but actually wrote them '
272 'for builders %s' % (
273 expected_builders_written, actual_builders_written))
274
275 def _load_actual_and_expected(self):
276 """Loads the results of all tests, across all builders (based on the
277 files within self._actuals_root and self._expected_root),
epoger@google.comdcb4e652013-10-11 18:45:33 +0000278 and stores them in self._results.
epoger@google.comf9d134d2013-09-27 15:02:44 +0000279 """
commit-bot@chromium.orga6ecbb82013-12-19 19:08:31 +0000280 logging.info('Reading actual-results JSON files from %s...' %
281 self._actuals_root)
epoger@google.comeb832592013-10-23 15:07:26 +0000282 actual_builder_dicts = Results._read_dicts_from_root(self._actuals_root)
commit-bot@chromium.orga6ecbb82013-12-19 19:08:31 +0000283 logging.info('Reading expected-results JSON files from %s...' %
284 self._expected_root)
epoger@google.comeb832592013-10-23 15:07:26 +0000285 expected_builder_dicts = Results._read_dicts_from_root(self._expected_root)
286
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000287 all_image_pairs = imagepairset.ImagePairSet(IMAGEPAIR_SET_DESCRIPTIONS)
288 failing_image_pairs = imagepairset.ImagePairSet(IMAGEPAIR_SET_DESCRIPTIONS)
epoger@google.com055e3b52013-10-26 14:31:11 +0000289
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000290 all_image_pairs.ensure_extra_column_values_in_summary(
291 column_id=KEY__EXTRACOLUMN__RESULT_TYPE, values=[
292 KEY__RESULT_TYPE__FAILED,
293 KEY__RESULT_TYPE__FAILUREIGNORED,
294 KEY__RESULT_TYPE__NOCOMPARISON,
295 KEY__RESULT_TYPE__SUCCEEDED,
epoger@google.com5f2bb002013-10-02 18:57:48 +0000296 ])
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000297 failing_image_pairs.ensure_extra_column_values_in_summary(
298 column_id=KEY__EXTRACOLUMN__RESULT_TYPE, values=[
299 KEY__RESULT_TYPE__FAILED,
300 KEY__RESULT_TYPE__FAILUREIGNORED,
301 KEY__RESULT_TYPE__NOCOMPARISON,
epoger@google.comdcb4e652013-10-11 18:45:33 +0000302 ])
epoger@google.com5f2bb002013-10-02 18:57:48 +0000303
commit-bot@chromium.orga6ecbb82013-12-19 19:08:31 +0000304 builders = sorted(actual_builder_dicts.keys())
305 num_builders = len(builders)
306 builder_num = 0
307 for builder in builders:
308 builder_num += 1
309 logging.info('Generating pixel diffs for builder #%d of %d, "%s"...' %
310 (builder_num, num_builders, builder))
epoger@google.comf9d134d2013-09-27 15:02:44 +0000311 actual_results_for_this_builder = (
epoger@google.comeb832592013-10-23 15:07:26 +0000312 actual_builder_dicts[builder][gm_json.JSONKEY_ACTUALRESULTS])
epoger@google.comf9d134d2013-09-27 15:02:44 +0000313 for result_type in sorted(actual_results_for_this_builder.keys()):
314 results_of_this_type = actual_results_for_this_builder[result_type]
315 if not results_of_this_type:
316 continue
317 for image_name in sorted(results_of_this_type.keys()):
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000318 (test, config) = IMAGE_FILENAME_RE.match(image_name).groups()
319 actual_image_relative_url = Results._create_relative_url(
320 hashtype_and_digest=results_of_this_type[image_name],
321 test_name=test)
epoger@google.com055e3b52013-10-26 14:31:11 +0000322
323 # Default empty expectations; overwrite these if we find any real ones
324 expectations_per_test = None
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000325 expected_image_relative_url = None
326 expectations_dict = None
epoger@google.comf9d134d2013-09-27 15:02:44 +0000327 try:
epoger@google.com055e3b52013-10-26 14:31:11 +0000328 expectations_per_test = (
329 expected_builder_dicts
330 [builder][gm_json.JSONKEY_EXPECTEDRESULTS][image_name])
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000331 # TODO(epoger): assumes a single allowed digest per test, which is
332 # fine; see https://code.google.com/p/skia/issues/detail?id=1787
333 expected_image_hashtype_and_digest = (
epoger@google.com055e3b52013-10-26 14:31:11 +0000334 expectations_per_test
335 [gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS][0])
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000336 expected_image_relative_url = Results._create_relative_url(
337 hashtype_and_digest=expected_image_hashtype_and_digest,
338 test_name=test)
339 expectations_dict = {}
340 for field in EXPECTATION_FIELDS_PASSED_THRU_VERBATIM:
341 expectations_dict[field] = expectations_per_test.get(field)
epoger@google.comf9d134d2013-09-27 15:02:44 +0000342 except (KeyError, TypeError):
343 # There are several cases in which we would expect to find
344 # no expectations for a given test:
345 #
346 # 1. result_type == NOCOMPARISON
347 # There are no expectations for this test yet!
348 #
epoger@google.com055e3b52013-10-26 14:31:11 +0000349 # 2. alternate rendering mode failures (e.g. serialized)
epoger@google.comf9d134d2013-09-27 15:02:44 +0000350 # In cases like
351 # https://code.google.com/p/skia/issues/detail?id=1684
352 # ('tileimagefilter GM test failing in serialized render mode'),
353 # the gm-actuals will list a failure for the alternate
354 # rendering mode even though we don't have explicit expectations
355 # for the test (the implicit expectation is that it must
356 # render the same in all rendering modes).
357 #
epoger@google.com055e3b52013-10-26 14:31:11 +0000358 # Don't log type 1, because it is common.
epoger@google.comf9d134d2013-09-27 15:02:44 +0000359 # Log other types, because they are rare and we should know about
360 # them, but don't throw an exception, because we need to keep our
361 # tools working in the meanwhile!
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000362 if result_type != KEY__RESULT_TYPE__NOCOMPARISON:
epoger@google.comdcb4e652013-10-11 18:45:33 +0000363 logging.warning('No expectations found for test: %s' % {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000364 KEY__EXTRACOLUMN__BUILDER: builder,
365 KEY__EXTRACOLUMN__RESULT_TYPE: result_type,
epoger@google.comf9d134d2013-09-27 15:02:44 +0000366 'image_name': image_name,
epoger@google.comdcb4e652013-10-11 18:45:33 +0000367 })
epoger@google.comf9d134d2013-09-27 15:02:44 +0000368
369 # If this test was recently rebaselined, it will remain in
epoger@google.com9fb6c8a2013-10-09 18:05:58 +0000370 # the 'failed' set of actuals until all the bots have
epoger@google.comf9d134d2013-09-27 15:02:44 +0000371 # cycled (although the expectations have indeed been set
372 # from the most recent actuals). Treat these as successes
373 # instead of failures.
374 #
375 # TODO(epoger): Do we need to do something similar in
376 # other cases, such as when we have recently marked a test
epoger@google.com9fb6c8a2013-10-09 18:05:58 +0000377 # as ignoreFailure but it still shows up in the 'failed'
epoger@google.comf9d134d2013-09-27 15:02:44 +0000378 # category? Maybe we should not rely on the result_type
379 # categories recorded within the gm_actuals AT ALL, and
380 # instead evaluate the result_type ourselves based on what
381 # we see in expectations vs actual checksum?
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000382 if expected_image_relative_url == actual_image_relative_url:
383 updated_result_type = KEY__RESULT_TYPE__SUCCEEDED
epoger@google.comf9d134d2013-09-27 15:02:44 +0000384 else:
385 updated_result_type = result_type
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000386 extra_columns_dict = {
387 KEY__EXTRACOLUMN__RESULT_TYPE: updated_result_type,
388 KEY__EXTRACOLUMN__BUILDER: builder,
389 KEY__EXTRACOLUMN__TEST: test,
390 KEY__EXTRACOLUMN__CONFIG: config,
epoger@google.comafaad3d2013-09-30 15:06:25 +0000391 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000392 image_pair = imagepair.ImagePair(
393 image_diff_db=self._image_diff_db,
394 base_url=gm_json.GM_ACTUALS_ROOT_HTTP_URL,
395 imageA_relative_url=expected_image_relative_url,
396 imageB_relative_url=actual_image_relative_url,
397 expectations=expectations_dict,
398 extra_columns=extra_columns_dict)
399 all_image_pairs.add_image_pair(image_pair)
400 if updated_result_type != KEY__RESULT_TYPE__SUCCEEDED:
401 failing_image_pairs.add_image_pair(image_pair)
epoger@google.comdcb4e652013-10-11 18:45:33 +0000402
403 self._results = {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000404 KEY__HEADER__RESULTS_ALL: all_image_pairs.as_dict(),
405 KEY__HEADER__RESULTS_FAILURES: failing_image_pairs.as_dict(),
epoger@google.comdcb4e652013-10-11 18:45:33 +0000406 }
epoger@google.comafaad3d2013-09-30 15:06:25 +0000407
commit-bot@chromium.org7b06c8e2013-12-23 22:47:15 +0000408
409def main():
410 logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
411 datefmt='%m/%d/%Y %H:%M:%S',
412 level=logging.INFO)
413 parser = argparse.ArgumentParser()
414 parser.add_argument(
415 '--actuals', required=True,
416 help='Directory containing all actual-result JSON files')
417 parser.add_argument(
418 '--expectations', required=True,
419 help='Directory containing all expected-result JSON files')
420 parser.add_argument(
421 '--outfile', required=True,
422 help='File to write result summary into, in JSON format')
423 parser.add_argument(
424 '--workdir', default='.workdir',
425 help='Directory within which to download images and generate diffs')
426 args = parser.parse_args()
427 results = Results(actuals_root=args.actuals,
428 expected_root=args.expectations,
429 generated_images_root=args.workdir)
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000430 gm_json.WriteToFile(results.get_results_of_type(KEY__HEADER__RESULTS_ALL),
431 args.outfile)
commit-bot@chromium.org7b06c8e2013-12-23 22:47:15 +0000432
433
434if __name__ == '__main__':
435 main()