| OLD | NEW |
| 1 #!/bin/env python | 1 #!/bin/env python |
| 2 # Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2006-2008 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 | 6 |
| 7 """Module to setup and generate code coverage data | 7 """Module to setup and generate code coverage data |
| 8 | 8 |
| 9 This module first sets up the environment for code coverage, instruments the | 9 This module first sets up the environment for code coverage, instruments the |
| 10 binaries, runs the tests and collects the code coverage data. | 10 binaries, runs the tests and collects the code coverage data. |
| (...skipping 12 matching lines...) Expand all Loading... |
| 23 import shutil | 23 import shutil |
| 24 import subprocess | 24 import subprocess |
| 25 import sys | 25 import sys |
| 26 import tempfile | 26 import tempfile |
| 27 | 27 |
| 28 import google.logging_utils | 28 import google.logging_utils |
| 29 import google.process_utils as proc | 29 import google.process_utils as proc |
| 30 | 30 |
| 31 | 31 |
| 32 # The list of binaries that will be instrumented for code coverage | 32 # The list of binaries that will be instrumented for code coverage |
| 33 windows_binaries = ['unit_tests.exe', | 33 windows_binaries = ['base_unittests.exe', |
| 34 'unit_tests.exe', |
| 35 'automated_ui_tests.exe', |
| 34 'ui_tests.exe', | 36 'ui_tests.exe', |
| 37 'installer_unittests.exe', |
| 35 'ipc_tests.exe', | 38 'ipc_tests.exe', |
| 36 'memory_test.exe', | 39 'memory_test.exe', |
| 37 'net_perftests.exe', | 40 'net_perftests.exe', |
| 38 'net_unittests.exe', | 41 'net_unittests.exe', |
| 39 'page_cycler_tests.exe', | 42 'page_cycler_tests.exe', |
| 40 'perf_tests.exe', | 43 'perf_tests.exe', |
| 41 'plugin_tests.exe', | 44 'plugin_tests.exe', |
| 45 'security_tests.dll', |
| 42 'selenium_tests.exe', | 46 'selenium_tests.exe', |
| 47 'startup_tests.exe', |
| 43 'tab_switching_test.exe', | 48 'tab_switching_test.exe', |
| 44 'test_shell_tests.exe', | 49 'test_shell_tests.exe', |
| 45 'test_shell.exe', | 50 'test_shell.exe', |
| 46 'activex_test_control.dll'] | 51 'activex_test_control.dll'] |
| 47 | 52 |
| 48 # The list of tests that will be run | 53 # The list of tests that will be run |
| 49 windows_tests = ['unit_tests.exe', | 54 windows_tests = ['unit_tests.exe', |
| 55 'base_unittests.exe', |
| 56 'automated_ui_tests.exe', |
| 50 'ui_tests.exe', | 57 'ui_tests.exe', |
| 58 'installer_unittests.exe', |
| 51 'ipc_tests.exe', | 59 'ipc_tests.exe', |
| 52 'net_perftests.exe', | 60 'net_perftests.exe', |
| 53 'net_unittests.exe', | 61 'net_unittests.exe', |
| 54 'plugin_tests.exe', | 62 'plugin_tests.exe', |
| 63 'startup_tests.exe', |
| 55 'tab_switching_test.exe', | 64 'tab_switching_test.exe', |
| 56 'test_shell_tests.exe'] | 65 'test_shell_tests.exe'] |
| 57 | 66 |
| 58 | 67 |
| 59 def IsWindows(): | 68 def IsWindows(): |
| 60 """Checks if the current platform is Windows. | 69 """Checks if the current platform is Windows. |
| 61 """ | 70 """ |
| 62 return sys.platform[:3] == 'win' | 71 return sys.platform[:3] == 'win' |
| 63 | 72 |
| 64 | 73 |
| (...skipping 17 matching lines...) Expand all Loading... |
| 82 Args: | 91 Args: |
| 83 revision: Revision number of the Chromium source tree. | 92 revision: Revision number of the Chromium source tree. |
| 84 src_path: Location of the Chromium source base. | 93 src_path: Location of the Chromium source base. |
| 85 tools_path: Location of the Visual Studio Team Tools. (Win32 only) | 94 tools_path: Location of the Visual Studio Team Tools. (Win32 only) |
| 86 """ | 95 """ |
| 87 google.logging_utils.config_root() | 96 google.logging_utils.config_root() |
| 88 self.revision = revision | 97 self.revision = revision |
| 89 self.instrumented = False | 98 self.instrumented = False |
| 90 self.tools_path = tools_path | 99 self.tools_path = tools_path |
| 91 self.src_path = src_path | 100 self.src_path = src_path |
| 92 self._dir = None | 101 self._dir = tempfile.mkdtemp() |
| 93 | 102 |
| 94 | 103 |
| 95 def SetUp(self, binaries): | 104 def SetUp(self, binaries): |
| 96 """Set up the platform specific environment and instrument the binaries for | 105 """Set up the platform specific environment and instrument the binaries for |
| 97 coverage. | 106 coverage. |
| 98 | 107 |
| 99 This method sets up the environment, instruments all the compiled binaries | 108 This method sets up the environment, instruments all the compiled binaries |
| 100 and sets up the code coverage counters. | 109 and sets up the code coverage counters. |
| 101 | 110 |
| 102 Args: | 111 Args: |
| 103 binaries: List of binaries that need to be instrumented. | 112 binaries: List of binaries that need to be instrumented. |
| 104 | 113 |
| 105 Returns: | 114 Returns: |
| 106 Path of the file containing code coverage data on successful | 115 True on success. |
| 107 instrumentation. | 116 False on error. |
| 108 None on error. | |
| 109 """ | 117 """ |
| 110 if self.instrumented: | 118 if self.instrumented: |
| 111 logging.error('Binaries already instrumented') | 119 logging.error('Binaries already instrumented') |
| 112 return None | 120 return False |
| 113 coverage_file = None | |
| 114 if IsWindows(): | 121 if IsWindows(): |
| 115 # Stop all previous instance of VSPerfMon counters | 122 # Stop all previous instance of VSPerfMon counters |
| 116 counters_command = ('%s -shutdown' % | 123 counters_command = ('%s -shutdown' % |
| 117 (os.path.join(self.tools_path, 'vsperfcmd.exe'))) | 124 (os.path.join(self.tools_path, 'vsperfcmd.exe'))) |
| 118 (retcode, output) = proc.RunCommandFull(counters_command, | 125 (retcode, output) = proc.RunCommandFull(counters_command, |
| 119 collect_output=True) | 126 collect_output=True) |
| 120 # TODO(niranjan): Add a check that to verify that the binaries were built | 127 # TODO(niranjan): Add a check that to verify that the binaries were built |
| 121 # using the /PROFILE linker flag. | 128 # using the /PROFILE linker flag. |
| 122 if self.tools_path == None: | 129 if self.tools_path == None: |
| 123 logging.error('Could not locate Visual Studio Team Server tools') | 130 logging.error('Could not locate Visual Studio Team Server tools') |
| 124 return None | 131 return False |
| 125 # Remove trailing slashes | 132 # Remove trailing slashes |
| 126 self.tools_path = self.tools_path.rstrip('\\') | 133 self.tools_path = self.tools_path.rstrip('\\') |
| 127 instrument_command = '%s /COVERAGE ' % (os.path.join(self.tools_path, | 134 instrument_command = '%s /COVERAGE ' % (os.path.join(self.tools_path, |
| 128 'vsinstr.exe')) | 135 'vsinstr.exe')) |
| 129 for binary in binaries: | 136 for binary in binaries: |
| 130 logging.info('binary = %s' % (binary)) | 137 logging.info('binary = %s' % (binary)) |
| 131 logging.info('instrument_command = %s' % (instrument_command)) | 138 logging.info('instrument_command = %s' % (instrument_command)) |
| 132 # Instrument each binary in the list | 139 # Instrument each binary in the list |
| 133 binary = os.path.join(self.src_path, 'chrome', 'Release', binary) | 140 binary = os.path.join(self.src_path, 'chrome', 'Release', binary) |
| 134 (retcode, output) = proc.RunCommandFull(instrument_command + binary, | 141 (retcode, output) = proc.RunCommandFull(instrument_command + binary, |
| 135 collect_output=True) | 142 collect_output=True) |
| 136 # Check if the file has been instrumented correctly. | 143 # Check if the file has been instrumented correctly. |
| 137 if output.pop().rfind('Successfully instrumented') == -1: | 144 if output.pop().rfind('Successfully instrumented') == -1: |
| 138 logging.error('Error instrumenting %s' % (binary)) | 145 logging.error('Error instrumenting %s' % (binary)) |
| 139 return None | 146 return False |
| 140 | |
| 141 # Generate the file name for the coverage results | |
| 142 self._dir = tempfile.mkdtemp() | |
| 143 coverage_file = os.path.join(self._dir, 'chrome_win32_%s.coverage' % | |
| 144 (self.revision)) | |
| 145 logging.info('.coverage file: %s' % (coverage_file)) | |
| 146 | |
| 147 # After all the binaries have been instrumented, we start the counters. | |
| 148 counters_command = ('%s -start:coverage -output:%s' % | |
| 149 (os.path.join(self.tools_path, 'vsperfcmd.exe'), | |
| 150 coverage_file)) | |
| 151 # Here we use subprocess.call() instead of the RunCommandFull because the | |
| 152 # VSPerfCmd spawns another process before terminating and this confuses | |
| 153 # the subprocess.Popen() used by RunCommandFull. | |
| 154 retcode = subprocess.call(counters_command) | |
| 155 # TODO(niranjan): Check whether the counters have been started | |
| 156 # successfully. | |
| 157 | |
| 158 # We are now ready to run tests and measure code coverage. | 147 # We are now ready to run tests and measure code coverage. |
| 159 self.instrumented = True | 148 self.instrumented = True |
| 160 else: | 149 return True |
| 161 return None | |
| 162 return coverage_file | |
| 163 | 150 |
| 164 | 151 |
| 165 def TearDown(self): | 152 def TearDown(self): |
| 166 """Tear down method. | 153 """Tear down method. |
| 167 | 154 |
| 168 This method shuts down the counters, and cleans up all the intermediate | 155 This method shuts down the counters, and cleans up all the intermediate |
| 169 artifacts. | 156 artifacts. |
| 170 """ | 157 """ |
| 171 if self.instrumented == False: | 158 if self.instrumented == False: |
| 172 return | 159 return |
| (...skipping 10 matching lines...) Expand all Loading... |
| 183 else: | 170 else: |
| 184 return | 171 return |
| 185 # Delete all the temp files and folders | 172 # Delete all the temp files and folders |
| 186 if self._dir != None: | 173 if self._dir != None: |
| 187 shutil.rmtree(self._dir, ignore_errors=True) | 174 shutil.rmtree(self._dir, ignore_errors=True) |
| 188 logging.info('Cleaned up temporary files and folders') | 175 logging.info('Cleaned up temporary files and folders') |
| 189 # Reset the instrumented flag. | 176 # Reset the instrumented flag. |
| 190 self.instrumented = False | 177 self.instrumented = False |
| 191 | 178 |
| 192 | 179 |
| 193 def Upload(self, coverage_file, upload_path, sym_path=None, src_root=None): | 180 def RunTest(self, test): |
| 181 """Run tests and collect the .coverage file |
| 182 |
| 183 Args: |
| 184 test: Path to the test to be run. |
| 185 |
| 186 Returns: |
| 187 Path of the intermediate .coverage file on success. |
| 188 None on error. |
| 189 """ |
| 190 # Generate the intermediate file name for the coverage results |
| 191 testname = os.path.split(test)[1].strip('.exe') |
| 192 coverage_file = os.path.join(self._dir, '%s_win32_%s.coverage' % |
| 193 (testname, self.revision)) |
| 194 logging.info('.coverage file for test %s: %s' % (test, coverage_file)) |
| 195 |
| 196 # After all the binaries have been instrumented, we start the counters. |
| 197 counters_command = ('%s -start:coverage -output:%s' % |
| 198 (os.path.join(self.tools_path, 'vsperfcmd.exe'), |
| 199 coverage_file)) |
| 200 # Here we use subprocess.call() instead of the RunCommandFull because the |
| 201 # VSPerfCmd spawns another process before terminating and this confuses |
| 202 # the subprocess.Popen() used by RunCommandFull. |
| 203 retcode = subprocess.call(counters_command) |
| 204 |
| 205 # Run the test binary |
| 206 logging.info('Executing test %s: ' % test) |
| 207 (retcode, output) = proc.RunCommandFull(test, collect_output=True) |
| 208 if retcode != 0: # Return error if the tests fail |
| 209 logging.error('One or more tests failed in %s.' % test) |
| 210 return None |
| 211 |
| 212 # Stop the counters |
| 213 counters_command = ('%s -shutdown' % |
| 214 (os.path.join(self.tools_path, 'vsperfcmd.exe'))) |
| 215 (retcode, output) = proc.RunCommandFull(counters_command, |
| 216 collect_output=True) |
| 217 logging.info('Counters shut down: %s' % (output)) |
| 218 # Return the intermediate .coverage file |
| 219 return coverage_file |
| 220 |
| 221 |
| 222 def Upload(self, list_coverage, upload_path, sym_path=None, src_root=None): |
| 194 """Upload the results to the dashboard. | 223 """Upload the results to the dashboard. |
| 195 | 224 |
| 196 This method uploads the coverage data to a dashboard where it will be | 225 This method uploads the coverage data to a dashboard where it will be |
| 197 processed. On Windows, this method will first convert the .coverage file to | 226 processed. On Windows, this method will first convert the .coverage file to |
| 198 the lcov format. This method needs to be called before the TearDown method. | 227 the lcov format. This method needs to be called before the TearDown method. |
| 199 | 228 |
| 200 Args: | 229 Args: |
| 201 coverage_file: The coverage data file to upload. | 230 list_coverage: The list of coverage data files to consoliate and upload. |
| 202 upload_path: Destination where the coverage data will be processed. | 231 upload_path: Destination where the coverage data will be processed. |
| 203 sym_path: Symbol path for the build (Win32 only) | 232 sym_path: Symbol path for the build (Win32 only) |
| 204 src_root: Root folder of the source tree (Win32 only) | 233 src_root: Root folder of the source tree (Win32 only) |
| 205 | 234 |
| 206 Returns: | 235 Returns: |
| 207 True on success. | 236 True on success. |
| 208 False on failure. | 237 False on failure. |
| 209 """ | 238 """ |
| 210 if IsWindows(): | 239 if IsWindows(): |
| 211 # Stop counters | 240 # Stop counters |
| 212 counters_command = ('%s -shutdown' % | 241 counters_command = ('%s -shutdown' % |
| 213 (os.path.join(self.tools_path, 'vsperfcmd.exe'))) | 242 (os.path.join(self.tools_path, 'vsperfcmd.exe'))) |
| 214 (retcode, output) = proc.RunCommandFull(counters_command, | 243 (retcode, output) = proc.RunCommandFull(counters_command, |
| 215 collect_output=True) | 244 collect_output=True) |
| 216 logging.info('Counters shut down: %s' % (output)) | 245 logging.info('Counters shut down: %s' % (output)) |
| 217 # Convert the .coverage file to lcov format | 246 lcov_file = os.path.join(upload_path, 'chrome_win32_%s.lcov' % |
| 218 if self.tools_path == None: | 247 (self.revision)) |
| 219 logging.error('Lcov converter tool not found') | 248 lcov = open(lcov_file, 'w') |
| 220 return False | 249 for coverage_file in list_coverage: |
| 221 self.tools_path = self.tools_path.rstrip('\\') | 250 |
| 222 convert_command = ('%s -sym_path=%s -src_root=%s %s' % | 251 # Convert the intermediate .coverage file to lcov format |
| 223 (os.path.join(self.tools_path, | 252 if self.tools_path == None: |
| 224 'coverage_analyzer.exe'), | 253 logging.error('Lcov converter tool not found') |
| 225 sym_path, | 254 return False |
| 226 src_root, | 255 self.tools_path = self.tools_path.rstrip('\\') |
| 227 coverage_file)) | 256 convert_command = ('%s -sym_path=%s -src_root=%s %s' % |
| 228 (retcode, output) = proc.RunCommandFull(convert_command, | 257 (os.path.join(self.tools_path, |
| 229 collect_output=True) | 258 'coverage_analyzer.exe'), |
| 230 if output != 0: | 259 sym_path, |
| 231 logging.error('Conversion to LCOV failed. Exiting.') | 260 src_root, |
| 232 sys.exit(1) | 261 coverage_file)) |
| 233 lcov_file = coverage_file + '.lcov' | 262 (retcode, output) = proc.RunCommandFull(convert_command, |
| 234 logging.info('Conversion to lcov complete') | 263 collect_output=True) |
| 235 shutil.copy(lcov_file, upload_path) | 264 if output != 0: |
| 265 logging.error('Conversion to LCOV failed. Exiting.') |
| 266 sys.exit(1) |
| 267 tmp_lcov_file = coverage_file + '.lcov' |
| 268 logging.info('Conversion to lcov complete for %s' % (coverage_file)) |
| 269 # Now append this .lcov file to the cumulative lcov file |
| 270 logging.info('Consolidating LCOV file: %s' % (tmp_lcov_file)) |
| 271 tmp_lcov = open(tmp_lcov_file, 'r') |
| 272 lcov.write(tmp_lcov.read()) |
| 273 tmp_lcov.close() |
| 274 lcov.close() |
| 275 # Finally upload the LCOV file |
| 236 logging.info('LCOV file uploaded to %s' % (upload_path)) | 276 logging.info('LCOV file uploaded to %s' % (upload_path)) |
| 237 | 277 |
| 238 | 278 |
| 239 def main(): | 279 def main(): |
| 240 # Command line parsing | 280 # Command line parsing |
| 241 parser = optparse.OptionParser() | 281 parser = optparse.OptionParser() |
| 242 # Path where the .coverage to .lcov converter tools are stored. | 282 # Path where the .coverage to .lcov converter tools are stored. |
| 243 parser.add_option('-t', | 283 parser.add_option('-t', |
| 244 '--tools_path', | 284 '--tools_path', |
| 245 dest='tools_path', | 285 dest='tools_path', |
| (...skipping 26 matching lines...) Expand all Loading... |
| 272 if options.src_root == None: | 312 if options.src_root == None: |
| 273 parser.error('Source root not specified') | 313 parser.error('Source root not specified') |
| 274 if options.upload_path == None: | 314 if options.upload_path == None: |
| 275 parser.error('Upload path not specified') | 315 parser.error('Upload path not specified') |
| 276 | 316 |
| 277 if IsWindows(): | 317 if IsWindows(): |
| 278 # Initialize coverage | 318 # Initialize coverage |
| 279 cov = Coverage(options.revision, | 319 cov = Coverage(options.revision, |
| 280 options.src_root, | 320 options.src_root, |
| 281 options.tools_path) | 321 options.tools_path) |
| 322 list_coverage = [] |
| 282 # Instrument the binaries | 323 # Instrument the binaries |
| 283 coverage_file = cov.SetUp(windows_binaries) | 324 if cov.SetUp(windows_binaries): |
| 284 if coverage_file != None: | |
| 285 # Run all the tests | 325 # Run all the tests |
| 286 for test in windows_tests: | 326 for test in windows_tests: |
| 287 test = os.path.join(options.src_root, 'chrome', 'Release', test) | 327 test = os.path.join(options.src_root, 'chrome', 'Release', test) |
| 288 logging.info('Executing test %s: ' % test) | 328 coverage = cov.RunTest(test) |
| 289 (retcode, output) = proc.RunCommandFull(test, collect_output=True) | 329 if coverage == None: # Indicate failure to the buildbots. |
| 290 if retcode != 0: # Die if the tests fail | 330 return 1 |
| 291 logging.error('One or more tests failed in %s. Exiting.' % test) | 331 # Collect the intermediate file |
| 292 sys.exit(retcode) | 332 list_coverage.append(coverage) |
| 293 else: | 333 else: |
| 294 logging.error('Error during instrumentation.') | 334 logging.error('Error during instrumentation.') |
| 295 sys.exit(1) | 335 sys.exit(1) |
| 296 | 336 |
| 297 cov.Upload(coverage_file, | 337 cov.Upload(list_coverage, |
| 298 options.upload_path, | 338 options.upload_path, |
| 299 os.path.join(options.src_root, 'chrome', 'Release'), | 339 os.path.join(options.src_root, 'chrome', 'Release'), |
| 300 options.src_root) | 340 options.src_root) |
| 301 cov.TearDown() | 341 cov.TearDown() |
| 302 | 342 |
| 303 | 343 |
| 304 if __name__ == '__main__': | 344 if __name__ == '__main__': |
| 305 sys.exit(main()) | 345 sys.exit(main()) |
| 306 | 346 |
| OLD | NEW |