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