Index: tools/code_coverage/coverage_posix.py |
=================================================================== |
--- tools/code_coverage/coverage_posix.py (revision 19085) |
+++ tools/code_coverage/coverage_posix.py (working copy) |
@@ -3,10 +3,12 @@ |
# Use of this source code is governed by a BSD-style license that can be |
# found in the LICENSE file. |
-"""Generate and process code coverage on POSIX systems. |
+"""Generate and process code coverage. |
-Written for and tested on Mac and Linux. To use this script to |
-generate coverage numbers, please run from within a gyp-generated |
+TODO(jrg): rename this from coverage_posix.py to coverage_all.py! |
Randall Spangler
2009/07/07 16:14:14
Yes!
|
+ |
+Written for and tested on Mac, Linux, and Windows. To use this script |
+to generate coverage numbers, please run from within a gyp-generated |
project. |
All platforms, to set up coverage: |
@@ -53,6 +55,7 @@ |
def __init__(self, directory, options, args): |
super(Coverage, self).__init__() |
+ logging.basicConfig(level=logging.DEBUG) |
self.directory = directory |
self.options = options |
self.args = args |
@@ -60,15 +63,48 @@ |
self.output_directory = os.path.join(self.directory, 'coverage') |
if not os.path.exists(self.output_directory): |
os.mkdir(self.output_directory) |
- self.lcov_directory = os.path.join(sys.path[0], |
- '../../third_party/lcov/bin') |
- self.lcov = os.path.join(self.lcov_directory, 'lcov') |
- self.mcov = os.path.join(self.lcov_directory, 'mcov') |
- self.genhtml = os.path.join(self.lcov_directory, 'genhtml') |
+ # The "final" lcov-format file |
self.coverage_info_file = os.path.join(self.directory, 'coverage.info') |
+ # If needed, an intermediate VSTS-format file |
+ self.vsts_output = os.path.join(self.directory, 'coverage.vsts') |
+ # Needed for Windows. |
+ self.src_root = options.src_root |
+ self.FindPrograms() |
self.ConfirmPlatformAndPaths() |
self.tests = [] |
+ def FindInPath(self, program): |
+ """Find program in our path. Return abs path to it, or None.""" |
+ if not 'PATH' in os.environ: |
+ logging.fatal('No PATH environment variable?') |
+ sys.exit(1) |
+ paths = os.environ['PATH'].split(os.pathsep) |
+ for path in paths: |
+ fullpath = os.path.join(path, program) |
+ if os.path.exists(fullpath): |
+ return fullpath |
+ return None |
+ |
+ def FindPrograms(self): |
+ """Find programs we may want to run.""" |
+ if self.IsPosix(): |
+ self.lcov_directory = os.path.join(sys.path[0], |
+ '../../third_party/lcov/bin') |
+ self.lcov = os.path.join(self.lcov_directory, 'lcov') |
+ self.mcov = os.path.join(self.lcov_directory, 'mcov') |
+ self.genhtml = os.path.join(self.lcov_directory, 'genhtml') |
+ self.programs = [self.lcov, self.mcov, self.genhtml] |
+ else: |
+ commands = ['vsperfcmd.exe', 'vsinstr.exe', 'coverage_analyzer.exe'] |
+ self.perf = self.FindInPath('vsperfcmd.exe') |
+ self.instrument = self.FindInPath('vsinstr.exe') |
+ self.analyzer = self.FindInPath('coverage_analyzer.exe') |
+ if not self.perf or not self.instrument or not self.analyzer: |
+ logging.fatal('Could not find Win performance commands.') |
+ logging.fatal('Commands needed in PATH: ' + str(commands)) |
+ sys.exit(1) |
+ self.programs = [self.perf, self.instrument, self.analyzer] |
+ |
def FindTests(self): |
"""Find unit tests to run; set self.tests to this list. |
@@ -86,28 +122,53 @@ |
self.tests += [os.path.join(self.directory, testname.split(':')[1])] |
else: |
self.tests += [os.path.join(self.directory, testname)] |
- |
- # Needs to be run in the "chrome" directory? |
- # ut = os.path.join(self.directory, 'unit_tests') |
- # if os.path.exists(ut): |
- # self.tests.append(ut) |
# Medium tests? |
# Not sure all of these work yet (e.g. page_cycler_tests) |
# self.tests += glob.glob(os.path.join(self.directory, '*_tests')) |
+ # If needed, append .exe to tests since vsinstr.exe likes it that |
+ # way. |
+ if self.IsWindows(): |
+ for ind in range(len(self.tests)): |
+ test = self.tests[ind] |
+ test_exe = test + '.exe' |
+ if not test.endswith('.exe') and os.path.exists(test_exe): |
+ self.tests[ind] = test_exe |
+ |
+ # Temporarily make Windows quick for bringup by filtering |
+ # out all except base_unittests. Easier than a chrome.cyp change. |
+ # TODO(jrg): remove this |
+ if self.IsWindows(): |
+ t2 = [] |
+ for test in self.tests: |
+ if 'base_unittests' in test: |
+ t2.append(test) |
+ self.tests = t2 |
+ |
+ |
+ |
def ConfirmPlatformAndPaths(self): |
"""Confirm OS and paths (e.g. lcov).""" |
- if not self.IsPosix(): |
- logging.fatal('Not posix.') |
- sys.exit(1) |
- programs = [self.lcov, self.genhtml] |
- if self.IsMac(): |
- programs.append(self.mcov) |
- for program in programs: |
+ for program in self.programs: |
if not os.path.exists(program): |
- logging.fatal('lcov program missing: ' + program) |
+ logging.fatal('Program missing: ' + program) |
sys.exit(1) |
+ def Run(self, cmdlist, ignore_error=False, ignore_retcode=None, |
+ explanation=None): |
+ """Run the command list; exit fatally on error.""" |
+ logging.info('Running ' + str(cmdlist)) |
+ retcode = subprocess.call(cmdlist) |
+ if retcode: |
+ if ignore_error or retcode == ignore_retcode: |
+ logging.warning('COVERAGE: %s unhappy but errors ignored %s' % |
+ (str(cmdlist), explanation or '')) |
+ else: |
+ logging.fatal('COVERAGE: %s failed; return code: %d' % |
+ (str(cmdlist), retcode)) |
+ sys.exit(retcode) |
+ |
+ |
def IsPosix(self): |
"""Return True if we are POSIX.""" |
return self.IsMac() or self.IsLinux() |
@@ -118,13 +179,36 @@ |
def IsLinux(self): |
return sys.platform == 'linux2' |
+ def IsWindows(self): |
+ """Return True if we are Windows.""" |
+ return sys.platform in ('win32', 'cygwin') |
+ |
def ClearData(self): |
"""Clear old gcda files""" |
+ if not self.IsPosix(): |
+ return |
subprocess.call([self.lcov, |
'--directory', self.directory_parent, |
'--zerocounters']) |
shutil.rmtree(os.path.join(self.directory, 'coverage')) |
+ def BeforeRunTests(self): |
+ """Do things before running tests.""" |
+ if not self.IsWindows(): |
+ return |
+ # Stop old counters if needed |
+ cmdlist = [self.perf, '-shutdown'] |
+ self.Run(cmdlist, ignore_error=True) |
+ # Instrument binaries |
+ for fulltest in self.tests: |
+ if os.path.exists(fulltest): |
+ cmdlist = [self.instrument, '/COVERAGE', fulltest] |
+ self.Run(cmdlist, ignore_retcode=4, |
+ explanation='OK with a multiple-instrument') |
+ # Start new counters |
+ cmdlist = [self.perf, '-start:coverage', '-output:' + self.vsts_output] |
+ self.Run(cmdlist) |
+ |
def RunTests(self): |
"""Run all unit tests.""" |
for fulltest in self.tests: |
@@ -138,7 +222,8 @@ |
# If asked, make this REAL fast for testing. |
if self.options.fast_test: |
- cmdlist.append('--gtest_filter=RenderWidgetHost*') |
+ # cmdlist.append('--gtest_filter=RenderWidgetHost*') |
+ cmdlist.append('--gtest_filter=CommandLine*') |
retcode = subprocess.call(cmdlist) |
if retcode: |
@@ -147,7 +232,17 @@ |
if self.options.strict: |
sys.exit(retcode) |
- def GenerateLcov(self): |
+ def AfterRunTests(self): |
+ """Do things right after running tests.""" |
+ if not self.IsWindows(): |
+ return |
+ # Stop counters |
+ cmdlist = [self.perf, '-shutdown'] |
+ self.Run(cmdlist) |
+ full_output = self.vsts_output + '.coverage' |
+ shutil.move(full_output, self.vsts_output) |
+ |
+ def GenerateLcovPosix(self): |
"""Convert profile data to lcov.""" |
command = [self.mcov, |
'--directory', self.directory_parent, |
@@ -160,6 +255,34 @@ |
if self.options.strict: |
sys.exit(retcode) |
+ def GenerateLcovWindows(self): |
+ """Convert VSTS format to lcov.""" |
+ lcov_file = self.vsts_output + '.lcov' |
+ if os.path.exists(lcov_file): |
+ os.remove(lcov_file) |
+ # generates the file (self.vsts_output + ".lcov") |
+ |
+ cmdlist = [self.analyzer, |
+ '-sym_path=' + self.directory, |
+ '-src_root=' + self.src_root, |
+ self.vsts_output] |
+ self.Run(cmdlist) |
+ if not os.path.exists(lcov_file): |
+ logging.fatal('Output file %s not created' % lcov_file) |
+ sys.exit(1) |
+ # So we name it appropriately |
+ if os.path.exists(self.coverage_info_file): |
+ os.remove(self.coverage_info_file) |
+ logging.info('Renaming LCOV file to %s to be consistent' % |
+ self.coverage_info_file) |
+ shutil.move(self.vsts_output + '.lcov', self.coverage_info_file) |
+ |
+ def GenerateLcov(self): |
+ if self.IsPosix(): |
+ self.GenerateLcovPosix() |
+ else: |
+ self.GenerateLcovWindows() |
+ |
def GenerateHtml(self): |
"""Convert lcov to html.""" |
# TODO(jrg): This isn't happy when run with unit_tests since V8 has a |
@@ -206,13 +329,20 @@ |
dest='strict', |
default=False, |
help='Be strict and die on test failure.') |
+ parser.add_option('-S', |
+ '--src_root', |
+ dest='src_root', |
+ default='.', |
+ help='Source root (only used on Windows)') |
(options, args) = parser.parse_args() |
if not options.directory: |
parser.error('Directory not specified') |
coverage = Coverage(options.directory, options, args) |
coverage.ClearData() |
coverage.FindTests() |
+ coverage.BeforeRunTests() |
coverage.RunTests() |
+ coverage.AfterRunTests() |
coverage.GenerateLcov() |
if options.genhtml: |
coverage.GenerateHtml() |