Chromium Code Reviews| 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 |