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