OLD | NEW |
| (Empty) |
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 | |
OLD | NEW |