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 |