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""" 8This script will take as an argument either a list of skp files or a 9set of directories that contains skp files. It will then test each 10skp file with the `render_pictures` program. If that program either 11spits out any unexpected output or doesn't return 0, I will flag that 12skp file as problematic. We then extract all of the embedded images 13inside the skp and test each one of them against the 14SkImageDecoder::DecodeFile function. Again, we consider any 15extraneous output or a bad return value an error. In the event of an 16error, we retain the image and print out information about the error. 17The output (on stdout) is formatted as a csv document. 18 19A copy of each bad image is left in a directory created by 20tempfile.mkdtemp(). 21""" 22 23import glob 24import os 25import re 26import shutil 27import subprocess 28import sys 29import tempfile 30import threading 31 32import test_rendering # skia/trunk/tools. reuse FindPathToProgram() 33 34USAGE = """ 35Usage: 36 {command} SKP_FILE [SKP_FILES] 37 {command} SKP_DIR [SKP_DIRS]\n 38Environment variables: 39 To run multiple worker threads, set NUM_THREADS. 40 To use a different temporary storage location, set TMPDIR. 41 42""" 43 44def 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 71def 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 91class 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 158def 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 194if __name__ == '__main__': 195 exit(main(sys.argv[1:])) 196 197# LocalWords: skp stdout csv 198