| #!/usr/bin/env python |
| #-*- coding: utf-8 -*- |
| # Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. |
| # |
| # Use of this source code is governed by a BSD-style license |
| # that can be found in the LICENSE file in the root of the source |
| # tree. An additional intellectual property rights grant can be found |
| # in the file PATENTS. All contributing project authors may |
| # be found in the AUTHORS file in the root of the source tree. |
| |
| """This script grabs and reports coverage information. |
| |
| It grabs coverage information from the latest Linux 32-bit build and |
| pushes it to the coverage tracker, enabling us to track code coverage |
| over time. This script is intended to run on the 32-bit Linux slave. |
| |
| This script requires an access.token file in the current directory, as |
| generated by the request_oauth_permission.py script. It also expects a file |
| customer.secret with a single line containing the customer secret. The |
| customer secret is an OAuth concept and is received when one registers the |
| application with the App Engine running the dashboard. |
| |
| The script assumes that all coverage data is stored under |
| /home/<build bot user>/www. |
| """ |
| |
| __author__ = 'phoglund@webrtc.org (Patrik Höglund)' |
| |
| import os |
| import re |
| import sys |
| import time |
| |
| import constants |
| import dashboard_connection |
| |
| |
| class FailedToParseCoverageHtml(Exception): |
| pass |
| |
| |
| class CouldNotFindCoverageDirectory(Exception): |
| pass |
| |
| |
| def _find_latest_build_coverage(www_directory_contents, coverage_www_dir, |
| directory_prefix): |
| """Finds the most recent coverage directory in the directory listing. |
| |
| We assume here that build numbers keep rising and never wrap around. |
| |
| Args: |
| www_directory_contents: A list of entries in the coverage directory. |
| coverage_www_dir: The coverage directory on the bot. |
| directory_prefix: Coverage directories have the form <prefix><number>, |
| and the prefix is different on different bots. The prefix is |
| generally the builder name, such as Linux32DBG. |
| |
| Returns: |
| The most recent directory name. |
| |
| Raises: |
| CouldNotFindCoverageDirectory: if we failed to find coverage data. |
| """ |
| |
| found_build_numbers = [] |
| for entry in www_directory_contents: |
| match = re.match(directory_prefix + '(\d+)', entry) |
| if match is not None: |
| found_build_numbers.append(int(match.group(1))) |
| |
| if not found_build_numbers: |
| raise CouldNotFindCoverageDirectory('Error: Found no directories %s* ' |
| 'in directory %s.' % |
| (directory_prefix, coverage_www_dir)) |
| |
| most_recent = max(found_build_numbers) |
| return directory_prefix + str(most_recent) |
| |
| |
| def _grab_coverage_percentage(label, index_html_contents): |
| """Extracts coverage from a LCOV coverage report. |
| |
| Grabs coverage by assuming that the label in the coverage HTML report |
| is close to the actual number and that the number is followed by a space |
| and a percentage sign. |
| """ |
| match = re.search('<td[^>]*>' + label + '</td>.*?(\d+\.\d) %', |
| index_html_contents, re.DOTALL) |
| if match is None: |
| raise FailedToParseCoverageHtml('Missing coverage at label "%s".' % label) |
| |
| try: |
| return float(match.group(1)) |
| except ValueError: |
| raise FailedToParseCoverageHtml('%s is not a float.' % match.group(1)) |
| |
| |
| def _report_coverage_to_dashboard(dashboard, line_coverage, function_coverage, |
| branch_coverage, report_category): |
| parameters = {'line_coverage': '%f' % line_coverage, |
| 'function_coverage': '%f' % function_coverage, |
| 'branch_coverage': '%f' % branch_coverage, |
| 'report_category': report_category, |
| } |
| |
| dashboard.send_post_request(constants.ADD_COVERAGE_DATA_URL, parameters) |
| |
| |
| def _main(report_category, directory_prefix): |
| """Grabs coverage data from disk on a bot and publishes it. |
| |
| Args: |
| report_category: The kind of coverage to report. The dashboard |
| application decides what is acceptable here (see |
| dashboard/add_coverage_data.py for more information). |
| directory_prefix: This bot's coverage directory prefix. Generally a bot's |
| coverage directories will have the form <prefix><build number>, |
| like Linux32DBG_345. |
| """ |
| dashboard = dashboard_connection.DashboardConnection(constants.CONSUMER_KEY) |
| dashboard.read_required_files(constants.CONSUMER_SECRET_FILE, |
| constants.ACCESS_TOKEN_FILE) |
| |
| coverage_www_dir = constants.BUILD_BOT_COVERAGE_WWW_DIRECTORY |
| www_dir_contents = os.listdir(coverage_www_dir) |
| latest_build_directory = _find_latest_build_coverage(www_dir_contents, |
| coverage_www_dir, |
| directory_prefix) |
| |
| index_html_path = os.path.join(coverage_www_dir, latest_build_directory, |
| 'index.html') |
| index_html_file = open(index_html_path) |
| whole_file = index_html_file.read() |
| |
| line_coverage = _grab_coverage_percentage('Lines:', whole_file) |
| function_coverage = _grab_coverage_percentage('Functions:', whole_file) |
| branch_coverage = _grab_coverage_percentage('Branches:', whole_file) |
| |
| _report_coverage_to_dashboard(dashboard, line_coverage, function_coverage, |
| branch_coverage, report_category) |
| |
| |
| def _parse_args(): |
| if len(sys.argv) != 3: |
| print ('Usage: %s <coverage category> <directory prefix>\n\n' |
| 'The coverage category describes the kind of coverage you are ' |
| 'uploading. Known acceptable values are small_medium_tests and' |
| 'large_tests. The directory prefix is what the directories in %s ' |
| 'are prefixed on this bot (such as Linux32DBG_).' % |
| (sys.argv[0], constants.BUILD_BOT_COVERAGE_WWW_DIRECTORY)) |
| return (None, None) |
| return (sys.argv[1], sys.argv[2]) |
| |
| |
| if __name__ == '__main__': |
| report_category, directory_prefix = _parse_args() |
| if report_category: |
| _main(report_category, directory_prefix) |
| |