Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/env python
      2 
      3 # Copyright 2013 Google Inc. All rights reserved.
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 """
      8 This script will take as an argument either a list of skp files or a
      9 set of directories that contains skp files.  It will then test each
     10 skp file with the `render_pictures` program. If that program either
     11 spits out any unexpected output or doesn't return 0, I will flag that
     12 skp file as problematic.  We then extract all of the embedded images
     13 inside the skp and test each one of them against the
     14 SkImageDecoder::DecodeFile function.  Again, we consider any
     15 extraneous output or a bad return value an error.  In the event of an
     16 error, we retain the image and print out information about the error.
     17 The output (on stdout) is formatted as a csv document.
     18 
     19 A copy of each bad image is left in a directory created by
     20 tempfile.mkdtemp().
     21 """
     22 
     23 import glob
     24 import os
     25 import re
     26 import shutil
     27 import subprocess
     28 import sys
     29 import tempfile
     30 import threading
     31 
     32 import test_rendering  # skia/trunk/tools.  reuse FindPathToProgram()
     33 
     34 USAGE = """
     35 Usage:
     36     {command} SKP_FILE [SKP_FILES]
     37     {command} SKP_DIR [SKP_DIRS]\n
     38 Environment variables:
     39     To run multiple worker threads, set NUM_THREADS.
     40     To use a different temporary storage location, set TMPDIR.
     41 
     42 """
     43 
     44 def execute_program(args, ignores=None):
     45     """
     46     Execute a process and waits for it to complete.  Returns all
     47     output (stderr and stdout) after (optional) filtering.
     48 
     49     @param args is passed into subprocess.Popen().
     50 
     51     @param ignores (optional) is a list of regular expression strings
     52     that will be ignored in the output.
     53 
     54     @returns a tuple (returncode, output)
     55     """
     56     if ignores is None:
     57         ignores = []
     58     else:
     59         ignores = [re.compile(ignore) for ignore in ignores]
     60     proc = subprocess.Popen(
     61         args,
     62         stdout=subprocess.PIPE,
     63         stderr=subprocess.STDOUT)
     64     output = ''.join(
     65         line for line in proc.stdout
     66         if not any(bool(ignore.match(line)) for ignore in ignores))
     67     returncode = proc.wait()
     68     return (returncode, output)
     69 
     70 
     71 def list_files(paths):
     72     """
     73     Accepts a list of directories or filenames on the command line.
     74     We do not choose to recurse into directories beyond one level.
     75     """
     76     class NotAFileException(Exception):
     77         pass
     78     for path in paths:
     79         for globbedpath in glob.iglob(path): # useful on win32
     80             if os.path.isdir(globbedpath):
     81                 for filename in os.listdir(globbedpath):
     82                     newpath = os.path.join(globbedpath, filename)
     83                     if os.path.isfile(newpath):
     84                         yield newpath
     85             elif os.path.isfile(globbedpath):
     86                 yield globbedpath
     87             else:
     88                 raise NotAFileException('{} is not a file'.format(globbedpath))
     89 
     90 
     91 class BadImageFinder(object):
     92 
     93     def __init__(self, directory=None):
     94         self.render_pictures = test_rendering.FindPathToProgram(
     95             'render_pictures')
     96         self.test_image_decoder = test_rendering.FindPathToProgram(
     97             'test_image_decoder')
     98         assert os.path.isfile(self.render_pictures)
     99         assert os.path.isfile(self.test_image_decoder)
    100         if directory is None:
    101             self.saved_image_dir = tempfile.mkdtemp(prefix='skia_skp_test_')
    102         else:
    103             assert os.path.isdir(directory)
    104             self.saved_image_dir = directory
    105         self.bad_image_count = 0
    106 
    107     def process_files(self, skp_files):
    108         for path in skp_files:
    109             self.process_file(path)
    110 
    111     def process_file(self, skp_file):
    112         assert self.saved_image_dir is not None
    113         assert os.path.isfile(skp_file)
    114         args = [self.render_pictures, '--readPath', skp_file]
    115         ignores = ['^process_in', '^deserializ', '^drawing...', '^Non-defaul']
    116         returncode, output = execute_program(args, ignores)
    117         if (returncode == 0) and not output:
    118             return
    119         temp_image_dir = tempfile.mkdtemp(prefix='skia_skp_test___')
    120         args = [ self.render_pictures, '--readPath', skp_file,
    121                  '--writePath', temp_image_dir, '--writeEncodedImages']
    122         subprocess.call(args, stderr=open(os.devnull,'w'),
    123                         stdout=open(os.devnull,'w'))
    124         for image_name in os.listdir(temp_image_dir):
    125             image_path = os.path.join(temp_image_dir, image_name)
    126             assert(os.path.isfile(image_path))
    127             args = [self.test_image_decoder, image_path]
    128             returncode, output = execute_program(args, [])
    129             if (returncode == 0) and not output:
    130                 os.remove(image_path)
    131                 continue
    132             try:
    133                 shutil.move(image_path, self.saved_image_dir)
    134             except (shutil.Error,):
    135                 # If this happens, don't stop the entire process,
    136                 # just warn the user.
    137                 os.remove(image_path)
    138                 sys.stderr.write('{0} is a repeat.\n'.format(image_name))
    139             self.bad_image_count += 1
    140             if returncode == 2:
    141                 returncode = 'SkImageDecoder::DecodeFile returns false'
    142             elif returncode == 0:
    143                 returncode = 'extra verbosity'
    144                 assert output
    145             elif returncode == -11:
    146                 returncode = 'segmentation violation'
    147             else:
    148                 returncode = 'returncode: {}'.format(returncode)
    149             output = output.strip().replace('\n',' ').replace('"','\'')
    150             suffix = image_name[-3:]
    151             output_line = '"{0}","{1}","{2}","{3}","{4}"\n'.format(
    152                 returncode, suffix, skp_file, image_name, output)
    153             sys.stdout.write(output_line)
    154             sys.stdout.flush()
    155         os.rmdir(temp_image_dir)
    156         return
    157 
    158 def main(main_argv):
    159     if not main_argv or main_argv[0] in ['-h', '-?', '-help', '--help']:
    160         sys.stderr.write(USAGE.format(command=__file__))
    161         return 1
    162     if 'NUM_THREADS' in os.environ:
    163         number_of_threads = int(os.environ['NUM_THREADS'])
    164         if number_of_threads < 1:
    165             number_of_threads = 1
    166     else:
    167         number_of_threads = 1
    168     os.environ['skia_images_png_suppressDecoderWarnings'] = 'true'
    169     os.environ['skia_images_jpeg_suppressDecoderWarnings'] = 'true'
    170 
    171     temp_dir = tempfile.mkdtemp(prefix='skia_skp_test_')
    172     sys.stderr.write('Directory for bad images: {}\n'.format(temp_dir))
    173     sys.stdout.write('"Error","Filetype","SKP File","Image File","Output"\n')
    174     sys.stdout.flush()
    175 
    176     finders = [
    177         BadImageFinder(temp_dir) for index in xrange(number_of_threads)]
    178     arguments = [[] for index in xrange(number_of_threads)]
    179     for index, item in enumerate(list_files(main_argv)):
    180         ## split up the given targets among the worker threads
    181         arguments[index % number_of_threads].append(item)
    182     threads = [
    183         threading.Thread(
    184             target=BadImageFinder.process_files, args=(finder,argument))
    185         for finder, argument in zip(finders, arguments)]
    186     for thread in threads:
    187         thread.start()
    188     for thread in threads:
    189         thread.join()
    190     number  = sum(finder.bad_image_count for finder in finders)
    191     sys.stderr.write('Number of bad images found: {}\n'.format(number))
    192     return 0
    193 
    194 if __name__ == '__main__':
    195     exit(main(sys.argv[1:]))
    196 
    197 #  LocalWords:  skp stdout csv
    198