go vendor

This commit is contained in:
lealife
2017-11-30 19:55:33 +08:00
parent 2856da6888
commit 0fb92efbf3
670 changed files with 199010 additions and 0 deletions

View File

@ -0,0 +1,327 @@
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package controllers
import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"reflect"
"sort"
"strings"
"github.com/revel/revel"
"github.com/revel/revel/testing"
)
// TestRunner is a controller which is used for running application tests in browser.
type TestRunner struct {
*revel.Controller
}
// TestSuiteDesc is used for storing information about a single test suite.
// This structure is required by revel test cmd.
type TestSuiteDesc struct {
Name string
Tests []TestDesc
// Elem is reflect.Type which can be used for accessing methods
// of the test suite.
Elem reflect.Type
}
// TestDesc is used for describing a single test of some test suite.
// This structure is required by revel test cmd.
type TestDesc struct {
Name string
}
// TestSuiteResult stores the results the whole test suite.
// This structure is required by revel test cmd.
type TestSuiteResult struct {
Name string
Passed bool
Results []TestResult
}
// TestResult represents the results of running a single test of some test suite.
// This structure is required by revel test cmd.
type TestResult struct {
Name string
Passed bool
ErrorHTML template.HTML
ErrorSummary string
}
var (
testSuites []TestSuiteDesc // A list of all available tests.
none = []reflect.Value{} // It is used as input for reflect call in a few places.
// registeredTests simplifies the search of test suites by their name.
// "TestSuite.TestName" is used as a key. Value represents index in testSuites.
registeredTests map[string]int
)
/*
Controller's action methods are below.
*/
// Index is an action which renders the full list of available test suites and their tests.
func (c TestRunner) Index() revel.Result {
c.ViewArgs["suiteFound"] = len(testSuites) > 0
return c.Render(testSuites)
}
// Suite method allows user to navigate to individual Test Suite and their tests
func (c TestRunner) Suite(suite string) revel.Result {
var foundTestSuites []TestSuiteDesc
for _, testSuite := range testSuites {
if strings.EqualFold(testSuite.Name, suite) {
foundTestSuites = append(foundTestSuites, testSuite)
}
}
c.ViewArgs["testSuites"] = foundTestSuites
c.ViewArgs["suiteFound"] = len(foundTestSuites) > 0
c.ViewArgs["suiteName"] = suite
return c.RenderTemplate("TestRunner/Index.html")
}
// Run runs a single test, given by the argument.
func (c TestRunner) Run(suite, test string) revel.Result {
// Check whether requested test exists.
suiteIndex, ok := registeredTests[suite+"."+test]
if !ok {
return c.NotFound("Test %s.%s does not exist", suite, test)
}
result := TestResult{Name: test}
// Found the suite, create a new instance and run the named method.
t := testSuites[suiteIndex].Elem
v := reflect.New(t)
func() {
// When the function stops executing try to recover from panic.
defer func() {
if err := recover(); err != nil {
// If panic error is empty, exit.
panicErr := revel.NewErrorFromPanic(err)
if panicErr == nil {
return
}
// Otherwise, prepare and format the response of server if possible.
testSuite := v.Elem().FieldByName("TestSuite").Interface().(testing.TestSuite)
res := formatResponse(testSuite)
// Render the error and save to the result structure.
var buffer bytes.Buffer
tmpl, _ := revel.MainTemplateLoader.TemplateLang("TestRunner/FailureDetail.html", "")
_ = tmpl.Render(&buffer, map[string]interface{}{
"error": panicErr,
"response": res,
"postfix": suite + "_" + test,
})
result.ErrorSummary = errorSummary(panicErr)
result.ErrorHTML = template.HTML(buffer.String())
}
}()
// Initialize the test suite with a NewTestSuite()
testSuiteInstance := v.Elem().FieldByName("TestSuite")
testSuiteInstance.Set(reflect.ValueOf(testing.NewTestSuite()))
// Make sure After method will be executed at the end.
if m := v.MethodByName("After"); m.IsValid() {
defer m.Call(none)
}
// Start from running Before method of test suite if exists.
if m := v.MethodByName("Before"); m.IsValid() {
m.Call(none)
}
// Start the test method itself.
v.MethodByName(test).Call(none)
// No panic means success.
result.Passed = true
}()
return c.RenderJSON(result)
}
// List returns a JSON list of test suites and tests.
// It is used by revel test command line tool.
func (c TestRunner) List() revel.Result {
return c.RenderJSON(testSuites)
}
/*
Below are helper functions.
*/
// describeSuite expects testsuite interface as input parameter
// and returns its description in a form of TestSuiteDesc structure.
func describeSuite(testSuite interface{}) TestSuiteDesc {
t := reflect.TypeOf(testSuite)
// Get a list of methods of the embedded test type.
// It will be used to make sure the same tests are not included in multiple test suites.
super := t.Elem().Field(0).Type
superMethods := map[string]bool{}
for i := 0; i < super.NumMethod(); i++ {
// Save the current method's name.
superMethods[super.Method(i).Name] = true
}
// Get a list of methods on the test suite that take no parameters, return
// no results, and were not part of the embedded type's method set.
var tests []TestDesc
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
mt := m.Type
// Make sure the test method meets the criterias:
// - method of testSuite without input parameters;
// - nothing is returned;
// - has "Test" prefix;
// - doesn't belong to the embedded structure.
methodWithoutParams := (mt.NumIn() == 1 && mt.In(0) == t)
nothingReturned := (mt.NumOut() == 0)
hasTestPrefix := (strings.HasPrefix(m.Name, "Test"))
if methodWithoutParams && nothingReturned && hasTestPrefix && !superMethods[m.Name] {
// Register the test suite's index so we can quickly find it by test's name later.
registeredTests[t.Elem().Name()+"."+m.Name] = len(testSuites)
// Add test to the list of tests.
tests = append(tests, TestDesc{m.Name})
}
}
return TestSuiteDesc{
Name: t.Elem().Name(),
Tests: tests,
Elem: t.Elem(),
}
}
// errorSummary gets an error and returns its summary in human readable format.
func errorSummary(err *revel.Error) (message string) {
expectedPrefix := "(expected)"
actualPrefix := "(actual)"
errDesc := err.Description
//strip the actual/expected stuff to provide more condensed display.
if strings.Index(errDesc, expectedPrefix) == 0 {
errDesc = errDesc[len(expectedPrefix):]
}
if strings.LastIndex(errDesc, actualPrefix) > 0 {
errDesc = errDesc[0 : len(errDesc)-len(actualPrefix)]
}
errFile := err.Path
slashIdx := strings.LastIndex(errFile, "/")
if slashIdx > 0 {
errFile = errFile[slashIdx+1:]
}
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
}
// formatResponse gets *revel.TestSuite as input parameter and
// transform response related info into a readable format.
func formatResponse(t testing.TestSuite) map[string]string {
if t.Response == nil {
return map[string]string{}
}
// Since Go 1.6 http.Request struct contains `Cancel <-chan struct{}` which
// results in `json: unsupported type: <-chan struct {}`
// So pull out required things for Request and Response
req := map[string]interface{}{
"Method": t.Response.Request.Method,
"URL": t.Response.Request.URL,
"Proto": t.Response.Request.Proto,
"ContentLength": t.Response.Request.ContentLength,
"Header": t.Response.Request.Header,
"Form": t.Response.Request.Form,
"PostForm": t.Response.Request.PostForm,
}
resp := map[string]interface{}{
"Status": t.Response.Status,
"StatusCode": t.Response.StatusCode,
"Proto": t.Response.Proto,
"Header": t.Response.Header,
"ContentLength": t.Response.ContentLength,
"TransferEncoding": t.Response.TransferEncoding,
}
// Beautify the response JSON to make it human readable.
respBytes, err := json.MarshalIndent(
map[string]interface{}{
"Response": resp,
"Request": req,
},
"",
" ")
if err != nil {
fmt.Println(err)
}
// Remove extra new line symbols so they do not take too much space on a result page.
// Allow no more than 1 line break at a time.
body := strings.Replace(string(t.ResponseBody), "\n\n", "\n", -1)
body = strings.Replace(body, "\r\n\r\n", "\r\n", -1)
return map[string]string{
"Headers": string(respBytes),
"Body": strings.TrimSpace(body),
}
}
//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))
}
})
}

View File

@ -0,0 +1,11 @@
package app
import (
"github.com/revel/revel"
)
func init() {
revel.OnAppStart(func() {
revel.AppLog.Info("Go to /@tests to run the tests.")
})
}

View File

@ -0,0 +1,45 @@
<div class="panel panel-default">
<div class="panel-heading">
<b>{{.error.Description}}</b>
</div>
<div class="panel-body">
<ul class="nav nav-tabs" role="tablist">
<li class="active"><a href="#error_{{.postfix}}" role="tab" data-toggle="tab">Error</a></li>
<li><a href="#stack_{{.postfix}}" role="tab" data-toggle="tab">Stack</a></li>
{{if .response}}
<li><a href="#headers_{{.postfix}}" role="tab" data-toggle="tab">Headers</a></li>
<li><a href="#body_{{.postfix}}" role="tab" data-toggle="tab">Response Body</a></li>
{{end}}
</ul>
<div class="tab-content" id="result_{{.postfix}}">
<div class="tab-pane active" id="error_{{.postfix}}">
<div class="panel panel-danger">
<div class="panel-heading">
In {{.error.Path}}{{if .error.Line}} (around {{if .error.Line}}line {{.error.Line}}{{end}}{{if .error.Column}} column {{.error.Column}}{{end}}){{end}}:
</div>
<div class="panel-body">
{{range .error.ContextSource}}
{{if .IsError}}
<pre><code class="go">{{.Source}}</code></pre>
{{end}}
{{end}}
</div>
</div>
</div>
<div class="tab-pane" id="stack_{{.postfix}}">
<pre><code class="bash">{{.error.Stack}}</code></pre>
</div>
{{if .response}}
<div class="tab-pane" id="headers_{{.postfix}}">
<pre><code class="json">{{.response.Headers}}</code></pre>
</div>
<div class="tab-pane" id="body_{{.postfix}}">
<pre><code class="html">{{.response.Body}}</code></pre>
</div>
{{end}}
</div>
</div>
</div>

View File

@ -0,0 +1,232 @@
<!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 { 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; }
.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, td.leftCol { width: 40px; }
pre { font-size:10px; white-space: pre; }
.panel-heading {
padding: 10px 5px 8px 5px
}
.name { width: 35%; }
.w100 { width: 100%; }
.logo, .logo:hover { text-decoration: none; color: inherit;}
.pnt-triangle { color: #777; font-size: 18px;}
.panel-group .panel-heading+.panel-collapse>.panel-body { border-top: none;}
</style>
</head>
<body>
<header>
<div class="container">
<h1 class="pull-left">
<a href="/@tests" class="logo">Test Runner</a> <small>- Run your application's tests here.</small>
</h1>
<div style="margin-top:16px" class="pull-right">
<button class="btn btn-success {{if not .suiteFound}}disabled{{end}}" all-tests="">Run All Tests</button>
<div><a class="small" href="#" id="allTestResults"></a></div>
</div>
</div>
</header>
<div class="panel-group container">
{{if not .suiteFound}}
<div style="margin-top:20px;padding:15px;" class="panel panel-default">
<span style="font-weight:bold;color:#777;">Suite "{{.suiteName}}" is not found.</span>
</div>
{{end}}
{{range .testSuites}}
{{ $testFile := .Name }}
<div style="margin-top:20px;" class="panel panel-default">
<div class="panel-heading collapseLnk" style="cursor:pointer" data-toggle="collapse" data-target="#{{.Name}}">
<button id="suite{{.Name}}" class="btn btn-xs btn-success" test-file="{{.Name}}">Run</button>
<span class="h5">&nbsp;<a href="/@tests/{{ .Name }}">{{.Name}}</a></span>
<span id="pointTriangle{{.Name}}" class="pnt-triangle pull-right" data-tri-open="open">&#9660</span>
</div>
<div id="{{.Name}}" class="panel-collapse collapse in">
<table class="panel-body table table-condensed tests" suite="{{.Name}}">
{{range .Tests}}
<tr id="testRow{{.Name}}">
<td class="leftCol"><button data-test-file="{{$testFile}}" test="{{.Name}}" class="leftbutton btn btn-success btn-xs">Run</button></td>
<td class="name">{{ .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;
var oneOpened = false;
var panels = $("div.panel-collapse");
//close any panels that were previously closed.
panels.each(function() {
divId = "#" + $(this).attr("id");
visible = parseBoolean(localStorage.getItem("testrunner_" + divId));
if (visible) {
togglePntTriangle(divId, visible);
oneOpened = visible;
} else {
$(divId).css("height", "0").removeClass("in"); //this is the way bootstrap does it.
$("div[data-target=" + divId + "]").addClass("collapsed");
togglePntTriangle(divId, visible);
}
});
if (!oneOpened && panels.length > 0) {
divId = "#" + panels[0].id;
$(divId).collapse('show');
togglePntTriangle(divId, true);
}
});
$(".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();
});
return false;
});
$("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();
});
$("div.collapseLnk").click(function() {
var tableId = $(this).data("target");
var visible = !$(tableId).is(":visible");
localStorage.setItem("testrunner_" + tableId, visible);
togglePntTriangle(tableId, visible);
});
function togglePntTriangle(suiteId, visible) {
if (suiteId.charAt(0) == '#') {
suiteId = suiteId.substring(1);
}
$('#pointTriangle' + suiteId).html(visible ? '&#9660' : '&#9654');
}
function parseBoolean(str) {
return /^true$/i.test(str);
}
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>

View File

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<title>Revel Test Runner</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style>
body {
margin: 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 20px;
color: #333333;
background-color: #ffffff;
}
header { padding:20px 0; }
header.passed { background-color: #90EE90 !important; }
header.failed { background-color: #FFB6C1 !important; }
table { margin-top: 20px; padding: 8px; line-height: 20px; }
td { vertical-align: top; padding-right:20px; }
a { color: #0088cc; }
.container { margin-left: auto; margin-right: auto; width: 940px; overflow: hidden; }
.result h2 { font-size: 16px; border-bottom: 1px solid #f0f0f0; padding-bottom: 0.2em; }
.result.failed b { font-weight:bold; color: #C00; font-size: 14px; }
.result.failed h2 { color: #C00; }
.result .info { font-size: 12px; }
.result .info pre { overflow: auto; background-color: #f0f0f0; width: 100%; max-height: 500px; }
</style>
</head>
<body>
<header class="{{if .Passed}}passed{{else}}failed{{end}}">
<div class="container">
<h1>{{.Name}}</h1>
<p>{{if .Passed}}PASSED{{else}}FAILED{{end}}</p>
</div>
</header>
<div class="container">
{{range .Results}}
<div class="result {{if .Passed}}passed{{else}}failed{{end}}">
<div><h2>{{.Name}}</h2></div>
<div class="info">{{if .ErrorHTML}}{{.ErrorHTML}}{{else}}PASSED{{end}}</div>
</div>
{{end}}
</div>
</body>
</html>

View File

@ -0,0 +1,5 @@
GET /@tests TestRunner.Index
GET /@tests.list TestRunner.List
GET /@tests/public/*filepath Static.ServeModule(testrunner,public)
GET /@tests/:suite TestRunner.Suite
GET /@tests/:suite/:test TestRunner.Run

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,127 @@
/*
github.com style (c) Vasily Polovnyov <vast@whiteants.net>
*/
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
color: #333;
background: #f8f8f8;
-webkit-text-size-adjust: none;
}
.hljs-comment,
.hljs-template_comment,
.diff .hljs-header,
.hljs-javadoc {
color: #998;
font-style: italic;
}
.hljs-keyword,
.css .rule .hljs-keyword,
.hljs-winutils,
.javascript .hljs-title,
.nginx .hljs-title,
.hljs-subst,
.hljs-request,
.hljs-status {
color: #333;
font-weight: bold;
}
.hljs-number,
.hljs-hexcolor,
.ruby .hljs-constant {
color: #008080;
}
.hljs-string,
.hljs-tag .hljs-value,
.hljs-phpdoc,
.hljs-dartdoc,
.tex .hljs-formula {
color: #d14;
}
.hljs-title,
.hljs-id,
.scss .hljs-preprocessor {
color: #900;
font-weight: bold;
}
.javascript .hljs-title,
.hljs-list .hljs-keyword,
.hljs-subst {
font-weight: normal;
}
.hljs-class .hljs-title,
.hljs-type,
.vhdl .hljs-literal,
.tex .hljs-command {
color: #458;
font-weight: bold;
}
.hljs-tag,
.hljs-tag .hljs-title,
.hljs-rules .hljs-property,
.django .hljs-tag .hljs-keyword {
color: #000080;
font-weight: normal;
}
.hljs-attribute,
.hljs-variable,
.lisp .hljs-body {
color: #008080;
}
.hljs-regexp {
color: #009926;
}
.hljs-symbol,
.ruby .hljs-symbol .hljs-string,
.lisp .hljs-keyword,
.clojure .hljs-keyword,
.scheme .hljs-keyword,
.tex .hljs-special,
.hljs-prompt {
color: #990073;
}
.hljs-built_in {
color: #0086b3;
}
.hljs-preprocessor,
.hljs-pragma,
.hljs-pi,
.hljs-doctype,
.hljs-shebang,
.hljs-cdata {
color: #999;
font-weight: bold;
}
.hljs-deletion {
background: #fdd;
}
.hljs-addition {
background: #dfd;
}
.diff .hljs-change {
background: #0086b3;
}
.hljs-chunk {
color: #aaa;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
package testrunner
// Required for vendoring see golang.org/issue/13832