 Chromium Code Reviews
 Chromium Code Reviews| 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() |