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 """Generate and process code coverage. 7 8 TODO(jrg): rename this from coverage_posix.py to coverage_all.py! 9 10 Written for and tested on Mac, Linux, and Windows. To use this script 11 to generate coverage numbers, please run from within a gyp-generated 12 project. 13 14 All platforms, to set up coverage: 15 cd ...../chromium ; src/tools/gyp/gyp_dogfood -Dcoverage=1 src/build/all.gyp 16 17 Run coverage on... 18 Mac: 19 ( cd src/chrome ; xcodebuild -configuration Debug -target coverage ) 20 Linux: 21 ( cd src/chrome ; hammer coverage ) 22 # In particular, don't try and run 'coverage' from src/build 23 24 25 --directory=DIR: specify directory that contains gcda files, and where 26 a "coverage" directory will be created containing the output html. 27 Example name: ..../chromium/src/xcodebuild/Debug. 28 If not specified (e.g. buildbot) we will try and figure it out based on 29 other options (e.g. --target and --build-dir; see below). 30 31 --genhtml: generate html output. If not specified only lcov is generated. 32 33 --all_unittests: if present, run all files named *_unittests that we 34 can find. 35 36 --fast_test: make the tests run real fast (just for testing) 37 38 --strict: if a test fails, we continue happily. --strict will cause 39 us to die immediately. 40 41 --trim=False: by default we trim away tests known to be problematic on 42 specific platforms. If set to false we do NOT trim out tests. 43 44 --xvfb=True: By default we use Xvfb to make sure DISPLAY is valid 45 (Linux only). if set to False, do not use Xvfb. TODO(jrg): convert 46 this script from the compile stage of a builder to a 47 RunPythonCommandInBuildDir() command to avoid the need for this 48 step. 49 50 --timeout=SECS: if a subprocess doesn't have output within SECS, 51 assume it's a hang. Kill it and give up. 52 53 --bundles=BUNDLEFILE: a file containing a python list of coverage 54 bundles to be eval'd. Example contents of the bundlefile: 55 ['../base/base.gyp:base_unittests'] 56 This is used as part of the coverage bot. 57 If no other bundlefile-finding args are used (--target, 58 --build-dir), this is assumed to be an absolute path. 59 If those args are used, find BUNDLEFILE in a way consistent with 60 other scripts launched by buildbot. Example of another script 61 launched by buildbot: 62 http://src.chromium.org/viewvc/chrome/trunk/tools/buildbot/scripts/slave/runtest.py 63 64 --target=NAME: specify the build target (e.g. 'Debug' or 'Release'). 65 This is used by buildbot scripts to help us find the output directory. 66 Must be used with --build-dir. 67 68 --build-dir=DIR: According to buildbot comments, this is the name of 69 the directory within the buildbot working directory in which the 70 solution, Debug, and Release directories are found. 71 It's usually "src/build", but on mac it's $DIR/../xcodebuild and on 72 Linux it's $DIR/out. 73 This is used by buildbot scripts to help us find the output directory. 74 Must be used with --target. 75 76 --no_exclusions: Do NOT use the exclusion list. This script keeps a 77 list of tests known to be problematic under coverage. For example, 78 ProcessUtilTest.SpawnChild will crash inside __gcov_fork() when 79 using the MacOS 10.6 SDK. Use of --no_exclusions prevents the use 80 of this exclusion list. 81 82 --dont-clear-coverage-data: Normally we clear coverage data from 83 previous runs. If this arg is used we do NOT clear the coverage 84 data. 85 86 Strings after all options are considered tests to run. Test names 87 have all text before a ':' stripped to help with gyp compatibility. 88 For example, ../base/base.gyp:base_unittests is interpreted as a test 89 named "base_unittests". 90 """ 91 92 import glob 93 import logging 94 import optparse 95 import os 96 import Queue 97 import re 98 import shutil 99 import signal 100 import subprocess 101 import sys 102 import tempfile 103 import threading 104 import time 105 import traceback 106 107 """Global list of child PIDs to kill when we die.""" 108 gChildPIDs = [] 109 110 """Exclusion list. Format is 111 { platform: { testname: (exclusion1, exclusion2, ... ), ... } } 112 113 Platform is a match for sys.platform and can be a list. 114 Matching code does an 'if sys.platform in (the key):'. 115 Similarly, matching does an 'if testname in thefulltestname:' 116 117 The Chromium convention has traditionally been to place the 118 exclusion list in a distinct file. Unlike valgrind (which has 119 frequent changes when things break and are fixed), the expectation 120 here is that exclusions remain relatively constant (e.g. OS bugs). 121 If that changes, revisit the decision to place inclusions in this 122 script. 123 124 Details: 125 ProcessUtilTest.SpawnChild: chokes in __gcov_fork on 10.6 126 IPCFuzzingTest.MsgBadPayloadArgs: ditto 127 PanelBrowserNavigatorTest.NavigateFromCrashedPanel: Fails on coverage bot. 128 WebGLConformanceTests.conformance_attribs_gl_enable_vertex_attrib: Fails 129 with timeout (45000 ms) exceeded error. crbug.com/143248 130 WebGLConformanceTests.conformance_attribs_gl_disabled_vertex_attrib: 131 ditto. 132 WebGLConformanceTests.conformance_attribs_gl_vertex_attrib_zero_issues: 133 ditto. 134 WebGLConformanceTests.conformance_attribs_gl_vertex_attrib: ditto. 135 WebGLConformanceTests.conformance_attribs_gl_vertexattribpointer_offsets: 136 ditto. 137 WebGLConformanceTests.conformance_attribs_gl_vertexattribpointer: ditto. 138 WebGLConformanceTests.conformance_buffers_buffer_bind_test: After 139 disabling WebGLConformanceTests specified above, this test fails when run 140 on local machine. 141 WebGLConformanceTests.conformance_buffers_buffer_data_array_buffer: ditto. 142 WebGLConformanceTests.conformance_buffers_index_validation_copies_indices: 143 ditto. 144 WebGLConformanceTests. 145 conformance_buffers_index_validation_crash_with_buffer_sub_data: ditto. 146 WebGLConformanceTests. 147 conformance_buffers_index_validation_verifies_too_many_indices: ditto. 148 WebGLConformanceTests. 149 conformance_buffers_index_validation_with_resized_buffer: ditto. 150 WebGLConformanceTests.conformance_canvas_buffer_offscreen_test: ditto. 151 WebGLConformanceTests.conformance_canvas_buffer_preserve_test: ditto. 152 WebGLConformanceTests.conformance_canvas_canvas_test: ditto. 153 WebGLConformanceTests.conformance_canvas_canvas_zero_size: ditto. 154 WebGLConformanceTests. 155 conformance_canvas_drawingbuffer_static_canvas_test: ditto. 156 WebGLConformanceTests.conformance_canvas_drawingbuffer_test: ditto. 157 PageCycler*.*: Fails on coverage bot with "Missing test directory 158 /....../slave/coverage-dbg-linux/build/src/data/page_cycler/moz" error. 159 *FrameRateCompositingTest.*: Fails with 160 "FATAL:chrome_content_browser_client.cc(893)] Check failed: 161 command_line->HasSwitch(switches::kEnableStatsTable)." 162 *FrameRateNoVsyncCanvasInternalTest.*: ditto. 163 *FrameRateGpuCanvasInternalTest.*: ditto. 164 IndexedDBTest.Perf: Fails with 'Timeout reached in WaitUntilCookieValue' 165 error. 166 TwoClientPasswordsSyncTest.DeleteAll: Fails on coverage bot. 167 MigrationTwoClientTest.MigrationHellWithoutNigori: Fails with timeout 168 (45000 ms) exceeded error. 169 TwoClientSessionsSyncTest.DeleteActiveSession: ditto. 170 MultipleClientSessionsSyncTest.EncryptedAndChanged: ditto. 171 MigrationSingleClientTest.AllTypesIndividuallyTriggerNotification: ditto. 172 *OldPanelResizeBrowserTest.*: crbug.com/143247 173 *OldPanelDragBrowserTest.*: ditto. 174 *OldPanelBrowserTest.*: ditto. 175 *OldPanelAndDesktopNotificationTest.*: ditto. 176 *OldDockedPanelBrowserTest.*: ditto. 177 *OldDetachedPanelBrowserTest.*: ditto. 178 PanelDragBrowserTest.AttachWithSqueeze: ditto. 179 *PanelBrowserTest.*: ditto. 180 *DockedPanelBrowserTest.*: ditto. 181 *DetachedPanelBrowserTest.*: ditto. 182 AutomatedUITest.TheOneAndOnlyTest: crbug.com/143419 183 AutomatedUITestBase.DragOut: ditto 184 185 """ 186 gTestExclusions = { 187 'darwin2': { 'base_unittests': ('ProcessUtilTest.SpawnChild',), 188 'ipc_tests': ('IPCFuzzingTest.MsgBadPayloadArgs',), }, 189 'linux2': { 190 'gpu_tests': 191 ('WebGLConformanceTests.conformance_attribs_gl_enable_vertex_attrib', 192 'WebGLConformanceTests.' 193 'conformance_attribs_gl_disabled_vertex_attrib', 194 'WebGLConformanceTests.' 195 'conformance_attribs_gl_vertex_attrib_zero_issues', 196 'WebGLConformanceTests.conformance_attribs_gl_vertex_attrib', 197 'WebGLConformanceTests.' 198 'conformance_attribs_gl_vertexattribpointer_offsets', 199 'WebGLConformanceTests.conformance_attribs_gl_vertexattribpointer', 200 'WebGLConformanceTests.conformance_buffers_buffer_bind_test', 201 'WebGLConformanceTests.' 202 'conformance_buffers_buffer_data_array_buffer', 203 'WebGLConformanceTests.' 204 'conformance_buffers_index_validation_copies_indices', 205 'WebGLConformanceTests.' 206 'conformance_buffers_index_validation_crash_with_buffer_sub_data', 207 'WebGLConformanceTests.' 208 'conformance_buffers_index_validation_verifies_too_many_indices', 209 'WebGLConformanceTests.' 210 'conformance_buffers_index_validation_with_resized_buffer', 211 'WebGLConformanceTests.conformance_canvas_buffer_offscreen_test', 212 'WebGLConformanceTests.conformance_canvas_buffer_preserve_test', 213 'WebGLConformanceTests.conformance_canvas_canvas_test', 214 'WebGLConformanceTests.conformance_canvas_canvas_zero_size', 215 'WebGLConformanceTests.' 216 'conformance_canvas_drawingbuffer_static_canvas_test', 217 'WebGLConformanceTests.conformance_canvas_drawingbuffer_test',), 218 'performance_ui_tests': 219 ('*PageCycler*.*', 220 '*FrameRateCompositingTest.*', 221 '*FrameRateNoVsyncCanvasInternalTest.*', 222 '*FrameRateGpuCanvasInternalTest.*', 223 'IndexedDBTest.Perf',), 224 'sync_integration_tests': 225 ('TwoClientPasswordsSyncTest.DeleteAll', 226 'MigrationTwoClientTest.MigrationHellWithoutNigori', 227 'TwoClientSessionsSyncTest.DeleteActiveSession', 228 'MultipleClientSessionsSyncTest.EncryptedAndChanged', 229 'MigrationSingleClientTest.' 230 'AllTypesIndividuallyTriggerNotification',), 231 'interactive_ui_tests': 232 ('*OldPanelResizeBrowserTest.*', 233 '*OldPanelDragBrowserTest.*', 234 '*OldPanelBrowserTest.*', 235 '*OldPanelAndDesktopNotificationTest.*', 236 '*OldDockedPanelBrowserTest.*', 237 '*OldDetachedPanelBrowserTest.*', 238 'PanelDragBrowserTest.AttachWithSqueeze', 239 '*PanelBrowserTest.*', 240 '*DockedPanelBrowserTest.*', 241 '*DetachedPanelBrowserTest.*',), 242 'automated_ui_tests': 243 ('AutomatedUITest.TheOneAndOnlyTest', 244 'AutomatedUITestBase.DragOut',), }, 245 } 246 247 """Since random tests are failing/hanging on coverage bot, we are enabling 248 tests feature by feature. crbug.com/159748 249 """ 250 gTestInclusions = { 251 'linux2': { 252 'browser_tests': 253 (# 'src/chrome/browser/downloads' 254 'SavePageBrowserTest.*', 255 'SavePageAsMHTMLBrowserTest.*', 256 'DownloadQueryTest.*', 257 'DownloadDangerPromptTest.*', 258 'DownloadTest.*', 259 # 'src/chrome/browser/net' 260 'CookiePolicyBrowserTest.*', 261 'FtpBrowserTest.*', 262 'LoadTimingObserverTest.*', 263 'PredictorBrowserTest.*', 264 'ProxyBrowserTest.*', 265 # 'src/chrome/browser/extensions' 266 'Extension*.*', 267 'WindowOpenPanelDisabledTest.*', 268 'WindowOpenPanelTest.*', 269 'WebstoreStandalone*.*', 270 'CommandLineWebstoreInstall.*', 271 'WebViewTest.*', 272 'RequirementsCheckerBrowserTest.*', 273 'ProcessManagementTest.*', 274 'PlatformAppBrowserTest.*', 275 'PlatformAppDevToolsBrowserTest.*', 276 'LazyBackgroundPageApiTest.*', 277 'IsolatedAppTest.*', 278 'PanelMessagingTest.*', 279 'GeolocationApiTest.*', 280 'ClipboardApiTest.*', 281 'ExecuteScriptApiTest.*', 282 'CalculatorBrowserTest.*', 283 'ChromeAppAPITest.*', 284 'AppApiTest.*', 285 'BlockedAppApiTest.*', 286 'AppBackgroundPageApiTest.*', 287 'WebNavigationApiTest.*', 288 'UsbApiTest.*', 289 'TabCaptureApiTest.*', 290 'SystemInfo*.*', 291 'SyncFileSystemApiTest.*', 292 'SocketApiTest.*', 293 'SerialApiTest.*', 294 'RecordApiTest.*', 295 'PushMessagingApiTest.*', 296 'ProxySettingsApiTest.*', 297 'ExperimentalApiTest.*', 298 'OmniboxApiTest.*', 299 'OffscreenTabsApiTest.*', 300 'NotificationApiTest.*', 301 'MediaGalleriesPrivateApiTest.*', 302 'PlatformAppMediaGalleriesBrowserTest.*', 303 'GetAuthTokenFunctionTest.*', 304 'LaunchWebAuthFlowFunctionTest.*', 305 'FileSystemApiTest.*', 306 'ScriptBadgeApiTest.*', 307 'PageAsBrowserActionApiTest.*', 308 'PageActionApiTest.*', 309 'BrowserActionApiTest.*', 310 'DownloadExtensionTest.*', 311 'DnsApiTest.*', 312 'DeclarativeApiTest.*', 313 'BluetoothApiTest.*', 314 'AllUrlsApiTest.*', 315 # 'src/chrome/browser/nacl_host' 316 'nacl_host.*', 317 # 'src/chrome/browser/automation' 318 'AutomationMiscBrowserTest.*', 319 # 'src/chrome/browser/autofill' 320 'FormStructureBrowserTest.*', 321 'AutofillPopupViewBrowserTest.*', 322 'AutofillTest.*', 323 # 'src/chrome/browser/autocomplete' 324 'AutocompleteBrowserTest.*', 325 # 'src/chrome/browser/captive_portal' 326 'CaptivePortalBrowserTest.*', 327 # 'src/chrome/browser/geolocation' 328 'GeolocationAccessTokenStoreTest.*', 329 'GeolocationBrowserTest.*', 330 # 'src/chrome/browser/nacl_host' 331 'NaClGdbTest.*', 332 # 'src/chrome/browser/devtools' 333 'DevToolsSanityTest.*', 334 'DevToolsExtensionTest.*', 335 'DevToolsExperimentalExtensionTest.*', 336 'WorkerDevToolsSanityTest.*', 337 # 'src/chrome/browser/first_run' 338 'FirstRunBrowserTest.*', 339 # 'src/chrome/browser/importer' 340 'ToolbarImporterUtilsTest.*', 341 # 'src/chrome/browser/page_cycler' 342 'PageCyclerBrowserTest.*', 343 'PageCyclerCachedBrowserTest.*', 344 # 'src/chrome/browser/performance_monitor' 345 'PerformanceMonitorBrowserTest.*', 346 'PerformanceMonitorUncleanExitBrowserTest.*', 347 'PerformanceMonitorSessionRestoreBrowserTest.*', 348 # 'src/chrome/browser/prerender' 349 'PrerenderBrowserTest.*', 350 'PrerenderBrowserTestWithNaCl.*', 351 'PrerenderBrowserTestWithExtensions.*', 352 'PrefetchBrowserTest.*', 353 'PrefetchBrowserTestNoPrefetching.*', ), 354 }, 355 } 356 357 358 def TerminateSignalHandler(sig, stack): 359 """When killed, try and kill our child processes.""" 360 signal.signal(sig, signal.SIG_DFL) 361 for pid in gChildPIDs: 362 if 'kill' in os.__all__: # POSIX 363 os.kill(pid, sig) 364 else: 365 subprocess.call(['taskkill.exe', '/PID', str(pid)]) 366 sys.exit(0) 367 368 369 class RunTooLongException(Exception): 370 """Thrown when a command runs too long without output.""" 371 pass 372 373 class BadUserInput(Exception): 374 """Thrown when arguments from the user are incorrectly formatted.""" 375 pass 376 377 378 class RunProgramThread(threading.Thread): 379 """A thread to run a subprocess. 380 381 We want to print the output of our subprocess in real time, but also 382 want a timeout if there has been no output for a certain amount of 383 time. Normal techniques (e.g. loop in select()) aren't cross 384 platform enough. the function seems simple: "print output of child, kill it 385 if there is no output by timeout. But it was tricky to get this right 386 in a x-platform way (see warnings about deadlock on the python 387 subprocess doc page). 388 389 """ 390 # Constants in our queue 391 PROGRESS = 0 392 DONE = 1 393 394 def __init__(self, cmd): 395 super(RunProgramThread, self).__init__() 396 self._cmd = cmd 397 self._process = None 398 self._queue = Queue.Queue() 399 self._retcode = None 400 401 def run(self): 402 if sys.platform in ('win32', 'cygwin'): 403 return self._run_windows() 404 else: 405 self._run_posix() 406 407 def _run_windows(self): 408 # We need to save stdout to a temporary file because of a bug on the 409 # windows implementation of python which can deadlock while waiting 410 # for the IO to complete while writing to the PIPE and the pipe waiting 411 # on us and us waiting on the child process. 412 stdout_file = tempfile.TemporaryFile() 413 try: 414 self._process = subprocess.Popen(self._cmd, 415 stdin=subprocess.PIPE, 416 stdout=stdout_file, 417 stderr=subprocess.STDOUT) 418 gChildPIDs.append(self._process.pid) 419 try: 420 # To make sure that the buildbot don't kill us if we run too long 421 # without any activity on the console output, we look for progress in 422 # the length of the temporary file and we print what was accumulated so 423 # far to the output console to make the buildbot know we are making some 424 # progress. 425 previous_tell = 0 426 # We will poll the process until we get a non-None return code. 427 self._retcode = None 428 while self._retcode is None: 429 self._retcode = self._process.poll() 430 current_tell = stdout_file.tell() 431 if current_tell > previous_tell: 432 # Report progress to our main thread so we don't timeout. 433 self._queue.put(RunProgramThread.PROGRESS) 434 # And print what was accumulated to far. 435 stdout_file.seek(previous_tell) 436 print stdout_file.read(current_tell - previous_tell), 437 previous_tell = current_tell 438 # Don't be selfish, let other threads do stuff while we wait for 439 # the process to complete. 440 time.sleep(0.5) 441 # OK, the child process has exited, let's print its output to our 442 # console to create debugging logs in case they get to be needed. 443 stdout_file.flush() 444 stdout_file.seek(previous_tell) 445 print stdout_file.read(stdout_file.tell() - previous_tell) 446 except IOError, e: 447 logging.exception('%s', e) 448 pass 449 finally: 450 stdout_file.close() 451 452 # If we get here the process is done. 453 gChildPIDs.remove(self._process.pid) 454 self._queue.put(RunProgramThread.DONE) 455 456 def _run_posix(self): 457 """No deadlock problem so use the simple answer. The windows solution 458 appears to add extra buffering which we don't want on other platforms.""" 459 self._process = subprocess.Popen(self._cmd, 460 stdout=subprocess.PIPE, 461 stderr=subprocess.STDOUT) 462 gChildPIDs.append(self._process.pid) 463 try: 464 while True: 465 line = self._process.stdout.readline() 466 if not line: # EOF 467 break 468 print line, 469 self._queue.put(RunProgramThread.PROGRESS, True) 470 except IOError: 471 pass 472 # If we get here the process is done. 473 gChildPIDs.remove(self._process.pid) 474 self._queue.put(RunProgramThread.DONE) 475 476 def stop(self): 477 self.kill() 478 479 def kill(self): 480 """Kill our running process if needed. Wait for kill to complete. 481 482 Should be called in the PARENT thread; we do not self-kill. 483 Returns the return code of the process. 484 Safe to call even if the process is dead. 485 """ 486 if not self._process: 487 return self.retcode() 488 if 'kill' in os.__all__: # POSIX 489 os.kill(self._process.pid, signal.SIGKILL) 490 else: 491 subprocess.call(['taskkill.exe', '/PID', str(self._process.pid)]) 492 return self.retcode() 493 494 def retcode(self): 495 """Return the return value of the subprocess. 496 497 Waits for process to die but does NOT kill it explicitly. 498 """ 499 if self._retcode == None: # must be none, not 0/False 500 self._retcode = self._process.wait() 501 return self._retcode 502 503 def RunUntilCompletion(self, timeout): 504 """Run thread until completion or timeout (in seconds). 505 506 Start the thread. Let it run until completion, or until we've 507 spent TIMEOUT without seeing output. On timeout throw 508 RunTooLongException. 509 """ 510 self.start() 511 while True: 512 try: 513 x = self._queue.get(True, timeout) 514 if x == RunProgramThread.DONE: 515 return self.retcode() 516 except Queue.Empty, e: # timed out 517 logging.info('TIMEOUT (%d seconds exceeded with no output): killing' % 518 timeout) 519 self.kill() 520 raise RunTooLongException() 521 522 523 class Coverage(object): 524 """Doitall class for code coverage.""" 525 526 def __init__(self, options, args): 527 super(Coverage, self).__init__() 528 logging.basicConfig(level=logging.DEBUG) 529 self.directory = options.directory 530 self.options = options 531 self.args = args 532 self.ConfirmDirectory() 533 self.directory_parent = os.path.dirname(self.directory) 534 self.output_directory = os.path.join(self.directory, 'coverage') 535 if not os.path.exists(self.output_directory): 536 os.mkdir(self.output_directory) 537 # The "final" lcov-format file 538 self.coverage_info_file = os.path.join(self.directory, 'coverage.info') 539 # If needed, an intermediate VSTS-format file 540 self.vsts_output = os.path.join(self.directory, 'coverage.vsts') 541 # Needed for Windows. 542 self.src_root = options.src_root 543 self.FindPrograms() 544 self.ConfirmPlatformAndPaths() 545 self.tests = [] # This can be a list of strings, lists or both. 546 self.xvfb_pid = 0 547 self.test_files = [] # List of files with test specifications. 548 self.test_filters = {} # Mapping from testname->--gtest_filter arg. 549 logging.info('self.directory: ' + self.directory) 550 logging.info('self.directory_parent: ' + self.directory_parent) 551 552 def FindInPath(self, program): 553 """Find program in our path. Return abs path to it, or None.""" 554 if not 'PATH' in os.environ: 555 logging.fatal('No PATH environment variable?') 556 sys.exit(1) 557 paths = os.environ['PATH'].split(os.pathsep) 558 for path in paths: 559 fullpath = os.path.join(path, program) 560 if os.path.exists(fullpath): 561 return fullpath 562 return None 563 564 def FindPrograms(self): 565 """Find programs we may want to run.""" 566 if self.IsPosix(): 567 self.lcov_directory = os.path.join(sys.path[0], 568 '../../third_party/lcov/bin') 569 self.lcov = os.path.join(self.lcov_directory, 'lcov') 570 self.mcov = os.path.join(self.lcov_directory, 'mcov') 571 self.genhtml = os.path.join(self.lcov_directory, 'genhtml') 572 self.programs = [self.lcov, self.mcov, self.genhtml] 573 else: 574 # Hack to get the buildbot working. 575 os.environ['PATH'] += r';c:\coverage\coverage_analyzer' 576 os.environ['PATH'] += r';c:\coverage\performance_tools' 577 # (end hack) 578 commands = ['vsperfcmd.exe', 'vsinstr.exe', 'coverage_analyzer.exe'] 579 self.perf = self.FindInPath('vsperfcmd.exe') 580 self.instrument = self.FindInPath('vsinstr.exe') 581 self.analyzer = self.FindInPath('coverage_analyzer.exe') 582 if not self.perf or not self.instrument or not self.analyzer: 583 logging.fatal('Could not find Win performance commands.') 584 logging.fatal('Commands needed in PATH: ' + str(commands)) 585 sys.exit(1) 586 self.programs = [self.perf, self.instrument, self.analyzer] 587 588 def PlatformBuildPrefix(self): 589 """Return a platform specific build directory prefix. 590 591 This prefix is prepended to the build target (Debug, Release) to 592 identify output as relative to the build directory. 593 These values are specific to Chromium's use of gyp. 594 """ 595 if self.IsMac(): 596 return '../xcodebuild' 597 if self.IsWindows(): 598 return '' 599 else: # Linux 600 return '../out' # assumes make, unlike runtest.py 601 602 def ConfirmDirectory(self): 603 """Confirm correctness of self.directory. 604 605 If it exists, happiness. If not, try and figure it out in a 606 manner similar to FindBundlesFile(). The 'figure it out' case 607 happens with buildbot where the directory isn't specified 608 explicitly. 609 """ 610 if (not self.directory and 611 not (self.options.target and self.options.build_dir)): 612 logging.fatal('Must use --directory or (--target and --build-dir)') 613 sys.exit(1) 614 615 if not self.directory: 616 self.directory = os.path.join(self.options.build_dir, 617 self.PlatformBuildPrefix(), 618 self.options.target) 619 620 if os.path.exists(self.directory): 621 logging.info('Directory: ' + self.directory) 622 return 623 else: 624 logging.fatal('Directory ' + 625 self.directory + ' doesn\'t exist') 626 sys.exit(1) 627 628 629 def FindBundlesFile(self): 630 """Find the bundlesfile. 631 632 The 'bundles' file can be either absolute path, or (if we are run 633 from buildbot) we need to find it based on other hints (--target, 634 --build-dir, etc). 635 """ 636 # If no bundle file, no problem! 637 if not self.options.bundles: 638 return 639 # If true, we're buildbot. Form a path. 640 # Else assume absolute. 641 if self.options.target and self.options.build_dir: 642 fullpath = os.path.join(self.options.build_dir, 643 self.PlatformBuildPrefix(), 644 self.options.target, 645 self.options.bundles) 646 self.options.bundles = fullpath 647 648 if os.path.exists(self.options.bundles): 649 logging.info('BundlesFile: ' + self.options.bundles) 650 return 651 else: 652 logging.fatal('bundlefile ' + 653 self.options.bundles + ' doesn\'t exist') 654 sys.exit(1) 655 656 657 def FindTests(self): 658 """Find unit tests to run; set self.tests to this list. 659 660 Assume all non-option items in the arg list are tests to be run. 661 """ 662 # Before we begin, find the bundles file if not an absolute path. 663 self.FindBundlesFile() 664 665 # Small tests: can be run in the "chromium" directory. 666 # If asked, run all we can find. 667 if self.options.all_unittests: 668 self.tests += glob.glob(os.path.join(self.directory, '*_unittests')) 669 self.tests += glob.glob(os.path.join(self.directory, '*unit_tests')) 670 elif self.options.all_browsertests: 671 # Run all tests in browser_tests and content_browsertests. 672 self.tests += glob.glob(os.path.join(self.directory, 'browser_tests')) 673 self.tests += glob.glob(os.path.join(self.directory, 674 'content_browsertests')) 675 676 # Tests can come in as args directly, indirectly (through a file 677 # of test lists) or as a file of bundles. 678 all_testnames = self.args[:] # Copy since we might modify 679 680 for test_file in self.options.test_files: 681 f = open(test_file) 682 for line in f: 683 line = re.sub(r"#.*$", "", line) 684 line = re.sub(r"\s*", "", line) 685 if re.match("\s*$"): 686 continue 687 all_testnames.append(line) 688 f.close() 689 690 tests_from_bundles = None 691 if self.options.bundles: 692 try: 693 tests_from_bundles = eval(open(self.options.bundles).read()) 694 except IOError: 695 logging.fatal('IO error in bundle file ' + 696 self.options.bundles + ' (doesn\'t exist?)') 697 except (NameError, SyntaxError): 698 logging.fatal('Parse or syntax error in bundle file ' + 699 self.options.bundles) 700 if hasattr(tests_from_bundles, '__iter__'): 701 all_testnames += tests_from_bundles 702 else: 703 logging.fatal('Fatal error with bundle file; could not get list from' + 704 self.options.bundles) 705 sys.exit(1) 706 707 # If told explicit tests, run those (after stripping the name as 708 # appropriate) 709 for testname in all_testnames: 710 mo = re.search(r"(.*)\[(.*)\]$", testname) 711 gtest_filter = None 712 if mo: 713 gtest_filter = mo.group(2) 714 testname = mo.group(1) 715 if ':' in testname: 716 testname = testname.split(':')[1] 717 # We need 'pyautolib' to run pyauto tests and 'pyautolib' itself is not an 718 # executable. So skip this test from adding into coverage_bundles.py. 719 if testname == 'pyautolib': 720 continue 721 self.tests += [os.path.join(self.directory, testname)] 722 if gtest_filter: 723 self.test_filters[testname] = gtest_filter 724 725 # Add 'src/test/functional/pyauto_functional.py' to self.tests. 726 # This file with '-v --suite=CODE_COVERAGE' arguments runs all pyauto tests. 727 # Pyauto tests are failing randomly on coverage bots. So excluding them. 728 # self.tests += [['src/chrome/test/functional/pyauto_functional.py', 729 # '-v', 730 # '--suite=CODE_COVERAGE']] 731 732 # Medium tests? 733 # Not sure all of these work yet (e.g. page_cycler_tests) 734 # self.tests += glob.glob(os.path.join(self.directory, '*_tests')) 735 736 # If needed, append .exe to tests since vsinstr.exe likes it that 737 # way. 738 if self.IsWindows(): 739 for ind in range(len(self.tests)): 740 test = self.tests[ind] 741 test_exe = test + '.exe' 742 if not test.endswith('.exe') and os.path.exists(test_exe): 743 self.tests[ind] = test_exe 744 745 def TrimTests(self): 746 """Trim specific tests for each platform.""" 747 if self.IsWindows(): 748 return 749 # TODO(jrg): remove when not needed 750 inclusion = ['unit_tests'] 751 keep = [] 752 for test in self.tests: 753 for i in inclusion: 754 if i in test: 755 keep.append(test) 756 self.tests = keep 757 logging.info('After trimming tests we have ' + ' '.join(self.tests)) 758 return 759 if self.IsLinux(): 760 # self.tests = filter(lambda t: t.endswith('base_unittests'), self.tests) 761 return 762 if self.IsMac(): 763 exclusion = ['automated_ui_tests'] 764 punted = [] 765 for test in self.tests: 766 for e in exclusion: 767 if test.endswith(e): 768 punted.append(test) 769 self.tests = filter(lambda t: t not in punted, self.tests) 770 if punted: 771 logging.info('Tests trimmed out: ' + str(punted)) 772 773 def ConfirmPlatformAndPaths(self): 774 """Confirm OS and paths (e.g. lcov).""" 775 for program in self.programs: 776 if not os.path.exists(program): 777 logging.fatal('Program missing: ' + program) 778 sys.exit(1) 779 780 def Run(self, cmdlist, ignore_error=False, ignore_retcode=None, 781 explanation=None): 782 """Run the command list; exit fatally on error. 783 784 Args: 785 cmdlist: a list of commands (e.g. to pass to subprocess.call) 786 ignore_error: if True log an error; if False then exit. 787 ignore_retcode: if retcode is non-zero, exit unless we ignore. 788 789 Returns: process return code. 790 Throws: RunTooLongException if the process does not produce output 791 within TIMEOUT seconds; timeout is specified as a command line 792 option to the Coverage class and is set on init. 793 """ 794 logging.info('Running ' + str(cmdlist)) 795 t = RunProgramThread(cmdlist) 796 retcode = t.RunUntilCompletion(self.options.timeout) 797 798 if retcode: 799 if ignore_error or retcode == ignore_retcode: 800 logging.warning('COVERAGE: %s unhappy but errors ignored %s' % 801 (str(cmdlist), explanation or '')) 802 else: 803 logging.fatal('COVERAGE: %s failed; return code: %d' % 804 (str(cmdlist), retcode)) 805 sys.exit(retcode) 806 return retcode 807 808 def IsPosix(self): 809 """Return True if we are POSIX.""" 810 return self.IsMac() or self.IsLinux() 811 812 def IsMac(self): 813 return sys.platform == 'darwin' 814 815 def IsLinux(self): 816 return sys.platform.startswith('linux') 817 818 def IsWindows(self): 819 """Return True if we are Windows.""" 820 return sys.platform in ('win32', 'cygwin') 821 822 def ClearData(self): 823 """Clear old gcda files and old coverage info files.""" 824 if self.options.dont_clear_coverage_data: 825 print 'Clearing of coverage data NOT performed.' 826 return 827 print 'Clearing coverage data from previous runs.' 828 if os.path.exists(self.coverage_info_file): 829 os.remove(self.coverage_info_file) 830 if self.IsPosix(): 831 subprocess.call([self.lcov, 832 '--directory', self.directory_parent, 833 '--zerocounters']) 834 shutil.rmtree(os.path.join(self.directory, 'coverage')) 835 if self.options.all_unittests: 836 if os.path.exists(os.path.join(self.directory, 'unittests_coverage')): 837 shutil.rmtree(os.path.join(self.directory, 'unittests_coverage')) 838 elif self.options.all_browsertests: 839 if os.path.exists(os.path.join(self.directory, 840 'browsertests_coverage')): 841 shutil.rmtree(os.path.join(self.directory, 'browsertests_coverage')) 842 else: 843 if os.path.exists(os.path.join(self.directory, 'total_coverage')): 844 shutil.rmtree(os.path.join(self.directory, 'total_coverage')) 845 846 def BeforeRunOneTest(self, testname): 847 """Do things before running each test.""" 848 if not self.IsWindows(): 849 return 850 # Stop old counters if needed 851 cmdlist = [self.perf, '-shutdown'] 852 self.Run(cmdlist, ignore_error=True) 853 # Instrument binaries 854 for fulltest in self.tests: 855 if os.path.exists(fulltest): 856 # See http://support.microsoft.com/kb/939818 for details on args 857 cmdlist = [self.instrument, '/d:ignorecverr', '/COVERAGE', fulltest] 858 self.Run(cmdlist, ignore_retcode=4, 859 explanation='OK with a multiple-instrument') 860 # Start new counters 861 cmdlist = [self.perf, '-start:coverage', '-output:' + self.vsts_output] 862 self.Run(cmdlist) 863 864 def BeforeRunAllTests(self): 865 """Called right before we run all tests.""" 866 if self.IsLinux() and self.options.xvfb: 867 self.StartXvfb() 868 869 def GtestFilter(self, fulltest, excl=None): 870 """Return a --gtest_filter=BLAH for this test. 871 872 Args: 873 fulltest: full name of test executable 874 exclusions: the exclusions list. Only set in a unit test; 875 else uses gTestExclusions. 876 Returns: 877 String of the form '--gtest_filter=BLAH', or None. 878 """ 879 positive_gfilter_list = [] 880 negative_gfilter_list = [] 881 882 # Exclude all flaky, failing, disabled and maybe tests; 883 # they don't count for code coverage. 884 negative_gfilter_list += ('*.FLAKY_*', '*.FAILS_*', 885 '*.DISABLED_*', '*.MAYBE_*') 886 887 if not self.options.no_exclusions: 888 exclusions = excl or gTestExclusions 889 excldict = exclusions.get(sys.platform) 890 if excldict: 891 for test in excldict.keys(): 892 # example: if base_unittests in ../blah/blah/base_unittests.exe 893 if test in fulltest: 894 negative_gfilter_list += excldict[test] 895 896 inclusions = gTestInclusions 897 include_dict = inclusions.get(sys.platform) 898 if include_dict: 899 for test in include_dict.keys(): 900 if test in fulltest: 901 positive_gfilter_list += include_dict[test] 902 903 fulltest_basename = os.path.basename(fulltest) 904 if fulltest_basename in self.test_filters: 905 specific_test_filters = self.test_filters[fulltest_basename].split('-') 906 if len(specific_test_filters) > 2: 907 logging.error('Multiple "-" symbols in filter list: %s' % 908 self.test_filters[fulltest_basename]) 909 raise BadUserInput() 910 if len(specific_test_filters) == 2: 911 # Remove trailing ':' 912 specific_test_filters[0] = specific_test_filters[0][:-1] 913 914 if specific_test_filters[0]: # Test for no positive filters. 915 positive_gfilter_list += specific_test_filters[0].split(':') 916 if len(specific_test_filters) > 1: 917 negative_gfilter_list += specific_test_filters[1].split(':') 918 919 if not positive_gfilter_list and not negative_gfilter_list: 920 return None 921 922 result = '--gtest_filter=' 923 if positive_gfilter_list: 924 result += ':'.join(positive_gfilter_list) 925 if negative_gfilter_list: 926 if positive_gfilter_list: result += ':' 927 result += '-' + ':'.join(negative_gfilter_list) 928 return result 929 930 def RunTests(self): 931 """Run all unit tests and generate appropriate lcov files.""" 932 self.BeforeRunAllTests() 933 for fulltest in self.tests: 934 if type(fulltest) is str: 935 if not os.path.exists(fulltest): 936 logging.info(fulltest + ' does not exist') 937 if self.options.strict: 938 sys.exit(2) 939 else: 940 logging.info('%s path exists' % fulltest) 941 cmdlist = [fulltest, '--gtest_print_time'] 942 943 # If asked, make this REAL fast for testing. 944 if self.options.fast_test: 945 logging.info('Running as a FAST test for testing') 946 # cmdlist.append('--gtest_filter=RenderWidgetHost*') 947 # cmdlist.append('--gtest_filter=CommandLine*') 948 cmdlist.append('--gtest_filter=C*') 949 950 # Possibly add a test-specific --gtest_filter 951 filter = self.GtestFilter(fulltest) 952 if filter: 953 cmdlist.append(filter) 954 elif type(fulltest) is list: 955 cmdlist = fulltest 956 957 self.BeforeRunOneTest(fulltest) 958 logging.info('Running test ' + str(cmdlist)) 959 try: 960 retcode = self.Run(cmdlist, ignore_retcode=True) 961 except SystemExit: # e.g. sys.exit() was called somewhere in here 962 raise 963 except: # can't "except WindowsError" since script runs on non-Windows 964 logging.info('EXCEPTION while running a unit test') 965 logging.info(traceback.format_exc()) 966 retcode = 999 967 self.AfterRunOneTest(fulltest) 968 969 if retcode: 970 logging.info('COVERAGE: test %s failed; return code: %d.' % 971 (fulltest, retcode)) 972 if self.options.strict: 973 logging.fatal('Test failure is fatal.') 974 sys.exit(retcode) 975 self.AfterRunAllTests() 976 977 def AfterRunOneTest(self, testname): 978 """Do things right after running each test.""" 979 if not self.IsWindows(): 980 return 981 # Stop counters 982 cmdlist = [self.perf, '-shutdown'] 983 self.Run(cmdlist) 984 full_output = self.vsts_output + '.coverage' 985 shutil.move(full_output, self.vsts_output) 986 # generate lcov! 987 self.GenerateLcovWindows(testname) 988 989 def AfterRunAllTests(self): 990 """Do things right after running ALL tests.""" 991 # On POSIX we can do it all at once without running out of memory. 992 # This contrasts with Windows where we must do it after each test. 993 if self.IsPosix(): 994 self.GenerateLcovPosix() 995 # Only on Linux do we have the Xvfb step. 996 if self.IsLinux() and self.options.xvfb: 997 self.StopXvfb() 998 999 def StartXvfb(self): 1000 """Start Xvfb and set an appropriate DISPLAY environment. Linux only. 1001 1002 Copied from http://src.chromium.org/viewvc/chrome/trunk/tools/buildbot/ 1003 scripts/slave/slave_utils.py?view=markup 1004 with some simplifications (e.g. no need to use xdisplaycheck, save 1005 pid in var not file, etc) 1006 """ 1007 logging.info('Xvfb: starting') 1008 proc = subprocess.Popen(["Xvfb", ":9", "-screen", "0", "1024x768x24", 1009 "-ac"], 1010 stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 1011 self.xvfb_pid = proc.pid 1012 if not self.xvfb_pid: 1013 logging.info('Could not start Xvfb') 1014 return 1015 os.environ['DISPLAY'] = ":9" 1016 # Now confirm, giving a chance for it to start if needed. 1017 logging.info('Xvfb: confirming') 1018 for test in range(10): 1019 proc = subprocess.Popen('xdpyinfo >/dev/null', shell=True) 1020 pid, retcode = os.waitpid(proc.pid, 0) 1021 if retcode == 0: 1022 break 1023 time.sleep(0.5) 1024 if retcode != 0: 1025 logging.info('Warning: could not confirm Xvfb happiness') 1026 else: 1027 logging.info('Xvfb: OK') 1028 1029 def StopXvfb(self): 1030 """Stop Xvfb if needed. Linux only.""" 1031 if self.xvfb_pid: 1032 logging.info('Xvfb: killing') 1033 try: 1034 os.kill(self.xvfb_pid, signal.SIGKILL) 1035 except: 1036 pass 1037 del os.environ['DISPLAY'] 1038 self.xvfb_pid = 0 1039 1040 def CopyCoverageFileToDestination(self, coverage_folder): 1041 coverage_dir = os.path.join(self.directory, coverage_folder) 1042 if not os.path.exists(coverage_dir): 1043 os.makedirs(coverage_dir) 1044 shutil.copyfile(self.coverage_info_file, os.path.join(coverage_dir, 1045 'coverage.info')) 1046 1047 def GenerateLcovPosix(self): 1048 """Convert profile data to lcov on Mac or Linux.""" 1049 start_dir = os.getcwd() 1050 logging.info('GenerateLcovPosix: start_dir=' + start_dir) 1051 if self.IsLinux(): 1052 # With Linux/make (e.g. the coverage_run target), the current 1053 # directory for this command is .../build/src/chrome but we need 1054 # to be in .../build/src for the relative path of source files 1055 # to be correct. However, when run from buildbot, the current 1056 # directory is .../build. Accommodate. 1057 # On Mac source files are compiled with abs paths so this isn't 1058 # a problem. 1059 # This is a bit of a hack. The best answer is to require this 1060 # script be run in a specific directory for all cases (from 1061 # Makefile or from buildbot). 1062 if start_dir.endswith('chrome'): 1063 logging.info('coverage_posix.py: doing a "cd .." ' 1064 'to accomodate Linux/make PWD') 1065 os.chdir('..') 1066 elif start_dir.endswith('build'): 1067 logging.info('coverage_posix.py: doing a "cd src" ' 1068 'to accomodate buildbot PWD') 1069 os.chdir('src') 1070 else: 1071 logging.info('coverage_posix.py: NOT changing directory.') 1072 elif self.IsMac(): 1073 pass 1074 1075 command = [self.mcov, 1076 '--directory', 1077 os.path.join(start_dir, self.directory_parent), 1078 '--output', 1079 os.path.join(start_dir, self.coverage_info_file)] 1080 logging.info('Assembly command: ' + ' '.join(command)) 1081 retcode = subprocess.call(command) 1082 if retcode: 1083 logging.fatal('COVERAGE: %s failed; return code: %d' % 1084 (command[0], retcode)) 1085 if self.options.strict: 1086 sys.exit(retcode) 1087 if self.IsLinux(): 1088 os.chdir(start_dir) 1089 1090 # Copy the unittests coverage information to a different folder. 1091 if self.options.all_unittests: 1092 self.CopyCoverageFileToDestination('unittests_coverage') 1093 elif self.options.all_browsertests: 1094 # Save browsertests only coverage information. 1095 self.CopyCoverageFileToDestination('browsertests_coverage') 1096 else: 1097 # Save the overall coverage information. 1098 self.CopyCoverageFileToDestination('total_coverage') 1099 1100 if not os.path.exists(self.coverage_info_file): 1101 logging.fatal('%s was not created. Coverage run failed.' % 1102 self.coverage_info_file) 1103 sys.exit(1) 1104 1105 def GenerateLcovWindows(self, testname=None): 1106 """Convert VSTS format to lcov. Appends coverage data to sum file.""" 1107 lcov_file = self.vsts_output + '.lcov' 1108 if os.path.exists(lcov_file): 1109 os.remove(lcov_file) 1110 # generates the file (self.vsts_output + ".lcov") 1111 1112 cmdlist = [self.analyzer, 1113 '-sym_path=' + self.directory, 1114 '-src_root=' + self.src_root, 1115 '-noxml', 1116 self.vsts_output] 1117 self.Run(cmdlist) 1118 if not os.path.exists(lcov_file): 1119 logging.fatal('Output file %s not created' % lcov_file) 1120 sys.exit(1) 1121 logging.info('Appending lcov for test %s to %s' % 1122 (testname, self.coverage_info_file)) 1123 size_before = 0 1124 if os.path.exists(self.coverage_info_file): 1125 size_before = os.stat(self.coverage_info_file).st_size 1126 src = open(lcov_file, 'r') 1127 dst = open(self.coverage_info_file, 'a') 1128 dst.write(src.read()) 1129 src.close() 1130 dst.close() 1131 size_after = os.stat(self.coverage_info_file).st_size 1132 logging.info('Lcov file growth for %s: %d --> %d' % 1133 (self.coverage_info_file, size_before, size_after)) 1134 1135 def GenerateHtml(self): 1136 """Convert lcov to html.""" 1137 # TODO(jrg): This isn't happy when run with unit_tests since V8 has a 1138 # different "base" so V8 includes can't be found in ".". Fix. 1139 command = [self.genhtml, 1140 self.coverage_info_file, 1141 '--output-directory', 1142 self.output_directory] 1143 print >>sys.stderr, 'html generation command: ' + ' '.join(command) 1144 retcode = subprocess.call(command) 1145 if retcode: 1146 logging.fatal('COVERAGE: %s failed; return code: %d' % 1147 (command[0], retcode)) 1148 if self.options.strict: 1149 sys.exit(retcode) 1150 1151 def CoverageOptionParser(): 1152 """Return an optparse.OptionParser() suitable for Coverage object creation.""" 1153 parser = optparse.OptionParser() 1154 parser.add_option('-d', 1155 '--directory', 1156 dest='directory', 1157 default=None, 1158 help='Directory of unit test files') 1159 parser.add_option('-a', 1160 '--all_unittests', 1161 dest='all_unittests', 1162 default=False, 1163 help='Run all tests we can find (*_unittests)') 1164 parser.add_option('-b', 1165 '--all_browsertests', 1166 dest='all_browsertests', 1167 default=False, 1168 help='Run all tests in browser_tests ' 1169 'and content_browsertests') 1170 parser.add_option('-g', 1171 '--genhtml', 1172 dest='genhtml', 1173 default=False, 1174 help='Generate html from lcov output') 1175 parser.add_option('-f', 1176 '--fast_test', 1177 dest='fast_test', 1178 default=False, 1179 help='Make the tests run REAL fast by doing little.') 1180 parser.add_option('-s', 1181 '--strict', 1182 dest='strict', 1183 default=False, 1184 help='Be strict and die on test failure.') 1185 parser.add_option('-S', 1186 '--src_root', 1187 dest='src_root', 1188 default='.', 1189 help='Source root (only used on Windows)') 1190 parser.add_option('-t', 1191 '--trim', 1192 dest='trim', 1193 default=True, 1194 help='Trim out tests? Default True.') 1195 parser.add_option('-x', 1196 '--xvfb', 1197 dest='xvfb', 1198 default=True, 1199 help='Use Xvfb for tests? Default True.') 1200 parser.add_option('-T', 1201 '--timeout', 1202 dest='timeout', 1203 default=5.0 * 60.0, 1204 type="int", 1205 help='Timeout before bailing if a subprocess has no output.' 1206 ' Default is 5min (Buildbot is 10min.)') 1207 parser.add_option('-B', 1208 '--bundles', 1209 dest='bundles', 1210 default=None, 1211 help='Filename of bundles for coverage.') 1212 parser.add_option('--build-dir', 1213 dest='build_dir', 1214 default=None, 1215 help=('Working directory for buildbot build.' 1216 'used for finding bundlefile.')) 1217 parser.add_option('--target', 1218 dest='target', 1219 default=None, 1220 help=('Buildbot build target; ' 1221 'used for finding bundlefile (e.g. Debug)')) 1222 parser.add_option('--no_exclusions', 1223 dest='no_exclusions', 1224 default=None, 1225 help=('Disable the exclusion list.')) 1226 parser.add_option('--dont-clear-coverage-data', 1227 dest='dont_clear_coverage_data', 1228 default=False, 1229 action='store_true', 1230 help=('Turn off clearing of cov data from a prev run')) 1231 parser.add_option('-F', 1232 '--test-file', 1233 dest="test_files", 1234 default=[], 1235 action='append', 1236 help=('Specify a file from which tests to be run will ' + 1237 'be extracted')) 1238 return parser 1239 1240 1241 def main(): 1242 # Print out the args to help someone do it by hand if needed 1243 print >>sys.stderr, sys.argv 1244 1245 # Try and clean up nice if we're killed by buildbot, Ctrl-C, ... 1246 signal.signal(signal.SIGINT, TerminateSignalHandler) 1247 signal.signal(signal.SIGTERM, TerminateSignalHandler) 1248 1249 parser = CoverageOptionParser() 1250 (options, args) = parser.parse_args() 1251 if options.all_unittests and options.all_browsertests: 1252 print 'Error! Can not have all_unittests and all_browsertests together!' 1253 sys.exit(1) 1254 coverage = Coverage(options, args) 1255 coverage.ClearData() 1256 coverage.FindTests() 1257 if options.trim: 1258 coverage.TrimTests() 1259 coverage.RunTests() 1260 if options.genhtml: 1261 coverage.GenerateHtml() 1262 return 0 1263 1264 1265 if __name__ == '__main__': 1266 sys.exit(main()) 1267