Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 fnmatch | |
| 13 import os | 14 import os |
| 14 import re | 15 import re |
| 15 import sys | 16 import sys |
| 16 | 17 |
| 17 # Imports from within Skia | 18 # Imports from within Skia |
| 18 # | 19 # |
| 19 # We need to add the 'gm' directory, so that we can import gm_json.py within | 20 # We need to add the 'gm' directory, so that we can import gm_json.py within |
| 20 # that directory. That script allows us to parse the actual-results.json file | 21 # that directory. That script allows us to parse the actual-results.json file |
| 21 # written out by the GM tool. | 22 # written out by the GM tool. |
| 22 # Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end* | 23 # Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end* |
| 23 # so any dirs that are already in the PYTHONPATH will be preferred. | 24 # so any dirs that are already in the PYTHONPATH will be preferred. |
| 24 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) | 25 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) |
| 25 GM_DIRECTORY = os.path.dirname(PARENT_DIRECTORY) | 26 GM_DIRECTORY = os.path.dirname(PARENT_DIRECTORY) |
| 26 if GM_DIRECTORY not in sys.path: | 27 if GM_DIRECTORY not in sys.path: |
| 27 sys.path.append(GM_DIRECTORY) | 28 sys.path.append(GM_DIRECTORY) |
| 28 import gm_json | 29 import gm_json |
| 30 import imagepairset | |
| 29 | 31 |
| 30 # Keys used to link an image to a particular GM test. | 32 # Keys used to link an image to a particular GM test. |
| 31 # NOTE: Keep these in sync with static/constants.js | 33 # NOTE: Keep these in sync with static/constants.js |
| 32 REBASELINE_SERVER_SCHEMA_VERSION_NUMBER = 2 | 34 REBASELINE_SERVER_SCHEMA_VERSION_NUMBER = 2 |
| 33 KEY__EXPECTATIONS__BUGS = gm_json.JSONKEY_EXPECTEDRESULTS_BUGS | 35 KEY__EXPECTATIONS__BUGS = gm_json.JSONKEY_EXPECTEDRESULTS_BUGS |
| 34 KEY__EXPECTATIONS__IGNOREFAILURE = gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE | 36 KEY__EXPECTATIONS__IGNOREFAILURE = gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE |
| 35 KEY__EXPECTATIONS__REVIEWED = gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED | 37 KEY__EXPECTATIONS__REVIEWED = gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED |
| 36 KEY__EXTRACOLUMN__BUILDER = 'builder' | 38 KEY__EXTRACOLUMN__BUILDER = 'builder' |
| 37 KEY__EXTRACOLUMN__CONFIG = 'config' | 39 KEY__EXTRACOLUMN__CONFIG = 'config' |
| 38 KEY__EXTRACOLUMN__RESULT_TYPE = 'resultType' | 40 KEY__EXTRACOLUMN__RESULT_TYPE = 'resultType' |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 49 KEY__HEADER__TIME_UPDATED = 'timeUpdated' | 51 KEY__HEADER__TIME_UPDATED = 'timeUpdated' |
| 50 KEY__HEADER__TYPE = 'type' | 52 KEY__HEADER__TYPE = 'type' |
| 51 KEY__NEW_IMAGE_URL = 'newImageUrl' | 53 KEY__NEW_IMAGE_URL = 'newImageUrl' |
| 52 KEY__RESULT_TYPE__FAILED = gm_json.JSONKEY_ACTUALRESULTS_FAILED | 54 KEY__RESULT_TYPE__FAILED = gm_json.JSONKEY_ACTUALRESULTS_FAILED |
| 53 KEY__RESULT_TYPE__FAILUREIGNORED = gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED | 55 KEY__RESULT_TYPE__FAILUREIGNORED = gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED |
| 54 KEY__RESULT_TYPE__NOCOMPARISON = gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON | 56 KEY__RESULT_TYPE__NOCOMPARISON = gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON |
| 55 KEY__RESULT_TYPE__SUCCEEDED = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED | 57 KEY__RESULT_TYPE__SUCCEEDED = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED |
| 56 | 58 |
| 57 IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN) | 59 IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN) |
| 58 IMAGE_FILENAME_FORMATTER = '%s_%s.png' # pass in (testname, config) | 60 IMAGE_FILENAME_FORMATTER = '%s_%s.png' # pass in (testname, config) |
| 61 | |
| 62 DEFAULT_ACTUALS_DIR = '.gm-actuals' | |
| 63 DEFAULT_GENERATED_IMAGES_ROOT = os.path.join( | |
| 64 PARENT_DIRECTORY, '.generated-images') | |
| 65 | |
| 66 | |
| 67 class BaseComparisons(object): | |
| 68 """ Base class for generating summary of comparisons between two image sets. | |
|
rmistry
2014/03/28 18:51:39
Nit: Extra space before 'Base', I mention this bec
epoger
2014/03/28 19:25:55
Fixed throughout CL.
| |
| 69 """ | |
| 70 | |
| 71 def __init__(self): | |
| 72 raise NotImplementedError('cannot instantiate the abstract base class') | |
| 73 | |
| 74 def get_results_of_type(self, results_type): | |
| 75 """Return results of some/all tests (depending on 'results_type' parameter). | |
| 76 | |
| 77 Args: | |
| 78 results_type: string describing which types of results to include; must | |
| 79 be one of the RESULTS_* constants | |
| 80 | |
| 81 Results are returned in a dictionary as output by ImagePairSet.as_dict(). | |
| 82 """ | |
| 83 return self._results[results_type] | |
| 84 | |
| 85 def get_packaged_results_of_type(self, results_type, reload_seconds=None, | |
| 86 is_editable=False, is_exported=True): | |
| 87 """ Package the results of some/all tests as a complete response_dict. | |
|
rmistry
2014/03/28 18:51:39
Nit: Extra space before 'Package'.
epoger
2014/03/28 19:25:55
Done.
| |
| 88 | |
| 89 Args: | |
| 90 results_type: string indicating which set of results to return; | |
| 91 must be one of the RESULTS_* constants | |
| 92 reload_seconds: if specified, note that new results may be available once | |
| 93 these results are reload_seconds old | |
| 94 is_editable: whether clients are allowed to submit new baselines | |
| 95 is_exported: whether these results are being made available to other | |
| 96 network hosts | |
| 97 """ | |
| 98 response_dict = self._results[results_type] | |
| 99 time_updated = self.get_timestamp() | |
| 100 response_dict[KEY__HEADER] = { | |
| 101 KEY__HEADER__SCHEMA_VERSION: ( | |
| 102 REBASELINE_SERVER_SCHEMA_VERSION_NUMBER), | |
| 103 | |
| 104 # Timestamps: | |
| 105 # 1. when this data was last updated | |
| 106 # 2. when the caller should check back for new data (if ever) | |
| 107 KEY__HEADER__TIME_UPDATED: time_updated, | |
| 108 KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: ( | |
| 109 (time_updated+reload_seconds) if reload_seconds else None), | |
| 110 | |
| 111 # The type we passed to get_results_of_type() | |
| 112 KEY__HEADER__TYPE: results_type, | |
| 113 | |
| 114 # Hash of dataset, which the client must return with any edits-- | |
| 115 # this ensures that the edits were made to a particular dataset. | |
| 116 KEY__HEADER__DATAHASH: str(hash(repr( | |
| 117 response_dict[imagepairset.KEY__IMAGEPAIRS]))), | |
| 118 | |
| 119 # Whether the server will accept edits back. | |
| 120 KEY__HEADER__IS_EDITABLE: is_editable, | |
| 121 | |
| 122 # Whether the service is accessible from other hosts. | |
| 123 KEY__HEADER__IS_EXPORTED: is_exported, | |
| 124 } | |
| 125 return response_dict | |
| 126 | |
| 127 def get_timestamp(self): | |
| 128 """Return the time at which this object was created, in seconds past epoch | |
| 129 (UTC). | |
| 130 """ | |
| 131 return self._timestamp | |
| 132 | |
| 133 @staticmethod | |
| 134 def _ignore_builder(builder): | |
| 135 """Returns True if we should ignore expectations and actuals for a builder. | |
| 136 | |
| 137 This allows us to ignore builders for which we don't maintain expectations | |
| 138 (trybots, Valgrind, ASAN, TSAN), and avoid problems like | |
| 139 https://code.google.com/p/skia/issues/detail?id=2036 ('rebaseline_server | |
| 140 produces error when trying to add baselines for ASAN/TSAN builders') | |
| 141 | |
| 142 Args: | |
| 143 builder: name of this builder, as a string | |
| 144 | |
| 145 Returns: | |
| 146 True if we should ignore expectations and actuals for this builder. | |
| 147 """ | |
| 148 return (builder.endswith('-Trybot') or | |
| 149 ('Valgrind' in builder) or | |
| 150 ('TSAN' in builder) or | |
| 151 ('ASAN' in builder)) | |
|
rmistry
2014/03/28 18:51:39
Should we create a global list of BUILDERS_TO_IGNO
epoger
2014/03/28 19:25:55
Took a stab at it, let me know what you think.
| |
| 152 | |
| 153 @staticmethod | |
| 154 def _read_dicts_from_root(root, pattern='*.json'): | |
| 155 """Read all JSON dictionaries within a directory tree. | |
| 156 | |
| 157 Args: | |
| 158 root: path to root of directory tree | |
| 159 pattern: which files to read within root (fnmatch-style pattern) | |
| 160 | |
| 161 Returns: | |
| 162 A meta-dictionary containing all the JSON dictionaries found within | |
| 163 the directory tree, keyed by the builder name of each dictionary. | |
| 164 | |
| 165 Raises: | |
| 166 IOError if root does not refer to an existing directory | |
| 167 """ | |
| 168 if not os.path.isdir(root): | |
| 169 raise IOError('no directory found at path %s' % root) | |
| 170 meta_dict = {} | |
| 171 for dirpath, dirnames, filenames in os.walk(root): | |
| 172 for matching_filename in fnmatch.filter(filenames, pattern): | |
| 173 builder = os.path.basename(dirpath) | |
| 174 if BaseComparisons._ignore_builder(builder): | |
| 175 continue | |
| 176 fullpath = os.path.join(dirpath, matching_filename) | |
| 177 meta_dict[builder] = gm_json.LoadFromFile(fullpath) | |
| 178 return meta_dict | |
| 179 | |
| 180 @staticmethod | |
| 181 def _create_relative_url(hashtype_and_digest, test_name): | |
| 182 """Returns the URL for this image, relative to GM_ACTUALS_ROOT_HTTP_URL. | |
| 183 | |
| 184 If we don't have a record of this image, returns None. | |
| 185 | |
| 186 Args: | |
| 187 hashtype_and_digest: (hash_type, hash_digest) tuple, or None if we | |
| 188 don't have a record of this image | |
| 189 test_name: string; name of the GM test that created this image | |
| 190 """ | |
| 191 if not hashtype_and_digest: | |
| 192 return None | |
| 193 return gm_json.CreateGmRelativeUrl( | |
| 194 test_name=test_name, | |
| 195 hash_type=hashtype_and_digest[0], | |
| 196 hash_digest=hashtype_and_digest[1]) | |
| OLD | NEW |