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.
6 """Generate and process code coverage.
8 TODO(jrg): rename this from coverage_posix.py to coverage_all.py!
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
14 All platforms, to set up coverage:
15 cd ...../chromium ; src/tools/gyp/gyp_dogfood -Dcoverage=1 src/build/all.gyp
19 ( cd src/chrome ; xcodebuild -configuration Debug -target coverage )
21 ( cd src/chrome ; hammer coverage )
22 # In particular, don't try and run 'coverage' from src/build
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).
31 --genhtml: generate html output. If not specified only lcov is generated.
33 --all_unittests: if present, run all files named *_unittests that we
36 --fast_test: make the tests run real fast (just for testing)
38 --strict: if a test fails, we continue happily. --strict will cause
39 us to die immediately.
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.
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
50 --timeout=SECS: if a subprocess doesn't have output within SECS,
51 assume it's a hang. Kill it and give up.
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
62 http://src.chromium.org/viewvc/chrome/trunk/tools/buildbot/scripts/slave/runtest.py
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.
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
73 This is used by buildbot scripts to help us find the output directory.
74 Must be used with --target.
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.
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
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".
107 """Global list of child PIDs to kill when we die."""
110 """Exclusion list. Format is
111 { platform: { testname: (exclusion1, exclusion2, ... ), ... } }
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:'
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
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:
132 WebGLConformanceTests.conformance_attribs_gl_vertex_attrib_zero_issues:
134 WebGLConformanceTests.conformance_attribs_gl_vertex_attrib: ditto.
135 WebGLConformanceTests.conformance_attribs_gl_vertexattribpointer_offsets:
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
141 WebGLConformanceTests.conformance_buffers_buffer_data_array_buffer: ditto.
142 WebGLConformanceTests.conformance_buffers_index_validation_copies_indices:
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'
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
187 'darwin2': { 'base_unittests': ('ProcessUtilTest.SpawnChild',),
188 'ipc_tests': ('IPCFuzzingTest.MsgBadPayloadArgs',), },
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':
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',), },
247 """Since random tests are failing/hanging on coverage bot, we are enabling
248 tests feature by feature. crbug.com/159748
253 (# 'src/chrome/browser/downloads'
254 'SavePageBrowserTest.*',
255 'SavePageAsMHTMLBrowserTest.*',
256 'DownloadQueryTest.*',
257 'DownloadDangerPromptTest.*',
259 # 'src/chrome/browser/net'
260 'CookiePolicyBrowserTest.*',
262 'LoadTimingObserverTest.*',
263 'PredictorBrowserTest.*',
264 'ProxyBrowserTest.*',
265 # 'src/chrome/browser/extensions'
267 'WindowOpenPanelDisabledTest.*',
268 'WindowOpenPanelTest.*',
269 'WebstoreStandalone*.*',
270 'CommandLineWebstoreInstall.*',
272 'RequirementsCheckerBrowserTest.*',
273 'ProcessManagementTest.*',
274 'PlatformAppBrowserTest.*',
275 'PlatformAppDevToolsBrowserTest.*',
276 'LazyBackgroundPageApiTest.*',
278 'PanelMessagingTest.*',
279 'GeolocationApiTest.*',
280 'ClipboardApiTest.*',
281 'ExecuteScriptApiTest.*',
282 'CalculatorBrowserTest.*',
283 'ChromeAppAPITest.*',
285 'BlockedAppApiTest.*',
286 'AppBackgroundPageApiTest.*',
287 'WebNavigationApiTest.*',
289 'TabCaptureApiTest.*',
291 'SyncFileSystemApiTest.*',
295 'PushMessagingApiTest.*',
296 'ProxySettingsApiTest.*',
297 'ExperimentalApiTest.*',
299 'OffscreenTabsApiTest.*',
300 'NotificationApiTest.*',
301 'MediaGalleriesPrivateApiTest.*',
302 'PlatformAppMediaGalleriesBrowserTest.*',
303 'GetAuthTokenFunctionTest.*',
304 'LaunchWebAuthFlowFunctionTest.*',
305 'FileSystemApiTest.*',
306 'ScriptBadgeApiTest.*',
307 'PageAsBrowserActionApiTest.*',
308 'PageActionApiTest.*',
309 'BrowserActionApiTest.*',
310 'DownloadExtensionTest.*',
312 'DeclarativeApiTest.*',
313 'BluetoothApiTest.*',
315 # 'src/chrome/browser/nacl_host'
317 # 'src/chrome/browser/automation'
318 'AutomationMiscBrowserTest.*',
319 # 'src/chrome/browser/autofill'
320 'FormStructureBrowserTest.*',
321 'AutofillPopupViewBrowserTest.*',
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'
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.*', ),
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
365 subprocess.call(['taskkill.exe', '/PID', str(pid)])
369 class RunTooLongException(Exception):
370 """Thrown when a command runs too long without output."""
373 class BadUserInput(Exception):
374 """Thrown when arguments from the user are incorrectly formatted."""
378 class RunProgramThread(threading.Thread):
379 """A thread to run a subprocess.
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).
390 # Constants in our queue
394 def __init__(self, cmd):
395 super(RunProgramThread, self).__init__()
398 self._queue = Queue.Queue()
402 if sys.platform in ('win32', 'cygwin'):
403 return self._run_windows()
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()
414 self._process = subprocess.Popen(self._cmd,
415 stdin=subprocess.PIPE,
417 stderr=subprocess.STDOUT)
418 gChildPIDs.append(self._process.pid)
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
426 # We will poll the process until we get a non-None return code.
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.
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.
444 stdout_file.seek(previous_tell)
445 print stdout_file.read(stdout_file.tell() - previous_tell)
447 logging.exception('%s', e)
452 # If we get here the process is done.
453 gChildPIDs.remove(self._process.pid)
454 self._queue.put(RunProgramThread.DONE)
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)
465 line = self._process.stdout.readline()
469 self._queue.put(RunProgramThread.PROGRESS, True)
472 # If we get here the process is done.
473 gChildPIDs.remove(self._process.pid)
474 self._queue.put(RunProgramThread.DONE)
480 """Kill our running process if needed. Wait for kill to complete.
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.
486 if not self._process:
487 return self.retcode()
488 if 'kill' in os.__all__: # POSIX
489 os.kill(self._process.pid, signal.SIGKILL)
491 subprocess.call(['taskkill.exe', '/PID', str(self._process.pid)])
492 return self.retcode()
495 """Return the return value of the subprocess.
497 Waits for process to die but does NOT kill it explicitly.
499 if self._retcode == None: # must be none, not 0/False
500 self._retcode = self._process.wait()
503 def RunUntilCompletion(self, timeout):
504 """Run thread until completion or timeout (in seconds).
506 Start the thread. Let it run until completion, or until we've
507 spent TIMEOUT without seeing output. On timeout throw
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' %
520 raise RunTooLongException()
523 class Coverage(object):
524 """Doitall class for code coverage."""
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
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
544 self.ConfirmPlatformAndPaths()
545 self.tests = [] # This can be a list of strings, lists or both.
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)
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?')
557 paths = os.environ['PATH'].split(os.pathsep)
559 fullpath = os.path.join(path, program)
560 if os.path.exists(fullpath):
564 def FindPrograms(self):
565 """Find programs we may want to run."""
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]
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'
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))
586 self.programs = [self.perf, self.instrument, self.analyzer]
588 def PlatformBuildPrefix(self):
589 """Return a platform specific build directory prefix.
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.
596 return '../xcodebuild'
600 return '../out' # assumes make, unlike runtest.py
602 def ConfirmDirectory(self):
603 """Confirm correctness of self.directory.
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
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)')
615 if not self.directory:
616 self.directory = os.path.join(self.options.build_dir,
617 self.PlatformBuildPrefix(),
620 if os.path.exists(self.directory):
621 logging.info('Directory: ' + self.directory)
624 logging.fatal('Directory ' +
625 self.directory + ' doesn\'t exist')
629 def FindBundlesFile(self):
630 """Find the bundlesfile.
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,
636 # If no bundle file, no problem!
637 if not self.options.bundles:
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(),
645 self.options.bundles)
646 self.options.bundles = fullpath
648 if os.path.exists(self.options.bundles):
649 logging.info('BundlesFile: ' + self.options.bundles)
652 logging.fatal('bundlefile ' +
653 self.options.bundles + ' doesn\'t exist')
658 """Find unit tests to run; set self.tests to this list.
660 Assume all non-option items in the arg list are tests to be run.
662 # Before we begin, find the bundles file if not an absolute path.
663 self.FindBundlesFile()
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'))
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
680 for test_file in self.options.test_files:
683 line = re.sub(r"#.*$", "", line)
684 line = re.sub(r"\s*", "", line)
687 all_testnames.append(line)
690 tests_from_bundles = None
691 if self.options.bundles:
693 tests_from_bundles = eval(open(self.options.bundles).read())
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
703 logging.fatal('Fatal error with bundle file; could not get list from' +
704 self.options.bundles)
707 # If told explicit tests, run those (after stripping the name as
709 for testname in all_testnames:
710 mo = re.search(r"(.*)\[(.*)\]$", testname)
713 gtest_filter = mo.group(2)
714 testname = mo.group(1)
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':
721 self.tests += [os.path.join(self.directory, testname)]
723 self.test_filters[testname] = gtest_filter
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',
730 # '--suite=CODE_COVERAGE']]
733 # Not sure all of these work yet (e.g. page_cycler_tests)
734 # self.tests += glob.glob(os.path.join(self.directory, '*_tests'))
736 # If needed, append .exe to tests since vsinstr.exe likes it that
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
746 """Trim specific tests for each platform."""
749 # TODO(jrg): remove when not needed
750 inclusion = ['unit_tests']
752 for test in self.tests:
757 logging.info('After trimming tests we have ' + ' '.join(self.tests))
760 # self.tests = filter(lambda t: t.endswith('base_unittests'), self.tests)
763 exclusion = ['automated_ui_tests']
765 for test in self.tests:
769 self.tests = filter(lambda t: t not in punted, self.tests)
771 logging.info('Tests trimmed out: ' + str(punted))
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)
780 def Run(self, cmdlist, ignore_error=False, ignore_retcode=None,
782 """Run the command list; exit fatally on error.
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.
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.
794 logging.info('Running ' + str(cmdlist))
795 t = RunProgramThread(cmdlist)
796 retcode = t.RunUntilCompletion(self.options.timeout)
799 if ignore_error or retcode == ignore_retcode:
800 logging.warning('COVERAGE: %s unhappy but errors ignored %s' %
801 (str(cmdlist), explanation or ''))
803 logging.fatal('COVERAGE: %s failed; return code: %d' %
804 (str(cmdlist), retcode))
809 """Return True if we are POSIX."""
810 return self.IsMac() or self.IsLinux()
813 return sys.platform == 'darwin'
816 return sys.platform.startswith('linux')
819 """Return True if we are Windows."""
820 return sys.platform in ('win32', 'cygwin')
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.'
827 print 'Clearing coverage data from previous runs.'
828 if os.path.exists(self.coverage_info_file):
829 os.remove(self.coverage_info_file)
831 subprocess.call([self.lcov,
832 '--directory', self.directory_parent,
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'))
843 if os.path.exists(os.path.join(self.directory, 'total_coverage')):
844 shutil.rmtree(os.path.join(self.directory, 'total_coverage'))
846 def BeforeRunOneTest(self, testname):
847 """Do things before running each test."""
848 if not self.IsWindows():
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')
861 cmdlist = [self.perf, '-start:coverage', '-output:' + self.vsts_output]
864 def BeforeRunAllTests(self):
865 """Called right before we run all tests."""
866 if self.IsLinux() and self.options.xvfb:
869 def GtestFilter(self, fulltest, excl=None):
870 """Return a --gtest_filter=BLAH for this test.
873 fulltest: full name of test executable
874 exclusions: the exclusions list. Only set in a unit test;
875 else uses gTestExclusions.
877 String of the form '--gtest_filter=BLAH', or None.
879 positive_gfilter_list = []
880 negative_gfilter_list = []
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_*')
887 if not self.options.no_exclusions:
888 exclusions = excl or gTestExclusions
889 excldict = exclusions.get(sys.platform)
891 for test in excldict.keys():
892 # example: if base_unittests in ../blah/blah/base_unittests.exe
894 negative_gfilter_list += excldict[test]
896 inclusions = gTestInclusions
897 include_dict = inclusions.get(sys.platform)
899 for test in include_dict.keys():
901 positive_gfilter_list += include_dict[test]
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])
910 if len(specific_test_filters) == 2:
911 # Remove trailing ':'
912 specific_test_filters[0] = specific_test_filters[0][:-1]
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(':')
919 if not positive_gfilter_list and not negative_gfilter_list:
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)
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:
940 logging.info('%s path exists' % fulltest)
941 cmdlist = [fulltest, '--gtest_print_time']
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*')
950 # Possibly add a test-specific --gtest_filter
951 filter = self.GtestFilter(fulltest)
953 cmdlist.append(filter)
954 elif type(fulltest) is list:
957 self.BeforeRunOneTest(fulltest)
958 logging.info('Running test ' + str(cmdlist))
960 retcode = self.Run(cmdlist, ignore_retcode=True)
961 except SystemExit: # e.g. sys.exit() was called somewhere in here
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())
967 self.AfterRunOneTest(fulltest)
970 logging.info('COVERAGE: test %s failed; return code: %d.' %
972 if self.options.strict:
973 logging.fatal('Test failure is fatal.')
975 self.AfterRunAllTests()
977 def AfterRunOneTest(self, testname):
978 """Do things right after running each test."""
979 if not self.IsWindows():
982 cmdlist = [self.perf, '-shutdown']
984 full_output = self.vsts_output + '.coverage'
985 shutil.move(full_output, self.vsts_output)
987 self.GenerateLcovWindows(testname)
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.
994 self.GenerateLcovPosix()
995 # Only on Linux do we have the Xvfb step.
996 if self.IsLinux() and self.options.xvfb:
1000 """Start Xvfb and set an appropriate DISPLAY environment. Linux only.
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)
1007 logging.info('Xvfb: starting')
1008 proc = subprocess.Popen(["Xvfb", ":9", "-screen", "0", "1024x768x24",
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')
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)
1025 logging.info('Warning: could not confirm Xvfb happiness')
1027 logging.info('Xvfb: OK')
1030 """Stop Xvfb if needed. Linux only."""
1032 logging.info('Xvfb: killing')
1034 os.kill(self.xvfb_pid, signal.SIGKILL)
1037 del os.environ['DISPLAY']
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,
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)
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
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')
1066 elif start_dir.endswith('build'):
1067 logging.info('coverage_posix.py: doing a "cd src" '
1068 'to accomodate buildbot PWD')
1071 logging.info('coverage_posix.py: NOT changing directory.')
1075 command = [self.mcov,
1077 os.path.join(start_dir, self.directory_parent),
1079 os.path.join(start_dir, self.coverage_info_file)]
1080 logging.info('Assembly command: ' + ' '.join(command))
1081 retcode = subprocess.call(command)
1083 logging.fatal('COVERAGE: %s failed; return code: %d' %
1084 (command[0], retcode))
1085 if self.options.strict:
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')
1097 # Save the overall coverage information.
1098 self.CopyCoverageFileToDestination('total_coverage')
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)
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")
1112 cmdlist = [self.analyzer,
1113 '-sym_path=' + self.directory,
1114 '-src_root=' + self.src_root,
1118 if not os.path.exists(lcov_file):
1119 logging.fatal('Output file %s not created' % lcov_file)
1121 logging.info('Appending lcov for test %s to %s' %
1122 (testname, self.coverage_info_file))
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())
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))
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)
1146 logging.fatal('COVERAGE: %s failed; return code: %d' %
1147 (command[0], retcode))
1148 if self.options.strict:
1151 def CoverageOptionParser():
1152 """Return an optparse.OptionParser() suitable for Coverage object creation."""
1153 parser = optparse.OptionParser()
1154 parser.add_option('-d',
1158 help='Directory of unit test files')
1159 parser.add_option('-a',
1161 dest='all_unittests',
1163 help='Run all tests we can find (*_unittests)')
1164 parser.add_option('-b',
1165 '--all_browsertests',
1166 dest='all_browsertests',
1168 help='Run all tests in browser_tests '
1169 'and content_browsertests')
1170 parser.add_option('-g',
1174 help='Generate html from lcov output')
1175 parser.add_option('-f',
1179 help='Make the tests run REAL fast by doing little.')
1180 parser.add_option('-s',
1184 help='Be strict and die on test failure.')
1185 parser.add_option('-S',
1189 help='Source root (only used on Windows)')
1190 parser.add_option('-t',
1194 help='Trim out tests? Default True.')
1195 parser.add_option('-x',
1199 help='Use Xvfb for tests? Default True.')
1200 parser.add_option('-T',
1205 help='Timeout before bailing if a subprocess has no output.'
1206 ' Default is 5min (Buildbot is 10min.)')
1207 parser.add_option('-B',
1211 help='Filename of bundles for coverage.')
1212 parser.add_option('--build-dir',
1215 help=('Working directory for buildbot build.'
1216 'used for finding bundlefile.'))
1217 parser.add_option('--target',
1220 help=('Buildbot build target; '
1221 'used for finding bundlefile (e.g. Debug)'))
1222 parser.add_option('--no_exclusions',
1223 dest='no_exclusions',
1225 help=('Disable the exclusion list.'))
1226 parser.add_option('--dont-clear-coverage-data',
1227 dest='dont_clear_coverage_data',
1229 action='store_true',
1230 help=('Turn off clearing of cov data from a prev run'))
1231 parser.add_option('-F',
1236 help=('Specify a file from which tests to be run will ' +
1242 # Print out the args to help someone do it by hand if needed
1243 print >>sys.stderr, sys.argv
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)
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!'
1254 coverage = Coverage(options, args)
1255 coverage.ClearData()
1256 coverage.FindTests()
1258 coverage.TrimTests()
1261 coverage.GenerateHtml()
1265 if __name__ == '__main__':