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