github.com/tirogen/go-ethereum@v1.10.12-0.20221226051715-250cfede41b6/internal/utesting/utesting.go (about)

     1  // Copyright 2020 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Package utesting provides a standalone replacement for package testing.
    18  //
    19  // This package exists because package testing cannot easily be embedded into a
    20  // standalone go program. It provides an API that mirrors the standard library
    21  // testing API.
    22  package utesting
    23  
    24  import (
    25  	"bytes"
    26  	"fmt"
    27  	"io"
    28  	"regexp"
    29  	"runtime"
    30  	"sync"
    31  	"time"
    32  )
    33  
    34  // Test represents a single test.
    35  type Test struct {
    36  	Name string
    37  	Fn   func(*T)
    38  }
    39  
    40  // Result is the result of a test execution.
    41  type Result struct {
    42  	Name     string
    43  	Failed   bool
    44  	Output   string
    45  	Duration time.Duration
    46  }
    47  
    48  // MatchTests returns the tests whose name matches a regular expression.
    49  func MatchTests(tests []Test, expr string) []Test {
    50  	var results []Test
    51  	re, err := regexp.Compile(expr)
    52  	if err != nil {
    53  		return nil
    54  	}
    55  	for _, test := range tests {
    56  		if re.MatchString(test.Name) {
    57  			results = append(results, test)
    58  		}
    59  	}
    60  	return results
    61  }
    62  
    63  // RunTests executes all given tests in order and returns their results.
    64  // If the report writer is non-nil, a test report is written to it in real time.
    65  func RunTests(tests []Test, report io.Writer) []Result {
    66  	if report == nil {
    67  		report = io.Discard
    68  	}
    69  	results := run(tests, newConsoleOutput(report))
    70  	fails := CountFailures(results)
    71  	fmt.Fprintf(report, "%v/%v tests passed.\n", len(tests)-fails, len(tests))
    72  	return results
    73  }
    74  
    75  // RunTAP runs the given tests and writes Test Anything Protocol output
    76  // to the report writer.
    77  func RunTAP(tests []Test, report io.Writer) []Result {
    78  	return run(tests, newTAP(report, len(tests)))
    79  }
    80  
    81  func run(tests []Test, output testOutput) []Result {
    82  	var results = make([]Result, len(tests))
    83  	for i, test := range tests {
    84  		buffer := new(bytes.Buffer)
    85  		logOutput := io.MultiWriter(buffer, output)
    86  
    87  		output.testStart(test.Name)
    88  		start := time.Now()
    89  		results[i].Name = test.Name
    90  		results[i].Failed = runTest(test, logOutput)
    91  		results[i].Duration = time.Since(start)
    92  		results[i].Output = buffer.String()
    93  		output.testResult(results[i])
    94  	}
    95  	return results
    96  }
    97  
    98  // testOutput is implemented by output formats.
    99  type testOutput interface {
   100  	testStart(name string)
   101  	Write([]byte) (int, error)
   102  	testResult(Result)
   103  }
   104  
   105  // consoleOutput prints test results similarly to go test.
   106  type consoleOutput struct {
   107  	out         io.Writer
   108  	indented    *indentWriter
   109  	curTest     string
   110  	wroteHeader bool
   111  }
   112  
   113  func newConsoleOutput(w io.Writer) *consoleOutput {
   114  	return &consoleOutput{
   115  		out:      w,
   116  		indented: newIndentWriter(" ", w),
   117  	}
   118  }
   119  
   120  // testStart signals the start of a new test.
   121  func (c *consoleOutput) testStart(name string) {
   122  	c.curTest = name
   123  	c.wroteHeader = false
   124  }
   125  
   126  // Write handles test log output.
   127  func (c *consoleOutput) Write(b []byte) (int, error) {
   128  	if !c.wroteHeader {
   129  		// This is the first output line from the test. Print a "-- RUN" header.
   130  		fmt.Fprintln(c.out, "-- RUN", c.curTest)
   131  		c.wroteHeader = true
   132  	}
   133  	return c.indented.Write(b)
   134  }
   135  
   136  // testResult prints the final test result line.
   137  func (c *consoleOutput) testResult(r Result) {
   138  	c.indented.flush()
   139  	pd := r.Duration.Truncate(100 * time.Microsecond)
   140  	if r.Failed {
   141  		fmt.Fprintf(c.out, "-- FAIL %s (%v)\n", r.Name, pd)
   142  	} else {
   143  		fmt.Fprintf(c.out, "-- OK %s (%v)\n", r.Name, pd)
   144  	}
   145  }
   146  
   147  // tapOutput produces Test Anything Protocol v13 output.
   148  type tapOutput struct {
   149  	out      io.Writer
   150  	indented *indentWriter
   151  	counter  int
   152  }
   153  
   154  func newTAP(out io.Writer, numTests int) *tapOutput {
   155  	fmt.Fprintf(out, "1..%d\n", numTests)
   156  	return &tapOutput{
   157  		out:      out,
   158  		indented: newIndentWriter("# ", out),
   159  	}
   160  }
   161  
   162  func (t *tapOutput) testStart(name string) {
   163  	t.counter++
   164  }
   165  
   166  // Write does nothing for TAP because there is no real-time output of test logs.
   167  func (t *tapOutput) Write(b []byte) (int, error) {
   168  	return len(b), nil
   169  }
   170  
   171  func (t *tapOutput) testResult(r Result) {
   172  	status := "ok"
   173  	if r.Failed {
   174  		status = "not ok"
   175  	}
   176  	fmt.Fprintln(t.out, status, t.counter, r.Name)
   177  	t.indented.Write([]byte(r.Output))
   178  	t.indented.flush()
   179  }
   180  
   181  // indentWriter indents all written text.
   182  type indentWriter struct {
   183  	out    io.Writer
   184  	indent string
   185  	inLine bool
   186  }
   187  
   188  func newIndentWriter(indent string, out io.Writer) *indentWriter {
   189  	return &indentWriter{out: out, indent: indent}
   190  }
   191  
   192  func (w *indentWriter) Write(b []byte) (n int, err error) {
   193  	for len(b) > 0 {
   194  		if !w.inLine {
   195  			if _, err = io.WriteString(w.out, w.indent); err != nil {
   196  				return n, err
   197  			}
   198  			w.inLine = true
   199  		}
   200  
   201  		end := bytes.IndexByte(b, '\n')
   202  		if end == -1 {
   203  			nn, err := w.out.Write(b)
   204  			n += nn
   205  			return n, err
   206  		}
   207  
   208  		line := b[:end+1]
   209  		nn, err := w.out.Write(line)
   210  		n += nn
   211  		if err != nil {
   212  			return n, err
   213  		}
   214  		b = b[end+1:]
   215  		w.inLine = false
   216  	}
   217  	return n, err
   218  }
   219  
   220  // flush ensures the current line is terminated.
   221  func (w *indentWriter) flush() {
   222  	if w.inLine {
   223  		fmt.Println(w.out)
   224  		w.inLine = false
   225  	}
   226  }
   227  
   228  // CountFailures returns the number of failed tests in the result slice.
   229  func CountFailures(rr []Result) int {
   230  	count := 0
   231  	for _, r := range rr {
   232  		if r.Failed {
   233  			count++
   234  		}
   235  	}
   236  	return count
   237  }
   238  
   239  // Run executes a single test.
   240  func Run(test Test) (bool, string) {
   241  	output := new(bytes.Buffer)
   242  	failed := runTest(test, output)
   243  	return failed, output.String()
   244  }
   245  
   246  func runTest(test Test, output io.Writer) bool {
   247  	t := &T{output: output}
   248  	done := make(chan struct{})
   249  	go func() {
   250  		defer close(done)
   251  		defer func() {
   252  			if err := recover(); err != nil {
   253  				buf := make([]byte, 4096)
   254  				i := runtime.Stack(buf, false)
   255  				t.Logf("panic: %v\n\n%s", err, buf[:i])
   256  				t.Fail()
   257  			}
   258  		}()
   259  		test.Fn(t)
   260  	}()
   261  	<-done
   262  	return t.failed
   263  }
   264  
   265  // T is the value given to the test function. The test can signal failures
   266  // and log output by calling methods on this object.
   267  type T struct {
   268  	mu     sync.Mutex
   269  	failed bool
   270  	output io.Writer
   271  }
   272  
   273  // Helper exists for compatibility with testing.T.
   274  func (t *T) Helper() {}
   275  
   276  // FailNow marks the test as having failed and stops its execution by calling
   277  // runtime.Goexit (which then runs all deferred calls in the current goroutine).
   278  func (t *T) FailNow() {
   279  	t.Fail()
   280  	runtime.Goexit()
   281  }
   282  
   283  // Fail marks the test as having failed but continues execution.
   284  func (t *T) Fail() {
   285  	t.mu.Lock()
   286  	defer t.mu.Unlock()
   287  	t.failed = true
   288  }
   289  
   290  // Failed reports whether the test has failed.
   291  func (t *T) Failed() bool {
   292  	t.mu.Lock()
   293  	defer t.mu.Unlock()
   294  	return t.failed
   295  }
   296  
   297  // Log formats its arguments using default formatting, analogous to Println, and records
   298  // the text in the error log.
   299  func (t *T) Log(vs ...interface{}) {
   300  	t.mu.Lock()
   301  	defer t.mu.Unlock()
   302  	fmt.Fprintln(t.output, vs...)
   303  }
   304  
   305  // Logf formats its arguments according to the format, analogous to Printf, and records
   306  // the text in the error log. A final newline is added if not provided.
   307  func (t *T) Logf(format string, vs ...interface{}) {
   308  	t.mu.Lock()
   309  	defer t.mu.Unlock()
   310  	if len(format) == 0 || format[len(format)-1] != '\n' {
   311  		format += "\n"
   312  	}
   313  	fmt.Fprintf(t.output, format, vs...)
   314  }
   315  
   316  // Error is equivalent to Log followed by Fail.
   317  func (t *T) Error(vs ...interface{}) {
   318  	t.Log(vs...)
   319  	t.Fail()
   320  }
   321  
   322  // Errorf is equivalent to Logf followed by Fail.
   323  func (t *T) Errorf(format string, vs ...interface{}) {
   324  	t.Logf(format, vs...)
   325  	t.Fail()
   326  }
   327  
   328  // Fatal is equivalent to Log followed by FailNow.
   329  func (t *T) Fatal(vs ...interface{}) {
   330  	t.Log(vs...)
   331  	t.FailNow()
   332  }
   333  
   334  // Fatalf is equivalent to Logf followed by FailNow.
   335  func (t *T) Fatalf(format string, vs ...interface{}) {
   336  	t.Logf(format, vs...)
   337  	t.FailNow()
   338  }