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

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

Issue 195943004: rebaseline_server: generate JSON that can be viewed without a live server (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: 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
« no previous file with comments | « no previous file | gm/rebaseline_server/server.py » ('j') | gm/rebaseline_server/server.py » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 2
epoger 2014/03/13 14:37:04 Probably. If it's OK with you, I'll wait until I
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 13 import argparse
14 import fnmatch 14 import fnmatch
15 import json 15 import json
16 import logging 16 import logging
17 import os 17 import os
18 import re 18 import re
19 import sys 19 import sys
20 import time 20 import time
21 21
22 # Imports from within Skia 22 # Imports from within Skia
23 # 23 #
24 # We need to add the 'gm' directory, so that we can import gm_json.py within 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 25 # that directory. That script allows us to parse the actual-results.json file
26 # written out by the GM tool. 26 # written out by the GM tool.
27 # Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end* 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. 28 # so any dirs that are already in the PYTHONPATH will be preferred.
29 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) 29 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
30 GM_DIRECTORY = os.path.dirname(PARENT_DIRECTORY) 30 GM_DIRECTORY = os.path.dirname(PARENT_DIRECTORY)
31 TRUNK_DIRECTORY = os.path.dirname(GM_DIRECTORY)
31 if GM_DIRECTORY not in sys.path: 32 if GM_DIRECTORY not in sys.path:
32 sys.path.append(GM_DIRECTORY) 33 sys.path.append(GM_DIRECTORY)
33 import gm_json 34 import gm_json
34 import imagediffdb 35 import imagediffdb
35 import imagepair 36 import imagepair
36 import imagepairset 37 import imagepairset
37 38
38 # Keys used to link an image to a particular GM test. 39 # Keys used to link an image to a particular GM test.
39 # NOTE: Keep these in sync with static/constants.js 40 # NOTE: Keep these in sync with static/constants.js
40 KEY__EXPECTATIONS__BUGS = gm_json.JSONKEY_EXPECTEDRESULTS_BUGS 41 KEY__EXPECTATIONS__BUGS = gm_json.JSONKEY_EXPECTEDRESULTS_BUGS
41 KEY__EXPECTATIONS__IGNOREFAILURE = gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE 42 KEY__EXPECTATIONS__IGNOREFAILURE = gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE
42 KEY__EXPECTATIONS__REVIEWED = gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED 43 KEY__EXPECTATIONS__REVIEWED = gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED
43 KEY__EXTRACOLUMN__BUILDER = 'builder' 44 KEY__EXTRACOLUMN__BUILDER = 'builder'
44 KEY__EXTRACOLUMN__CONFIG = 'config' 45 KEY__EXTRACOLUMN__CONFIG = 'config'
45 KEY__EXTRACOLUMN__RESULT_TYPE = 'resultType' 46 KEY__EXTRACOLUMN__RESULT_TYPE = 'resultType'
46 KEY__EXTRACOLUMN__TEST = 'test' 47 KEY__EXTRACOLUMN__TEST = 'test'
48 KEY__HEADER = 'header'
49 KEY__HEADER__DATAHASH = 'dataHash'
50 KEY__HEADER__IS_EDITABLE = 'isEditable'
51 KEY__HEADER__IS_EXPORTED = 'isExported'
52 KEY__HEADER__IS_STILL_LOADING = 'resultsStillLoading'
47 KEY__HEADER__RESULTS_ALL = 'all' 53 KEY__HEADER__RESULTS_ALL = 'all'
48 KEY__HEADER__RESULTS_FAILURES = 'failures' 54 KEY__HEADER__RESULTS_FAILURES = 'failures'
55 KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE = 'timeNextUpdateAvailable'
56 KEY__HEADER__TIME_UPDATED = 'timeUpdated'
57 KEY__HEADER__TYPE = 'type'
49 KEY__NEW_IMAGE_URL = 'newImageUrl' 58 KEY__NEW_IMAGE_URL = 'newImageUrl'
50 KEY__RESULT_TYPE__FAILED = gm_json.JSONKEY_ACTUALRESULTS_FAILED 59 KEY__RESULT_TYPE__FAILED = gm_json.JSONKEY_ACTUALRESULTS_FAILED
51 KEY__RESULT_TYPE__FAILUREIGNORED = gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED 60 KEY__RESULT_TYPE__FAILUREIGNORED = gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED
52 KEY__RESULT_TYPE__NOCOMPARISON = gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON 61 KEY__RESULT_TYPE__NOCOMPARISON = gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON
53 KEY__RESULT_TYPE__SUCCEEDED = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED 62 KEY__RESULT_TYPE__SUCCEEDED = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED
54 63
55 EXPECTATION_FIELDS_PASSED_THRU_VERBATIM = [ 64 EXPECTATION_FIELDS_PASSED_THRU_VERBATIM = [
56 KEY__EXPECTATIONS__BUGS, 65 KEY__EXPECTATIONS__BUGS,
57 KEY__EXPECTATIONS__IGNOREFAILURE, 66 KEY__EXPECTATIONS__IGNOREFAILURE,
58 KEY__EXPECTATIONS__REVIEWED, 67 KEY__EXPECTATIONS__REVIEWED,
59 ] 68 ]
60 69
61 IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN) 70 IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN)
62 IMAGE_FILENAME_FORMATTER = '%s_%s.png' # pass in (testname, config) 71 IMAGE_FILENAME_FORMATTER = '%s_%s.png' # pass in (testname, config)
63 72
64 IMAGEPAIR_SET_DESCRIPTIONS = ('expected image', 'actual image') 73 IMAGEPAIR_SET_DESCRIPTIONS = ('expected image', 'actual image')
65 74
75 DEFAULT_ACTUALS_DIR = '.gm-actuals'
76 DEFAULT_EXPECTATIONS_DIR = os.path.join(TRUNK_DIRECTORY, 'expectations', 'gm')
77 DEFAULT_GENERATED_IMAGES_ROOT = os.path.join(PARENT_DIRECTORY, 'static',
78 'generated-images')
79
66 80
67 class Results(object): 81 class Results(object):
68 """ Loads actual and expected GM results into an ImagePairSet. 82 """ Loads actual and expected GM results into an ImagePairSet.
69 83
70 Loads actual and expected results from all builders, except for those skipped 84 Loads actual and expected results from all builders, except for those skipped
71 by _ignore_builder(). 85 by _ignore_builder().
72 86
73 Once this object has been constructed, the results (in self._results[]) 87 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 88 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.""" 89 file contents, you will need to create a new Results object."""
76 90
77 def __init__(self, actuals_root, expected_root, generated_images_root): 91 def __init__(self, actuals_root=DEFAULT_ACTUALS_DIR,
92 expected_root=DEFAULT_EXPECTATIONS_DIR,
93 generated_images_root=DEFAULT_GENERATED_IMAGES_ROOT):
78 """ 94 """
79 Args: 95 Args:
80 actuals_root: root directory containing all actual-results.json files 96 actuals_root: root directory containing all actual-results.json files
81 expected_root: root directory containing all expected-results.json files 97 expected_root: root directory containing all expected-results.json files
82 generated_images_root: directory within which to create all pixel diffs; 98 generated_images_root: directory within which to create all pixel diffs;
83 if this directory does not yet exist, it will be created 99 if this directory does not yet exist, it will be created
84 """ 100 """
85 time_start = int(time.time()) 101 time_start = int(time.time())
86 self._image_diff_db = imagediffdb.ImageDiffDB(generated_images_root) 102 self._image_diff_db = imagediffdb.ImageDiffDB(generated_images_root)
87 self._actuals_root = actuals_root 103 self._actuals_root = actuals_root
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
143 new_expectations[field] = value 159 new_expectations[field] = value
144 builder_dict = expected_builder_dicts[ 160 builder_dict = expected_builder_dicts[
145 mod[imagepair.KEY__EXTRA_COLUMN_VALUES][KEY__EXTRACOLUMN__BUILDER]] 161 mod[imagepair.KEY__EXTRA_COLUMN_VALUES][KEY__EXTRACOLUMN__BUILDER]]
146 builder_expectations = builder_dict.get(gm_json.JSONKEY_EXPECTEDRESULTS) 162 builder_expectations = builder_dict.get(gm_json.JSONKEY_EXPECTEDRESULTS)
147 if not builder_expectations: 163 if not builder_expectations:
148 builder_expectations = {} 164 builder_expectations = {}
149 builder_dict[gm_json.JSONKEY_EXPECTEDRESULTS] = builder_expectations 165 builder_dict[gm_json.JSONKEY_EXPECTEDRESULTS] = builder_expectations
150 builder_expectations[image_name] = new_expectations 166 builder_expectations[image_name] = new_expectations
151 Results._write_dicts_to_root(expected_builder_dicts, self._expected_root) 167 Results._write_dicts_to_root(expected_builder_dicts, self._expected_root)
152 168
153 def get_results_of_type(self, type): 169 def get_results_of_type(self, results_type):
154 """Return results of some/all tests (depending on 'type' parameter). 170 """Return results of some/all tests (depending on 'results_type' parameter).
155 171
156 Args: 172 Args:
157 type: string describing which types of results to include; must be one 173 results_type: string describing which types of results to include; must
158 of the RESULTS_* constants 174 be one of the RESULTS_* constants
159 175
160 Results are returned in a dictionary as output by ImagePairSet.as_dict(). 176 Results are returned in a dictionary as output by ImagePairSet.as_dict().
161 """ 177 """
162 return self._results[type] 178 return self._results[results_type]
179
180 def get_packaged_results_of_type(self, results_type, reload_seconds=None,
epoger 2014/03/12 21:08:35 moved here from server.py
181 is_editable=False, is_exported=True):
182 """ Package the results of some/all tests as a complete response_dict.
183
184 Args:
185 results_type: string indicating which set of results to return;
186 must be one of the RESULTS_* constants
187 reload_seconds: if specified, note that new results may be available once
188 these results are reload_seconds old
189 is_editable: whether clients are allowed to submit new baselines
190 is_exported: whether these results are being made available to other
191 network hosts
192 """
193 response_dict = self._results[results_type]
194 time_updated = self.get_timestamp()
195 response_dict[KEY__HEADER] = {
196 # Timestamps:
197 # 1. when this data was last updated
198 # 2. when the caller should check back for new data (if ever)
199 KEY__HEADER__TIME_UPDATED: time_updated,
200 KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: (
201 (time_updated+reload_seconds) if reload_seconds else None),
202
203 # The type we passed to get_results_of_type()
204 KEY__HEADER__TYPE: results_type,
205
206 # Hash of dataset, which the client must return with any edits--
207 # this ensures that the edits were made to a particular dataset.
208 KEY__HEADER__DATAHASH: str(hash(repr(
209 response_dict[imagepairset.KEY__IMAGEPAIRS]))),
210
211 # Whether the server will accept edits back.
212 KEY__HEADER__IS_EDITABLE: is_editable,
213
214 # Whether the service is accessible from other hosts.
215 KEY__HEADER__IS_EXPORTED: is_exported,
216 }
217 return response_dict
163 218
164 @staticmethod 219 @staticmethod
165 def _ignore_builder(builder): 220 def _ignore_builder(builder):
166 """Returns True if we should ignore expectations and actuals for a builder. 221 """Returns True if we should ignore expectations and actuals for a builder.
167 222
168 This allows us to ignore builders for which we don't maintain expectations 223 This allows us to ignore builders for which we don't maintain expectations
169 (trybots, Valgrind, ASAN, TSAN), and avoid problems like 224 (trybots, Valgrind, ASAN, TSAN), and avoid problems like
170 https://code.google.com/p/skia/issues/detail?id=2036 ('rebaseline_server 225 https://code.google.com/p/skia/issues/detail?id=2036 ('rebaseline_server
171 produces error when trying to add baselines for ASAN/TSAN builders') 226 produces error when trying to add baselines for ASAN/TSAN builders')
172 227
(...skipping 235 matching lines...) Expand 10 before | Expand all | Expand 10 after
408 KEY__HEADER__RESULTS_FAILURES: failing_image_pairs.as_dict(), 463 KEY__HEADER__RESULTS_FAILURES: failing_image_pairs.as_dict(),
409 } 464 }
410 465
411 466
412 def main(): 467 def main():
413 logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', 468 logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
414 datefmt='%m/%d/%Y %H:%M:%S', 469 datefmt='%m/%d/%Y %H:%M:%S',
415 level=logging.INFO) 470 level=logging.INFO)
416 parser = argparse.ArgumentParser() 471 parser = argparse.ArgumentParser()
417 parser.add_argument( 472 parser.add_argument(
418 '--actuals', required=True, 473 '--actuals', default=DEFAULT_ACTUALS_DIR,
419 help='Directory containing all actual-result JSON files') 474 help='Directory containing all actual-result JSON files')
420 parser.add_argument( 475 parser.add_argument(
421 '--expectations', required=True, 476 '--expectations', default=DEFAULT_EXPECTATIONS_DIR,
422 help='Directory containing all expected-result JSON files') 477 help='Directory containing all expected-result JSON files; defaults to '
478 '\'%(default)s\' .')
423 parser.add_argument( 479 parser.add_argument(
424 '--outfile', required=True, 480 '--outfile', required=True,
425 help='File to write result summary into, in JSON format') 481 help='File to write result summary into, in JSON format.')
426 parser.add_argument( 482 parser.add_argument(
427 '--workdir', default='.workdir', 483 '--results', default=KEY__HEADER__RESULTS_FAILURES,
428 help='Directory within which to download images and generate diffs') 484 help='Which result types to include. Defaults to \'%(default)s\'; '
485 'must be one of ' +
486 str([KEY__HEADER__RESULTS_FAILURES, KEY__HEADER__RESULTS_ALL]))
487 parser.add_argument(
488 '--workdir', default=DEFAULT_GENERATED_IMAGES_ROOT,
489 help='Directory within which to download images and generate diffs; '
490 'defaults to \'%(default)s\' .')
rmistry 2014/03/13 13:11:46 This is cool, I did not know you could do this to
429 args = parser.parse_args() 491 args = parser.parse_args()
430 results = Results(actuals_root=args.actuals, 492 results = Results(actuals_root=args.actuals,
431 expected_root=args.expectations, 493 expected_root=args.expectations,
432 generated_images_root=args.workdir) 494 generated_images_root=args.workdir)
433 gm_json.WriteToFile(results.get_results_of_type(KEY__HEADER__RESULTS_ALL), 495 gm_json.WriteToFile(
434 args.outfile) 496 results.get_packaged_results_of_type(results_type=args.results),
497 args.outfile)
435 498
436 499
437 if __name__ == '__main__': 500 if __name__ == '__main__':
438 main() 501 main()
OLDNEW
« no previous file with comments | « no previous file | gm/rebaseline_server/server.py » ('j') | gm/rebaseline_server/server.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698