123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- // Copyright 2020 The go-ethereum Authors
- // This file is part of the go-ethereum library.
- //
- // The go-ethereum library is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Lesser General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // The go-ethereum library is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Lesser General Public License for more details.
- //
- // You should have received a copy of the GNU Lesser General Public License
- // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
- // Package utesting provides a standalone replacement for package testing.
- //
- // This package exists because package testing cannot easily be embedded into a
- // standalone go program. It provides an API that mirrors the standard library
- // testing API.
- package utesting
- import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "regexp"
- "runtime"
- "sync"
- "time"
- )
- // Test represents a single test.
- type Test struct {
- Name string
- Fn func(*T)
- }
- // Result is the result of a test execution.
- type Result struct {
- Name string
- Failed bool
- Output string
- Duration time.Duration
- }
- // MatchTests returns the tests whose name matches a regular expression.
- func MatchTests(tests []Test, expr string) []Test {
- var results []Test
- re, err := regexp.Compile(expr)
- if err != nil {
- return nil
- }
- for _, test := range tests {
- if re.MatchString(test.Name) {
- results = append(results, test)
- }
- }
- return results
- }
- // 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 {
- 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 {
- buffer := new(bytes.Buffer)
- logOutput := io.MultiWriter(buffer, output)
- output.testStart(test.Name)
- start := time.Now()
- results[i].Name = test.Name
- results[i].Failed = runTest(test, logOutput)
- results[i].Duration = time.Since(start)
- results[i].Output = buffer.String()
- output.testResult(results[i])
- }
- return results
- }
- // 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(c.out, "-- FAIL %s (%v)\n", r.Name, pd)
- } else {
- 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
- }
- }
- // CountFailures returns the number of failed tests in the result slice.
- func CountFailures(rr []Result) int {
- count := 0
- for _, r := range rr {
- if r.Failed {
- count++
- }
- }
- return count
- }
- // Run executes a single test.
- func Run(test Test) (bool, string) {
- output := new(bytes.Buffer)
- failed := runTest(test, output)
- return failed, output.String()
- }
- func runTest(test Test, output io.Writer) bool {
- t := &T{output: output}
- done := make(chan struct{})
- go func() {
- defer close(done)
- defer func() {
- if err := recover(); err != nil {
- buf := make([]byte, 4096)
- i := runtime.Stack(buf, false)
- t.Logf("panic: %v\n\n%s", err, buf[:i])
- t.Fail()
- }
- }()
- test.Fn(t)
- }()
- <-done
- return t.failed
- }
- // T is the value given to the test function. The test can signal failures
- // and log output by calling methods on this object.
- type T struct {
- mu sync.Mutex
- failed bool
- 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() {
- t.Fail()
- runtime.Goexit()
- }
- // Fail marks the test as having failed but continues execution.
- func (t *T) Fail() {
- t.mu.Lock()
- defer t.mu.Unlock()
- t.failed = true
- }
- // Failed reports whether the test has failed.
- func (t *T) Failed() bool {
- t.mu.Lock()
- defer t.mu.Unlock()
- return t.failed
- }
- // Log formats its arguments using default formatting, analogous to Println, and records
- // the text in the error log.
- func (t *T) Log(vs ...interface{}) {
- t.mu.Lock()
- defer t.mu.Unlock()
- fmt.Fprintln(t.output, vs...)
- }
- // Logf formats its arguments according to the format, analogous to Printf, and records
- // the text in the error log. A final newline is added if not provided.
- func (t *T) Logf(format string, vs ...interface{}) {
- t.mu.Lock()
- defer t.mu.Unlock()
- if len(format) == 0 || format[len(format)-1] != '\n' {
- format += "\n"
- }
- fmt.Fprintf(t.output, format, vs...)
- }
- // Error is equivalent to Log followed by Fail.
- func (t *T) Error(vs ...interface{}) {
- t.Log(vs...)
- t.Fail()
- }
- // Errorf is equivalent to Logf followed by Fail.
- func (t *T) Errorf(format string, vs ...interface{}) {
- t.Logf(format, vs...)
- t.Fail()
- }
- // Fatal is equivalent to Log followed by FailNow.
- func (t *T) Fatal(vs ...interface{}) {
- t.Log(vs...)
- t.FailNow()
- }
- // Fatalf is equivalent to Logf followed by FailNow.
- func (t *T) Fatalf(format string, vs ...interface{}) {
- t.Logf(format, vs...)
- t.FailNow()
- }
|