phoglund@webrtc.org | d4f0a0e | 2012-02-01 10:59:23 +0000 | [diff] [blame^] | 1 | #!/usr/bin/env python |
| 2 | #-*- coding: utf-8 -*- |
| 3 | # Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. |
| 4 | # |
| 5 | # Use of this source code is governed by a BSD-style license |
| 6 | # that can be found in the LICENSE file in the root of the source |
| 7 | # tree. An additional intellectual property rights grant can be found |
| 8 | # in the file PATENTS. All contributing project authors may |
| 9 | # be found in the AUTHORS file in the root of the source tree. |
| 10 | |
| 11 | """This script grabs and reports coverage information. |
| 12 | |
| 13 | It grabs coverage information from the latest Linux 32-bit build and |
| 14 | pushes it to the coverage tracker, enabling us to track code coverage |
| 15 | over time. This script is intended to run on the 32-bit Linux slave. |
| 16 | |
| 17 | This script requires an access.token file in the current directory, as |
| 18 | generated by the request_oauth_permission.py script. It also expects a file |
| 19 | customer.secret with a single line containing the customer secret. The |
| 20 | customer secret is an OAuth concept and is received when one registers the |
| 21 | application with the App Engine running the dashboard. |
| 22 | |
| 23 | The script assumes that all coverage data is stored under |
| 24 | /home/<build bot user>/www. |
| 25 | """ |
| 26 | |
| 27 | __author__ = 'phoglund@webrtc.org (Patrik Höglund)' |
| 28 | |
| 29 | import os |
| 30 | import re |
| 31 | import sys |
| 32 | import time |
| 33 | |
| 34 | import constants |
| 35 | import dashboard_connection |
| 36 | |
| 37 | |
| 38 | class FailedToParseCoverageHtml(Exception): |
| 39 | pass |
| 40 | |
| 41 | |
| 42 | class CouldNotFindCoverageDirectory(Exception): |
| 43 | pass |
| 44 | |
| 45 | |
| 46 | def _find_latest_32bit_debug_build(www_directory_contents, coverage_www_dir): |
| 47 | """Finds the latest 32-bit coverage directory in the directory listing. |
| 48 | |
| 49 | Coverage directories have the form Linux32bitDBG_<number>. There may be |
| 50 | other directories in the list though, for instance for other build |
| 51 | configurations. |
| 52 | """ |
| 53 | |
| 54 | # This sort ensures we will encounter the directory with the highest number |
| 55 | # first. |
| 56 | www_directory_contents.sort(reverse=True) |
| 57 | |
| 58 | for entry in www_directory_contents: |
| 59 | match = re.match('Linux32bitDBG_\d+', entry) |
| 60 | if match is not None: |
| 61 | return entry |
| 62 | |
| 63 | raise CouldNotFindCoverageDirectory('Error: Found no 32-bit ' |
| 64 | 'debug build in directory %s.' % |
| 65 | coverage_www_dir) |
| 66 | |
| 67 | |
| 68 | def _grab_coverage_percentage(label, index_html_contents): |
| 69 | """Extracts coverage from a LCOV coverage report. |
| 70 | |
| 71 | Grabs coverage by assuming that the label in the coverage HTML report |
| 72 | is close to the actual number and that the number is followed by a space |
| 73 | and a percentage sign. |
| 74 | """ |
| 75 | match = re.search('<td[^>]*>' + label + '</td>.*?(\d+\.\d) %', |
| 76 | index_html_contents, re.DOTALL) |
| 77 | if match is None: |
| 78 | raise FailedToParseCoverageHtml('Missing coverage at label "%s".' % label) |
| 79 | |
| 80 | try: |
| 81 | return float(match.group(1)) |
| 82 | except ValueError: |
| 83 | raise FailedToParseCoverageHtml('%s is not a float.' % match.group(1)) |
| 84 | |
| 85 | |
| 86 | def _report_coverage_to_dashboard(dashboard, now, line_coverage, |
| 87 | function_coverage): |
| 88 | parameters = {'date': '%d' % now, |
| 89 | 'line_coverage': '%f' % line_coverage, |
| 90 | 'function_coverage': '%f' % function_coverage |
| 91 | } |
| 92 | |
| 93 | response = dashboard.send_post_request(constants.ADD_COVERAGE_DATA_URL, |
| 94 | parameters) |
| 95 | |
| 96 | # The response content should be empty on success, so check that: |
| 97 | response_content = response.read() |
| 98 | if response_content: |
| 99 | message = ('Error: Dashboard reported the following error: %s.' % |
| 100 | response_content) |
| 101 | raise dashboard_connection.FailedToReportToDashboard(message) |
| 102 | |
| 103 | |
| 104 | def _main(): |
| 105 | dashboard = dashboard_connection.DashboardConnection(constants.CONSUMER_KEY) |
| 106 | dashboard.read_required_files(constants.CONSUMER_SECRET_FILE, |
| 107 | constants.ACCESS_TOKEN_FILE) |
| 108 | |
| 109 | coverage_www_dir = os.path.join('/home', constants.BUILD_BOT_USER, 'www') |
| 110 | |
| 111 | www_dir_contents = os.listdir(coverage_www_dir) |
| 112 | latest_build_directory = _find_latest_32bit_debug_build(www_dir_contents, |
| 113 | coverage_www_dir) |
| 114 | |
| 115 | index_html_path = os.path.join(coverage_www_dir, latest_build_directory, |
| 116 | 'index.html') |
| 117 | index_html_file = open(index_html_path) |
| 118 | whole_file = index_html_file.read() |
| 119 | |
| 120 | line_coverage = _grab_coverage_percentage('Lines:', whole_file) |
| 121 | function_coverage = _grab_coverage_percentage('Functions:', whole_file) |
| 122 | now = int(time.time()) |
| 123 | |
| 124 | _report_coverage_to_dashboard(dashboard, now, line_coverage, |
| 125 | function_coverage) |
| 126 | |
| 127 | |
| 128 | if __name__ == '__main__': |
| 129 | _main() |
| 130 | |