Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 41 additions & 11 deletions testrunner/app/controllers/testrunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"html/template"
"reflect"
"sort"
"strings"

"github.com/revel/revel"
Expand Down Expand Up @@ -191,21 +192,40 @@ func describeSuite(testSuite interface{}) TestSuiteDesc {

// errorSummary gets an error and returns its summary in human readable format.
func errorSummary(err *revel.Error) (message string) {
message = fmt.Sprintf("%4sStatus: %s\n%4sIn %s", "", err.Description, "", err.Path)
expected_prefix := "(expected)"
actual_prefix := "(actual)"
errDesc := err.Description
//strip the actual/expected stuff to provide more condensed display.
if strings.Index(errDesc, expected_prefix) == 0 {
errDesc = errDesc[len(expected_prefix):len(errDesc)]
}
if strings.LastIndex(errDesc, actual_prefix) > 0 {
errDesc = errDesc[0 : len(errDesc)-len(actual_prefix)]
}

// If line of error isn't known return the message as is.
if err.Line == 0 {
return
errFile := err.Path
slashIdx := strings.LastIndex(errFile, "/")
if slashIdx > 0 {
errFile = errFile[slashIdx+1 : len(errFile)]
}

// Otherwise, include info about the line number and the relevant
// source code lines.
message += fmt.Sprintf(" (around line %d): ", err.Line)
for _, line := range err.ContextSource() {
if line.IsError {
message += line.Source
message = fmt.Sprintf("%s %s#%d", errDesc, errFile, err.Line)

/*
// If line of error isn't known return the message as is.
if err.Line == 0 {
return
}
}

// Otherwise, include info about the line number and the relevant
// source code lines.
message += fmt.Sprintf(" (around line %d): ", err.Line)
for _, line := range err.ContextSource() {
if line.IsError {
message += line.Source
}
}
*/

return
}
Expand Down Expand Up @@ -234,13 +254,23 @@ func formatResponse(t testing.TestSuite) map[string]string {
}
}

//sortbySuiteName sorts the testsuites by name.
type sortBySuiteName []interface{}

func (a sortBySuiteName) Len() int { return len(a) }
func (a sortBySuiteName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a sortBySuiteName) Less(i, j int) bool {
return reflect.TypeOf(a[i]).Elem().Name() < reflect.TypeOf(a[j]).Elem().Name()
}

func init() {
// Every time app is restarted convert the list of available test suites
// provided by the revel testing package into a format which will be used by
// the testrunner module and revel test cmd.
revel.OnAppStart(func() {
// Extracting info about available test suites from revel/testing package.
registeredTests = map[string]int{}
sort.Sort(sortBySuiteName(testing.TestSuites))
for _, testSuite := range testing.TestSuites {
testSuites = append(testSuites, describeSuite(testSuite))
}
Expand Down
309 changes: 202 additions & 107 deletions testrunner/app/views/TestRunner/Index.html
Original file line number Diff line number Diff line change
@@ -1,110 +1,205 @@
<!DOCTYPE html>
<html>
<head>
<title>Revel Test Runner</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link href="{{url `Root`}}/@tests/public/css/bootstrap.min.css" type="text/css" rel="stylesheet"></link>
<link href="{{url `Root`}}/@tests/public/css/github.css" type="text/css" rel="stylesheet"></link>
<script src="{{url `Root`}}/@tests/public/js/jquery-1.9.1.min.js" type="text/javascript"></script>
<script src="{{url `Root`}}/@tests/public/js/bootstrap.min.js" type="text/javascript"></script>
<script src="{{url `Root`}}/@tests/public/js/highlight.pack.js" type="text/javascript"></script>
<style>
header { padding:20px 0; background-color:#ADD8E6 }
.passed td { background-color: #90EE90 !important; }
.failed td { background-color: #FFB6C1 !important; }
.tests td.name, .tests td.result { padding-top: 13px; }
pre { font-size:10px; white-space: pre; }
.name { width: 25%; }
.w100 { width: 100%; }
</style>
</head>
<body>
<header>
<div class="container">
<table class="w100"><tr><td>
<h1>Test Runner</h1>
<p class="lead">Run all of your application's tests from here.</p>
</td><td style="padding-left:150px;" class="text-right">
<button class="btn btn-lg btn-success" all-tests="">Run All Tests</button>
</td></tr></table>
</div>
</header>

<div class="container">
{{range .testSuites}}
<p class="lead" style="margin-top:20px;">{{.Name}}</p>
<table class="table table-striped tests" suite="{{.Name}}">
{{range .Tests}}
<tr>
<td class="name">{{.Name}}</td>
<td class="result">
</td>
<td class="text-right"><button test="{{.Name}}" class="btn btn-success">Run</button></td>
</tr>
{{end}}
</table>
{{end}}
</div>

<script>
var buttons = [];
var running;

$("button[test]").click(function() {
var button = $(this).addClass("disabled").text("Running");
addToQueue(button);
});

$("button[all-tests]").click(function() {
var button = $(this).addClass("disabled").text("Running");
$("button[test]").click();
});

function addToQueue(button) {
buttons.push(button);
if (!running) {
running = true;
nextTest();
}
}

function nextTest() {
if (buttons.length == 0) {
running = false;
} else {
var next = buttons.shift();
runTest(next);
}
}

function runTest(button) {
var suite = button.parents("table").attr("suite");
var test = button.attr("test");
var row = button.parents("tr");
var resultCell = row.children(".result");
$.ajax({
dataType: "json",
url: "{{url `Root`}}/@tests/"+suite+"/"+test,
success: function(result) {
row.attr("class", result.Passed ? "passed" : "failed");
if (result.Passed) {
resultCell.html("");
} else {
resultCell.html(result.ErrorHTML);

$("#result_" + suite + "_" + test + " pre code").each(function(i, block) {
hljs.highlightBlock(block);
});
}
button.removeClass("disabled").text("Run");
if (buttons.length == 0) {
$("button[all-tests]").removeClass("disabled").text("Run All Tests");
}
nextTest();
}
});
}
</script>

</body>
<head>
<title>Revel Test Runner</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link href="{{url `Root`}}/@tests/public/css/bootstrap.min.css" type="text/css" rel="stylesheet"></link>
<link href="{{url `Root`}}/@tests/public/css/github.css" type="text/css" rel="stylesheet"></link>
<script src="{{url `Root`}}/@tests/public/js/jquery-1.9.1.min.js" type="text/javascript"></script>
<script src="{{url `Root`}}/@tests/public/js/bootstrap.min.js" type="text/javascript"></script>
<script src="{{url `Root`}}/@tests/public/js/highlight.pack.js" type="text/javascript"></script>
<style>
header { background-color:#ADD8E6 }
header h1 {margin-top: 10px; margin-bottom:20px;}
header table {margin-bottom: 0px }
td .btn {margin-bottom: 1px; }
button.file-test { margin-bottom: 0px; margin-left: 2px }
table.tests tr { border-bottom: 1px solid #ddd; background-color: #f9f9f9; }
.table > tbody > tr > td { padding-top:1px; padding-bottom:2px; vertical-align: middle; }
.passed td { background-color: #90EE90 !important; }
.failed td { background-color: #FFB6C1 !important; }
td.result div.panel-default{ display:none; }
td.result > a { color: red; }
td.rightCol { width: 40px; }
pre { font-size:10px; white-space: pre; }
.panel-heading {
padding: 2px 2px
}
.panel-heading a:after {
margin-top:4px;
margin-right:4px;
content:"\25bc";
float: right;
color: grey;
}
.panel-heading a.collapsed:after {
content:"\25b6";
}
.name { width: 35%; }
.w100 { width: 100%; }
</style>
</head>
<body>
<header>
<div class="container">
<h1 class="pull-left">Test Runner <small>- Run your unit tests here.</small> </h1>
<div style="margin-top:16px" class="pull-right">
<button class="btn btn-success" all-tests="">Run All Tests</button>
<div><a class="small" href="#" id="allTestResults"></a></div>
</div>
</div>
</header>
<div class="panel-group container">
{{range .testSuites}}
{{ $testFile := .Name }}
<div style="margin-top:10px;" class="panel panel-default">
<div class="panel-heading">
<button class="btn btn-xs btn-success file-test" test-file="{{.Name}}">Run</button>
<span class="h5">&nbsp;<a id="link-{{ $testFile }}" class="collapseLnk" data-toggle="collapse" data-target="#{{.Name}}" href="#">{{.Name}}</a></span>
</div>
<div id="{{.Name}}" class="panel-collapse collapse in">
<table class="panel-body table table-condensed tests" suite="{{.Name}}">
{{range .Tests}}
<tr>
<td class="name"><button data-test-file="{{$testFile}}" test="{{.Name}}" class="leftbutton btn btn-success btn-xs">Run</button>&nbsp;&nbsp;{{ .Name }}</td>
<td class="result"><a href="#"></a></td>
<td class="rightCol"><button data-test-file="{{$testFile}}" test="{{.Name}}" class="pull-right btn btn-success btn-xs">Run</button></td>
</tr>
{{end}}
</table>
</div>
</div>
{{end}}
</div>

<script>
var passCount = 0;
var failCount = 0;
var buttons = [];
var running;

$(function() {
var divId, visible;
//close any panels that were previously closed.
$("div.panel-collapse").each(function() {
divId = "#" + $(this).attr("id");
visible = localStorage.getItem("testrunner_" + divId);
if (visible == "false") {
$(divId).css("height", "0").removeClass("in"); //this is the way bootstrap does it.
$("a[data-target=" + divId + "]").addClass("collapsed");
}
});
});

$(".fileTestLnk").click(function() {
var tableId = $(this).attr("href");
$(tableId).toggle();
return false;
});

$("#allTestResults").click(function() {
var badRows = $("tr.failed");
if (badRows.length >= 0) {
badRows[0].scrollIntoView();
}

return false;
});

$("button[test]").click(function() {
$("#allTestResults").text("");
var button = $(this).addClass("disabled").text("Running");
$(this).closest("tr").removeClass("passed").removeClass("failed");
addToQueue(button);
});

$("td.result a").click(function() { //show/hide the extended error div
$(this).siblings().toggle();
return false;
});

$("button[test-file]").click(function() {
$("#allTestResults").text("");
var testfile = $(this).attr('test-file');
$("button").each(function() {
if ($(this).data("test-file") == testfile)
$(this).click();
});
});

$("button[all-tests]").click(function() {
$("tr").removeClass("passed").removeClass("failed");
passCount = 0;
failCount = 0;
var button = $(this).addClass("disabled").text("Running");
$("button.leftbutton[test]").click();
});

$("a.collapseLnk").click(function() {
var tableId = $(this).data("target");
var visible = !$(tableId).is(":visible");
localStorage.setItem("testrunner_" + tableId, visible);
});

function addToQueue(button) {
buttons.push(button);
if (!running) {
running = true;
nextTest();
}
}

function nextTest() {
if (buttons.length == 0) {
running = false;
} else {
var next = buttons.shift();
runTest(next);
}
}

function runTest(button) {
var suite = button.parents("table").attr("suite");
var test = button.attr("test");
var row = button.parents("tr");
var resultCell = row.children(".result");
$("a", resultCell).text("");
$("div.panel-default", resultCell).remove();
$.ajax({
dataType: "json",
url: "{{url `Root`}}/@tests/"+suite+"/"+test,
success: function(result) {
row.attr("class", result.Passed ? "passed" : "failed");
if (result.Passed) {
passCount++;
} else {
console.log("fail: result.Name");
failCount++;
var resultLnk = $("a", resultCell);
$(resultLnk).text(result.ErrorSummary);
resultCell.append(result.ErrorHTML);

var pnlDiv = row.closest("div");
if ($(pnlDiv).hasClass("in") == false) {
$("#link-" + suite).click();
}

$("#result_" + suite + "_" + test + " pre code").each(function(i, block) {
hljs.highlightBlock(block);
});
}
button.removeClass("disabled").text("Run");
var runAllBut = $("button[all-tests]");
if (buttons.length == 0 && runAllBut.hasClass("disabled")) {
runAllBut.removeClass("disabled").text("Run All Tests");
var resMsg = passCount + " passed, " + failCount + " failed.";
$("#allTestResults").text(resMsg);
}
nextTest();
}
});
}
</script>

</body>
</html>