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 |
phoglund@webrtc.org | fc40276 | 2012-03-12 09:12:32 +0000 | [diff] [blame^] | 31 | import sys |
phoglund@webrtc.org | d4f0a0e | 2012-02-01 10:59:23 +0000 | [diff] [blame] | 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 | |
phoglund@webrtc.org | fc40276 | 2012-03-12 09:12:32 +0000 | [diff] [blame^] | 46 | def _find_latest_build_coverage(www_directory_contents, coverage_www_dir, |
| 47 | directory_prefix): |
| 48 | """Finds the most recent coverage directory in the directory listing. |
phoglund@webrtc.org | d4f0a0e | 2012-02-01 10:59:23 +0000 | [diff] [blame] | 49 | |
phoglund@webrtc.org | fc40276 | 2012-03-12 09:12:32 +0000 | [diff] [blame^] | 50 | We assume here that build numbers keep rising and never wrap around. |
| 51 | |
| 52 | Args: |
| 53 | www_directory_contents: A list of entries in the coverage directory. |
| 54 | coverage_www_dir: The coverage directory on the bot. |
| 55 | directory_prefix: Coverage directories have the form <prefix><number>, |
| 56 | and the prefix is different on different bots. The prefix is |
| 57 | generally the builder name, such as Linux32DBG. |
| 58 | |
| 59 | Returns: |
| 60 | The most recent directory name. |
| 61 | |
| 62 | Raises: |
| 63 | CouldNotFindCoverageDirectory: if we failed to find coverage data. |
phoglund@webrtc.org | d4f0a0e | 2012-02-01 10:59:23 +0000 | [diff] [blame] | 64 | """ |
| 65 | |
phoglund@webrtc.org | 0f1a96a | 2012-03-01 15:50:30 +0000 | [diff] [blame] | 66 | found_build_numbers = [] |
phoglund@webrtc.org | d4f0a0e | 2012-02-01 10:59:23 +0000 | [diff] [blame] | 67 | for entry in www_directory_contents: |
phoglund@webrtc.org | fc40276 | 2012-03-12 09:12:32 +0000 | [diff] [blame^] | 68 | match = re.match(directory_prefix + '(\d+)', entry) |
phoglund@webrtc.org | d4f0a0e | 2012-02-01 10:59:23 +0000 | [diff] [blame] | 69 | if match is not None: |
phoglund@webrtc.org | 0f1a96a | 2012-03-01 15:50:30 +0000 | [diff] [blame] | 70 | found_build_numbers.append(int(match.group(1))) |
phoglund@webrtc.org | d4f0a0e | 2012-02-01 10:59:23 +0000 | [diff] [blame] | 71 | |
phoglund@webrtc.org | 0f1a96a | 2012-03-01 15:50:30 +0000 | [diff] [blame] | 72 | if not found_build_numbers: |
phoglund@webrtc.org | fc40276 | 2012-03-12 09:12:32 +0000 | [diff] [blame^] | 73 | raise CouldNotFindCoverageDirectory('Error: Found no directories %s* ' |
| 74 | 'in directory %s.' % |
| 75 | (directory_prefix, coverage_www_dir)) |
phoglund@webrtc.org | 0f1a96a | 2012-03-01 15:50:30 +0000 | [diff] [blame] | 76 | |
| 77 | most_recent = max(found_build_numbers) |
phoglund@webrtc.org | fc40276 | 2012-03-12 09:12:32 +0000 | [diff] [blame^] | 78 | return directory_prefix + str(most_recent) |
phoglund@webrtc.org | d4f0a0e | 2012-02-01 10:59:23 +0000 | [diff] [blame] | 79 | |
| 80 | |
| 81 | def _grab_coverage_percentage(label, index_html_contents): |
| 82 | """Extracts coverage from a LCOV coverage report. |
| 83 | |
| 84 | Grabs coverage by assuming that the label in the coverage HTML report |
| 85 | is close to the actual number and that the number is followed by a space |
| 86 | and a percentage sign. |
| 87 | """ |
| 88 | match = re.search('<td[^>]*>' + label + '</td>.*?(\d+\.\d) %', |
| 89 | index_html_contents, re.DOTALL) |
| 90 | if match is None: |
| 91 | raise FailedToParseCoverageHtml('Missing coverage at label "%s".' % label) |
| 92 | |
| 93 | try: |
| 94 | return float(match.group(1)) |
| 95 | except ValueError: |
| 96 | raise FailedToParseCoverageHtml('%s is not a float.' % match.group(1)) |
| 97 | |
| 98 | |
phoglund@webrtc.org | fc40276 | 2012-03-12 09:12:32 +0000 | [diff] [blame^] | 99 | def _report_coverage_to_dashboard(dashboard, line_coverage, function_coverage, |
| 100 | branch_coverage, report_category): |
| 101 | parameters = {'line_coverage': '%f' % line_coverage, |
| 102 | 'function_coverage': '%f' % function_coverage, |
| 103 | 'branch_coverage': '%f' % branch_coverage, |
| 104 | 'report_category': report_category, |
phoglund@webrtc.org | d4f0a0e | 2012-02-01 10:59:23 +0000 | [diff] [blame] | 105 | } |
| 106 | |
phoglund@webrtc.org | 86ce46d | 2012-02-06 10:55:12 +0000 | [diff] [blame] | 107 | dashboard.send_post_request(constants.ADD_COVERAGE_DATA_URL, parameters) |
phoglund@webrtc.org | d4f0a0e | 2012-02-01 10:59:23 +0000 | [diff] [blame] | 108 | |
| 109 | |
phoglund@webrtc.org | fc40276 | 2012-03-12 09:12:32 +0000 | [diff] [blame^] | 110 | def _main(report_category, directory_prefix): |
| 111 | """Grabs coverage data from disk on a bot and publishes it. |
| 112 | |
| 113 | Args: |
| 114 | report_category: The kind of coverage to report. The dashboard |
| 115 | application decides what is acceptable here (see |
| 116 | dashboard/add_coverage_data.py for more information). |
| 117 | directory_prefix: This bot's coverage directory prefix. Generally a bot's |
| 118 | coverage directories will have the form <prefix><build number>, |
| 119 | like Linux32DBG_345. |
| 120 | """ |
phoglund@webrtc.org | d4f0a0e | 2012-02-01 10:59:23 +0000 | [diff] [blame] | 121 | dashboard = dashboard_connection.DashboardConnection(constants.CONSUMER_KEY) |
| 122 | dashboard.read_required_files(constants.CONSUMER_SECRET_FILE, |
| 123 | constants.ACCESS_TOKEN_FILE) |
| 124 | |
phoglund@webrtc.org | 0f1a96a | 2012-03-01 15:50:30 +0000 | [diff] [blame] | 125 | coverage_www_dir = constants.BUILD_BOT_COVERAGE_WWW_DIRECTORY |
| 126 | www_dir_contents = os.listdir(coverage_www_dir) |
phoglund@webrtc.org | fc40276 | 2012-03-12 09:12:32 +0000 | [diff] [blame^] | 127 | latest_build_directory = _find_latest_build_coverage(www_dir_contents, |
| 128 | coverage_www_dir, |
| 129 | directory_prefix) |
phoglund@webrtc.org | d4f0a0e | 2012-02-01 10:59:23 +0000 | [diff] [blame] | 130 | |
| 131 | index_html_path = os.path.join(coverage_www_dir, latest_build_directory, |
| 132 | 'index.html') |
| 133 | index_html_file = open(index_html_path) |
| 134 | whole_file = index_html_file.read() |
| 135 | |
| 136 | line_coverage = _grab_coverage_percentage('Lines:', whole_file) |
| 137 | function_coverage = _grab_coverage_percentage('Functions:', whole_file) |
phoglund@webrtc.org | fc40276 | 2012-03-12 09:12:32 +0000 | [diff] [blame^] | 138 | branch_coverage = _grab_coverage_percentage('Branches:', whole_file) |
phoglund@webrtc.org | d4f0a0e | 2012-02-01 10:59:23 +0000 | [diff] [blame] | 139 | |
phoglund@webrtc.org | fc40276 | 2012-03-12 09:12:32 +0000 | [diff] [blame^] | 140 | _report_coverage_to_dashboard(dashboard, line_coverage, function_coverage, |
| 141 | branch_coverage, report_category) |
| 142 | |
| 143 | |
| 144 | def _parse_args(): |
| 145 | if len(sys.argv) != 3: |
| 146 | print ('Usage: %s <coverage category> <directory prefix>\n\n' |
| 147 | 'The coverage category describes the kind of coverage you are ' |
| 148 | 'uploading. Known acceptable values are small_medium_tests and' |
| 149 | 'large_tests. The directory prefix is what the directories in %s ' |
| 150 | 'are prefixed on this bot (such as Linux32DBG_).' % |
| 151 | (sys.argv[0], constants.BUILD_BOT_COVERAGE_WWW_DIRECTORY)) |
| 152 | return (None, None) |
| 153 | return (sys.argv[1], sys.argv[2]) |
phoglund@webrtc.org | d4f0a0e | 2012-02-01 10:59:23 +0000 | [diff] [blame] | 154 | |
| 155 | |
| 156 | if __name__ == '__main__': |
phoglund@webrtc.org | fc40276 | 2012-03-12 09:12:32 +0000 | [diff] [blame^] | 157 | report_category, directory_prefix = _parse_args() |
| 158 | if report_category: |
| 159 | _main(report_category, directory_prefix) |
phoglund@webrtc.org | d4f0a0e | 2012-02-01 10:59:23 +0000 | [diff] [blame] | 160 | |