1f9d134da93b8c78e7127efd84c9a26f99a73527eepoger@google.com#!/usr/bin/python
2f9d134da93b8c78e7127efd84c9a26f99a73527eepoger@google.com
39fb6c8ac9ce7dd5d3319b4e3affd5f1e051162a2epoger@google.com"""
4f9d134da93b8c78e7127efd84c9a26f99a73527eepoger@google.comCopyright 2013 Google Inc.
5f9d134da93b8c78e7127efd84c9a26f99a73527eepoger@google.com
6f9d134da93b8c78e7127efd84c9a26f99a73527eepoger@google.comUse of this source code is governed by a BSD-style license that can be
7f9d134da93b8c78e7127efd84c9a26f99a73527eepoger@google.comfound in the LICENSE file.
8f9d134da93b8c78e7127efd84c9a26f99a73527eepoger@google.com
9f9d134da93b8c78e7127efd84c9a26f99a73527eepoger@google.comRepackage expected/actual GM results as needed by our HTML rebaseline viewer.
109fb6c8ac9ce7dd5d3319b4e3affd5f1e051162a2epoger@google.com"""
11f9d134da93b8c78e7127efd84c9a26f99a73527eepoger@google.com
12f9d134da93b8c78e7127efd84c9a26f99a73527eepoger@google.com# System-level imports
1331d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.orgimport fnmatch
14f9d134da93b8c78e7127efd84c9a26f99a73527eepoger@google.comimport os
15f9d134da93b8c78e7127efd84c9a26f99a73527eepoger@google.comimport re
16f9d134da93b8c78e7127efd84c9a26f99a73527eepoger@google.com
17f9d134da93b8c78e7127efd84c9a26f99a73527eepoger@google.com# Imports from within Skia
18b144271179aaf82cb1151e9dfd8e866747402594epogerimport fix_pythonpath  # must do this first
19f9d134da93b8c78e7127efd84c9a26f99a73527eepoger@google.comimport gm_json
2031d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.orgimport imagepairset
2116f418080ff6751e15e0193263149412de9c848acommit-bot@chromium.org
2216f418080ff6751e15e0193263149412de9c848acommit-bot@chromium.org# Keys used to link an image to a particular GM test.
2316f418080ff6751e15e0193263149412de9c848acommit-bot@chromium.org# NOTE: Keep these in sync with static/constants.js
2468a3815401f461976f76891d0477cb1440fa0abacommit-bot@chromium.orgVALUE__HEADER__SCHEMA_VERSION = 3
2516f418080ff6751e15e0193263149412de9c848acommit-bot@chromium.orgKEY__EXPECTATIONS__BUGS = gm_json.JSONKEY_EXPECTEDRESULTS_BUGS
2616f418080ff6751e15e0193263149412de9c848acommit-bot@chromium.orgKEY__EXPECTATIONS__IGNOREFAILURE = gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE
2716f418080ff6751e15e0193263149412de9c848acommit-bot@chromium.orgKEY__EXPECTATIONS__REVIEWED = gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED
2868a3815401f461976f76891d0477cb1440fa0abacommit-bot@chromium.orgKEY__EXTRACOLUMNS__BUILDER = 'builder'
2968a3815401f461976f76891d0477cb1440fa0abacommit-bot@chromium.orgKEY__EXTRACOLUMNS__CONFIG = 'config'
3068a3815401f461976f76891d0477cb1440fa0abacommit-bot@chromium.orgKEY__EXTRACOLUMNS__RESULT_TYPE = 'resultType'
3168a3815401f461976f76891d0477cb1440fa0abacommit-bot@chromium.orgKEY__EXTRACOLUMNS__TEST = 'test'
327498d95bde16eeaa4b20643fb930b6a3be7face1commit-bot@chromium.orgKEY__HEADER__DATAHASH = 'dataHash'
337498d95bde16eeaa4b20643fb930b6a3be7face1commit-bot@chromium.orgKEY__HEADER__IS_EDITABLE = 'isEditable'
347498d95bde16eeaa4b20643fb930b6a3be7face1commit-bot@chromium.orgKEY__HEADER__IS_EXPORTED = 'isExported'
357498d95bde16eeaa4b20643fb930b6a3be7face1commit-bot@chromium.orgKEY__HEADER__IS_STILL_LOADING = 'resultsStillLoading'
3616f418080ff6751e15e0193263149412de9c848acommit-bot@chromium.orgKEY__HEADER__RESULTS_ALL = 'all'
3716f418080ff6751e15e0193263149412de9c848acommit-bot@chromium.orgKEY__HEADER__RESULTS_FAILURES = 'failures'
38ea770f155e72f88e66a4d9c652552517d46a8605commit-bot@chromium.orgKEY__HEADER__SCHEMA_VERSION = 'schemaVersion'
397498d95bde16eeaa4b20643fb930b6a3be7face1commit-bot@chromium.orgKEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE = 'timeNextUpdateAvailable'
407498d95bde16eeaa4b20643fb930b6a3be7face1commit-bot@chromium.orgKEY__HEADER__TIME_UPDATED = 'timeUpdated'
417498d95bde16eeaa4b20643fb930b6a3be7face1commit-bot@chromium.orgKEY__HEADER__TYPE = 'type'
4216f418080ff6751e15e0193263149412de9c848acommit-bot@chromium.orgKEY__RESULT_TYPE__FAILED = gm_json.JSONKEY_ACTUALRESULTS_FAILED
4316f418080ff6751e15e0193263149412de9c848acommit-bot@chromium.orgKEY__RESULT_TYPE__FAILUREIGNORED = gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED
4416f418080ff6751e15e0193263149412de9c848acommit-bot@chromium.orgKEY__RESULT_TYPE__NOCOMPARISON = gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON
4516f418080ff6751e15e0193263149412de9c848acommit-bot@chromium.orgKEY__RESULT_TYPE__SUCCEEDED = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED
4616f418080ff6751e15e0193263149412de9c848acommit-bot@chromium.org
47f9d134da93b8c78e7127efd84c9a26f99a73527eepoger@google.comIMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN)
48eb832599b631a09b180ea3615347adba6bd5e363epoger@google.comIMAGE_FILENAME_FORMATTER = '%s_%s.png'  # pass in (testname, config)
4931d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
50b144271179aaf82cb1151e9dfd8e866747402594epogerPARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
51defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.orgDEFAULT_ACTUALS_DIR = '.gm-actuals'
52defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.orgDEFAULT_GENERATED_IMAGES_ROOT = os.path.join(
53defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    PARENT_DIRECTORY, '.generated-images')
54defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org
55defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org# Define the default set of builders we will process expectations/actuals for.
5631d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org# This allows us to ignore builders for which we don't maintain expectations
5731d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org# (trybots, Valgrind, ASAN, TSAN), and avoid problems like
5831d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org# https://code.google.com/p/skia/issues/detail?id=2036 ('rebaseline_server
5931d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org# produces error when trying to add baselines for ASAN/TSAN builders')
60defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.orgDEFAULT_MATCH_BUILDERS_PATTERN_LIST = ['.*']
61defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.orgDEFAULT_SKIP_BUILDERS_PATTERN_LIST = [
62defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    '.*-Trybot', '.*Valgrind.*', '.*TSAN.*', '.*ASAN.*']
6331d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
6431d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
6531d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.orgclass BaseComparisons(object):
6631d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org  """Base class for generating summary of comparisons between two image sets.
6731d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org  """
6831d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
6931d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org  def get_results_of_type(self, results_type):
7031d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    """Return results of some/all tests (depending on 'results_type' parameter).
7131d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
7231d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    Args:
7331d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org      results_type: string describing which types of results to include; must
7431d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org          be one of the RESULTS_* constants
7531d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
7631d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    Results are returned in a dictionary as output by ImagePairSet.as_dict().
7731d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    """
7831d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    return self._results[results_type]
7931d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
8031d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org  def get_packaged_results_of_type(self, results_type, reload_seconds=None,
8131d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org                                   is_editable=False, is_exported=True):
8231d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    """Package the results of some/all tests as a complete response_dict.
8331d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
8431d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    Args:
8531d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org      results_type: string indicating which set of results to return;
8631d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org          must be one of the RESULTS_* constants
8731d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org      reload_seconds: if specified, note that new results may be available once
8831d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org          these results are reload_seconds old
8931d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org      is_editable: whether clients are allowed to submit new baselines
9031d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org      is_exported: whether these results are being made available to other
9131d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org          network hosts
9231d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    """
9331d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    response_dict = self._results[results_type]
9431d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    time_updated = self.get_timestamp()
9568a3815401f461976f76891d0477cb1440fa0abacommit-bot@chromium.org    response_dict[imagepairset.KEY__ROOT__HEADER] = {
9631d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        KEY__HEADER__SCHEMA_VERSION: (
9768a3815401f461976f76891d0477cb1440fa0abacommit-bot@chromium.org            VALUE__HEADER__SCHEMA_VERSION),
9831d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
9931d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        # Timestamps:
10031d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        # 1. when this data was last updated
10131d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        # 2. when the caller should check back for new data (if ever)
10231d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        KEY__HEADER__TIME_UPDATED: time_updated,
10331d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: (
10431d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org            (time_updated+reload_seconds) if reload_seconds else None),
10531d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
10631d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        # The type we passed to get_results_of_type()
10731d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        KEY__HEADER__TYPE: results_type,
10831d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
10931d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        # Hash of dataset, which the client must return with any edits--
11031d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        # this ensures that the edits were made to a particular dataset.
11131d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        KEY__HEADER__DATAHASH: str(hash(repr(
11268a3815401f461976f76891d0477cb1440fa0abacommit-bot@chromium.org            response_dict[imagepairset.KEY__ROOT__IMAGEPAIRS]))),
11331d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
11431d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        # Whether the server will accept edits back.
11531d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        KEY__HEADER__IS_EDITABLE: is_editable,
11631d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
11731d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        # Whether the service is accessible from other hosts.
11831d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        KEY__HEADER__IS_EXPORTED: is_exported,
11931d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    }
12031d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    return response_dict
12131d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
12231d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org  def get_timestamp(self):
12331d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    """Return the time at which this object was created, in seconds past epoch
12431d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    (UTC).
12531d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    """
12631d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    return self._timestamp
12731d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
128defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org  _match_builders_pattern_list = [
129defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org      re.compile(p) for p in DEFAULT_MATCH_BUILDERS_PATTERN_LIST]
130defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org  _skip_builders_pattern_list = [
131defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org      re.compile(p) for p in DEFAULT_SKIP_BUILDERS_PATTERN_LIST]
132defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org
133defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org  def set_match_builders_pattern_list(self, pattern_list):
134defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    """Override the default set of builders we should process.
135defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org
136defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    The default is DEFAULT_MATCH_BUILDERS_PATTERN_LIST .
137defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org
138defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    Note that skip_builders_pattern_list overrides this; regardless of whether a
139defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    builder is in the "match" list, if it's in the "skip" list, we will skip it.
140defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org
141defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    Args:
142defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org      pattern_list: list of regex patterns; process builders that match any
143defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org          entry within this list
144defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    """
145defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    if pattern_list == None:
146defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org      pattern_list = []
147defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    self._match_builders_pattern_list = [re.compile(p) for p in pattern_list]
148defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org
149defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org  def set_skip_builders_pattern_list(self, pattern_list):
150defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    """Override the default set of builders we should skip while processing.
151defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org
152defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    The default is DEFAULT_SKIP_BUILDERS_PATTERN_LIST .
153defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org
154defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    This overrides match_builders_pattern_list; regardless of whether a
155defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    builder is in the "match" list, if it's in the "skip" list, we will skip it.
156defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org
157defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    Args:
158defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org      pattern_list: list of regex patterns; skip builders that match any
159defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org          entry within this list
160defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    """
161defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    if pattern_list == None:
162defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org      pattern_list = []
163defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    self._skip_builders_pattern_list = [re.compile(p) for p in pattern_list]
164defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org
165defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org  def _ignore_builder(self, builder):
166defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    """Returns True if we should skip processing this builder.
16731d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
16831d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    Args:
16931d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org      builder: name of this builder, as a string
17031d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
17131d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    Returns:
17231d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org      True if we should ignore expectations and actuals for this builder.
17331d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    """
174defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    for pattern in self._skip_builders_pattern_list:
17531d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org      if pattern.match(builder):
17631d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        return True
177defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    for pattern in self._match_builders_pattern_list:
178defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org      if pattern.match(builder):
179defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org        return False
180defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org    return True
18131d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
1827418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org  def _read_builder_dicts_from_root(self, root, pattern='*.json'):
18331d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    """Read all JSON dictionaries within a directory tree.
18431d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
1857418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org    Skips any dictionaries belonging to a builder we have chosen to ignore.
1867418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org
18731d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    Args:
18831d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org      root: path to root of directory tree
18931d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org      pattern: which files to read within root (fnmatch-style pattern)
19031d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
19131d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    Returns:
19231d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org      A meta-dictionary containing all the JSON dictionaries found within
1937418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org      the directory tree, keyed by builder name (the basename of the directory
1947418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org      where each JSON dictionary was found).
19531d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
19631d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    Raises:
19731d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org      IOError if root does not refer to an existing directory
19831d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    """
1997418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org    # I considered making this call _read_dicts_from_root(), but I decided
2007418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org    # it was better to prune out the ignored builders within the os.walk().
20131d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    if not os.path.isdir(root):
20231d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org      raise IOError('no directory found at path %s' % root)
20331d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    meta_dict = {}
20431d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    for dirpath, dirnames, filenames in os.walk(root):
20531d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org      for matching_filename in fnmatch.filter(filenames, pattern):
20631d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        builder = os.path.basename(dirpath)
207defe6fdbc8edb2df0887c007450a8d8cc446f420commit-bot@chromium.org        if self._ignore_builder(builder):
20831d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org          continue
2097418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org        full_path = os.path.join(dirpath, matching_filename)
2107418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org        meta_dict[builder] = gm_json.LoadFromFile(full_path)
2117418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org    return meta_dict
2127418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org
2137418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org  def _read_dicts_from_root(self, root, pattern='*.json'):
2147418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org    """Read all JSON dictionaries within a directory tree.
2157418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org
2167418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org    Args:
2177418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org      root: path to root of directory tree
2187418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org      pattern: which files to read within root (fnmatch-style pattern)
2197418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org
2207418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org    Returns:
2217418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org      A meta-dictionary containing all the JSON dictionaries found within
2227418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org      the directory tree, keyed by the pathname (relative to root) of each JSON
2237418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org      dictionary.
2247418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org
2257418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org    Raises:
2267418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org      IOError if root does not refer to an existing directory
2277418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org    """
2287418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org    if not os.path.isdir(root):
2297418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org      raise IOError('no directory found at path %s' % root)
2307418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org    meta_dict = {}
2317418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org    for abs_dirpath, dirnames, filenames in os.walk(root):
2327418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org      rel_dirpath = os.path.relpath(abs_dirpath, root)
2337418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org      for matching_filename in fnmatch.filter(filenames, pattern):
2347418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org        abs_path = os.path.join(abs_dirpath, matching_filename)
2357418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org        rel_path = os.path.join(rel_dirpath, matching_filename)
2367418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org        meta_dict[rel_path] = gm_json.LoadFromFile(abs_path)
23731d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    return meta_dict
23831d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
23931d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org  @staticmethod
2404cef1be82193a9d527f7b1f873197c443d0dde2ecommit-bot@chromium.org  def _read_noncomment_lines(path):
2414cef1be82193a9d527f7b1f873197c443d0dde2ecommit-bot@chromium.org    """Return a list of all noncomment lines within a file.
2424cef1be82193a9d527f7b1f873197c443d0dde2ecommit-bot@chromium.org
2434cef1be82193a9d527f7b1f873197c443d0dde2ecommit-bot@chromium.org    (A "noncomment" line is one that does not start with a '#'.)
2444cef1be82193a9d527f7b1f873197c443d0dde2ecommit-bot@chromium.org
2454cef1be82193a9d527f7b1f873197c443d0dde2ecommit-bot@chromium.org    Args:
2464cef1be82193a9d527f7b1f873197c443d0dde2ecommit-bot@chromium.org      path: path to file
2474cef1be82193a9d527f7b1f873197c443d0dde2ecommit-bot@chromium.org    """
2484cef1be82193a9d527f7b1f873197c443d0dde2ecommit-bot@chromium.org    lines = []
2494cef1be82193a9d527f7b1f873197c443d0dde2ecommit-bot@chromium.org    with open(path, 'r') as fh:
2504cef1be82193a9d527f7b1f873197c443d0dde2ecommit-bot@chromium.org      for line in fh:
2514cef1be82193a9d527f7b1f873197c443d0dde2ecommit-bot@chromium.org        if not line.startswith('#'):
2524cef1be82193a9d527f7b1f873197c443d0dde2ecommit-bot@chromium.org          lines.append(line.strip())
2534cef1be82193a9d527f7b1f873197c443d0dde2ecommit-bot@chromium.org    return lines
2544cef1be82193a9d527f7b1f873197c443d0dde2ecommit-bot@chromium.org
2554cef1be82193a9d527f7b1f873197c443d0dde2ecommit-bot@chromium.org  @staticmethod
25631d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org  def _create_relative_url(hashtype_and_digest, test_name):
25731d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    """Returns the URL for this image, relative to GM_ACTUALS_ROOT_HTTP_URL.
25831d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
25931d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    If we don't have a record of this image, returns None.
26031d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org
26131d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    Args:
26231d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org      hashtype_and_digest: (hash_type, hash_digest) tuple, or None if we
26331d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org          don't have a record of this image
26431d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org      test_name: string; name of the GM test that created this image
26531d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    """
26631d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    if not hashtype_and_digest:
26731d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org      return None
26831d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org    return gm_json.CreateGmRelativeUrl(
26931d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        test_name=test_name,
27031d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        hash_type=hashtype_and_digest[0],
27131d0b3d806a1aa86b7edaa442b3821f5d548e184commit-bot@chromium.org        hash_digest=hashtype_and_digest[1])
2723eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org
2733eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org  @staticmethod
2743eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org  def combine_subdicts(input_dict):
2753eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org    """ Flatten out a dictionary structure by one level.
2763eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org
2773eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org    Input:
2783eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org      {
2797418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org        KEY_A1 : {
2807418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org          KEY_B1 : VALUE_B1,
2813eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org        },
2827418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org        KEY_A2 : {
2837418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org          KEY_B2 : VALUE_B2,
2843eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org        }
2853eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org      }
2863eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org
2873eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org    Output:
2883eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org      {
2897418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org        KEY_B1 : VALUE_B1,
2907418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org        KEY_B2 : VALUE_B2,
2913eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org      }
2923eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org
2933eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org    If this would result in any repeated keys, it will raise an Exception.
2943eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org    """
2953eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org    output_dict = {}
2963eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org    for key, subdict in input_dict.iteritems():
2973eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org      for subdict_key, subdict_value in subdict.iteritems():
2983eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org        if subdict_key in output_dict:
2993eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org          raise Exception('duplicate key %s in combine_subdicts' % subdict_key)
3003eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org        output_dict[subdict_key] = subdict_value
3013eb77e4d5a381fa55197f6bd03c599e709146069commit-bot@chromium.org    return output_dict
3027418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org
3037418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org  @staticmethod
3047418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org  def get_multilevel(input_dict, *keys):
3057418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org    """ Returns input_dict[key1][key2][...], or None if any key is not found.
3067418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org    """
3077418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org    for key in keys:
3087418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org      if input_dict == None:
3097418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org        return None
3107418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org      input_dict = input_dict.get(key, None)
3117418bd8cad3576294b48dd8e5015301e184c6af1commit-bot@chromium.org    return input_dict
312