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 }