123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- #!/usr/bin/python
- '''
- Copyright 2013 Google Inc.
- Use of this source code is governed by a BSD-style license that can be
- found in the LICENSE file.
- '''
- '''
- Gathers diffs between 2 JSON expectations files, or between actual and
- expected results within a single JSON actual-results file,
- and generates an old-vs-new diff dictionary.
- TODO(epoger): Fix indentation in this file (2-space indents, not 4-space).
- '''
- # System-level imports
- import argparse
- import json
- import os
- import sys
- import urllib2
- # Imports from within Skia
- #
- # We need to add the 'gm' directory, so that we can import gm_json.py within
- # that directory. That script allows us to parse the actual-results.json file
- # written out by the GM tool.
- # Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end*
- # so any dirs that are already in the PYTHONPATH will be preferred.
- #
- # This assumes that the 'gm' directory has been checked out as a sibling of
- # the 'tools' directory containing this script, which will be the case if
- # 'trunk' was checked out as a single unit.
- GM_DIRECTORY = os.path.realpath(
- os.path.join(os.path.dirname(os.path.dirname(__file__)), 'gm'))
- if GM_DIRECTORY not in sys.path:
- sys.path.append(GM_DIRECTORY)
- import gm_json
- # Object that generates diffs between two JSON gm result files.
- class GMDiffer(object):
- def __init__(self):
- pass
- def _GetFileContentsAsString(self, filepath):
- """Returns the full contents of a file, as a single string.
- If the filename looks like a URL, download its contents.
- If the filename is None, return None."""
- if filepath is None:
- return None
- elif filepath.startswith('http:') or filepath.startswith('https:'):
- return urllib2.urlopen(filepath).read()
- else:
- return open(filepath, 'r').read()
- def _GetExpectedResults(self, contents):
- """Returns the dictionary of expected results from a JSON string,
- in this form:
- {
- 'test1' : 14760033689012826769,
- 'test2' : 9151974350149210736,
- ...
- }
- We make these simplifying assumptions:
- 1. Each test has either 0 or 1 allowed results.
- 2. All expectations are of type JSONKEY_HASHTYPE_BITMAP_64BITMD5.
- Any tests which violate those assumptions will cause an exception to
- be raised.
- Any tests for which we have no expectations will be left out of the
- returned dictionary.
- """
- result_dict = {}
- json_dict = gm_json.LoadFromString(contents)
- all_expectations = json_dict[gm_json.JSONKEY_EXPECTEDRESULTS]
- # Prevent https://code.google.com/p/skia/issues/detail?id=1588
- if not all_expectations:
- return result_dict
- for test_name in all_expectations.keys():
- test_expectations = all_expectations[test_name]
- allowed_digests = test_expectations[
- gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS]
- if allowed_digests:
- num_allowed_digests = len(allowed_digests)
- if num_allowed_digests > 1:
- raise ValueError(
- 'test %s has %d allowed digests' % (
- test_name, num_allowed_digests))
- digest_pair = allowed_digests[0]
- if digest_pair[0] != gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5:
- raise ValueError(
- 'test %s has unsupported hashtype %s' % (
- test_name, digest_pair[0]))
- result_dict[test_name] = digest_pair[1]
- return result_dict
- def _GetActualResults(self, contents):
- """Returns the dictionary of actual results from a JSON string,
- in this form:
- {
- 'test1' : 14760033689012826769,
- 'test2' : 9151974350149210736,
- ...
- }
- We make these simplifying assumptions:
- 1. All results are of type JSONKEY_HASHTYPE_BITMAP_64BITMD5.
- Any tests which violate those assumptions will cause an exception to
- be raised.
- Any tests for which we have no actual results will be left out of the
- returned dictionary.
- """
- result_dict = {}
- json_dict = gm_json.LoadFromString(contents)
- all_result_types = json_dict[gm_json.JSONKEY_ACTUALRESULTS]
- for result_type in all_result_types.keys():
- results_of_this_type = all_result_types[result_type]
- if results_of_this_type:
- for test_name in results_of_this_type.keys():
- digest_pair = results_of_this_type[test_name]
- if (digest_pair[0] !=
- gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5):
- raise ValueError(
- 'test %s has unsupported hashtype %s' % (
- test_name, digest_pair[0]))
- result_dict[test_name] = digest_pair[1]
- return result_dict
- def _DictionaryDiff(self, old_dict, new_dict):
- """Generate a dictionary showing diffs between old_dict and new_dict.
- Any entries which are identical across them will be left out."""
- diff_dict = {}
- all_keys = set(old_dict.keys() + new_dict.keys())
- for key in all_keys:
- if old_dict.get(key) != new_dict.get(key):
- new_entry = {}
- new_entry['old'] = old_dict.get(key)
- new_entry['new'] = new_dict.get(key)
- diff_dict[key] = new_entry
- return diff_dict
- def GenerateDiffDict(self, oldfile, newfile=None):
- """Generate a dictionary showing the diffs:
- old = expectations within oldfile
- new = expectations within newfile
- If newfile is not specified, then 'new' is the actual results within
- oldfile.
- """
- return self.GenerateDiffDictFromStrings(
- self._GetFileContentsAsString(oldfile),
- self._GetFileContentsAsString(newfile))
- def GenerateDiffDictFromStrings(self, oldjson, newjson=None):
- """Generate a dictionary showing the diffs:
- old = expectations within oldjson
- new = expectations within newjson
- If newfile is not specified, then 'new' is the actual results within
- oldfile.
- """
- old_results = self._GetExpectedResults(oldjson)
- if newjson:
- new_results = self._GetExpectedResults(newjson)
- else:
- new_results = self._GetActualResults(oldjson)
- return self._DictionaryDiff(old_results, new_results)
- def _Main():
- parser = argparse.ArgumentParser()
- parser.add_argument(
- 'old',
- help='Path to JSON file whose expectations to display on ' +
- 'the "old" side of the diff. This can be a filepath on ' +
- 'local storage, or a URL.')
- parser.add_argument(
- 'new', nargs='?',
- help='Path to JSON file whose expectations to display on ' +
- 'the "new" side of the diff; if not specified, uses the ' +
- 'ACTUAL results from the "old" JSON file. This can be a ' +
- 'filepath on local storage, or a URL.')
- args = parser.parse_args()
- differ = GMDiffer()
- diffs = differ.GenerateDiffDict(oldfile=args.old, newfile=args.new)
- json.dump(diffs, sys.stdout, sort_keys=True, indent=2)
- if __name__ == '__main__':
- _Main()
|