OLD | NEW |
| (Empty) |
1 #!/usr/bin/python | |
2 | |
3 """ | |
4 Copyright 2014 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 Expectations on local disk that we can modify. | |
10 """ | |
11 | |
12 # System-level imports | |
13 import logging | |
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 from py.utils import git_utils | |
22 import compare_rendered_pictures | |
23 import gm_json | |
24 import imagepair | |
25 import results | |
26 | |
27 FILEPATH_RE = re.compile('.+/' + gm_json.IMAGE_FILENAME_PATTERN) | |
28 | |
29 SKIA_REPO = os.path.abspath(os.path.join( | |
30 os.path.dirname(__file__), os.pardir, os.pardir, '.git')) | |
31 | |
32 | |
33 class WritableExpectations(git_utils.NewGitCheckout): | |
34 """Expectations on local disk that we can modify.""" | |
35 | |
36 def __init__(self, set_descriptions): | |
37 """Creates a sandbox on local disk containing writable expectations. | |
38 | |
39 You must use the 'with' statement to create this object in such a way that | |
40 it cleans up after itself: | |
41 | |
42 with WritableExpectations(*args) as writable_expectations: | |
43 # make modifications | |
44 # use the modified results | |
45 # the sandbox on local disk is automatically cleaned up here | |
46 | |
47 Args: | |
48 set_descriptions: SET_DESCRIPTIONS dict describing the set we want to | |
49 update expectations within; this tells us the subdirectory within the | |
50 Skia repo where we keep these expectations, and the commithash at | |
51 which the user evaluated new baselines. | |
52 """ | |
53 file_section = set_descriptions[results.KEY__SET_DESCRIPTIONS__SECTION] | |
54 assert file_section == gm_json.JSONKEY_EXPECTEDRESULTS | |
55 | |
56 source_dir = _unicode_to_ascii( | |
57 set_descriptions[results.KEY__SET_DESCRIPTIONS__DIR]) | |
58 assert source_dir.startswith(compare_rendered_pictures.REPO_URL_PREFIX) | |
59 repo_subdir = source_dir[len(compare_rendered_pictures.REPO_URL_PREFIX):] | |
60 repo_revision = _unicode_to_ascii( | |
61 set_descriptions[results.KEY__SET_DESCRIPTIONS__REPO_REVISION]) | |
62 | |
63 logging.info('Creating a writable Skia checkout at revision "%s"...' % | |
64 repo_revision) | |
65 super(WritableExpectations, self).__init__( | |
66 repository=SKIA_REPO, commit=repo_revision, subdir=repo_subdir) | |
67 | |
68 def modify(self, modifications): | |
69 """Modify the contents of the checkout, using modifications from the UI. | |
70 | |
71 Args: | |
72 modifications: data[KEY__LIVE_EDITS__MODIFICATIONS] coming back from the | |
73 rebaseline_server UI frontend | |
74 """ | |
75 logging.info('Reading in dicts from writable Skia checkout in %s ...' % | |
76 self.root) | |
77 dicts = results.BaseComparisons.read_dicts_from_root(self.root) | |
78 | |
79 # Make sure we have expected-results sections in all our output dicts. | |
80 for pathname, adict in dicts.iteritems(): | |
81 if not adict: | |
82 adict = { | |
83 # TODO(stephana): These values should be defined as constants | |
84 # somewhere, to be kept in sync between this file and | |
85 # compare_rendered_pictures.py. | |
86 gm_json.JSONKEY_HEADER: { | |
87 gm_json.JSONKEY_HEADER_TYPE: 'ChecksummedImages', | |
88 gm_json.JSONKEY_HEADER_REVISION: 1, | |
89 } | |
90 } | |
91 if not adict.get(gm_json.JSONKEY_EXPECTEDRESULTS, None): | |
92 adict[gm_json.JSONKEY_EXPECTEDRESULTS] = {} | |
93 dicts[pathname] = adict | |
94 | |
95 for modification in modifications: | |
96 expectations = modification[imagepair.KEY__IMAGEPAIRS__EXPECTATIONS] | |
97 _add_image_info_to_expectations( | |
98 expectations=expectations, | |
99 filepath=modification[imagepair.KEY__IMAGEPAIRS__IMAGE_B_URL]) | |
100 extra_columns = modification[imagepair.KEY__IMAGEPAIRS__EXTRACOLUMNS] | |
101 dictname = modification[imagepair.KEY__IMAGEPAIRS__SOURCE_JSON_FILE] | |
102 dict_to_modify = dicts[dictname][gm_json.JSONKEY_EXPECTEDRESULTS] | |
103 test_name = extra_columns[compare_rendered_pictures.COLUMN__SOURCE_SKP] | |
104 test_record = dict_to_modify.get(test_name, {}) | |
105 if (extra_columns[compare_rendered_pictures.COLUMN__TILED_OR_WHOLE] == | |
106 compare_rendered_pictures.COLUMN__TILED_OR_WHOLE__TILED): | |
107 test_tiles_list = test_record.get( | |
108 gm_json.JSONKEY_SOURCE_TILEDIMAGES, []) | |
109 tilenum = int(extra_columns[compare_rendered_pictures.COLUMN__TILENUM]) | |
110 _replace_list_item(test_tiles_list, tilenum, expectations) | |
111 test_record[gm_json.JSONKEY_SOURCE_TILEDIMAGES] = test_tiles_list | |
112 else: | |
113 test_record[gm_json.JSONKEY_SOURCE_WHOLEIMAGE] = expectations | |
114 dict_to_modify[test_name] = test_record | |
115 | |
116 # Write the modified files back to disk. | |
117 self._write_dicts_to_root(meta_dict=dicts, root=self.root) | |
118 | |
119 def get_diffs(self): | |
120 """Return patchfile describing any modifications to this checkout.""" | |
121 return self._run_in_git_root(args=[git_utils.GIT, 'diff']) | |
122 | |
123 @staticmethod | |
124 def _write_dicts_to_root(meta_dict, root): | |
125 """Write out multiple dictionaries in JSON format. | |
126 | |
127 Args: | |
128 meta_dict: a builder-keyed meta-dictionary containing all the JSON | |
129 dictionaries we want to write out | |
130 root: path to root of directory tree within which to write files | |
131 """ | |
132 if not os.path.isdir(root): | |
133 raise IOError('no directory found at path %s' % root) | |
134 | |
135 for rel_path in meta_dict.keys(): | |
136 full_path = os.path.join(root, rel_path) | |
137 gm_json.WriteToFile(meta_dict[rel_path], full_path) | |
138 | |
139 | |
140 def _unicode_to_ascii(unicode_string): | |
141 """Returns the plain ASCII form of a unicode string. | |
142 | |
143 TODO(stephana): We created this because we get unicode strings out of the | |
144 JSON file, while the git filenames and revision tags are plain ASCII. | |
145 There may be a better way to handle this... maybe set the JSON util to just | |
146 return ASCII strings? | |
147 """ | |
148 return unicode_string.encode('ascii', 'ignore') | |
149 | |
150 | |
151 def _replace_list_item(a_list, index, value): | |
152 """Replaces value at index "index" within a_list. | |
153 | |
154 Args: | |
155 a_list: a list | |
156 index: index indicating which item in a_list to replace | |
157 value: value to set a_list[index] to | |
158 | |
159 If a_list does not contain this index, it will be extended with None entries | |
160 to that length. | |
161 """ | |
162 length = len(a_list) | |
163 while index >= length: | |
164 a_list.append(None) | |
165 length += 1 | |
166 a_list[index] = value | |
167 | |
168 | |
169 def _add_image_info_to_expectations(expectations, filepath): | |
170 """Add JSONKEY_IMAGE_* info to an existing expectations dictionary. | |
171 | |
172 TODO(stephana): This assumes that the checksumAlgorithm and checksumValue | |
173 can be derived from the filepath, which is currently true but may not always | |
174 be true. | |
175 | |
176 Args: | |
177 expectations: the expectations dict to augment | |
178 filepath: relative path to the image file | |
179 """ | |
180 (checksum_algorithm, checksum_value) = FILEPATH_RE.match(filepath).groups() | |
181 expectations[gm_json.JSONKEY_IMAGE_CHECKSUMALGORITHM] = checksum_algorithm | |
182 expectations[gm_json.JSONKEY_IMAGE_CHECKSUMVALUE] = checksum_value | |
183 expectations[gm_json.JSONKEY_IMAGE_FILEPATH] = filepath | |
OLD | NEW |