1 ''' 2 Compares the gm results within the local checkout against those already 3 committed to the Skia repository. 4 5 Launch with --help to see more information. 6 7 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 ''' 13 14 # common Python modules 15 import fnmatch 16 import optparse 17 import os 18 import shutil 19 import tempfile 20 21 # modules declared within this same directory 22 import svn 23 24 USAGE_STRING = 'Usage: %s [options]' 25 HOWTO_STRING = ''' 26 To update the checked-in baselines across all platforms, follow these steps: 27 28 cd .../trunk 29 svn update 30 svn stat # and make sure there are no files awaiting svn commit 31 make tools BUILDTYPE=Release 32 python tools/download_baselines.py 33 python tools/compare_baselines.py 34 # view compare_baselines output in a browser and make sure it's reasonable 35 # upload CL for review 36 # validate that the diffs look right in the review tool 37 # commit CL 38 39 Note that the above instructions will only *update* already-checked-in 40 baseline images; if you want to check in new baseline images (ones that the 41 bots have been generating but we don't have a golden master for yet), you need 42 to use download_baselines.py's --add-new-files option. 43 ''' 44 HELP_STRING = ''' 45 46 Compares the gm results within the local checkout against those already 47 committed to the Skia repository. Relies on skdiff to do the low-level 48 comparison. 49 50 ''' + HOWTO_STRING 51 52 TRUNK_PATH = os.path.join(os.path.dirname(__file__), os.pardir) 53 54 OPTION_GM_BASEDIR = '--gm-basedir' 55 DEFAULT_GM_BASEDIR = os.path.join(TRUNK_PATH, os.pardir, 'gm-expected') 56 OPTION_PATH_TO_SKDIFF = '--path-to-skdiff' 57 # default PATH_TO_SKDIFF is determined at runtime 58 OPTION_SVN_GM_URL = '--svn-gm-url' 59 DEFAULT_SVN_GM_URL = 'http://skia.googlecode.com/svn/gm-expected' 60 61 def CopyAllFilesAddingPrefix(source_dir, dest_dir, prefix): 62 """Copy all files from source_dir into dest_dir, adding prefix to the name 63 of each one as we copy it. 64 prefixes. 65 66 @param source_dir 67 @param dest_dir where to save the copied files 68 @param prefix prefix to add to each filename when we make the copy 69 """ 70 all_filenames = os.listdir(source_dir) 71 for filename in all_filenames: 72 source_path = os.path.join(source_dir, filename) 73 if os.path.isdir(source_path): 74 print 'skipping %s because it is a directory, not a file' % filename 75 continue 76 dest_path = os.path.join(dest_dir, '%s%s' % (prefix, filename)) 77 shutil.copyfile(source_path, dest_path) 78 79 def Flatten(source_dir, dest_dir, subdirectory_pattern): 80 """Copy all files from matching subdirectories under source_dir into 81 dest_dir, flattened into a single directory using subdirectory names as 82 prefixes. 83 84 @param source_dir 85 @param dest_dir where to save the copied files 86 @param subdirectory_pattern only copy files from subdirectories that match 87 this Unix-style filename pattern (e.g., 'base-*') 88 """ 89 all_filenames = os.listdir(source_dir) 90 matching_filenames = fnmatch.filter(all_filenames, subdirectory_pattern) 91 for filename in matching_filenames: 92 source_path = os.path.join(source_dir, filename) 93 if not os.path.isdir(source_path): 94 print 'skipping %s because it is a file, not a directory' % filename 95 continue 96 print 'flattening directory %s' % source_path 97 CopyAllFilesAddingPrefix(source_dir=source_path, dest_dir=dest_dir, 98 prefix='%s_' % filename) 99 100 def RunCommand(command): 101 """Run a command, raising an exception if it fails. 102 103 @param command the command as a single string 104 """ 105 print 'running command [%s]...' % command 106 retval = os.system(command) 107 #if retval is not 0: 108 # raise Exception('command [%s] failed' % command) 109 110 def FindPathToSkDiff(user_set_path=None): 111 """Return path to an existing skdiff binary, or raise an exception if we 112 cannot find one. 113 114 @param user_set_path if None, the user did not specify a path, so look in 115 some likely places; otherwise, only check at this path 116 """ 117 if user_set_path is not None: 118 if os.path.isfile(user_set_path): 119 return user_set_path 120 raise Exception('unable to find skdiff at user-set path %s' % 121 user_set_path) 122 trunk_path = os.path.join(os.path.dirname(__file__), os.pardir) 123 possible_paths = [os.path.join(trunk_path, 'out', 'Release', 'skdiff'), 124 os.path.join(trunk_path, 'out', 'Debug', 'skdiff')] 125 for try_path in possible_paths: 126 if os.path.isfile(try_path): 127 return try_path 128 raise Exception('cannot find skdiff in paths %s; maybe you need to ' 129 'specify the %s option or build skdiff?' % ( 130 possible_paths, OPTION_PATH_TO_SKDIFF)) 131 132 def CompareBaselines(gm_basedir, path_to_skdiff, svn_gm_url): 133 """Compare the gm results within gm_basedir against those already 134 committed to the Skia repository. 135 136 @param gm_basedir 137 @param path_to_skdiff 138 @param svn_gm_url base URL of Subversion repository where we store the 139 expected GM results 140 """ 141 # Validate parameters, filling in default values if necessary and possible. 142 if not os.path.isdir(gm_basedir): 143 raise Exception('cannot find gm_basedir at %s; maybe you need to ' 144 'specify the %s option?' % ( 145 gm_basedir, OPTION_GM_BASEDIR)) 146 path_to_skdiff = FindPathToSkDiff(path_to_skdiff) 147 148 tempdir_base = tempfile.mkdtemp() 149 150 # Download all checked-in baseline images to a temp directory 151 checkedin_dir = os.path.join(tempdir_base, 'checkedin') 152 os.mkdir(checkedin_dir) 153 svn.Svn(checkedin_dir).Checkout(svn_gm_url, '.') 154 155 # Flatten those checked-in baseline images into checkedin_flattened_dir 156 checkedin_flattened_dir = os.path.join(tempdir_base, 'checkedin_flattened') 157 os.mkdir(checkedin_flattened_dir) 158 Flatten(source_dir=checkedin_dir, dest_dir=checkedin_flattened_dir, 159 subdirectory_pattern='base-*') 160 161 # Flatten the local baseline images into local_flattened_dir 162 local_flattened_dir = os.path.join(tempdir_base, 'local_flattened') 163 os.mkdir(local_flattened_dir) 164 Flatten(source_dir=gm_basedir, dest_dir=local_flattened_dir, 165 subdirectory_pattern='base-*') 166 167 # Run skdiff to compare checkedin_flattened_dir against local_flattened_dir 168 diff_dir = os.path.join(tempdir_base, 'diffs') 169 os.mkdir(diff_dir) 170 RunCommand('%s %s %s %s' % (path_to_skdiff, checkedin_flattened_dir, 171 local_flattened_dir, diff_dir)) 172 print '\nskdiff results are ready in file://%s/index.html' % diff_dir 173 # TODO(epoger): delete tempdir_base tree to clean up after ourselves (but 174 # not before the user gets a chance to examine the results), and/or 175 # allow user to specify a different directory to write into? 176 177 def RaiseUsageException(): 178 raise Exception('%s\nRun with --help for more detail.' % ( 179 USAGE_STRING % __file__)) 180 181 def Main(options, args): 182 """Allow other scripts to call this script with fake command-line args. 183 """ 184 num_args = len(args) 185 if num_args != 0: 186 RaiseUsageException() 187 CompareBaselines(gm_basedir=options.gm_basedir, 188 path_to_skdiff=options.path_to_skdiff, 189 svn_gm_url=options.svn_gm_url) 190 191 if __name__ == '__main__': 192 parser = optparse.OptionParser(USAGE_STRING % '%prog' + HELP_STRING) 193 parser.add_option(OPTION_GM_BASEDIR, 194 action='store', type='string', default=DEFAULT_GM_BASEDIR, 195 help='path to root of locally stored baseline images ' 196 'to compare against those checked into the svn repo; ' 197 'defaults to "%s"' % DEFAULT_GM_BASEDIR) 198 parser.add_option(OPTION_PATH_TO_SKDIFF, 199 action='store', type='string', default=None, 200 help='path to already-built skdiff tool; if not set, ' 201 'will search for it in typical directories near this ' 202 'script') 203 parser.add_option(OPTION_SVN_GM_URL, 204 action='store', type='string', default=DEFAULT_SVN_GM_URL, 205 help='URL of SVN repository within which we store the ' 206 'expected GM baseline images; defaults to "%s"' % 207 DEFAULT_SVN_GM_URL) 208 (options, args) = parser.parse_args() 209 Main(options, args) 210