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

Side by Side Diff: tools/svndiff.py

Issue 19112002: svndiff.py: add ability to compare before-and-after JSON files, not just raw images (Closed) Base URL: http://skia.googlecode.com/svn/trunk/
Patch Set: sync_to_r10112 Created 7 years, 5 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 | Annotate | Revision Log
« no previous file with comments | « tools/rebaseline_imagefiles.py ('k') | tools/verify_images_for_gm_results.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 ''' 2 '''
3 Copyright 2012 Google Inc.
4
5 Use of this source code is governed by a BSD-style license that can be
6 found in the LICENSE file.
7 '''
8
9 '''
3 Generates a visual diff of all pending changes in the local SVN checkout. 10 Generates a visual diff of all pending changes in the local SVN checkout.
4 11
5 Launch with --help to see more information. 12 Launch with --help to see more information.
6 13
7 14 TODO(epoger): Fix indentation in this file (2-space indents, not 4-space).
8 Copyright 2012 Google Inc.
9
10 Use of this source code is governed by a BSD-style license that can be
11 found in the LICENSE file.
12 ''' 15 '''
13 16
14 # common Python modules 17 # common Python modules
15 import optparse 18 import optparse
16 import os 19 import os
17 import re 20 import re
18 import shutil 21 import shutil
22 import sys
19 import tempfile 23 import tempfile
24 import urllib2
20 25
21 # modules declared within this same directory 26 # Imports from within Skia
27 #
28 # We need to add the 'gm' directory, so that we can import gm_json.py within
29 # that directory. That script allows us to parse the actual-results.json file
30 # written out by the GM tool.
31 # Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end*
32 # so any dirs that are already in the PYTHONPATH will be preferred.
33 #
34 # This assumes that the 'gm' directory has been checked out as a sibling of
35 # the 'tools' directory containing this script, which will be the case if
36 # 'trunk' was checked out as a single unit.
37 GM_DIRECTORY = os.path.realpath(
38 os.path.join(os.path.dirname(os.path.dirname(__file__)), 'gm'))
39 if GM_DIRECTORY not in sys.path:
40 sys.path.append(GM_DIRECTORY)
41 import gm_json
42 import jsondiff
22 import svn 43 import svn
23 44
24 USAGE_STRING = 'Usage: %s [options]' 45 USAGE_STRING = 'Usage: %s [options]'
25 HELP_STRING = ''' 46 HELP_STRING = '''
26 47
27 Generates a visual diff of all pending changes in the local SVN checkout. 48 Generates a visual diff of all pending changes in the local SVN checkout.
28 49
29 This includes a list of all files that have been added, deleted, or modified 50 This includes a list of all files that have been added, deleted, or modified
30 (as far as SVN knows about). For any image modifications, pixel diffs will 51 (as far as SVN knows about). For any image modifications, pixel diffs will
31 be generated. 52 be generated.
32 53
33 ''' 54 '''
34 55
56 IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN)
57
35 TRUNK_PATH = os.path.join(os.path.dirname(__file__), os.pardir) 58 TRUNK_PATH = os.path.join(os.path.dirname(__file__), os.pardir)
36 59
37 OPTION_DEST_DIR = '--dest-dir' 60 OPTION_DEST_DIR = '--dest-dir'
38 # default DEST_DIR is determined at runtime
39 OPTION_PATH_TO_SKDIFF = '--path-to-skdiff' 61 OPTION_PATH_TO_SKDIFF = '--path-to-skdiff'
40 # default PATH_TO_SKDIFF is determined at runtime 62 OPTION_SOURCE_DIR = '--source-dir'
41 63
42 def RunCommand(command): 64 def RunCommand(command):
43 """Run a command, raising an exception if it fails. 65 """Run a command, raising an exception if it fails.
44 66
45 @param command the command as a single string 67 @param command the command as a single string
46 """ 68 """
47 print 'running command [%s]...' % command 69 print 'running command [%s]...' % command
48 retval = os.system(command) 70 retval = os.system(command)
49 if retval is not 0: 71 if retval is not 0:
50 raise Exception('command [%s] failed' % command) 72 raise Exception('command [%s] failed' % command)
(...skipping 13 matching lines...) Expand all
64 trunk_path = os.path.join(os.path.dirname(__file__), os.pardir) 86 trunk_path = os.path.join(os.path.dirname(__file__), os.pardir)
65 possible_paths = [os.path.join(trunk_path, 'out', 'Release', 'skdiff'), 87 possible_paths = [os.path.join(trunk_path, 'out', 'Release', 'skdiff'),
66 os.path.join(trunk_path, 'out', 'Debug', 'skdiff')] 88 os.path.join(trunk_path, 'out', 'Debug', 'skdiff')]
67 for try_path in possible_paths: 89 for try_path in possible_paths:
68 if os.path.isfile(try_path): 90 if os.path.isfile(try_path):
69 return try_path 91 return try_path
70 raise Exception('cannot find skdiff in paths %s; maybe you need to ' 92 raise Exception('cannot find skdiff in paths %s; maybe you need to '
71 'specify the %s option or build skdiff?' % ( 93 'specify the %s option or build skdiff?' % (
72 possible_paths, OPTION_PATH_TO_SKDIFF)) 94 possible_paths, OPTION_PATH_TO_SKDIFF))
73 95
74 def SvnDiff(path_to_skdiff, dest_dir): 96 def _DownloadUrlToFile(source_url, dest_path):
75 """Generates a visual diff of all pending changes in the local SVN checkout. 97 """Download source_url, and save its contents to dest_path.
98 Raises an exception if there were any problems."""
99 reader = urllib2.urlopen(source_url)
100 writer = open(dest_path, 'wb')
101 writer.write(reader.read())
102 writer.close()
103
104 def _CreateGSUrl(imagename, hash_type, hash_digest):
105 """Return the HTTP URL we can use to download this particular version of
106 the actually-generated GM image with this imagename.
107
108 imagename: name of the test image, e.g. 'perlinnoise_msaa4.png'
109 hash_type: string indicating the hash type used to generate hash_digest,
110 e.g. gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5
111 hash_digest: the hash digest of the image to retrieve
112 """
113 return gm_json.CreateGmActualUrl(
114 test_name=IMAGE_FILENAME_RE.match(imagename).group(1),
115 hash_type=hash_type,
116 hash_digest=hash_digest)
117
118 def _CallJsonDiff(old_json_path, new_json_path,
119 old_flattened_dir, new_flattened_dir,
120 filename_prefix):
121 """Using jsondiff.py, write the images that differ between two GM
122 expectations summary files (old and new) into old_flattened_dir and
123 new_flattened_dir.
124
125 filename_prefix: prefix to prepend to filenames of all images we write
126 into the flattened directories
127 """
128 json_differ = jsondiff.GMDiffer()
129 diff_dict = json_differ.GenerateDiffDict(oldfile=old_json_path,
130 newfile=new_json_path)
131 for (imagename, results) in diff_dict.iteritems():
132 old_checksum = results['old']
133 new_checksum = results['new']
134 # TODO(epoger): Currently, this assumes that all images have been
135 # checksummed using gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5
136 old_image_url = _CreateGSUrl(
137 imagename=imagename,
138 hash_type=gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5,
139 hash_digest=old_checksum)
140 new_image_url = _CreateGSUrl(
141 imagename=imagename,
142 hash_type=gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5,
143 hash_digest=new_checksum)
144 _DownloadUrlToFile(
145 source_url=old_image_url,
146 dest_path=os.path.join(old_flattened_dir,
147 filename_prefix + imagename))
148 _DownloadUrlToFile(
149 source_url=new_image_url,
150 dest_path=os.path.join(new_flattened_dir,
151 filename_prefix + imagename))
152
153 def SvnDiff(path_to_skdiff, dest_dir, source_dir):
154 """Generates a visual diff of all pending changes in source_dir.
76 155
77 @param path_to_skdiff 156 @param path_to_skdiff
78 @param dest_dir existing directory within which to write results 157 @param dest_dir existing directory within which to write results
158 @param source_dir
79 """ 159 """
80 # Validate parameters, filling in default values if necessary and possible. 160 # Validate parameters, filling in default values if necessary and possible.
81 path_to_skdiff = FindPathToSkDiff(path_to_skdiff) 161 path_to_skdiff = os.path.abspath(FindPathToSkDiff(path_to_skdiff))
82 if not dest_dir: 162 if not dest_dir:
83 dest_dir = tempfile.mkdtemp() 163 dest_dir = tempfile.mkdtemp()
164 dest_dir = os.path.abspath(dest_dir)
165
166 os.chdir(source_dir)
84 167
85 # Prepare temporary directories. 168 # Prepare temporary directories.
86 modified_flattened_dir = os.path.join(dest_dir, 'modified_flattened') 169 modified_flattened_dir = os.path.join(dest_dir, 'modified_flattened')
87 original_flattened_dir = os.path.join(dest_dir, 'original_flattened') 170 original_flattened_dir = os.path.join(dest_dir, 'original_flattened')
88 diff_dir = os.path.join(dest_dir, 'diffs') 171 diff_dir = os.path.join(dest_dir, 'diffs')
89 for dir in [modified_flattened_dir, original_flattened_dir, diff_dir] : 172 for dir in [modified_flattened_dir, original_flattened_dir, diff_dir] :
90 shutil.rmtree(dir, ignore_errors=True) 173 shutil.rmtree(dir, ignore_errors=True)
91 os.mkdir(dir) 174 os.mkdir(dir)
92 175
93 # Get a list of all locally modified (including added/deleted) files, 176 # Get a list of all locally modified (including added/deleted) files,
94 # descending subdirectories. 177 # descending subdirectories.
95 svn_repo = svn.Svn('.') 178 svn_repo = svn.Svn('.')
96 modified_file_paths = svn_repo.GetFilesWithStatus( 179 modified_file_paths = svn_repo.GetFilesWithStatus(
97 svn.STATUS_ADDED | svn.STATUS_DELETED | svn.STATUS_MODIFIED) 180 svn.STATUS_ADDED | svn.STATUS_DELETED | svn.STATUS_MODIFIED)
98 181
99 # For each modified file: 182 # For each modified file:
100 # 1. copy its current contents into modified_flattened_dir 183 # 1. copy its current contents into modified_flattened_dir
101 # 2. copy its original contents into original_flattened_dir 184 # 2. copy its original contents into original_flattened_dir
102 for modified_file_path in modified_file_paths: 185 for modified_file_path in modified_file_paths:
103 dest_filename = re.sub(os.sep, '__', modified_file_path) 186 if modified_file_path.endswith('.json'):
104 # If the file had STATUS_DELETED, it won't exist anymore... 187 # Special handling for JSON files, in the hopes that they
105 if os.path.isfile(modified_file_path): 188 # contain GM result summaries.
106 shutil.copyfile(modified_file_path, 189 (_unused, original_file_path) = tempfile.mkstemp()
107 os.path.join(modified_flattened_dir, dest_filename)) 190 svn_repo.ExportBaseVersionOfFile(modified_file_path,
108 svn_repo.ExportBaseVersionOfFile( 191 original_file_path)
109 modified_file_path, 192 platform_prefix = re.sub(os.sep, '__',
110 os.path.join(original_flattened_dir, dest_filename)) 193 os.path.dirname(modified_file_path)) + '__'
194 _CallJsonDiff(old_json_path=original_file_path,
195 new_json_path=modified_file_path,
196 old_flattened_dir=original_flattened_dir,
197 new_flattened_dir=modified_flattened_dir,
198 filename_prefix=platform_prefix)
199 os.remove(original_file_path)
200 else:
201 dest_filename = re.sub(os.sep, '__', modified_file_path)
202 # If the file had STATUS_DELETED, it won't exist anymore...
203 if os.path.isfile(modified_file_path):
204 shutil.copyfile(modified_file_path,
205 os.path.join(modified_flattened_dir, dest_filena me))
206 svn_repo.ExportBaseVersionOfFile(
207 modified_file_path,
208 os.path.join(original_flattened_dir, dest_filename))
111 209
112 # Run skdiff: compare original_flattened_dir against modified_flattened_dir 210 # Run skdiff: compare original_flattened_dir against modified_flattened_dir
113 RunCommand('%s %s %s %s' % (path_to_skdiff, original_flattened_dir, 211 RunCommand('%s %s %s %s' % (path_to_skdiff, original_flattened_dir,
114 modified_flattened_dir, diff_dir)) 212 modified_flattened_dir, diff_dir))
115 print '\nskdiff results are ready in file://%s/index.html' % diff_dir 213 print '\nskdiff results are ready in file://%s/index.html' % diff_dir
116 214
117 def RaiseUsageException(): 215 def RaiseUsageException():
118 raise Exception('%s\nRun with --help for more detail.' % ( 216 raise Exception('%s\nRun with --help for more detail.' % (
119 USAGE_STRING % __file__)) 217 USAGE_STRING % __file__))
120 218
121 def Main(options, args): 219 def Main(options, args):
122 """Allow other scripts to call this script with fake command-line args. 220 """Allow other scripts to call this script with fake command-line args.
123 """ 221 """
124 num_args = len(args) 222 num_args = len(args)
125 if num_args != 0: 223 if num_args != 0:
126 RaiseUsageException() 224 RaiseUsageException()
127 SvnDiff(path_to_skdiff=options.path_to_skdiff, dest_dir=options.dest_dir) 225 SvnDiff(path_to_skdiff=options.path_to_skdiff, dest_dir=options.dest_dir,
226 source_dir=options.source_dir)
128 227
129 if __name__ == '__main__': 228 if __name__ == '__main__':
130 parser = optparse.OptionParser(USAGE_STRING % '%prog' + HELP_STRING) 229 parser = optparse.OptionParser(USAGE_STRING % '%prog' + HELP_STRING)
131 parser.add_option(OPTION_DEST_DIR, 230 parser.add_option(OPTION_DEST_DIR,
132 action='store', type='string', default=None, 231 action='store', type='string', default=None,
133 help='existing directory within which to write results; ' 232 help='existing directory within which to write results; '
134 'if not set, will create a temporary directory which ' 233 'if not set, will create a temporary directory which '
135 'will remain in place after this script completes') 234 'will remain in place after this script completes')
136 parser.add_option(OPTION_PATH_TO_SKDIFF, 235 parser.add_option(OPTION_PATH_TO_SKDIFF,
137 action='store', type='string', default=None, 236 action='store', type='string', default=None,
138 help='path to already-built skdiff tool; if not set, ' 237 help='path to already-built skdiff tool; if not set, '
139 'will search for it in typical directories near this ' 238 'will search for it in typical directories near this '
140 'script') 239 'script')
240 parser.add_option(OPTION_SOURCE_DIR,
241 action='store', type='string', default='.',
242 help='root directory within which to compare all ' +
243 'files; defaults to "%default"')
141 (options, args) = parser.parse_args() 244 (options, args) = parser.parse_args()
142 Main(options, args) 245 Main(options, args)
OLDNEW
« no previous file with comments | « tools/rebaseline_imagefiles.py ('k') | tools/verify_images_for_gm_results.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698