#!/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//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_. 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(']*>' + label + '.*?(\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()