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

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

Issue 856103002: Revert "Revert "delete old things!"" (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Created 5 years, 11 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 | « gm/rebaseline_server/imagepairset_test.py ('k') | gm/rebaseline_server/results_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 Repackage expected/actual GM results as needed by our HTML rebaseline viewer.
10 """
11
12 # System-level imports
13 import fnmatch
14 import os
15 import re
16
17 # Must fix up PYTHONPATH before importing from within Skia
18 import rs_fixpypath # pylint: disable=W0611
19
20 # Imports from within Skia
21 import gm_json
22 import imagepairset
23
24 # Keys used to link an image to a particular GM test.
25 # NOTE: Keep these in sync with static/constants.js
26 VALUE__HEADER__SCHEMA_VERSION = 5
27 KEY__EXPECTATIONS__BUGS = gm_json.JSONKEY_EXPECTEDRESULTS_BUGS
28 KEY__EXPECTATIONS__IGNOREFAILURE = gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE
29 KEY__EXPECTATIONS__REVIEWED = gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED
30 KEY__EXTRACOLUMNS__BUILDER = 'builder'
31 KEY__EXTRACOLUMNS__CONFIG = 'config'
32 KEY__EXTRACOLUMNS__RESULT_TYPE = 'resultType'
33 KEY__EXTRACOLUMNS__TEST = 'test'
34 KEY__HEADER__DATAHASH = 'dataHash'
35 KEY__HEADER__IS_EDITABLE = 'isEditable'
36 KEY__HEADER__IS_EXPORTED = 'isExported'
37 KEY__HEADER__IS_STILL_LOADING = 'resultsStillLoading'
38 KEY__HEADER__RESULTS_ALL = 'all'
39 KEY__HEADER__RESULTS_FAILURES = 'failures'
40 KEY__HEADER__SCHEMA_VERSION = 'schemaVersion'
41 KEY__HEADER__SET_A_DESCRIPTIONS = 'setA'
42 KEY__HEADER__SET_B_DESCRIPTIONS = 'setB'
43 KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE = 'timeNextUpdateAvailable'
44 KEY__HEADER__TIME_UPDATED = 'timeUpdated'
45 KEY__HEADER__TYPE = 'type'
46 KEY__RESULT_TYPE__FAILED = gm_json.JSONKEY_ACTUALRESULTS_FAILED
47 KEY__RESULT_TYPE__FAILUREIGNORED = gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED
48 KEY__RESULT_TYPE__NOCOMPARISON = gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON
49 KEY__RESULT_TYPE__SUCCEEDED = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED
50 KEY__SET_DESCRIPTIONS__DIR = 'dir'
51 KEY__SET_DESCRIPTIONS__REPO_REVISION = 'repoRevision'
52 KEY__SET_DESCRIPTIONS__SECTION = 'section'
53
54 IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN)
55 IMAGE_FILENAME_FORMATTER = '%s_%s.png' # pass in (testname, config)
56
57 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
58 DEFAULT_ACTUALS_DIR = '.gm-actuals'
59 DEFAULT_GENERATED_IMAGES_ROOT = os.path.join(
60 PARENT_DIRECTORY, '.generated-images')
61
62 # Define the default set of builders we will process expectations/actuals for.
63 # This allows us to ignore builders for which we don't maintain expectations
64 # (trybots, Valgrind, ASAN, TSAN), and avoid problems like
65 # https://code.google.com/p/skia/issues/detail?id=2036 ('rebaseline_server
66 # produces error when trying to add baselines for ASAN/TSAN builders')
67 DEFAULT_MATCH_BUILDERS_PATTERN_LIST = ['.*']
68 DEFAULT_SKIP_BUILDERS_PATTERN_LIST = [
69 '.*-Trybot', '.*Valgrind.*', '.*TSAN.*', '.*ASAN.*']
70
71
72 class BaseComparisons(object):
73 """Base class for generating summary of comparisons between two image sets.
74 """
75
76 def __init__(self):
77 """Base constructor; most subclasses will override."""
78 self._setA_descriptions = None
79 self._setB_descriptions = None
80
81 def get_results_of_type(self, results_type):
82 """Return results of some/all tests (depending on 'results_type' parameter).
83
84 Args:
85 results_type: string describing which types of results to include; must
86 be one of the RESULTS_* constants
87
88 Results are returned in a dictionary as output by ImagePairSet.as_dict().
89 """
90 return self._results[results_type]
91
92 def get_packaged_results_of_type(self, results_type, reload_seconds=None,
93 is_editable=False, is_exported=True):
94 """Package the results of some/all tests as a complete response_dict.
95
96 Args:
97 results_type: string indicating which set of results to return;
98 must be one of the RESULTS_* constants
99 reload_seconds: if specified, note that new results may be available once
100 these results are reload_seconds old
101 is_editable: whether clients are allowed to submit new baselines
102 is_exported: whether these results are being made available to other
103 network hosts
104 """
105 response_dict = self._results[results_type]
106 time_updated = self.get_timestamp()
107 header_dict = {
108 KEY__HEADER__SCHEMA_VERSION: (
109 VALUE__HEADER__SCHEMA_VERSION),
110
111 # Timestamps:
112 # 1. when this data was last updated
113 # 2. when the caller should check back for new data (if ever)
114 KEY__HEADER__TIME_UPDATED: time_updated,
115 KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: (
116 (time_updated+reload_seconds) if reload_seconds else None),
117
118 # The type we passed to get_results_of_type()
119 KEY__HEADER__TYPE: results_type,
120
121 # Hash of dataset, which the client must return with any edits--
122 # this ensures that the edits were made to a particular dataset.
123 KEY__HEADER__DATAHASH: str(hash(repr(
124 response_dict[imagepairset.KEY__ROOT__IMAGEPAIRS]))),
125
126 # Whether the server will accept edits back.
127 KEY__HEADER__IS_EDITABLE: is_editable,
128
129 # Whether the service is accessible from other hosts.
130 KEY__HEADER__IS_EXPORTED: is_exported,
131 }
132 if self._setA_descriptions:
133 header_dict[KEY__HEADER__SET_A_DESCRIPTIONS] = self._setA_descriptions
134 if self._setB_descriptions:
135 header_dict[KEY__HEADER__SET_B_DESCRIPTIONS] = self._setB_descriptions
136 response_dict[imagepairset.KEY__ROOT__HEADER] = header_dict
137 return response_dict
138
139 def get_timestamp(self):
140 """Return the time at which this object was created, in seconds past epoch
141 (UTC).
142 """
143 return self._timestamp
144
145 _match_builders_pattern_list = [
146 re.compile(p) for p in DEFAULT_MATCH_BUILDERS_PATTERN_LIST]
147 _skip_builders_pattern_list = [
148 re.compile(p) for p in DEFAULT_SKIP_BUILDERS_PATTERN_LIST]
149
150 def set_match_builders_pattern_list(self, pattern_list):
151 """Override the default set of builders we should process.
152
153 The default is DEFAULT_MATCH_BUILDERS_PATTERN_LIST .
154
155 Note that skip_builders_pattern_list overrides this; regardless of whether a
156 builder is in the "match" list, if it's in the "skip" list, we will skip it.
157
158 Args:
159 pattern_list: list of regex patterns; process builders that match any
160 entry within this list
161 """
162 if pattern_list == None:
163 pattern_list = []
164 self._match_builders_pattern_list = [re.compile(p) for p in pattern_list]
165
166 def set_skip_builders_pattern_list(self, pattern_list):
167 """Override the default set of builders we should skip while processing.
168
169 The default is DEFAULT_SKIP_BUILDERS_PATTERN_LIST .
170
171 This overrides match_builders_pattern_list; regardless of whether a
172 builder is in the "match" list, if it's in the "skip" list, we will skip it.
173
174 Args:
175 pattern_list: list of regex patterns; skip builders that match any
176 entry within this list
177 """
178 if pattern_list == None:
179 pattern_list = []
180 self._skip_builders_pattern_list = [re.compile(p) for p in pattern_list]
181
182 def _ignore_builder(self, builder):
183 """Returns True if we should skip processing this builder.
184
185 Args:
186 builder: name of this builder, as a string
187
188 Returns:
189 True if we should ignore expectations and actuals for this builder.
190 """
191 for pattern in self._skip_builders_pattern_list:
192 if pattern.match(builder):
193 return True
194 for pattern in self._match_builders_pattern_list:
195 if pattern.match(builder):
196 return False
197 return True
198
199 def _read_builder_dicts_from_root(self, root, pattern='*.json'):
200 """Read all JSON dictionaries within a directory tree.
201
202 Skips any dictionaries belonging to a builder we have chosen to ignore.
203
204 Args:
205 root: path to root of directory tree
206 pattern: which files to read within root (fnmatch-style pattern)
207
208 Returns:
209 A meta-dictionary containing all the JSON dictionaries found within
210 the directory tree, keyed by builder name (the basename of the directory
211 where each JSON dictionary was found).
212
213 Raises:
214 IOError if root does not refer to an existing directory
215 """
216 # I considered making this call read_dicts_from_root(), but I decided
217 # it was better to prune out the ignored builders within the os.walk().
218 if not os.path.isdir(root):
219 raise IOError('no directory found at path %s' % root)
220 meta_dict = {}
221 for dirpath, _, filenames in os.walk(root):
222 for matching_filename in fnmatch.filter(filenames, pattern):
223 builder = os.path.basename(dirpath)
224 if self._ignore_builder(builder):
225 continue
226 full_path = os.path.join(dirpath, matching_filename)
227 meta_dict[builder] = gm_json.LoadFromFile(full_path)
228 return meta_dict
229
230 @staticmethod
231 def read_dicts_from_root(root, pattern='*.json'):
232 """Read all JSON dictionaries within a directory tree.
233
234 TODO(stephana): Factor this out into a utility module, as a standalone
235 function (not part of a class).
236
237 Args:
238 root: path to root of directory tree
239 pattern: which files to read within root (fnmatch-style pattern)
240
241 Returns:
242 A meta-dictionary containing all the JSON dictionaries found within
243 the directory tree, keyed by the pathname (relative to root) of each JSON
244 dictionary.
245
246 Raises:
247 IOError if root does not refer to an existing directory
248 """
249 if not os.path.isdir(root):
250 raise IOError('no directory found at path %s' % root)
251 meta_dict = {}
252 for abs_dirpath, _, filenames in os.walk(root):
253 rel_dirpath = os.path.relpath(abs_dirpath, root)
254 for matching_filename in fnmatch.filter(filenames, pattern):
255 abs_path = os.path.join(abs_dirpath, matching_filename)
256 rel_path = os.path.join(rel_dirpath, matching_filename)
257 meta_dict[rel_path] = gm_json.LoadFromFile(abs_path)
258 return meta_dict
259
260 @staticmethod
261 def _read_noncomment_lines(path):
262 """Return a list of all noncomment lines within a file.
263
264 (A "noncomment" line is one that does not start with a '#'.)
265
266 Args:
267 path: path to file
268 """
269 lines = []
270 with open(path, 'r') as fh:
271 for line in fh:
272 if not line.startswith('#'):
273 lines.append(line.strip())
274 return lines
275
276 @staticmethod
277 def _create_relative_url(hashtype_and_digest, test_name):
278 """Returns the URL for this image, relative to GM_ACTUALS_ROOT_HTTP_URL.
279
280 If we don't have a record of this image, returns None.
281
282 Args:
283 hashtype_and_digest: (hash_type, hash_digest) tuple, or None if we
284 don't have a record of this image
285 test_name: string; name of the GM test that created this image
286 """
287 if not hashtype_and_digest:
288 return None
289 return gm_json.CreateGmRelativeUrl(
290 test_name=test_name,
291 hash_type=hashtype_and_digest[0],
292 hash_digest=hashtype_and_digest[1])
293
294 @staticmethod
295 def combine_subdicts(input_dict):
296 """ Flatten out a dictionary structure by one level.
297
298 Input:
299 {
300 KEY_A1 : {
301 KEY_B1 : VALUE_B1,
302 },
303 KEY_A2 : {
304 KEY_B2 : VALUE_B2,
305 }
306 }
307
308 Output:
309 {
310 KEY_B1 : VALUE_B1,
311 KEY_B2 : VALUE_B2,
312 }
313
314 If this would result in any repeated keys, it will raise an Exception.
315 """
316 output_dict = {}
317 for subdict in input_dict.values():
318 for subdict_key, subdict_value in subdict.iteritems():
319 if subdict_key in output_dict:
320 raise Exception('duplicate key %s in combine_subdicts' % subdict_key)
321 output_dict[subdict_key] = subdict_value
322 return output_dict
323
324 @staticmethod
325 def get_default(input_dict, default_value, *keys):
326 """Returns input_dict[key1][key2][...], or default_value.
327
328 If input_dict is None, or any key is missing along the way, this returns
329 default_value.
330
331 Args:
332 input_dict: dictionary to look within
333 key: key indicating which value to return from input_dict
334 default_value: value to return if input_dict is None or any key cannot
335 be found along the way
336 """
337 if input_dict == None:
338 return default_value
339 for key in keys:
340 input_dict = input_dict.get(key, None)
341 if input_dict == None:
342 return default_value
343 return input_dict
OLDNEW
« no previous file with comments | « gm/rebaseline_server/imagepairset_test.py ('k') | gm/rebaseline_server/results_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698