blob: 5dcf6a477e3ba5cbaa29ec9896ea152fcf79b31c [file] [log] [blame]
halcanary@google.comfed30372013-10-04 12:46:45 +00001#!/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