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