github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/gnovm/stdlibs/testing/testing.gno (about)

     1  // Shim for Go's "testing" package to support minimal testing types.
     2  package testing
     3  
     4  import (
     5  	"encoding/json"
     6  	"fmt"
     7  	"os"
     8  	"regexp"
     9  	"strconv"
    10  	"strings"
    11  )
    12  
    13  //----------------------------------------
    14  // Top level functions
    15  
    16  // skipErr is the type of the panic created by SkipNow
    17  // and FailNow. Having it as a simple string means that it can be fmt.Printf'd
    18  // easily (and doesn't get "corrupted" through gno2go).
    19  type skipErr string
    20  
    21  func (s skipErr) Error() string {
    22  	return string(s)
    23  }
    24  
    25  // Recover functions like recover(), but it ensures that the recovered error is
    26  // not an internal error of the testing package.
    27  //
    28  // Due to a lack of goroutines and thus runtime.Goexit, gno's testing system resorts
    29  // to panics to abort testing with FailNow (and Fatal* functions) or SkipNow
    30  // (and Skip* functions).
    31  //
    32  // NOTE: Recover() is likely to be removed.
    33  func Recover(result Setter) {
    34  	r := recover()
    35  	if _, ok := r.(skipErr); !ok {
    36  		result.Set(r)
    37  		return
    38  	}
    39  
    40  	panic(r)
    41  }
    42  
    43  type Setter interface {
    44  	Set(v interface{})
    45  }
    46  
    47  func Short() bool {
    48  	return true // TODO configure somehow.
    49  }
    50  
    51  func Verbose() bool {
    52  	return true // TODO configure somehow.
    53  }
    54  
    55  // Like AllocsPerRun() but returns an integer.
    56  // TODO: actually compute allocations; for now return 0.
    57  func AllocsPerRun2(runs int, f func()) (total int) {
    58  	for i := 0; i < runs; i++ {
    59  		f()
    60  	}
    61  	return 0
    62  }
    63  
    64  //----------------------------------------
    65  // T
    66  
    67  type T struct {
    68  	name      string
    69  	failed    bool
    70  	skipped   bool
    71  	subs      []*T
    72  	parent    *T
    73  	output    []byte // Output generated by test
    74  	verbose   bool
    75  	runFilter filterMatch
    76  	dur       string
    77  }
    78  
    79  func NewT(name string) *T {
    80  	return &T{name: name}
    81  }
    82  
    83  type testingFunc func(*T)
    84  
    85  // Not yet implemented:
    86  // func (t *T) Cleanup(f func()) {
    87  // func (t *T) Deadline() (deadline time.Time, ok bool)
    88  func (t *T) Error(args ...interface{}) {
    89  	t.Log(args...)
    90  	t.Fail()
    91  }
    92  
    93  func (t *T) Errorf(format string, args ...interface{}) {
    94  	t.Logf(format, args...)
    95  	t.Fail()
    96  }
    97  
    98  func (t *T) Fail() {
    99  	t.failed = true
   100  }
   101  
   102  func (t *T) FailNow() {
   103  	t.Fail()
   104  	panic(skipErr("testing: you have recovered a panic attempting to interrupt a test, as a consequence of FailNow. " +
   105  		"Use testing.Recover to recover panics within tests"))
   106  }
   107  
   108  func (t *T) Failed() bool {
   109  	if t.failed {
   110  		return true
   111  	}
   112  	for _, sub := range t.subs {
   113  		if sub.Failed() {
   114  			return true
   115  		}
   116  	}
   117  	return false
   118  }
   119  
   120  // only called when verbose == false
   121  func (t *T) printFailure() {
   122  	fmt.Fprintf(os.Stderr, "--- FAIL: %s (%s)\n", t.name, t.dur)
   123  	if t.failed {
   124  		fmt.Fprint(os.Stderr, string(t.output))
   125  	}
   126  	for _, sub := range t.subs {
   127  		if sub.Failed() {
   128  			sub.printFailure()
   129  		}
   130  	}
   131  }
   132  
   133  func (t *T) Fatal(args ...interface{}) {
   134  	t.Log(args...)
   135  	t.FailNow()
   136  }
   137  
   138  func (t *T) Fatalf(format string, args ...interface{}) {
   139  	t.Logf(format, args...)
   140  	t.FailNow()
   141  }
   142  
   143  func (t *T) Log(args ...interface{}) {
   144  	t.log(fmt.Sprintln(args...))
   145  }
   146  
   147  func (t *T) Logf(format string, args ...interface{}) {
   148  	t.log(fmt.Sprintf(format, args...))
   149  	t.log(fmt.Sprintln())
   150  }
   151  
   152  func (t *T) Name() string {
   153  	return t.name
   154  }
   155  
   156  func (t *T) Parallel() {
   157  	// does nothing.
   158  }
   159  
   160  func (t *T) Run(name string, f testingFunc) bool {
   161  	fullName := t.name + "/" + rewrite(name)
   162  
   163  	subT := &T{
   164  		parent:    t,
   165  		name:      fullName,
   166  		verbose:   t.verbose,
   167  		runFilter: t.runFilter,
   168  	}
   169  
   170  	t.subs = append(t.subs, subT)
   171  
   172  	tRunner(subT, f, t.verbose)
   173  	return true
   174  }
   175  
   176  func (t *T) Setenv(key, value string) {
   177  	panic("not yet implemented")
   178  }
   179  
   180  func (t *T) Skip(args ...interface{}) {
   181  	t.Log(args...)
   182  	t.SkipNow()
   183  }
   184  
   185  func (t *T) SkipNow() {
   186  	t.skipped = true
   187  	panic(skipErr("testing: you have recovered a panic attempting to interrupt a test, as a consequence of SkipNow. " +
   188  		"Use testing.Recover to recover panics within tests"))
   189  }
   190  
   191  func (t *T) Skipped() bool {
   192  	return t.skipped
   193  }
   194  
   195  func (t *T) Skipf(format string, args ...interface{}) {
   196  	t.Logf(format, args...)
   197  	t.SkipNow()
   198  }
   199  
   200  func (t *T) TempDir() string {
   201  	panic("not yet implemented")
   202  }
   203  
   204  func (t *T) Helper() {
   205  }
   206  
   207  func (t *T) log(s string) {
   208  	if t.verbose {
   209  		// verbose, print immediately
   210  		fmt.Fprint(os.Stderr, s)
   211  	} else {
   212  		// defer printing only if test is failed
   213  		t.output = append(t.output, s...)
   214  	}
   215  }
   216  
   217  type Report struct {
   218  	Failed  bool
   219  	Skipped bool
   220  }
   221  
   222  func (t *T) report() Report {
   223  	return Report{
   224  		Failed:  t.Failed(),
   225  		Skipped: t.skipped,
   226  	}
   227  }
   228  
   229  //----------------------------------------
   230  // B
   231  // TODO: actually implement
   232  
   233  type B struct {
   234  	N int
   235  }
   236  
   237  func (b *B) Cleanup(f func())                          { panic("not yet implemented") }
   238  func (b *B) Error(args ...interface{})                 { panic("not yet implemented") }
   239  func (b *B) Errorf(format string, args ...interface{}) { panic("not yet implemented") }
   240  func (b *B) Fail()                                     { panic("not yet implemented") }
   241  func (b *B) FailNow()                                  { panic("not yet implemented") }
   242  func (b *B) Failed() bool                              { panic("not yet implemented") }
   243  func (b *B) Fatal(args ...interface{})                 { panic("not yet implemented") }
   244  func (b *B) Fatalf(format string, args ...interface{}) { panic("not yet implemented") }
   245  func (b *B) Helper()                                   { panic("not yet implemented") }
   246  func (b *B) Log(args ...interface{})                   { panic("not yet implemented") }
   247  func (b *B) Logf(format string, args ...interface{})   { panic("not yet implemented") }
   248  func (b *B) Name() string                              { panic("not yet implemented") }
   249  func (b *B) ReportAllocs()                             { panic("not yet implemented") }
   250  func (b *B) ReportMetric(n float64, unit string)       { panic("not yet implemented") }
   251  func (b *B) ResetTimer()                               { panic("not yet implemented") }
   252  func (b *B) Run(name string, f func(b *B)) bool        { panic("not yet implemented") }
   253  func (b *B) RunParallel(body func(*PB))                { panic("not yet implemented") }
   254  func (b *B) SetBytes(n int64)                          { panic("not yet implemented") }
   255  func (b *B) SetParallelism(p int)                      { panic("not yet implemented") }
   256  func (b *B) Setenv(key, value string)                  { panic("not yet implemented") }
   257  func (b *B) Skip(args ...interface{})                  { panic("not yet implemented") }
   258  func (b *B) SkipNow()                                  { panic("not yet implemented") }
   259  func (b *B) Skipf(format string, args ...interface{})  { panic("not yet implemented") }
   260  func (b *B) Skipped() bool                             { panic("not yet implemented") }
   261  func (b *B) StartTimer()                               { panic("not yet implemented") }
   262  func (b *B) StopTimer()                                { panic("not yet implemented") }
   263  func (b *B) TempDir() string                           { panic("not yet implemented") }
   264  
   265  //----------------------------------------
   266  // PB
   267  // TODO: actually implement
   268  
   269  type PB struct{}
   270  
   271  func (pb *PB) Next() bool { panic("not yet implemented") }
   272  
   273  type InternalTest struct {
   274  	Name string
   275  	F    testingFunc
   276  }
   277  
   278  func (t *T) shouldRun(name string) bool {
   279  	if t.runFilter == nil {
   280  		return true
   281  	}
   282  
   283  	elem := strings.Split(name, "/")
   284  	ok, partial := t.runFilter.matches(elem, matchString)
   285  	_ = partial // we don't care right now
   286  	return ok
   287  }
   288  
   289  func RunTest(runFlag string, verbose bool, test InternalTest) (ret string) {
   290  	t := &T{
   291  		name:    test.Name,
   292  		verbose: verbose,
   293  	}
   294  
   295  	if runFlag != "" {
   296  		t.runFilter = splitRegexp(runFlag)
   297  	}
   298  
   299  	tRunner(t, test.F, verbose)
   300  	if !t.verbose && t.Failed() {
   301  		// use printFailure to print output log of this
   302  		// and/or any subtests that may have failed.
   303  		t.printFailure()
   304  	}
   305  
   306  	report := t.report()
   307  	out, _ := json.Marshal(report)
   308  	return string(out)
   309  }
   310  
   311  func formatDur(dur int64) string {
   312  	// XXX switch to FormatFloat after it's been added
   313  	// 1 sec = 1e9 nsec
   314  	// this gets us the "centiseconds" which is what we show in tests.
   315  	dstr := strconv.Itoa(int(dur / 1e7))
   316  	if len(dstr) < 3 {
   317  		const pad = "000"
   318  		dstr = pad[:3-len(dstr)] + dstr
   319  	}
   320  	return dstr[:len(dstr)-2] + "." + dstr[len(dstr)-2:] + "s"
   321  }
   322  
   323  // used to calculate execution times; only present in testing stdlibs
   324  func unixNano() int64
   325  
   326  func tRunner(t *T, fn testingFunc, verbose bool) {
   327  	if !t.shouldRun(t.name) {
   328  		return
   329  	}
   330  
   331  	start := unixNano()
   332  
   333  	defer func() {
   334  		err := recover()
   335  		switch err.(type) {
   336  		case nil:
   337  		case skipErr:
   338  		default:
   339  			t.Fail()
   340  			fmt.Fprintf(os.Stderr, "panic: %v\n", err)
   341  		}
   342  
   343  		dur := unixNano() - start
   344  		t.dur = formatDur(dur)
   345  
   346  		if t.verbose {
   347  			switch {
   348  			case t.Failed():
   349  				fmt.Fprintf(os.Stderr, "--- FAIL: %s (%s)\n", t.name, t.dur)
   350  			case t.skipped:
   351  				fmt.Fprintf(os.Stderr, "--- SKIP: %s (%s)\n", t.name, t.dur)
   352  			case t.verbose:
   353  				fmt.Fprintf(os.Stderr, "--- PASS: %s (%s)\n", t.name, t.dur)
   354  			}
   355  		}
   356  	}()
   357  
   358  	if verbose {
   359  		fmt.Fprintf(os.Stderr, "=== RUN   %s\n", t.name)
   360  	}
   361  
   362  	fn(t)
   363  }