- add sources.
[platform/framework/web/crosswalk.git] / src / tools / code_coverage / coverage_posix.py
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())