Home | History | Annotate | Download | only in tools
      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