| #!/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 appengine 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 httplib |
| import os |
| import re |
| import shelve |
| import sys |
| import time |
| import oauth.oauth as oauth |
| |
| # The build-bot user which runs build bot jobs. |
| BUILD_BOT_USER = 'phoglund' |
| |
| # The server to send coverage data to. |
| # TODO(phoglund): replace with real server once we get it up. |
| DASHBOARD_SERVER = 'localhost:8080' |
| CONSUMER_KEY = DASHBOARD_SERVER |
| ADD_COVERAGE_DATA_URL = '/add_coverage_data' |
| |
| |
| class FailedToParseCoverageHtml(Exception): |
| pass |
| |
| |
| class FailedToReportToDashboard(Exception): |
| pass |
| |
| |
| class FailedToReadRequiredInputFile(Exception): |
| pass |
| |
| |
| def _read_access_token_from_file(filename): |
| input_file = shelve.open(filename) |
| |
| if not input_file.has_key('access_token'): |
| raise FailedToReadRequiredInputFile('Missing %s file in current directory. ' |
| 'You may have to run ' |
| 'request_oauth_permission.py.') |
| |
| return oauth.OAuthToken.from_string(input_file['access_token']) |
| |
| |
| def _read_customer_secret_from_file(filename): |
| try: |
| input_file = open(filename, 'r') |
| except IOError as error: |
| raise FailedToReadRequiredInputFile(error) |
| |
| whole_file = input_file.read() |
| if '\n' in whole_file or not whole_file.strip(): |
| raise FailedToReadRequiredInputFile('Expected a single line with the ' |
| 'customer secret in file %s.' % |
| filename) |
| return whole_file |
| |
| |
| def _find_latest_32bit_debug_build(www_directory_contents): |
| # Build directories have the form Linux32bitDebug_<number>. There may be other |
| # directories in the list though, for instance for other build configurations. |
| # This sort ensures we will encounter the directory with the highest number |
| # first. |
| www_directory_contents.sort(reverse=True) |
| |
| for entry in www_directory_contents: |
| match = re.match('Linux32bitDBG_\d+', entry) |
| if match is not None: |
| return entry |
| |
| # Didn't find it |
| return None |
| |
| |
| 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(now, line_coverage, function_coverage, |
| access_token, consumer_key, consumer_secret): |
| parameters = {'date': '%d' % now, |
| 'line_coverage': '%f' % line_coverage, |
| 'function_coverage': '%f' % function_coverage |
| } |
| consumer = oauth.OAuthConsumer(consumer_key, consumer_secret) |
| create_oauth_request = oauth.OAuthRequest.from_consumer_and_token |
| oauth_request = create_oauth_request(consumer, |
| token=access_token, |
| http_method='POST', |
| http_url=ADD_COVERAGE_DATA_URL, |
| parameters=parameters) |
| |
| signature_method_hmac_sha1 = oauth.OAuthSignatureMethod_HMAC_SHA1() |
| oauth_request.sign_request(signature_method_hmac_sha1, consumer, access_token) |
| |
| connection = httplib.HTTPConnection(DASHBOARD_SERVER) |
| |
| headers = {'Content-Type': 'application/x-www-form-urlencoded'} |
| connection.request('POST', ADD_COVERAGE_DATA_URL, |
| body=oauth_request.to_postdata(), |
| headers=headers) |
| |
| response = connection.getresponse() |
| if response.status != 200: |
| message = ('Error: Failed to report to %s%s: got response %d (%s)' % |
| (DASHBOARD_SERVER, request_string, response.status, |
| response.reason)) |
| raise FailedToReportToDashboard(message) |
| |
| # The response content should be empty on success, so check that: |
| response_content = response.read() |
| if response_content: |
| message = ('Error: Dashboard reported the following error: %s.' % |
| response_content) |
| raise FailedToReportToDashboard(message) |
| |
| |
| def _main(): |
| access_token = _read_access_token_from_file('access.token') |
| customer_secret = _read_customer_secret_from_file('customer.secret') |
| |
| coverage_www_dir = os.path.join('/home', BUILD_BOT_USER, 'www') |
| |
| www_dir_contents = os.listdir(coverage_www_dir) |
| latest_build_directory = _find_latest_32bit_debug_build(www_dir_contents) |
| |
| if latest_build_directory is None: |
| print 'Error: Found no 32-bit debug build in directory ' + coverage_www_dir |
| sys.exit(1) |
| |
| 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) |
| now = int(time.time()) |
| |
| _report_coverage_to_dashboard(now, line_coverage, function_coverage, |
| access_token, CONSUMER_KEY, customer_secret) |
| |
| if __name__ == '__main__': |
| _main() |
| |