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. |
7 | 7 |
8 Written for and tested on Mac and Linux. To use this script to | 8 TODO(jrg): rename this from coverage_posix.py to coverage_all.py! |
Randall Spangler
2009/07/07 16:14:14
Yes!
| |
9 generate coverage numbers, please run from within a gyp-generated | 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 | |
10 project. | 12 project. |
11 | 13 |
12 All platforms, to set up coverage: | 14 All platforms, to set up coverage: |
13 cd ...../chromium ; src/tools/gyp/gyp_dogfood -Dcoverage=1 src/build/all.gyp | 15 cd ...../chromium ; src/tools/gyp/gyp_dogfood -Dcoverage=1 src/build/all.gyp |
14 | 16 |
15 Run coverage on... | 17 Run coverage on... |
16 Mac: | 18 Mac: |
17 ( cd src/chrome ; xcodebuild -configuration Debug -target coverage ) | 19 ( cd src/chrome ; xcodebuild -configuration Debug -target coverage ) |
18 Linux: | 20 Linux: |
19 ( cd src/chrome ; hammer coverage ) | 21 ( cd src/chrome ; hammer coverage ) |
(...skipping 26 matching lines...) Expand all Loading... | |
46 import os | 48 import os |
47 import shutil | 49 import shutil |
48 import subprocess | 50 import subprocess |
49 import sys | 51 import sys |
50 | 52 |
51 class Coverage(object): | 53 class Coverage(object): |
52 """Doitall class for code coverage.""" | 54 """Doitall class for code coverage.""" |
53 | 55 |
54 def __init__(self, directory, options, args): | 56 def __init__(self, directory, options, args): |
55 super(Coverage, self).__init__() | 57 super(Coverage, self).__init__() |
58 logging.basicConfig(level=logging.DEBUG) | |
56 self.directory = directory | 59 self.directory = directory |
57 self.options = options | 60 self.options = options |
58 self.args = args | 61 self.args = args |
59 self.directory_parent = os.path.dirname(self.directory) | 62 self.directory_parent = os.path.dirname(self.directory) |
60 self.output_directory = os.path.join(self.directory, 'coverage') | 63 self.output_directory = os.path.join(self.directory, 'coverage') |
61 if not os.path.exists(self.output_directory): | 64 if not os.path.exists(self.output_directory): |
62 os.mkdir(self.output_directory) | 65 os.mkdir(self.output_directory) |
63 self.lcov_directory = os.path.join(sys.path[0], | 66 # The "final" lcov-format file |
64 '../../third_party/lcov/bin') | |
65 self.lcov = os.path.join(self.lcov_directory, 'lcov') | |
66 self.mcov = os.path.join(self.lcov_directory, 'mcov') | |
67 self.genhtml = os.path.join(self.lcov_directory, 'genhtml') | |
68 self.coverage_info_file = os.path.join(self.directory, 'coverage.info') | 67 self.coverage_info_file = os.path.join(self.directory, 'coverage.info') |
68 # If needed, an intermediate VSTS-format file | |
69 self.vsts_output = os.path.join(self.directory, 'coverage.vsts') | |
70 # Needed for Windows. | |
71 self.src_root = options.src_root | |
72 self.FindPrograms() | |
69 self.ConfirmPlatformAndPaths() | 73 self.ConfirmPlatformAndPaths() |
70 self.tests = [] | 74 self.tests = [] |
71 | 75 |
76 def FindInPath(self, program): | |
77 """Find program in our path. Return abs path to it, or None.""" | |
78 if not 'PATH' in os.environ: | |
79 logging.fatal('No PATH environment variable?') | |
80 sys.exit(1) | |
81 paths = os.environ['PATH'].split(os.pathsep) | |
82 for path in paths: | |
83 fullpath = os.path.join(path, program) | |
84 if os.path.exists(fullpath): | |
85 return fullpath | |
86 return None | |
87 | |
88 def FindPrograms(self): | |
89 """Find programs we may want to run.""" | |
90 if self.IsPosix(): | |
91 self.lcov_directory = os.path.join(sys.path[0], | |
92 '../../third_party/lcov/bin') | |
93 self.lcov = os.path.join(self.lcov_directory, 'lcov') | |
94 self.mcov = os.path.join(self.lcov_directory, 'mcov') | |
95 self.genhtml = os.path.join(self.lcov_directory, 'genhtml') | |
96 self.programs = [self.lcov, self.mcov, self.genhtml] | |
97 else: | |
98 commands = ['vsperfcmd.exe', 'vsinstr.exe', 'coverage_analyzer.exe'] | |
99 self.perf = self.FindInPath('vsperfcmd.exe') | |
100 self.instrument = self.FindInPath('vsinstr.exe') | |
101 self.analyzer = self.FindInPath('coverage_analyzer.exe') | |
102 if not self.perf or not self.instrument or not self.analyzer: | |
103 logging.fatal('Could not find Win performance commands.') | |
104 logging.fatal('Commands needed in PATH: ' + str(commands)) | |
105 sys.exit(1) | |
106 self.programs = [self.perf, self.instrument, self.analyzer] | |
107 | |
72 def FindTests(self): | 108 def FindTests(self): |
73 """Find unit tests to run; set self.tests to this list. | 109 """Find unit tests to run; set self.tests to this list. |
74 | 110 |
75 Assume all non-option items in the arg list are tests to be run. | 111 Assume all non-option items in the arg list are tests to be run. |
76 """ | 112 """ |
77 # Small tests: can be run in the "chromium" directory. | 113 # Small tests: can be run in the "chromium" directory. |
78 # If asked, run all we can find. | 114 # If asked, run all we can find. |
79 if self.options.all_unittests: | 115 if self.options.all_unittests: |
80 self.tests += glob.glob(os.path.join(self.directory, '*_unittests')) | 116 self.tests += glob.glob(os.path.join(self.directory, '*_unittests')) |
81 | 117 |
82 # If told explicit tests, run those (after stripping the name as | 118 # If told explicit tests, run those (after stripping the name as |
83 # appropriate) | 119 # appropriate) |
84 for testname in self.args: | 120 for testname in self.args: |
85 if ':' in testname: | 121 if ':' in testname: |
86 self.tests += [os.path.join(self.directory, testname.split(':')[1])] | 122 self.tests += [os.path.join(self.directory, testname.split(':')[1])] |
87 else: | 123 else: |
88 self.tests += [os.path.join(self.directory, testname)] | 124 self.tests += [os.path.join(self.directory, testname)] |
89 | |
90 # Needs to be run in the "chrome" directory? | |
91 # ut = os.path.join(self.directory, 'unit_tests') | |
92 # if os.path.exists(ut): | |
93 # self.tests.append(ut) | |
94 # Medium tests? | 125 # Medium tests? |
95 # Not sure all of these work yet (e.g. page_cycler_tests) | 126 # Not sure all of these work yet (e.g. page_cycler_tests) |
96 # self.tests += glob.glob(os.path.join(self.directory, '*_tests')) | 127 # self.tests += glob.glob(os.path.join(self.directory, '*_tests')) |
97 | 128 |
129 # If needed, append .exe to tests since vsinstr.exe likes it that | |
130 # way. | |
131 if self.IsWindows(): | |
132 for ind in range(len(self.tests)): | |
133 test = self.tests[ind] | |
134 test_exe = test + '.exe' | |
135 if not test.endswith('.exe') and os.path.exists(test_exe): | |
136 self.tests[ind] = test_exe | |
137 | |
138 # Temporarily make Windows quick for bringup by filtering | |
139 # out all except base_unittests. Easier than a chrome.cyp change. | |
140 # TODO(jrg): remove this | |
141 if self.IsWindows(): | |
142 t2 = [] | |
143 for test in self.tests: | |
144 if 'base_unittests' in test: | |
145 t2.append(test) | |
146 self.tests = t2 | |
147 | |
148 | |
149 | |
98 def ConfirmPlatformAndPaths(self): | 150 def ConfirmPlatformAndPaths(self): |
99 """Confirm OS and paths (e.g. lcov).""" | 151 """Confirm OS and paths (e.g. lcov).""" |
100 if not self.IsPosix(): | 152 for program in self.programs: |
101 logging.fatal('Not posix.') | |
102 sys.exit(1) | |
103 programs = [self.lcov, self.genhtml] | |
104 if self.IsMac(): | |
105 programs.append(self.mcov) | |
106 for program in programs: | |
107 if not os.path.exists(program): | 153 if not os.path.exists(program): |
108 logging.fatal('lcov program missing: ' + program) | 154 logging.fatal('Program missing: ' + program) |
109 sys.exit(1) | 155 sys.exit(1) |
110 | 156 |
157 def Run(self, cmdlist, ignore_error=False, ignore_retcode=None, | |
158 explanation=None): | |
159 """Run the command list; exit fatally on error.""" | |
160 logging.info('Running ' + str(cmdlist)) | |
161 retcode = subprocess.call(cmdlist) | |
162 if retcode: | |
163 if ignore_error or retcode == ignore_retcode: | |
164 logging.warning('COVERAGE: %s unhappy but errors ignored %s' % | |
165 (str(cmdlist), explanation or '')) | |
166 else: | |
167 logging.fatal('COVERAGE: %s failed; return code: %d' % | |
168 (str(cmdlist), retcode)) | |
169 sys.exit(retcode) | |
170 | |
171 | |
111 def IsPosix(self): | 172 def IsPosix(self): |
112 """Return True if we are POSIX.""" | 173 """Return True if we are POSIX.""" |
113 return self.IsMac() or self.IsLinux() | 174 return self.IsMac() or self.IsLinux() |
114 | 175 |
115 def IsMac(self): | 176 def IsMac(self): |
116 return sys.platform == 'darwin' | 177 return sys.platform == 'darwin' |
117 | 178 |
118 def IsLinux(self): | 179 def IsLinux(self): |
119 return sys.platform == 'linux2' | 180 return sys.platform == 'linux2' |
120 | 181 |
182 def IsWindows(self): | |
183 """Return True if we are Windows.""" | |
184 return sys.platform in ('win32', 'cygwin') | |
185 | |
121 def ClearData(self): | 186 def ClearData(self): |
122 """Clear old gcda files""" | 187 """Clear old gcda files""" |
188 if not self.IsPosix(): | |
189 return | |
123 subprocess.call([self.lcov, | 190 subprocess.call([self.lcov, |
124 '--directory', self.directory_parent, | 191 '--directory', self.directory_parent, |
125 '--zerocounters']) | 192 '--zerocounters']) |
126 shutil.rmtree(os.path.join(self.directory, 'coverage')) | 193 shutil.rmtree(os.path.join(self.directory, 'coverage')) |
127 | 194 |
195 def BeforeRunTests(self): | |
196 """Do things before running tests.""" | |
197 if not self.IsWindows(): | |
198 return | |
199 # Stop old counters if needed | |
200 cmdlist = [self.perf, '-shutdown'] | |
201 self.Run(cmdlist, ignore_error=True) | |
202 # Instrument binaries | |
203 for fulltest in self.tests: | |
204 if os.path.exists(fulltest): | |
205 cmdlist = [self.instrument, '/COVERAGE', fulltest] | |
206 self.Run(cmdlist, ignore_retcode=4, | |
207 explanation='OK with a multiple-instrument') | |
208 # Start new counters | |
209 cmdlist = [self.perf, '-start:coverage', '-output:' + self.vsts_output] | |
210 self.Run(cmdlist) | |
211 | |
128 def RunTests(self): | 212 def RunTests(self): |
129 """Run all unit tests.""" | 213 """Run all unit tests.""" |
130 for fulltest in self.tests: | 214 for fulltest in self.tests: |
131 if not os.path.exists(fulltest): | 215 if not os.path.exists(fulltest): |
132 logging.fatal(fulltest + ' does not exist') | 216 logging.fatal(fulltest + ' does not exist') |
133 if self.options.strict: | 217 if self.options.strict: |
134 sys.exit(2) | 218 sys.exit(2) |
135 # TODO(jrg): add timeout? | 219 # TODO(jrg): add timeout? |
136 print >>sys.stderr, 'Running test: ' + fulltest | 220 print >>sys.stderr, 'Running test: ' + fulltest |
137 cmdlist = [fulltest, '--gtest_print_time'] | 221 cmdlist = [fulltest, '--gtest_print_time'] |
138 | 222 |
139 # If asked, make this REAL fast for testing. | 223 # If asked, make this REAL fast for testing. |
140 if self.options.fast_test: | 224 if self.options.fast_test: |
141 cmdlist.append('--gtest_filter=RenderWidgetHost*') | 225 # cmdlist.append('--gtest_filter=RenderWidgetHost*') |
226 cmdlist.append('--gtest_filter=CommandLine*') | |
142 | 227 |
143 retcode = subprocess.call(cmdlist) | 228 retcode = subprocess.call(cmdlist) |
144 if retcode: | 229 if retcode: |
145 logging.fatal('COVERAGE: test %s failed; return code: %d' % | 230 logging.fatal('COVERAGE: test %s failed; return code: %d' % |
146 (fulltest, retcode)) | 231 (fulltest, retcode)) |
147 if self.options.strict: | 232 if self.options.strict: |
148 sys.exit(retcode) | 233 sys.exit(retcode) |
149 | 234 |
150 def GenerateLcov(self): | 235 def AfterRunTests(self): |
236 """Do things right after running tests.""" | |
237 if not self.IsWindows(): | |
238 return | |
239 # Stop counters | |
240 cmdlist = [self.perf, '-shutdown'] | |
241 self.Run(cmdlist) | |
242 full_output = self.vsts_output + '.coverage' | |
243 shutil.move(full_output, self.vsts_output) | |
244 | |
245 def GenerateLcovPosix(self): | |
151 """Convert profile data to lcov.""" | 246 """Convert profile data to lcov.""" |
152 command = [self.mcov, | 247 command = [self.mcov, |
153 '--directory', self.directory_parent, | 248 '--directory', self.directory_parent, |
154 '--output', self.coverage_info_file] | 249 '--output', self.coverage_info_file] |
155 print >>sys.stderr, 'Assembly command: ' + ' '.join(command) | 250 print >>sys.stderr, 'Assembly command: ' + ' '.join(command) |
156 retcode = subprocess.call(command) | 251 retcode = subprocess.call(command) |
157 if retcode: | 252 if retcode: |
158 logging.fatal('COVERAGE: %s failed; return code: %d' % | 253 logging.fatal('COVERAGE: %s failed; return code: %d' % |
159 (command[0], retcode)) | 254 (command[0], retcode)) |
160 if self.options.strict: | 255 if self.options.strict: |
161 sys.exit(retcode) | 256 sys.exit(retcode) |
162 | 257 |
258 def GenerateLcovWindows(self): | |
259 """Convert VSTS format to lcov.""" | |
260 lcov_file = self.vsts_output + '.lcov' | |
261 if os.path.exists(lcov_file): | |
262 os.remove(lcov_file) | |
263 # generates the file (self.vsts_output + ".lcov") | |
264 | |
265 cmdlist = [self.analyzer, | |
266 '-sym_path=' + self.directory, | |
267 '-src_root=' + self.src_root, | |
268 self.vsts_output] | |
269 self.Run(cmdlist) | |
270 if not os.path.exists(lcov_file): | |
271 logging.fatal('Output file %s not created' % lcov_file) | |
272 sys.exit(1) | |
273 # So we name it appropriately | |
274 if os.path.exists(self.coverage_info_file): | |
275 os.remove(self.coverage_info_file) | |
276 logging.info('Renaming LCOV file to %s to be consistent' % | |
277 self.coverage_info_file) | |
278 shutil.move(self.vsts_output + '.lcov', self.coverage_info_file) | |
279 | |
280 def GenerateLcov(self): | |
281 if self.IsPosix(): | |
282 self.GenerateLcovPosix() | |
283 else: | |
284 self.GenerateLcovWindows() | |
285 | |
163 def GenerateHtml(self): | 286 def GenerateHtml(self): |
164 """Convert lcov to html.""" | 287 """Convert lcov to html.""" |
165 # TODO(jrg): This isn't happy when run with unit_tests since V8 has a | 288 # TODO(jrg): This isn't happy when run with unit_tests since V8 has a |
166 # different "base" so V8 includes can't be found in ".". Fix. | 289 # different "base" so V8 includes can't be found in ".". Fix. |
167 command = [self.genhtml, | 290 command = [self.genhtml, |
168 self.coverage_info_file, | 291 self.coverage_info_file, |
169 '--output-directory', | 292 '--output-directory', |
170 self.output_directory] | 293 self.output_directory] |
171 print >>sys.stderr, 'html generation command: ' + ' '.join(command) | 294 print >>sys.stderr, 'html generation command: ' + ' '.join(command) |
172 retcode = subprocess.call(command) | 295 retcode = subprocess.call(command) |
(...skipping 26 matching lines...) Expand all Loading... | |
199 parser.add_option('-f', | 322 parser.add_option('-f', |
200 '--fast_test', | 323 '--fast_test', |
201 dest='fast_test', | 324 dest='fast_test', |
202 default=False, | 325 default=False, |
203 help='Make the tests run REAL fast by doing little.') | 326 help='Make the tests run REAL fast by doing little.') |
204 parser.add_option('-s', | 327 parser.add_option('-s', |
205 '--strict', | 328 '--strict', |
206 dest='strict', | 329 dest='strict', |
207 default=False, | 330 default=False, |
208 help='Be strict and die on test failure.') | 331 help='Be strict and die on test failure.') |
332 parser.add_option('-S', | |
333 '--src_root', | |
334 dest='src_root', | |
335 default='.', | |
336 help='Source root (only used on Windows)') | |
209 (options, args) = parser.parse_args() | 337 (options, args) = parser.parse_args() |
210 if not options.directory: | 338 if not options.directory: |
211 parser.error('Directory not specified') | 339 parser.error('Directory not specified') |
212 coverage = Coverage(options.directory, options, args) | 340 coverage = Coverage(options.directory, options, args) |
213 coverage.ClearData() | 341 coverage.ClearData() |
214 coverage.FindTests() | 342 coverage.FindTests() |
343 coverage.BeforeRunTests() | |
215 coverage.RunTests() | 344 coverage.RunTests() |
345 coverage.AfterRunTests() | |
216 coverage.GenerateLcov() | 346 coverage.GenerateLcov() |
217 if options.genhtml: | 347 if options.genhtml: |
218 coverage.GenerateHtml() | 348 coverage.GenerateHtml() |
219 return 0 | 349 return 0 |
220 | 350 |
221 | 351 |
222 if __name__ == '__main__': | 352 if __name__ == '__main__': |
223 sys.exit(main()) | 353 sys.exit(main()) |
OLD | NEW |