| OLD | NEW | 
|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python | 
| 2 # Copyright (c) 2009 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2009 The Chromium Authors. All rights reserved. | 
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be | 
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. | 
| 5 | 5 | 
| 6 """Generate and process code coverage on POSIX systems. | 6 """Generate and process code coverage on POSIX systems. | 
| 7 | 7 | 
| 8 Written for and tested on Mac and Linux.  To use this script to | 8 Written for and tested on Mac and Linux.  To use this script to | 
| 9 generate coverage numbers, please run from within a gyp-generated | 9 generate coverage numbers, please run from within a gyp-generated | 
| 10 project. | 10 project. | 
| 11 | 11 | 
| 12 All platforms, to set up coverage: | 12 All platforms, to set up coverage: | 
| 13   cd ...../chromium ; src/tools/gyp/gyp_dogfood -Dcoverage=1 src/build/all.gyp | 13   cd ...../chromium ; src/tools/gyp/gyp_dogfood -Dcoverage=1 src/build/all.gyp | 
| 14 | 14 | 
| 15 Run coverage on... | 15 Run coverage on... | 
| 16 Mac: | 16 Mac: | 
| 17   ( cd src/chrome ; xcodebuild -configuration Debug -target coverage ) | 17   ( cd src/chrome ; xcodebuild -configuration Debug -target coverage ) | 
| 18 Linux: | 18 Linux: | 
| 19   ( cd src/chrome ; hammer coverage ) | 19   ( cd src/chrome ; hammer coverage ) | 
| 20   # In particular, don't try and run 'coverage' from src/build | 20   # In particular, don't try and run 'coverage' from src/build | 
| 21 | 21 | 
| 22 | 22 | 
| 23 --directory=DIR: specify directory that contains gcda files, and where | 23 --directory=DIR: specify directory that contains gcda files, and where | 
| 24   a "coverage" directory will be created containing the output html. | 24   a "coverage" directory will be created containing the output html. | 
| 25   Example name:   ..../chromium/src/xcodebuild/Debug | 25   Example name:   ..../chromium/src/xcodebuild/Debug | 
| 26 | 26 | 
| 27 --all_unittests: is present, run all files named *_unittests that we | 27 --genhtml: generate html output.  If not specified only lcov is generated. | 
|  | 28 | 
|  | 29 --all_unittests: if present, run all files named *_unittests that we | 
| 28   can find. | 30   can find. | 
| 29 | 31 | 
|  | 32 --fast_test: make the tests run real fast (just for testing) | 
|  | 33 | 
| 30 Strings after all options are considered tests to run.  Test names | 34 Strings after all options are considered tests to run.  Test names | 
| 31 have all text before a ':' stripped to help with gyp compatibility. | 35 have all text before a ':' stripped to help with gyp compatibility. | 
| 32 For example, ../base/base.gyp:base_unittests is interpreted as a test | 36 For example, ../base/base.gyp:base_unittests is interpreted as a test | 
| 33 named "base_unittests". | 37 named "base_unittests". | 
| 34 """ | 38 """ | 
| 35 | 39 | 
| 36 import glob | 40 import glob | 
| 37 import logging | 41 import logging | 
| 38 import optparse | 42 import optparse | 
| 39 import os | 43 import os | 
| 40 import shutil | 44 import shutil | 
| 41 import subprocess | 45 import subprocess | 
| 42 import sys | 46 import sys | 
| 43 | 47 | 
| 44 class Coverage(object): | 48 class Coverage(object): | 
| 45   """Doitall class for code coverage.""" | 49   """Doitall class for code coverage.""" | 
| 46 | 50 | 
| 47   def __init__(self, directory): | 51   def __init__(self, directory, options, args): | 
| 48     super(Coverage, self).__init__() | 52     super(Coverage, self).__init__() | 
| 49     self.directory = directory | 53     self.directory = directory | 
|  | 54     self.options = options | 
|  | 55     self.args = args | 
| 50     self.directory_parent = os.path.dirname(self.directory) | 56     self.directory_parent = os.path.dirname(self.directory) | 
| 51     self.output_directory = os.path.join(self.directory, 'coverage') | 57     self.output_directory = os.path.join(self.directory, 'coverage') | 
| 52     if not os.path.exists(self.output_directory): | 58     if not os.path.exists(self.output_directory): | 
| 53       os.mkdir(self.output_directory) | 59       os.mkdir(self.output_directory) | 
| 54     self.lcov_directory = os.path.join(sys.path[0], | 60     self.lcov_directory = os.path.join(sys.path[0], | 
| 55                                        '../../third_party/lcov/bin') | 61                                        '../../third_party/lcov/bin') | 
| 56     self.lcov = os.path.join(self.lcov_directory, 'lcov') | 62     self.lcov = os.path.join(self.lcov_directory, 'lcov') | 
| 57     self.mcov = os.path.join(self.lcov_directory, 'mcov') | 63     self.mcov = os.path.join(self.lcov_directory, 'mcov') | 
| 58     self.genhtml = os.path.join(self.lcov_directory, 'genhtml') | 64     self.genhtml = os.path.join(self.lcov_directory, 'genhtml') | 
| 59     self.coverage_info_file = os.path.join(self.directory, 'coverage.info') | 65     self.coverage_info_file = os.path.join(self.directory, 'coverage.info') | 
| 60     self.ConfirmPlatformAndPaths() | 66     self.ConfirmPlatformAndPaths() | 
| 61     self.tests = [] | 67     self.tests = [] | 
| 62 | 68 | 
| 63   def FindTests(self, options, args): | 69   def FindTests(self): | 
| 64     """Find unit tests to run; set self.tests to this list. | 70     """Find unit tests to run; set self.tests to this list. | 
| 65 | 71 | 
| 66     Obtain instructions from the command line seen in the provided | 72     Assume all non-option items in the arg list are tests to be run. | 
| 67     parsed options and post-option args. |  | 
| 68     """ | 73     """ | 
| 69     # Small tests: can be run in the "chromium" directory. | 74     # Small tests: can be run in the "chromium" directory. | 
| 70     # If asked, run all we can find. | 75     # If asked, run all we can find. | 
| 71     if options.all_unittests: | 76     if self.options.all_unittests: | 
| 72       self.tests += glob.glob(os.path.join(self.directory, '*_unittests')) | 77       self.tests += glob.glob(os.path.join(self.directory, '*_unittests')) | 
| 73 | 78 | 
| 74     # If told explicit tests, run those (after stripping the name as | 79     # If told explicit tests, run those (after stripping the name as | 
| 75     # appropriate) | 80     # appropriate) | 
| 76     for testname in args: | 81     for testname in self.args: | 
| 77       if ':' in testname: | 82       if ':' in testname: | 
| 78         self.tests += [os.path.join(self.directory, testname.split(':')[1])] | 83         self.tests += [os.path.join(self.directory, testname.split(':')[1])] | 
| 79       else: | 84       else: | 
| 80         self.tests += [os.path.join(self.directory, testname)] | 85         self.tests += [os.path.join(self.directory, testname)] | 
| 81 | 86 | 
| 82     # Needs to be run in the "chrome" directory? | 87     # Needs to be run in the "chrome" directory? | 
| 83     # ut = os.path.join(self.directory, 'unit_tests') | 88     # ut = os.path.join(self.directory, 'unit_tests') | 
| 84     # if os.path.exists(ut): | 89     # if os.path.exists(ut): | 
| 85     #  self.tests.append(ut) | 90     #  self.tests.append(ut) | 
| 86     # Medium tests? | 91     # Medium tests? | 
| 87     # Not sure all of these work yet (e.g. page_cycler_tests) | 92     # Not sure all of these work yet (e.g. page_cycler_tests) | 
| 88     # self.tests += glob.glob(os.path.join(self.directory, '*_tests')) | 93     # self.tests += glob.glob(os.path.join(self.directory, '*_tests')) | 
| 89 | 94 | 
| 90   def ConfirmPlatformAndPaths(self): | 95   def ConfirmPlatformAndPaths(self): | 
| 91     """Confirm OS and paths (e.g. lcov).""" | 96     """Confirm OS and paths (e.g. lcov).""" | 
| 92     if not self.IsPosix(): | 97     if not self.IsPosix(): | 
| 93       logging.fatal('Not posix.') | 98       logging.fatal('Not posix.') | 
|  | 99       sys.exit(1) | 
| 94     programs = [self.lcov, self.genhtml] | 100     programs = [self.lcov, self.genhtml] | 
| 95     if self.IsMac(): | 101     if self.IsMac(): | 
| 96       programs.append(self.mcov) | 102       programs.append(self.mcov) | 
| 97     for program in programs: | 103     for program in programs: | 
| 98       if not os.path.exists(program): | 104       if not os.path.exists(program): | 
| 99         logging.fatal('lcov program missing: ' + program) | 105         logging.fatal('lcov program missing: ' + program) | 
|  | 106         sys.exit(1) | 
| 100 | 107 | 
| 101   def IsPosix(self): | 108   def IsPosix(self): | 
| 102     """Return True if we are POSIX.""" | 109     """Return True if we are POSIX.""" | 
| 103     return self.IsMac() or self.IsLinux() | 110     return self.IsMac() or self.IsLinux() | 
| 104 | 111 | 
| 105   def IsMac(self): | 112   def IsMac(self): | 
| 106     return sys.platform == 'darwin' | 113     return sys.platform == 'darwin' | 
| 107 | 114 | 
| 108   def IsLinux(self): | 115   def IsLinux(self): | 
| 109     return sys.platform == 'linux2' | 116     return sys.platform == 'linux2' | 
| 110 | 117 | 
| 111   def ClearData(self): | 118   def ClearData(self): | 
| 112     """Clear old gcda files""" | 119     """Clear old gcda files""" | 
| 113     subprocess.call([self.lcov, | 120     subprocess.call([self.lcov, | 
| 114                      '--directory', self.directory_parent, | 121                      '--directory', self.directory_parent, | 
| 115                      '--zerocounters']) | 122                      '--zerocounters']) | 
| 116     shutil.rmtree(os.path.join(self.directory, 'coverage')) | 123     shutil.rmtree(os.path.join(self.directory, 'coverage')) | 
| 117 | 124 | 
| 118   def RunTests(self): | 125   def RunTests(self): | 
| 119     """Run all unit tests.""" | 126     """Run all unit tests.""" | 
| 120     for fulltest in self.tests: | 127     for fulltest in self.tests: | 
| 121       if not os.path.exists(fulltest): | 128       if not os.path.exists(fulltest): | 
| 122         logging.fatal(fulltest + ' does not exist') | 129         logging.fatal(fulltest + ' does not exist') | 
|  | 130         sys.exit(2) | 
| 123       # TODO(jrg): add timeout? | 131       # TODO(jrg): add timeout? | 
| 124       # TODO(jrg): check return value and choke if it failed? | 132       print >>sys.stderr, 'Running test: ' + fulltest | 
| 125       # TODO(jrg): add --gtest_print_time like as run from XCode? | 133       cmdlist = [fulltest, '--gtest_print_time'] | 
| 126       print 'Running test: ' + fulltest |  | 
| 127       # subprocess.call([fulltest, '--gtest_filter=TupleTest*'])  # quick check |  | 
| 128       subprocess.call([fulltest]) |  | 
| 129 | 134 | 
| 130   def GenerateOutput(self): | 135       # If asked, make this REAL fast for testing. | 
| 131     """Convert profile data to html.""" | 136       if self.options.fast_test: | 
|  | 137         cmdlist.append('--gtest_filter=TupleTest*') | 
|  | 138 | 
|  | 139       retcode = subprocess.call(cmdlist) | 
|  | 140       if retcode: | 
|  | 141         logging.fatal('COVERAGE: test %s failed; return code: %d' % | 
|  | 142                       (fulltest, retcode)) | 
|  | 143         sys.exit(retcode) | 
|  | 144 | 
|  | 145   def GenerateLcov(self): | 
|  | 146     """Convert profile data to lcov.""" | 
| 132     if self.IsLinux(): | 147     if self.IsLinux(): | 
| 133       command = [self.lcov, | 148       command = [self.lcov, | 
| 134                  '--directory', self.directory, | 149                  '--directory', self.directory, | 
| 135                  '--capture', | 150                  '--capture', | 
| 136                  '--output-file', | 151                  '--output-file', | 
| 137                  self.coverage_info_file] | 152                  self.coverage_info_file] | 
| 138     else: | 153     else: | 
| 139       command = [self.mcov, | 154       command = [self.mcov, | 
| 140                  '--directory', self.directory_parent, | 155                  '--directory', self.directory_parent, | 
| 141                  '--output', self.coverage_info_file] | 156                  '--output', self.coverage_info_file] | 
| 142     print 'Assembly command: ' + ' '.join(command) | 157     print >>sys.stderr, 'Assembly command: ' + ' '.join(command) | 
| 143     subprocess.call(command) | 158     retcode = subprocess.call(command) | 
|  | 159     if retcode: | 
|  | 160       logging.fatal('COVERAGE: %s failed; return code: %d' % | 
|  | 161                     (command[0], retcode)) | 
|  | 162       sys.exit(retcode) | 
| 144 | 163 | 
|  | 164   def GenerateHtml(self): | 
|  | 165     """Convert lcov to html.""" | 
|  | 166     # TODO(jrg): This isn't happy when run with unit_tests since V8 has a | 
|  | 167     # different "base" so V8 includes can't be found in ".".  Fix. | 
| 145     command = [self.genhtml, | 168     command = [self.genhtml, | 
| 146                self.coverage_info_file, | 169                self.coverage_info_file, | 
| 147                '--output-directory', | 170                '--output-directory', | 
| 148                self.output_directory] | 171                self.output_directory] | 
| 149     print 'html generation command: ' + ' '.join(command) | 172     print >>sys.stderr, 'html generation command: ' + ' '.join(command) | 
| 150     subprocess.call(command) | 173     retcode = subprocess.call(command) | 
|  | 174     if retcode: | 
|  | 175       logging.fatal('COVERAGE: %s failed; return code: %d' % | 
|  | 176                     (command[0], retcode)) | 
|  | 177       sys.exit(retcode) | 
| 151 | 178 | 
| 152 def main(): | 179 def main(): | 
|  | 180   # Print out the args to help someone do it by hand if needed | 
|  | 181   print >>sys.stderr, sys.argv | 
|  | 182 | 
| 153   parser = optparse.OptionParser() | 183   parser = optparse.OptionParser() | 
| 154   parser.add_option('-d', | 184   parser.add_option('-d', | 
| 155                     '--directory', | 185                     '--directory', | 
| 156                     dest='directory', | 186                     dest='directory', | 
| 157                     default=None, | 187                     default=None, | 
| 158                     help='Directory of unit test files') | 188                     help='Directory of unit test files') | 
| 159   parser.add_option('-a', | 189   parser.add_option('-a', | 
| 160                     '--all_unittests', | 190                     '--all_unittests', | 
| 161                     dest='all_unittests', | 191                     dest='all_unittests', | 
| 162                     default=False, | 192                     default=False, | 
| 163                     help='Run all tests we can find (*_unittests)') | 193                     help='Run all tests we can find (*_unittests)') | 
|  | 194   parser.add_option('-g', | 
|  | 195                     '--genhtml', | 
|  | 196                     dest='genhtml', | 
|  | 197                     default=False, | 
|  | 198                     help='Generate html from lcov output') | 
|  | 199   parser.add_option('-f', | 
|  | 200                     '--fast_test', | 
|  | 201                     dest='fast_test', | 
|  | 202                     default=False, | 
|  | 203                     help='Make the tests run REAL fast by doing little.') | 
| 164   (options, args) = parser.parse_args() | 204   (options, args) = parser.parse_args() | 
| 165   if not options.directory: | 205   if not options.directory: | 
| 166     parser.error('Directory not specified') | 206     parser.error('Directory not specified') | 
| 167 | 207   coverage = Coverage(options.directory, options, args) | 
| 168   coverage = Coverage(options.directory) |  | 
| 169   coverage.ClearData() | 208   coverage.ClearData() | 
| 170   coverage.FindTests(options, args) | 209   coverage.FindTests() | 
| 171   coverage.RunTests() | 210   coverage.RunTests() | 
| 172   coverage.GenerateOutput() | 211   coverage.GenerateLcov() | 
|  | 212   if options.genhtml: | 
|  | 213     coverage.GenerateHtml() | 
| 173   return 0 | 214   return 0 | 
| 174 | 215 | 
| 175 | 216 | 
| 176 if __name__ == '__main__': | 217 if __name__ == '__main__': | 
| 177   sys.exit(main()) | 218   sys.exit(main()) | 
| OLD | NEW | 
|---|