From e70c24a54020fd7efc987ef9a2a97055a4fea156 Mon Sep 17 00:00:00 2001 From: "niranjan@google.com" Date: Thu, 14 Aug 2008 23:15:40 +0000 Subject: [PATCH] Initial check-in for the Coverage class. This checkin includes methods to setup and teardown all that is needed measuring code coverage (instrumenting binaries, starting/stopping counters, etc). Next step is to modify the test runners to make use of this class. git-svn-id: svn://svn.chromium.org/chrome/trunk/src@909 0039d316-1c4b-4281-b951-d872f2087c98 --- tools/code_coverage/coverage.py | 195 ++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 tools/code_coverage/coverage.py diff --git a/tools/code_coverage/coverage.py b/tools/code_coverage/coverage.py new file mode 100644 index 00000000000000..7727908814725f --- /dev/null +++ b/tools/code_coverage/coverage.py @@ -0,0 +1,195 @@ +#!/bin/env python +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""Module to setup and generate code coverage data +""" + +import optparse +import os +import shutil +import sys +import tempfile + +import google.process_utils as proc + +class Coverage(object): + """Class to set up and generate code coverage. + + This class contains methods that are useful to set up the environment for + code coverage. + + Attributes: + instrumented: A boolean indicating if all the binaries have been + instrumented. + """ + + def __init__(self, + revision, + vsts_path = None, + lcov_converter_path = None): + self.revision = revision + self.instrumented = False + self.vsts_path = vsts_path + self.lcov_converter_path = lcov_converter_path + + + def _IsWindows(self): + """Checks if the current platform is Windows. + """ + return sys.platform[:3] == 'win': + + + def SetUp(self, binaries): + """Set up the platform specific environment and instrument the binaries for + coverage. + + This method sets up the environment, instruments all the compiled binaries + and sets up the code coverage counters. + + Args: + binaries: List of binaries that need to be instrumented. + + Returns: + Path of the file containing code coverage data on successful + instrumentation. + None on error. + """ + if self.instrumented: + return None + coverage_file = None + if self._IsWindows(): + # TODO(niranjan): Add a check that to verify that the binaries were built + # using the /PROFILE linker flag. + if self.vsts_path == None or self.lcov_converter_path == None: + return None + # Remove trailing slashes + self.vsts_path = self.vsts_path.rstrip('\\') + instrument_command = '%s /COVERAGE ' % (os.path.join(self.vsts_path, + 'vsinstr.exe')) + for binary in binaries: + print 'binary = %s' % binary + print 'instrument_command = %s' % instrument_command + # Instrument each binary in the list + (retcode, output) = proc.RunCommandFull(instrument_command + binary, + collect_output=True) + print output + # Check if the file has been instrumented correctly. + if output.pop().rfind('Successfully instrumented') == -1: + # TODO(niranjan): Change print to logging. + print 'Error instrumenting %s' % binary + return None + + # Generate the file name for the coverage results + self._dir = tempfile.mkdtemp() + coverage_file = os.path.join(self._dir, 'chrome_win32_%s.coverage' % + (self.revision)) + + # After all the binaries have been instrumented, we start the counters. + counters_command = ('%s -start:coverage -output:%s' % + (os.path.join(self.vsts_path, 'vsperfcmd.exe'), + coverage_file)) + (retcode, output) = proc.RunCommandFull(counters_command, + collect_output=True) + print output + # TODO(niranjan): Check whether the counters have been started + # successfully. + + # We are now ready to run tests and measure code coverage. + self.instrumented = True + else: + return None + return coverage_file + + + def TearDown(self): + """Tear down method. + + This method shuts down the counters, and cleans up all the intermediate + artifacts. + """ + if self.instrumented == False: + return + + if self._IsWindows(): + # Stop counters + counters_command = ('%s -start:coverage -shutdown' % + (os.path.join(self.vsts_path, 'vsperfcmd.exe'))) + (retcode, output) = proc.RunCommandFull(counters_command, + collect_output=True) + + # TODO(niranjan): Revert the instrumented binaries to their original + # versions. + else: + return + # Delete all the temp files and folders + if self._dir != None: + os.removedirs(self._dir) + # Reset the instrumented flag. + self.instrumented = False + + + def Upload(self, coverage_file, upload_path, sym_path=None, src_root=None): + """Upload the results to the dashboard. + + This method uploads the coverage data to a dashboard where it will be + processed. On Windows, this method will first convert the .coverage file to + the lcov format. + + Args: + coverage_file: The coverage data file to upload. + upload_path: Destination where the coverage data will be processed. + sym_path: Symbol path for the build (Win32 only) + src_root: Root folder of the source tree (Win32 only) + + Returns: + True on success. + False on failure. + """ + if self._IsWindows(): + # Stop counters + counters_command = ('%s -start:coverage -shutdown' % + (os.path.join(self.vsts_path, 'vsperfcmd.exe'))) + (retcode, output) = proc.RunCommandFull(counters_command, + collect_output=True) + # Convert the .coverage file to lcov format + if self.lcov_converter_path == False: + return False + self.lcov_converter_path = self.lcov_converter_path.rstrip('\\') + convert_command = ('%s -sym_path=%s -src_root=%s ' % + os.path.join(self.lcov_converter_path, + 'coverage_analyzer.exe'), + sym_path, + src_root) + (retcode, output) = proc.RunCommandFull(convert_command + coverage_file, + collect_output=True) + shutil.copy(coverage_file, coverage_file.replace('.coverage', '')) + # TODO(niranjan): Upload this somewhere! +