| #!/usr/bin/env python | 
 |  | 
 | # Copyright 2013 Google Inc. All rights reserved. | 
 | # Use of this source code is governed by a BSD-style license that can be | 
 | # found in the LICENSE file. | 
 |  | 
 | """ | 
 | This script will take as an argument either a list of skp files or a | 
 | set of directories that contains skp files.  It will then test each | 
 | skp file with the `render_pictures` program. If that program either | 
 | spits out any unexpected output or doesn't return 0, I will flag that | 
 | skp file as problematic.  We then extract all of the embedded images | 
 | inside the skp and test each one of them against the | 
 | SkImageDecoder::DecodeFile function.  Again, we consider any | 
 | extraneous output or a bad return value an error.  In the event of an | 
 | error, we retain the image and print out information about the error. | 
 | The output (on stdout) is formatted as a csv document. | 
 |  | 
 | A copy of each bad image is left in a directory created by | 
 | tempfile.mkdtemp(). | 
 | """ | 
 |  | 
 | import glob | 
 | import os | 
 | import re | 
 | import shutil | 
 | import subprocess | 
 | import sys | 
 | import tempfile | 
 | import threading | 
 |  | 
 | import test_rendering  # skia/trunk/tools.  reuse FindPathToProgram() | 
 |  | 
 | USAGE = """ | 
 | Usage: | 
 |     {command} SKP_FILE [SKP_FILES] | 
 |     {command} SKP_DIR [SKP_DIRS]\n | 
 | Environment variables: | 
 |     To run multiple worker threads, set NUM_THREADS. | 
 |     To use a different temporary storage location, set TMPDIR. | 
 |  | 
 | """ | 
 |  | 
 | def execute_program(args, ignores=None): | 
 |     """ | 
 |     Execute a process and waits for it to complete.  Returns all | 
 |     output (stderr and stdout) after (optional) filtering. | 
 |  | 
 |     @param args is passed into subprocess.Popen(). | 
 |  | 
 |     @param ignores (optional) is a list of regular expression strings | 
 |     that will be ignored in the output. | 
 |  | 
 |     @returns a tuple (returncode, output) | 
 |     """ | 
 |     if ignores is None: | 
 |         ignores = [] | 
 |     else: | 
 |         ignores = [re.compile(ignore) for ignore in ignores] | 
 |     proc = subprocess.Popen( | 
 |         args, | 
 |         stdout=subprocess.PIPE, | 
 |         stderr=subprocess.STDOUT) | 
 |     output = ''.join( | 
 |         line for line in proc.stdout | 
 |         if not any(bool(ignore.match(line)) for ignore in ignores)) | 
 |     returncode = proc.wait() | 
 |     return (returncode, output) | 
 |  | 
 |  | 
 | def list_files(paths): | 
 |     """ | 
 |     Accepts a list of directories or filenames on the command line. | 
 |     We do not choose to recurse into directories beyond one level. | 
 |     """ | 
 |     class NotAFileException(Exception): | 
 |         pass | 
 |     for path in paths: | 
 |         for globbedpath in glob.iglob(path): # useful on win32 | 
 |             if os.path.isdir(globbedpath): | 
 |                 for filename in os.listdir(globbedpath): | 
 |                     newpath = os.path.join(globbedpath, filename) | 
 |                     if os.path.isfile(newpath): | 
 |                         yield newpath | 
 |             elif os.path.isfile(globbedpath): | 
 |                 yield globbedpath | 
 |             else: | 
 |                 raise NotAFileException('{} is not a file'.format(globbedpath)) | 
 |  | 
 |  | 
 | class BadImageFinder(object): | 
 |  | 
 |     def __init__(self, directory=None): | 
 |         self.render_pictures = test_rendering.FindPathToProgram( | 
 |             'render_pictures') | 
 |         self.test_image_decoder = test_rendering.FindPathToProgram( | 
 |             'test_image_decoder') | 
 |         assert os.path.isfile(self.render_pictures) | 
 |         assert os.path.isfile(self.test_image_decoder) | 
 |         if directory is None: | 
 |             self.saved_image_dir = tempfile.mkdtemp(prefix='skia_skp_test_') | 
 |         else: | 
 |             assert os.path.isdir(directory) | 
 |             self.saved_image_dir = directory | 
 |         self.bad_image_count = 0 | 
 |  | 
 |     def process_files(self, skp_files): | 
 |         for path in skp_files: | 
 |             self.process_file(path) | 
 |  | 
 |     def process_file(self, skp_file): | 
 |         assert self.saved_image_dir is not None | 
 |         assert os.path.isfile(skp_file) | 
 |         args = [self.render_pictures, '--readPath', skp_file] | 
 |         ignores = ['^process_in', '^deserializ', '^drawing...', '^Non-defaul'] | 
 |         returncode, output = execute_program(args, ignores) | 
 |         if (returncode == 0) and not output: | 
 |             return | 
 |         temp_image_dir = tempfile.mkdtemp(prefix='skia_skp_test___') | 
 |         args = [ self.render_pictures, '--readPath', skp_file, | 
 |                  '--writePath', temp_image_dir, '--writeEncodedImages'] | 
 |         subprocess.call(args, stderr=open(os.devnull,'w'), | 
 |                         stdout=open(os.devnull,'w')) | 
 |         for image_name in os.listdir(temp_image_dir): | 
 |             image_path = os.path.join(temp_image_dir, image_name) | 
 |             assert(os.path.isfile(image_path)) | 
 |             args = [self.test_image_decoder, image_path] | 
 |             returncode, output = execute_program(args, []) | 
 |             if (returncode == 0) and not output: | 
 |                 os.remove(image_path) | 
 |                 continue | 
 |             try: | 
 |                 shutil.move(image_path, self.saved_image_dir) | 
 |             except (shutil.Error,): | 
 |                 # If this happens, don't stop the entire process, | 
 |                 # just warn the user. | 
 |                 os.remove(image_path) | 
 |                 sys.stderr.write('{0} is a repeat.\n'.format(image_name)) | 
 |             self.bad_image_count += 1 | 
 |             if returncode == 2: | 
 |                 returncode = 'SkImageDecoder::DecodeFile returns false' | 
 |             elif returncode == 0: | 
 |                 returncode = 'extra verbosity' | 
 |                 assert output | 
 |             elif returncode == -11: | 
 |                 returncode = 'segmentation violation' | 
 |             else: | 
 |                 returncode = 'returncode: {}'.format(returncode) | 
 |             output = output.strip().replace('\n',' ').replace('"','\'') | 
 |             suffix = image_name[-3:] | 
 |             output_line = '"{0}","{1}","{2}","{3}","{4}"\n'.format( | 
 |                 returncode, suffix, skp_file, image_name, output) | 
 |             sys.stdout.write(output_line) | 
 |             sys.stdout.flush() | 
 |         os.rmdir(temp_image_dir) | 
 |         return | 
 |  | 
 | def main(main_argv): | 
 |     if not main_argv or main_argv[0] in ['-h', '-?', '-help', '--help']: | 
 |         sys.stderr.write(USAGE.format(command=__file__)) | 
 |         return 1 | 
 |     if 'NUM_THREADS' in os.environ: | 
 |         number_of_threads = int(os.environ['NUM_THREADS']) | 
 |         if number_of_threads < 1: | 
 |             number_of_threads = 1 | 
 |     else: | 
 |         number_of_threads = 1 | 
 |     os.environ['skia_images_png_suppressDecoderWarnings'] = 'true' | 
 |     os.environ['skia_images_jpeg_suppressDecoderWarnings'] = 'true' | 
 |  | 
 |     temp_dir = tempfile.mkdtemp(prefix='skia_skp_test_') | 
 |     sys.stderr.write('Directory for bad images: {}\n'.format(temp_dir)) | 
 |     sys.stdout.write('"Error","Filetype","SKP File","Image File","Output"\n') | 
 |     sys.stdout.flush() | 
 |  | 
 |     finders = [ | 
 |         BadImageFinder(temp_dir) for index in xrange(number_of_threads)] | 
 |     arguments = [[] for index in xrange(number_of_threads)] | 
 |     for index, item in enumerate(list_files(main_argv)): | 
 |         ## split up the given targets among the worker threads | 
 |         arguments[index % number_of_threads].append(item) | 
 |     threads = [ | 
 |         threading.Thread( | 
 |             target=BadImageFinder.process_files, args=(finder,argument)) | 
 |         for finder, argument in zip(finders, arguments)] | 
 |     for thread in threads: | 
 |         thread.start() | 
 |     for thread in threads: | 
 |         thread.join() | 
 |     number  = sum(finder.bad_image_count for finder in finders) | 
 |     sys.stderr.write('Number of bad images found: {}\n'.format(number)) | 
 |     return 0 | 
 |  | 
 | if __name__ == '__main__': | 
 |     exit(main(sys.argv[1:])) | 
 |  | 
 | #  LocalWords:  skp stdout csv |