|
|
@@ -25,6 +25,7 @@ import (
|
|
|
"bytes"
|
|
|
"fmt"
|
|
|
"io"
|
|
|
+ "io/ioutil"
|
|
|
"regexp"
|
|
|
"runtime"
|
|
|
"sync"
|
|
|
@@ -63,32 +64,165 @@ func MatchTests(tests []Test, expr string) []Test {
|
|
|
// RunTests executes all given tests in order and returns their results.
|
|
|
// If the report writer is non-nil, a test report is written to it in real time.
|
|
|
func RunTests(tests []Test, report io.Writer) []Result {
|
|
|
- results := make([]Result, len(tests))
|
|
|
+ if report == nil {
|
|
|
+ report = ioutil.Discard
|
|
|
+ }
|
|
|
+ results := run(tests, newConsoleOutput(report))
|
|
|
+ fails := CountFailures(results)
|
|
|
+ fmt.Fprintf(report, "%v/%v tests passed.\n", len(tests)-fails, len(tests))
|
|
|
+ return results
|
|
|
+}
|
|
|
+
|
|
|
+// RunTAP runs the given tests and writes Test Anything Protocol output
|
|
|
+// to the report writer.
|
|
|
+func RunTAP(tests []Test, report io.Writer) []Result {
|
|
|
+ return run(tests, newTAP(report, len(tests)))
|
|
|
+}
|
|
|
+
|
|
|
+func run(tests []Test, output testOutput) []Result {
|
|
|
+ var results = make([]Result, len(tests))
|
|
|
for i, test := range tests {
|
|
|
- var output io.Writer
|
|
|
buffer := new(bytes.Buffer)
|
|
|
- output = buffer
|
|
|
- if report != nil {
|
|
|
- output = io.MultiWriter(buffer, report)
|
|
|
- }
|
|
|
+ logOutput := io.MultiWriter(buffer, output)
|
|
|
+
|
|
|
+ output.testStart(test.Name)
|
|
|
start := time.Now()
|
|
|
results[i].Name = test.Name
|
|
|
- results[i].Failed = run(test, output)
|
|
|
+ results[i].Failed = runTest(test, logOutput)
|
|
|
results[i].Duration = time.Since(start)
|
|
|
results[i].Output = buffer.String()
|
|
|
- if report != nil {
|
|
|
- printResult(results[i], report)
|
|
|
- }
|
|
|
+ output.testResult(results[i])
|
|
|
}
|
|
|
return results
|
|
|
}
|
|
|
|
|
|
-func printResult(r Result, w io.Writer) {
|
|
|
+// testOutput is implemented by output formats.
|
|
|
+type testOutput interface {
|
|
|
+ testStart(name string)
|
|
|
+ Write([]byte) (int, error)
|
|
|
+ testResult(Result)
|
|
|
+}
|
|
|
+
|
|
|
+// consoleOutput prints test results similarly to go test.
|
|
|
+type consoleOutput struct {
|
|
|
+ out io.Writer
|
|
|
+ indented *indentWriter
|
|
|
+ curTest string
|
|
|
+ wroteHeader bool
|
|
|
+}
|
|
|
+
|
|
|
+func newConsoleOutput(w io.Writer) *consoleOutput {
|
|
|
+ return &consoleOutput{
|
|
|
+ out: w,
|
|
|
+ indented: newIndentWriter(" ", w),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// testStart signals the start of a new test.
|
|
|
+func (c *consoleOutput) testStart(name string) {
|
|
|
+ c.curTest = name
|
|
|
+ c.wroteHeader = false
|
|
|
+}
|
|
|
+
|
|
|
+// Write handles test log output.
|
|
|
+func (c *consoleOutput) Write(b []byte) (int, error) {
|
|
|
+ if !c.wroteHeader {
|
|
|
+ // This is the first output line from the test. Print a "-- RUN" header.
|
|
|
+ fmt.Fprintln(c.out, "-- RUN", c.curTest)
|
|
|
+ c.wroteHeader = true
|
|
|
+ }
|
|
|
+ return c.indented.Write(b)
|
|
|
+}
|
|
|
+
|
|
|
+// testResult prints the final test result line.
|
|
|
+func (c *consoleOutput) testResult(r Result) {
|
|
|
+ c.indented.flush()
|
|
|
pd := r.Duration.Truncate(100 * time.Microsecond)
|
|
|
if r.Failed {
|
|
|
- fmt.Fprintf(w, "-- FAIL %s (%v)\n", r.Name, pd)
|
|
|
+ fmt.Fprintf(c.out, "-- FAIL %s (%v)\n", r.Name, pd)
|
|
|
} else {
|
|
|
- fmt.Fprintf(w, "-- OK %s (%v)\n", r.Name, pd)
|
|
|
+ fmt.Fprintf(c.out, "-- OK %s (%v)\n", r.Name, pd)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// tapOutput produces Test Anything Protocol v13 output.
|
|
|
+type tapOutput struct {
|
|
|
+ out io.Writer
|
|
|
+ indented *indentWriter
|
|
|
+ counter int
|
|
|
+}
|
|
|
+
|
|
|
+func newTAP(out io.Writer, numTests int) *tapOutput {
|
|
|
+ fmt.Fprintf(out, "1..%d\n", numTests)
|
|
|
+ return &tapOutput{
|
|
|
+ out: out,
|
|
|
+ indented: newIndentWriter("# ", out),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (t *tapOutput) testStart(name string) {
|
|
|
+ t.counter++
|
|
|
+}
|
|
|
+
|
|
|
+// Write does nothing for TAP because there is no real-time output of test logs.
|
|
|
+func (t *tapOutput) Write(b []byte) (int, error) {
|
|
|
+ return len(b), nil
|
|
|
+}
|
|
|
+
|
|
|
+func (t *tapOutput) testResult(r Result) {
|
|
|
+ status := "ok"
|
|
|
+ if r.Failed {
|
|
|
+ status = "not ok"
|
|
|
+ }
|
|
|
+ fmt.Fprintln(t.out, status, t.counter, r.Name)
|
|
|
+ t.indented.Write([]byte(r.Output))
|
|
|
+ t.indented.flush()
|
|
|
+}
|
|
|
+
|
|
|
+// indentWriter indents all written text.
|
|
|
+type indentWriter struct {
|
|
|
+ out io.Writer
|
|
|
+ indent string
|
|
|
+ inLine bool
|
|
|
+}
|
|
|
+
|
|
|
+func newIndentWriter(indent string, out io.Writer) *indentWriter {
|
|
|
+ return &indentWriter{out: out, indent: indent}
|
|
|
+}
|
|
|
+
|
|
|
+func (w *indentWriter) Write(b []byte) (n int, err error) {
|
|
|
+ for len(b) > 0 {
|
|
|
+ if !w.inLine {
|
|
|
+ if _, err = io.WriteString(w.out, w.indent); err != nil {
|
|
|
+ return n, err
|
|
|
+ }
|
|
|
+ w.inLine = true
|
|
|
+ }
|
|
|
+
|
|
|
+ end := bytes.IndexByte(b, '\n')
|
|
|
+ if end == -1 {
|
|
|
+ nn, err := w.out.Write(b)
|
|
|
+ n += nn
|
|
|
+ return n, err
|
|
|
+ }
|
|
|
+
|
|
|
+ line := b[:end+1]
|
|
|
+ nn, err := w.out.Write(line)
|
|
|
+ n += nn
|
|
|
+ if err != nil {
|
|
|
+ return n, err
|
|
|
+ }
|
|
|
+ b = b[end+1:]
|
|
|
+ w.inLine = false
|
|
|
+ }
|
|
|
+ return n, err
|
|
|
+}
|
|
|
+
|
|
|
+// flush ensures the current line is terminated.
|
|
|
+func (w *indentWriter) flush() {
|
|
|
+ if w.inLine {
|
|
|
+ fmt.Println(w.out)
|
|
|
+ w.inLine = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -106,11 +240,11 @@ func CountFailures(rr []Result) int {
|
|
|
// Run executes a single test.
|
|
|
func Run(test Test) (bool, string) {
|
|
|
output := new(bytes.Buffer)
|
|
|
- failed := run(test, output)
|
|
|
+ failed := runTest(test, output)
|
|
|
return failed, output.String()
|
|
|
}
|
|
|
|
|
|
-func run(test Test, output io.Writer) bool {
|
|
|
+func runTest(test Test, output io.Writer) bool {
|
|
|
t := &T{output: output}
|
|
|
done := make(chan struct{})
|
|
|
go func() {
|
|
|
@@ -137,6 +271,9 @@ type T struct {
|
|
|
output io.Writer
|
|
|
}
|
|
|
|
|
|
+// Helper exists for compatibility with testing.T.
|
|
|
+func (t *T) Helper() {}
|
|
|
+
|
|
|
// FailNow marks the test as having failed and stops its execution by calling
|
|
|
// runtime.Goexit (which then runs all deferred calls in the current goroutine).
|
|
|
func (t *T) FailNow() {
|