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

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

Powered by Google App Engine
This is Rietveld 408576698