Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(50)

Side by Side Diff: tools/code_coverage/coverage_posix.py

Issue 155123: Code coverage on Windows (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 11 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/chrome.gyp ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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())
OLDNEW
« no previous file with comments | « chrome/chrome.gyp ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698