OLD | NEW |
---|---|
(Empty) | |
1 #!/usr/bin/env python | |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 ''' Runs various chrome tests through valgrind_test.py.''' | |
7 | |
8 import glob | |
9 import logging | |
10 import multiprocessing | |
Lei Zhang
2015/11/17 23:33:04
no longer used
zhaoqin
2015/11/18 16:26:36
Done.
| |
11 import optparse | |
12 import os | |
13 import stat | |
Lei Zhang
2015/11/17 23:33:04
no longer used
zhaoqin
2015/11/18 16:26:36
Done.
| |
14 import subprocess | |
15 import sys | |
16 | |
17 import logging_utils | |
18 import path_utils | |
19 | |
20 import common | |
21 import valgrind_test | |
22 | |
23 class TestNotFound(Exception): pass | |
24 | |
25 class MultipleGTestFiltersSpecified(Exception): pass | |
26 | |
27 class BuildDirNotFound(Exception): pass | |
28 | |
29 class BuildDirAmbiguous(Exception): pass | |
30 | |
31 class ExecutableNotFound(Exception): pass | |
32 | |
33 class BadBinary(Exception): pass | |
34 | |
35 class ChromeTests: | |
36 SLOW_TOOLS = ["memcheck", "drmemory"] | |
Lei Zhang
2015/11/17 23:33:04
Remove memcheck reference.
zhaoqin
2015/11/18 16:26:36
Done.
| |
37 | |
38 def __init__(self, options, args, test): | |
39 if ':' in test: | |
40 (self._test, self._gtest_filter) = test.split(':', 1) | |
41 else: | |
42 self._test = test | |
43 self._gtest_filter = options.gtest_filter | |
44 | |
45 if self._test not in self._test_list: | |
46 raise TestNotFound("Unknown test: %s" % test) | |
47 | |
48 if options.gtest_filter and options.gtest_filter != self._gtest_filter: | |
49 raise MultipleGTestFiltersSpecified("Can not specify both --gtest_filter " | |
50 "and --test %s" % test) | |
51 | |
52 self._options = options | |
53 self._args = args | |
54 | |
55 script_dir = path_utils.ScriptDir() | |
56 # Compute the top of the tree (the "source dir") from the script dir (where | |
57 # this script lives). We assume that the script dir is in tools/valgrind/ | |
58 # relative to the top of the tree. | |
59 self._source_dir = os.path.dirname(os.path.dirname(script_dir)) | |
60 # since this path is used for string matching, make sure it's always | |
61 # an absolute Unix-style path | |
62 self._source_dir = os.path.abspath(self._source_dir).replace('\\', '/') | |
63 valgrind_test_script = os.path.join(script_dir, "valgrind_test.py") | |
64 self._command_preamble = ["--source-dir=%s" % (self._source_dir)] | |
65 | |
66 if not self._options.build_dir: | |
67 dirs = [ | |
68 os.path.join(self._source_dir, "xcodebuild", "Debug"), | |
69 os.path.join(self._source_dir, "out", "Debug"), | |
70 os.path.join(self._source_dir, "build", "Debug"), | |
71 ] | |
72 build_dir = [d for d in dirs if os.path.isdir(d)] | |
73 if len(build_dir) > 1: | |
74 raise BuildDirAmbiguous("Found more than one suitable build dir:\n" | |
75 "%s\nPlease specify just one " | |
76 "using --build-dir" % ", ".join(build_dir)) | |
77 elif build_dir: | |
78 self._options.build_dir = build_dir[0] | |
79 else: | |
80 self._options.build_dir = None | |
81 | |
82 if self._options.build_dir: | |
83 build_dir = os.path.abspath(self._options.build_dir) | |
84 self._command_preamble += ["--build-dir=%s" % (self._options.build_dir)] | |
85 | |
86 def _EnsureBuildDirFound(self): | |
87 if not self._options.build_dir: | |
88 raise BuildDirNotFound("Oops, couldn't find a build dir, please " | |
89 "specify it manually using --build-dir") | |
90 | |
91 def _DefaultCommand(self, tool, exe=None, valgrind_test_args=None): | |
92 '''Generates the default command array that most tests will use.''' | |
93 if exe and common.IsWindows(): | |
94 exe += '.exe' | |
95 | |
96 cmd = list(self._command_preamble) | |
97 | |
98 # Find all suppressions matching the following pattern: | |
99 # tools/valgrind/TOOL/suppressions[_PLATFORM].txt | |
100 # and list them with --suppressions= prefix. | |
101 script_dir = path_utils.ScriptDir() | |
102 tool_name = tool.ToolName(); | |
103 suppression_file = os.path.join(script_dir, tool_name, "suppressions.txt") | |
104 if os.path.exists(suppression_file): | |
105 cmd.append("--suppressions=%s" % suppression_file) | |
106 # Platform-specific suppression | |
107 for platform in common.PlatformNames(): | |
108 platform_suppression_file = \ | |
109 os.path.join(script_dir, tool_name, 'suppressions_%s.txt' % platform) | |
110 if os.path.exists(platform_suppression_file): | |
111 cmd.append("--suppressions=%s" % platform_suppression_file) | |
112 | |
113 if self._options.valgrind_tool_flags: | |
114 cmd += self._options.valgrind_tool_flags.split(" ") | |
115 if self._options.keep_logs: | |
116 cmd += ["--keep_logs"] | |
117 if valgrind_test_args != None: | |
118 for arg in valgrind_test_args: | |
119 cmd.append(arg) | |
120 if exe: | |
121 self._EnsureBuildDirFound() | |
122 exe_path = os.path.join(self._options.build_dir, exe) | |
123 if not os.path.exists(exe_path): | |
124 raise ExecutableNotFound("Couldn't find '%s'" % exe_path) | |
125 | |
126 # Make sure we don't try to test ASan-built binaries | |
127 # with other dynamic instrumentation-based tools. | |
128 # TODO(timurrrr): also check TSan and MSan? | |
129 # `nm` might not be available, so use try-except. | |
130 try: | |
131 # Do not perform this check on OS X, as 'nm' on 10.6 can't handle | |
132 # binaries built with Clang 3.5+. | |
133 if not common.IsMac(): | |
134 nm_output = subprocess.check_output(["nm", exe_path]) | |
135 if nm_output.find("__asan_init") != -1: | |
136 raise BadBinary("You're trying to run an executable instrumented " | |
137 "with AddressSanitizer under %s. Please provide " | |
138 "an uninstrumented executable." % tool_name) | |
139 except OSError: | |
140 pass | |
141 | |
142 cmd.append(exe_path) | |
143 # Valgrind runs tests slowly, so slow tests hurt more; show elapased time | |
144 # so we can find the slowpokes. | |
145 cmd.append("--gtest_print_time") | |
146 # Built-in test launcher for gtest-based executables runs tests using | |
147 # multiple process by default. Force the single-process mode back. | |
148 cmd.append("--single-process-tests") | |
149 if self._options.gtest_repeat: | |
150 cmd.append("--gtest_repeat=%s" % self._options.gtest_repeat) | |
151 if self._options.gtest_shuffle: | |
152 cmd.append("--gtest_shuffle") | |
153 if self._options.gtest_break_on_failure: | |
154 cmd.append("--gtest_break_on_failure") | |
155 if self._options.test_launcher_bot_mode: | |
156 cmd.append("--test-launcher-bot-mode") | |
157 if self._options.test_launcher_total_shards is not None: | |
158 cmd.append("--test-launcher-total-shards=%d" % self._options.test_launcher _total_shards) | |
159 if self._options.test_launcher_shard_index is not None: | |
160 cmd.append("--test-launcher-shard-index=%d" % self._options.test_launcher_ shard_index) | |
161 return cmd | |
162 | |
163 def Run(self): | |
164 ''' Runs the test specified by command-line argument --test ''' | |
165 logging.info("running test %s" % (self._test)) | |
166 return self._test_list[self._test](self) | |
167 | |
168 def _AppendGtestFilter(self, tool, name, cmd): | |
169 '''Append an appropriate --gtest_filter flag to the googletest binary | |
170 invocation. | |
171 If the user passed his own filter mentioning only one test, just use it. | |
172 Othewise, filter out tests listed in the appropriate gtest_exclude files. | |
173 ''' | |
174 if (self._gtest_filter and | |
175 ":" not in self._gtest_filter and | |
176 "?" not in self._gtest_filter and | |
177 "*" not in self._gtest_filter): | |
178 cmd.append("--gtest_filter=%s" % self._gtest_filter) | |
179 return | |
180 | |
181 filters = [] | |
182 gtest_files_dir = os.path.join(path_utils.ScriptDir(), "gtest_exclude") | |
183 | |
184 gtest_filter_files = [ | |
185 os.path.join(gtest_files_dir, name + ".gtest-%s.txt" % tool.ToolName())] | |
186 # Use ".gtest.txt" files only for slow tools, as they now contain | |
187 # Valgrind- and Dr.Memory-specific filters. | |
188 # TODO(glider): rename the files to ".gtest_slow.txt" | |
189 if tool.ToolName() in ChromeTests.SLOW_TOOLS: | |
190 gtest_filter_files += [os.path.join(gtest_files_dir, name + ".gtest.txt")] | |
191 for platform_suffix in common.PlatformNames(): | |
192 gtest_filter_files += [ | |
193 os.path.join(gtest_files_dir, name + ".gtest_%s.txt" % platform_suffix), | |
194 os.path.join(gtest_files_dir, name + ".gtest-%s_%s.txt" % \ | |
195 (tool.ToolName(), platform_suffix))] | |
196 logging.info("Reading gtest exclude filter files:") | |
197 for filename in gtest_filter_files: | |
198 # strip the leading absolute path (may be very long on the bot) | |
199 # and the following / or \. | |
200 readable_filename = filename.replace("\\", "/") # '\' on Windows | |
201 readable_filename = readable_filename.replace(self._source_dir, "")[1:] | |
202 if not os.path.exists(filename): | |
203 logging.info(" \"%s\" - not found" % readable_filename) | |
204 continue | |
205 logging.info(" \"%s\" - OK" % readable_filename) | |
206 f = open(filename, 'r') | |
207 for line in f.readlines(): | |
208 if line.startswith("#") or line.startswith("//") or line.isspace(): | |
209 continue | |
210 line = line.rstrip() | |
211 test_prefixes = ["FLAKY", "FAILS"] | |
212 for p in test_prefixes: | |
213 # Strip prefixes from the test names. | |
214 line = line.replace(".%s_" % p, ".") | |
215 # Exclude the original test name. | |
216 filters.append(line) | |
217 if line[-2:] != ".*": | |
218 # List all possible prefixes if line doesn't end with ".*". | |
219 for p in test_prefixes: | |
220 filters.append(line.replace(".", ".%s_" % p)) | |
221 # Get rid of duplicates. | |
222 filters = set(filters) | |
223 gtest_filter = self._gtest_filter | |
224 if len(filters): | |
225 if gtest_filter: | |
226 gtest_filter += ":" | |
227 if gtest_filter.find("-") < 0: | |
228 gtest_filter += "-" | |
229 else: | |
230 gtest_filter = "-" | |
231 gtest_filter += ":".join(filters) | |
232 if gtest_filter: | |
233 cmd.append("--gtest_filter=%s" % gtest_filter) | |
234 | |
235 @staticmethod | |
236 def ShowTests(): | |
237 test_to_names = {} | |
238 for name, test_function in ChromeTests._test_list.iteritems(): | |
239 test_to_names.setdefault(test_function, []).append(name) | |
240 | |
241 name_to_aliases = {} | |
242 for names in test_to_names.itervalues(): | |
243 names.sort(key=lambda name: len(name)) | |
244 name_to_aliases[names[0]] = names[1:] | |
245 | |
246 print | |
247 print "Available tests:" | |
248 print "----------------" | |
249 for name, aliases in sorted(name_to_aliases.iteritems()): | |
250 if aliases: | |
251 print " {} (aka {})".format(name, ', '.join(aliases)) | |
252 else: | |
253 print " {}".format(name) | |
254 | |
255 def SetupLdPath(self, requires_build_dir): | |
256 if requires_build_dir: | |
257 self._EnsureBuildDirFound() | |
258 elif not self._options.build_dir: | |
259 return | |
260 | |
261 # Append build_dir to LD_LIBRARY_PATH so external libraries can be loaded. | |
262 if (os.getenv("LD_LIBRARY_PATH")): | |
263 os.putenv("LD_LIBRARY_PATH", "%s:%s" % (os.getenv("LD_LIBRARY_PATH"), | |
264 self._options.build_dir)) | |
265 else: | |
266 os.putenv("LD_LIBRARY_PATH", self._options.build_dir) | |
267 | |
268 def SimpleTest(self, module, name, valgrind_test_args=None, cmd_args=None): | |
269 tool = valgrind_test.CreateTool(self._options.valgrind_tool) | |
270 cmd = self._DefaultCommand(tool, name, valgrind_test_args) | |
271 self._AppendGtestFilter(tool, name, cmd) | |
272 cmd.extend(['--test-tiny-timeout=1000']) | |
273 if cmd_args: | |
274 cmd.extend(cmd_args) | |
275 | |
276 self.SetupLdPath(True) | |
277 return tool.Run(cmd, module) | |
278 | |
279 def RunCmdLine(self): | |
280 tool = valgrind_test.CreateTool(self._options.valgrind_tool) | |
281 cmd = self._DefaultCommand(tool, None, self._args) | |
282 self.SetupLdPath(False) | |
283 return tool.Run(cmd, None) | |
284 | |
285 def TestPDFiumUnitTests(self): | |
286 return self.SimpleTest("pdfium_unittests", "pdfium_unittests") | |
287 | |
288 def TestPDFiumEmbedderTests(self): | |
289 return self.SimpleTest("pdfium_embeddertests", "pdfium_embeddertests") | |
290 | |
291 # The known list of tests. | |
292 # Recognise the original abbreviations as well as full executable names. | |
Lei Zhang
2015/11/17 23:33:04
Comment not relevant here.
zhaoqin
2015/11/18 16:26:36
Done.
| |
293 _test_list = { | |
294 "cmdline" : RunCmdLine, | |
295 "pdfium_unittests": TestPDFiumUnitTests, | |
296 "pdfium_embeddertests": TestPDFiumEmbedderTests, | |
297 } | |
298 | |
299 | |
300 def _main(): | |
301 parser = optparse.OptionParser("usage: %prog -b <dir> -t <test> " | |
302 "[-t <test> ...]") | |
303 | |
304 parser.add_option("--help-tests", dest="help_tests", action="store_true", | |
305 default=False, help="List all available tests") | |
306 parser.add_option("-b", "--build-dir", | |
307 help="the location of the compiler output") | |
308 parser.add_option("--target", help="Debug or Release") | |
309 parser.add_option("-t", "--test", action="append", default=[], | |
310 help="which test to run, supports test:gtest_filter format " | |
311 "as well.") | |
312 parser.add_option("--baseline", action="store_true", default=False, | |
Lei Zhang
2015/11/17 23:33:04
Not used
zhaoqin
2015/11/18 16:26:36
Done.
| |
313 help="generate baseline data instead of validating") | |
314 parser.add_option("--gtest_filter", | |
315 help="additional arguments to --gtest_filter") | |
316 parser.add_option("--gtest_repeat", help="argument for --gtest_repeat") | |
317 parser.add_option("--gtest_shuffle", action="store_true", default=False, | |
318 help="Randomize tests' orders on every iteration.") | |
319 parser.add_option("--gtest_break_on_failure", action="store_true", | |
320 default=False, | |
321 help="Drop in to debugger on assertion failure. Also " | |
322 "useful for forcing tests to exit with a stack dump " | |
323 "on the first assertion failure when running with " | |
324 "--gtest_repeat=-1") | |
325 parser.add_option("-v", "--verbose", action="store_true", default=False, | |
326 help="verbose output - enable debug log messages") | |
327 parser.add_option("--tool", dest="valgrind_tool", default="drmemory_full", | |
328 help="specify a valgrind tool to run the tests under") | |
329 parser.add_option("--tool_flags", dest="valgrind_tool_flags", default="", | |
330 help="specify custom flags for the selected valgrind tool") | |
331 parser.add_option("--keep_logs", action="store_true", default=False, | |
332 help="store memory tool logs in the <tool>.logs directory " | |
333 "instead of /tmp.\nThis can be useful for tool " | |
334 "developers/maintainers.\nPlease note that the <tool>" | |
335 ".logs directory will be clobbered on tool startup.") | |
336 parser.add_option("--test-launcher-bot-mode", action="store_true", | |
337 help="run the tests with --test-launcher-bot-mode") | |
338 parser.add_option("--test-launcher-total-shards", type=int, | |
339 help="run the tests with --test-launcher-total-shards") | |
340 parser.add_option("--test-launcher-shard-index", type=int, | |
341 help="run the tests with --test-launcher-shard-index") | |
342 | |
343 options, args = parser.parse_args() | |
344 | |
345 # Bake target into build_dir. | |
346 if options.target and options.build_dir: | |
347 assert (options.target != | |
348 os.path.basename(os.path.dirname(options.build_dir))) | |
349 options.build_dir = os.path.join(os.path.abspath(options.build_dir), | |
350 options.target) | |
351 | |
352 if options.verbose: | |
353 logging_utils.config_root(logging.DEBUG) | |
354 else: | |
355 logging_utils.config_root() | |
356 | |
357 if options.help_tests: | |
358 ChromeTests.ShowTests() | |
359 return 0 | |
360 | |
361 if not options.test: | |
362 parser.error("--test not specified") | |
363 | |
364 if len(options.test) != 1 and options.gtest_filter: | |
365 parser.error("--gtest_filter and multiple tests don't make sense together") | |
366 | |
367 for t in options.test: | |
368 tests = ChromeTests(options, args, t) | |
369 ret = tests.Run() | |
370 if ret: return ret | |
371 return 0 | |
372 | |
373 | |
374 if __name__ == "__main__": | |
375 sys.exit(_main()) | |
OLD | NEW |