Initial version of code coverage tracker / dashboard for WebRTC.
BUG= TEST= Review URL: https://webrtc-codereview.appspot.com/350001 git-svn-id: http://webrtc.googlecode.com/svn/trunk@1440 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
parent
2877bdc590
commit
bd88f39767
1
.gitignore
vendored
1
.gitignore
vendored
@ -40,6 +40,7 @@
|
||||
/third_party/cygwin
|
||||
/third_party/expat
|
||||
/third_party/google-gflags/src
|
||||
/third_party/google-visualization-python
|
||||
/third_party/jsoncpp
|
||||
/third_party/libjingle
|
||||
/third_party/libjpeg
|
||||
|
3
DEPS
3
DEPS
@ -82,6 +82,9 @@ deps = {
|
||||
|
||||
"trunk/third_party/libyuv":
|
||||
(Var("googlecode_url") % "libyuv") + "/trunk@121",
|
||||
|
||||
"trunk/third_party/google-visualization-python":
|
||||
(Var("googlecode_url") % "google-visualization-python") + "/trunk@15",
|
||||
}
|
||||
|
||||
deps_os = {
|
||||
|
2
tools/coverage/OWNERS
Normal file
2
tools/coverage/OWNERS
Normal file
@ -0,0 +1,2 @@
|
||||
phoglund@webrtc.org
|
||||
kjellander@webrtc.org
|
23
tools/coverage/README
Normal file
23
tools/coverage/README
Normal file
@ -0,0 +1,23 @@
|
||||
This file describes the coverage tracking script and the coverage dashboard.
|
||||
|
||||
ABSTRACT:
|
||||
The intention of this small tracking system is to track code coverage data
|
||||
over time. Since code coverage is continuously recomputed on the build bots,
|
||||
the track_coverage.py script is intended to run on the build bot as a cron job
|
||||
and extract the data from there. The dashboard doesn't care how often this
|
||||
script runs, but running each hour should be more than enough.
|
||||
|
||||
The track_coverage.py script communicates with the dashboard using plain GET
|
||||
requests (that, and POST, are basically the only way to get data into a
|
||||
appengine application such as the dashboard). The dashboard is intented to
|
||||
run on the Google appengine.
|
||||
|
||||
HOW TO RUN LOCALLY:
|
||||
Follow the following instructions:
|
||||
http://code.google.com/appengine/docs/python/gettingstartedpython27/devenvironment.html
|
||||
The dashboard can be started on 127.0.0.1:8080 using the dev_appserver.py script
|
||||
as described in the above URL (and in the following 'hello world' page).
|
||||
|
||||
HOW TO DEPLOY:
|
||||
Follow the following instructions:
|
||||
http://code.google.com/appengine/docs/python/gettingstartedpython27/uploading.html
|
9
tools/coverage/dashboard/app.yaml
Normal file
9
tools/coverage/dashboard/app.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
application: dashboard
|
||||
version: 1
|
||||
runtime: python27
|
||||
api_version: 1
|
||||
threadsafe: true
|
||||
|
||||
handlers:
|
||||
- url: /.*
|
||||
script: dashboard.app
|
101
tools/coverage/dashboard/dashboard.py
Normal file
101
tools/coverage/dashboard/dashboard.py
Normal file
@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python
|
||||
# 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.
|
||||
|
||||
"""Implements the coverage tracker dashboard and reporting facilities."""
|
||||
|
||||
__author__ = 'phoglund@webrtc.org (Patrik Hoglund)'
|
||||
|
||||
import datetime
|
||||
from google.appengine.ext import db
|
||||
import webapp2
|
||||
import gviz_api
|
||||
|
||||
|
||||
class CoverageData(db.Model):
|
||||
"""This represents one coverage report from the build bot."""
|
||||
date = db.DateTimeProperty(required=True)
|
||||
line_coverage = db.FloatProperty(required=True)
|
||||
function_coverage = db.FloatProperty(required=True)
|
||||
|
||||
|
||||
class ShowDashboard(webapp2.RequestHandler):
|
||||
"""Shows the dashboard page.
|
||||
|
||||
The page is shown by grabbing data we have stored previously
|
||||
in the App Engine database using the AddCoverageData handler.
|
||||
"""
|
||||
|
||||
def get(self):
|
||||
page_template_filename = 'templates/dashboard_template.html'
|
||||
|
||||
# Load the page HTML template.
|
||||
try:
|
||||
template_file = open(page_template_filename)
|
||||
page_template = template_file.read()
|
||||
template_file.close()
|
||||
except IOError as exception:
|
||||
self.ShowErrorPage('Cannot open page template file: %s<br>Details: %s' %
|
||||
(page_template_filename, exception))
|
||||
return
|
||||
|
||||
coverage_entries = db.GqlQuery('SELECT * '
|
||||
'FROM CoverageData '
|
||||
'ORDER BY date ASC')
|
||||
data = []
|
||||
for coverage_entry in coverage_entries:
|
||||
data.append({'date': coverage_entry.date,
|
||||
'line_coverage': coverage_entry.line_coverage,
|
||||
'function_coverage': coverage_entry.function_coverage,
|
||||
})
|
||||
|
||||
description = {
|
||||
'date': ('datetime', 'Date'),
|
||||
'line_coverage': ('number', 'Line Coverage'),
|
||||
'function_coverage': ('number', 'Function Coverage')
|
||||
}
|
||||
coverage_data = gviz_api.DataTable(description, data)
|
||||
coverage_json_data = coverage_data.ToJSon(order_by='date')
|
||||
|
||||
# Fill in the template with the data and respond:
|
||||
self.response.write(page_template % vars())
|
||||
|
||||
def ShowErrorPage(self, error_message):
|
||||
self.response.write('<html><body>%s</body></html>' % error_message)
|
||||
|
||||
|
||||
class AddCoverageData(webapp2.RequestHandler):
|
||||
"""Used to report coverage data.
|
||||
|
||||
It will verify the data, but not the sender. Thus, it should be secured
|
||||
more properly if accessible from an outside network.
|
||||
"""
|
||||
|
||||
def get(self):
|
||||
try:
|
||||
posix_time = int(self.request.get('date'))
|
||||
parsed_date = datetime.datetime.fromtimestamp(posix_time)
|
||||
|
||||
line_coverage = float(self.request.get('line_coverage'))
|
||||
function_coverage = float(self.request.get('function_coverage'))
|
||||
except ValueError as exception:
|
||||
self.ShowErrorPage('Invalid parameter in request. Details: %s' %
|
||||
exception)
|
||||
return
|
||||
|
||||
item = CoverageData(date=parsed_date,
|
||||
line_coverage=line_coverage,
|
||||
function_coverage=function_coverage)
|
||||
item.put()
|
||||
|
||||
def ShowErrorPage(self, error_message):
|
||||
self.response.write('<html><body>%s</body></html>' % error_message)
|
||||
|
||||
app = webapp2.WSGIApplication([('/', ShowDashboard),
|
||||
('/add_coverage_data', AddCoverageData)],
|
||||
debug=True)
|
1
tools/coverage/dashboard/gviz_api.py
Symbolic link
1
tools/coverage/dashboard/gviz_api.py
Symbolic link
@ -0,0 +1 @@
|
||||
../../../third_party/google-visualization-python/gviz_api.py
|
47
tools/coverage/dashboard/templates/dashboard_template.html
Normal file
47
tools/coverage/dashboard/templates/dashboard_template.html
Normal file
@ -0,0 +1,47 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
|
||||
"http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<!--
|
||||
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.
|
||||
|
||||
Template file to be used to generate the WebRTC dashboard.
|
||||
-->
|
||||
<head>
|
||||
<title>WebRTC Coverage Dashboard</title>
|
||||
<link href="http://code.google.com/css/codesite.pack.04102009.css"
|
||||
rel="stylesheet" type="text/css">
|
||||
|
||||
<script src="https://www.google.com/jsapi" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
google.load('visualization', '1', {packages:['table', 'corechart']});
|
||||
|
||||
google.setOnLoadCallback(drawTable);
|
||||
function drawTable() {
|
||||
/* Build data tables and views */
|
||||
var coverage_data_table =
|
||||
new google.visualization.DataTable(%(coverage_json_data)s);
|
||||
|
||||
/* Display tables and charts */
|
||||
var coverage_chart = new google.visualization.LineChart(
|
||||
document.getElementById('table_div_coverage'));
|
||||
coverage_chart.draw(coverage_data_table, {
|
||||
colors: ['blue', 'red'],
|
||||
vAxis: {title: 'Coverage'},
|
||||
hAxis: {title: 'Date'},
|
||||
width: 1200, height: 300,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>WebRTC Dashboard</h1>
|
||||
<h3>Coverage:</h3>
|
||||
<div id="table_div_coverage"></div>
|
||||
</body>
|
||||
</html>
|
121
tools/coverage/track_coverage.py
Executable file
121
tools/coverage/track_coverage.py
Executable file
@ -0,0 +1,121 @@
|
||||
#!/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.
|
||||
"""
|
||||
|
||||
__author__ = 'phoglund@webrtc.org (Patrik Höglund)'
|
||||
|
||||
import httplib
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
|
||||
# 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'
|
||||
|
||||
|
||||
class FailedToParseCoverageHtml(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class FailedToReportToDashboard(Exception):
|
||||
pass
|
||||
|
||||
|
||||
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):
|
||||
request_string = ('/add_coverage_data?'
|
||||
'date=%d&line_coverage=%f&function_coverage=%f' %
|
||||
(now, line_coverage, function_coverage))
|
||||
|
||||
connection = httplib.HTTPConnection(DASHBOARD_SERVER)
|
||||
connection.request('GET', request_string)
|
||||
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():
|
||||
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)
|
||||
|
||||
if __name__ == '__main__':
|
||||
_main()
|
||||
|
File diff suppressed because it is too large
Load Diff
1
tools/python_charts/gviz_api.py
Symbolic link
1
tools/python_charts/gviz_api.py
Symbolic link
@ -0,0 +1 @@
|
||||
../../third_party/google-visualization-python/gviz_api.py
|
Loading…
x
Reference in New Issue
Block a user