├── Makefile ├── README.markdown ├── natto.go ├── natto_test.go └── terst └── terst.go /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test test-race test-release release 2 | 3 | test: test-release 4 | 5 | test-release: 6 | go test -i 7 | go test 8 | 9 | test-race: 10 | go test -race -i 11 | go test -race 12 | 13 | release: test-race test-release 14 | for package in . ; do (cd $$package && godocdown --signature > README.markdown); done 15 | @echo PASS 16 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # natto 2 | -- 3 | import "github.com/robertkrimen/natto" 4 | 5 | Package natto is an example/offshoot of otto that implements an event loop 6 | (supporting setTimeout/setInterval). 7 | 8 | http://godoc.org/github.com/robertkrimen/natto 9 | 10 | otto: http://github.com/robertkrimen/otto 11 | (http://godoc.org/github.com/robertkrimen/otto) 12 | 13 | ## Usage 14 | 15 | #### func Run 16 | 17 | ```go 18 | func Run(src string) error 19 | ``` 20 | Run will execute the given JavaScript, continuing to run until all timers have 21 | finished executing (if any). The VM has the following functions available: 22 | 23 | = setTimeout(, , []) 24 | = setInterval(, , []) 25 | clearTimeout() 26 | clearInterval() 27 | 28 | -- 29 | **godocdown** http://github.com/robertkrimen/godocdown 30 | -------------------------------------------------------------------------------- /natto.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package natto is an example/offshoot of otto that implements an event loop (supporting setTimeout/setInterval). 3 | 4 | http://godoc.org/github.com/robertkrimen/natto 5 | 6 | otto: http://github.com/robertkrimen/otto (http://godoc.org/github.com/robertkrimen/otto) 7 | 8 | */ 9 | package natto 10 | 11 | import ( 12 | "time" 13 | 14 | "github.com/robertkrimen/otto" 15 | ) 16 | 17 | type _timer struct { 18 | timer *time.Timer 19 | duration time.Duration 20 | interval bool 21 | call otto.FunctionCall 22 | } 23 | 24 | // Run will execute the given JavaScript, continuing to run until all timers have finished executing (if any). 25 | // The VM has the following functions available: 26 | // 27 | // = setTimeout(, , []) 28 | // = setInterval(, , []) 29 | // clearTimeout() 30 | // clearInterval() 31 | // 32 | func Run(src string) error { 33 | 34 | vm := otto.New() 35 | registry := map[*_timer]*_timer{} 36 | ready := make(chan *_timer) 37 | 38 | newTimer := func(call otto.FunctionCall, interval bool) (*_timer, otto.Value) { 39 | delay, _ := call.Argument(1).ToInteger() 40 | if 0 >= delay { 41 | delay = 1 42 | } 43 | 44 | timer := &_timer{ 45 | duration: time.Duration(delay) * time.Millisecond, 46 | call: call, 47 | interval: interval, 48 | } 49 | registry[timer] = timer 50 | 51 | timer.timer = time.AfterFunc(timer.duration, func() { 52 | ready <- timer 53 | }) 54 | 55 | value, err := call.Otto.ToValue(timer) 56 | if err != nil { 57 | panic(err) 58 | } 59 | 60 | return timer, value 61 | } 62 | 63 | setTimeout := func(call otto.FunctionCall) otto.Value { 64 | _, value := newTimer(call, false) 65 | return value 66 | } 67 | vm.Set("setTimeout", setTimeout) 68 | 69 | setInterval := func(call otto.FunctionCall) otto.Value { 70 | _, value := newTimer(call, true) 71 | return value 72 | } 73 | vm.Set("setInterval", setInterval) 74 | 75 | clearTimeout := func(call otto.FunctionCall) otto.Value { 76 | timer, _ := call.Argument(0).Export() 77 | if timer, ok := timer.(*_timer); ok { 78 | timer.timer.Stop() 79 | delete(registry, timer) 80 | } 81 | return otto.UndefinedValue() 82 | } 83 | vm.Set("clearTimeout", clearTimeout) 84 | vm.Set("clearInterval", clearTimeout) 85 | 86 | _, err := vm.Run(src) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | for { 92 | select { 93 | case timer := <-ready: 94 | var arguments []interface{} 95 | if len(timer.call.ArgumentList) > 2 { 96 | tmp := timer.call.ArgumentList[2:] 97 | arguments = make([]interface{}, 2+len(tmp)) 98 | for i, value := range tmp { 99 | arguments[i+2] = value 100 | } 101 | } else { 102 | arguments = make([]interface{}, 1) 103 | } 104 | arguments[0] = timer.call.ArgumentList[0] 105 | _, err := vm.Call(`Function.call.call`, nil, arguments...) 106 | if err != nil { 107 | for _, timer := range registry { 108 | timer.timer.Stop() 109 | delete(registry, timer) 110 | return err 111 | } 112 | } 113 | if timer.interval { 114 | timer.timer.Reset(timer.duration) 115 | } else { 116 | delete(registry, timer) 117 | } 118 | default: 119 | // Escape valve! 120 | // If this isn't here, we deadlock... 121 | } 122 | if len(registry) == 0 { 123 | break 124 | } 125 | } 126 | 127 | return nil 128 | } 129 | -------------------------------------------------------------------------------- /natto_test.go: -------------------------------------------------------------------------------- 1 | package natto 2 | 3 | import ( 4 | "./terst" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | var is = terst.Is 10 | 11 | func Test(t *testing.T) { 12 | terst.Terst(t, func() { 13 | start := time.Now() 14 | Run(` 15 | setTimeout(function(){}, 1000); 16 | `) 17 | duration := time.Since(start) 18 | is(duration, ">=", 1*time.Second) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /terst/terst.go: -------------------------------------------------------------------------------- 1 | // This file was AUTOMATICALLY GENERATED by terst-import (smuggol) from github.com/robertkrimen/terst 2 | 3 | /* 4 | Package terst is a terse (terst = test + terse), easy-to-use testing library for Go. 5 | 6 | terst is compatible with (and works via) the standard testing package: http://golang.org/pkg/testing 7 | 8 | var is = terst.Is 9 | 10 | func Test(t *testing.T) { 11 | terst.Terst(t, func() { 12 | is("abc", "abc") 13 | 14 | is(1, ">", 0) 15 | 16 | var abc []int 17 | is(abc, nil) 18 | } 19 | } 20 | 21 | Do not import terst directly, instead use `terst-import` to copy it into your testing environment: 22 | 23 | https://github.com/robertkrimen/terst/tree/master/terst-import 24 | 25 | $ go get github.com/robertkrimen/terst/terst-import 26 | 27 | $ terst-import 28 | 29 | */ 30 | package terst 31 | 32 | import ( 33 | "bytes" 34 | "errors" 35 | "fmt" 36 | "math/big" 37 | "reflect" 38 | "regexp" 39 | "runtime" 40 | "strings" 41 | "sync" 42 | "testing" 43 | "time" 44 | ) 45 | 46 | // Is compares two values (got & expect) and returns true if the comparison is true, 47 | // false otherwise. In addition, if the comparison is false, Is will report the error 48 | // in a manner similar to testing.T.Error(...). Is also takes an optional argument, 49 | // a comparator, that changes how the comparison is made. The following 50 | // comparators are available: 51 | // 52 | // == # got == expect (default) 53 | // != # got != expect 54 | // 55 | // > # got > expect (float32, uint, uint16, int, int64, ...) 56 | // >= # got >= expect 57 | // < # got < expect 58 | // <= # got <= expect 59 | // 60 | // =~ # regexp.MustCompile(expect).Match{String}(got) 61 | // !~ # !regexp.MustCompile(expect).Match{String}(got) 62 | // 63 | // Basic usage with the default comparator (==): 64 | // 65 | // Is(, ) 66 | // 67 | // Specifying a different comparator: 68 | // 69 | // Is(, , ) 70 | // 71 | // A simple comparison: 72 | // 73 | // Is(2 + 2, 4) 74 | // 75 | // A bit trickier: 76 | // 77 | // Is(1, ">", 0) 78 | // Is(2 + 2, "!=", 5) 79 | // Is("Nothing happens.", "=~", `ing(\s+)happens\.$`) 80 | // 81 | // Is should only be called under a Terst(t, ...) call. For a standalone version, 82 | // use IsErr. If no scope is found and the comparison is false, then Is will panic the error. 83 | // 84 | func Is(arguments ...interface{}) bool { 85 | err := IsErr(arguments...) 86 | if err != nil { 87 | call := Caller() 88 | if call == nil { 89 | panic(err) 90 | } 91 | call.Error(err) 92 | return false 93 | } 94 | return true 95 | } 96 | 97 | type ( 98 | // ErrFail indicates a comparison failure (e.g. 0 > 1). 99 | ErrFail error 100 | 101 | // ErrInvalid indicates an invalid comparison (e.g. bool == string). 102 | ErrInvalid error 103 | ) 104 | 105 | var errInvalid = errors.New("invalid") 106 | 107 | var registry = struct { 108 | table map[uintptr]*_scope 109 | lock sync.RWMutex 110 | }{ 111 | table: map[uintptr]*_scope{}, 112 | } 113 | 114 | func registerScope(pc uintptr, scope *_scope) { 115 | registry.lock.Lock() 116 | defer registry.lock.Unlock() 117 | registry.table[pc] = scope 118 | } 119 | 120 | func scope() *_scope { 121 | scope, _ := findScope() 122 | return scope 123 | } 124 | 125 | func floatCompare(a float64, b float64) int { 126 | if a > b { 127 | return 1 128 | } else if a < b { 129 | return -1 130 | } 131 | // NaN == NaN 132 | return 0 133 | } 134 | 135 | func bigIntCompare(a *big.Int, b *big.Int) int { 136 | return a.Cmp(b) 137 | } 138 | 139 | func bigInt(value int64) *big.Int { 140 | return big.NewInt(value) 141 | } 142 | 143 | func bigUint(value uint64) *big.Int { 144 | return big.NewInt(0).SetUint64(value) 145 | } 146 | 147 | type _toString interface { 148 | String() string 149 | } 150 | 151 | func toString(value interface{}) (string, error) { 152 | switch value := value.(type) { 153 | case string: 154 | return value, nil 155 | case _toString: 156 | return value.String(), nil 157 | case error: 158 | return value.Error(), nil 159 | } 160 | return "", errInvalid 161 | } 162 | 163 | func matchString(got string, expect *regexp.Regexp) (int, error) { 164 | if expect.MatchString(got) { 165 | return 0, nil 166 | } 167 | return -1, nil 168 | } 169 | 170 | func match(got []byte, expect *regexp.Regexp) (int, error) { 171 | if expect.Match(got) { 172 | return 0, nil 173 | } 174 | return -1, nil 175 | } 176 | 177 | func compareMatch(got, expect interface{}) (int, error) { 178 | switch got := got.(type) { 179 | case []byte: 180 | switch expect := expect.(type) { 181 | case string: 182 | matcher, err := regexp.Compile(expect) 183 | if err != nil { 184 | return 0, err 185 | } 186 | return match(got, matcher) 187 | case *regexp.Regexp: 188 | return match(got, expect) 189 | } 190 | default: 191 | if got, err := toString(got); err == nil { 192 | switch expect := expect.(type) { 193 | case string: 194 | matcher, err := regexp.Compile(expect) 195 | if err != nil { 196 | return 0, err 197 | } 198 | return matchString(got, matcher) 199 | case *regexp.Regexp: 200 | return matchString(got, expect) 201 | } 202 | } else { 203 | return 0, err 204 | } 205 | } 206 | return 0, errInvalid 207 | } 208 | 209 | func floatPromote(value reflect.Value) (float64, error) { 210 | kind := value.Kind() 211 | if reflect.Int <= kind && kind <= reflect.Int64 { 212 | return float64(value.Int()), nil 213 | } 214 | if reflect.Uint <= kind && kind <= reflect.Uint64 { 215 | return float64(value.Uint()), nil 216 | } 217 | if reflect.Float32 <= kind && kind <= reflect.Float64 { 218 | return value.Float(), nil 219 | } 220 | return 0, errInvalid 221 | } 222 | 223 | func bigIntPromote(value reflect.Value) (*big.Int, error) { 224 | kind := value.Kind() 225 | if reflect.Int <= kind && kind <= reflect.Int64 { 226 | return bigInt(value.Int()), nil 227 | } 228 | if reflect.Uint <= kind && kind <= reflect.Uint64 { 229 | return bigUint(value.Uint()), nil 230 | } 231 | return nil, errInvalid 232 | } 233 | 234 | func compareOther(got, expect interface{}) (int, error) { 235 | { 236 | switch expect.(type) { 237 | case float32, float64: 238 | return compareNumber(got, expect) 239 | case uint, uint8, uint16, uint32, uint64: 240 | return compareNumber(got, expect) 241 | case int, int8, int16, int32, int64: 242 | return compareNumber(got, expect) 243 | case string: 244 | var err error 245 | got, err = toString(got) 246 | if err != nil { 247 | return 0, err 248 | } 249 | case nil: 250 | got := reflect.ValueOf(got) 251 | switch got.Kind() { 252 | case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.Interface: 253 | if got.IsNil() { 254 | return 0, nil 255 | } 256 | return -1, nil 257 | case reflect.Invalid: // reflect.Invalid: var abc interface{} = nil 258 | return 0, nil 259 | } 260 | return 0, errInvalid 261 | } 262 | } 263 | 264 | if reflect.ValueOf(got).Type() != reflect.ValueOf(expect).Type() { 265 | return 0, errInvalid 266 | } 267 | 268 | if reflect.DeepEqual(got, expect) { 269 | return 0, nil 270 | } 271 | return -1, nil 272 | } 273 | 274 | func compareNumber(got, expect interface{}) (int, error) { 275 | { 276 | got := reflect.ValueOf(got) 277 | k0 := got.Kind() 278 | expect := reflect.ValueOf(expect) 279 | k1 := expect.Kind() 280 | if reflect.Float32 <= k0 && k0 <= reflect.Float64 || 281 | reflect.Float32 <= k1 && k1 <= reflect.Float64 { 282 | got, err := floatPromote(got) 283 | if err != nil { 284 | return 0, err 285 | } 286 | expect, err := floatPromote(expect) 287 | if err != nil { 288 | return 0, err 289 | } 290 | return floatCompare(got, expect), nil 291 | } else { 292 | got, err := bigIntPromote(got) 293 | if err != nil { 294 | return 0, err 295 | } 296 | expect, err := bigIntPromote(expect) 297 | if err != nil { 298 | return 0, err 299 | } 300 | return got.Cmp(expect), nil 301 | } 302 | } 303 | 304 | return 0, errInvalid 305 | } 306 | 307 | // IsErr compares two values (got & expect) and returns nil if the comparison is true, an ErrFail if 308 | // the comparison is false, or an ErrInvalid if the comparison is invalid. IsErr also 309 | // takes an optional argument, a comparator, that changes how the comparison is made. 310 | // 311 | // Is & IsErr are similar but different: 312 | // 313 | // Is(...) // Should only be called within a Terst(...) call 314 | // IsErr(...) // A standalone comparator, the same as Is, just without the automatic reporting 315 | // 316 | func IsErr(arguments ...interface{}) error { 317 | var got, expect interface{} 318 | comparator := "==" 319 | switch len(arguments) { 320 | case 0, 1: 321 | return fmt.Errorf("invalid number of arguments to IsErr: %d", len(arguments)) 322 | case 2: 323 | got, expect = arguments[0], arguments[1] 324 | default: 325 | if value, ok := arguments[1].(string); ok { 326 | comparator = value 327 | } else { 328 | return fmt.Errorf("invalid comparator: %v", arguments[1]) 329 | } 330 | got, expect = arguments[0], arguments[2] 331 | } 332 | 333 | var result int 334 | var err error 335 | 336 | switch comparator { 337 | case "<", "<=", ">", ">=": 338 | result, err = compareNumber(got, expect) 339 | case "=~", "!~": 340 | result, err = compareMatch(got, expect) 341 | case "==", "!=": 342 | result, err = compareOther(got, expect) 343 | default: 344 | return fmt.Errorf("invalid comparator: %s", comparator) 345 | } 346 | 347 | if err == errInvalid { 348 | return ErrInvalid(fmt.Errorf( 349 | "\nINVALID (%s):\n got: %v (%T)\n expected: %v (%T)", 350 | comparator, 351 | got, got, 352 | expect, expect, 353 | )) 354 | } else if err != nil { 355 | return err 356 | } 357 | 358 | equality, pass := false, false 359 | 360 | switch comparator { 361 | case "==", "=~": 362 | equality = true 363 | pass = result == 0 364 | case "!=", "!~": 365 | equality = true 366 | pass = result != 0 367 | case "<": 368 | pass = result < 0 369 | case "<=": 370 | pass = result <= 0 371 | case ">": 372 | pass = result > 0 373 | case ">=": 374 | pass = result >= 0 375 | } 376 | 377 | if !pass { 378 | if equality { 379 | if comparator[1] == '~' { 380 | if value, ok := got.([]byte); ok { 381 | return ErrFail(fmt.Errorf( 382 | "\nFAIL (%s)\n got: %s %v%s\nexpected: %v%s", 383 | comparator, 384 | value, got, typeKindString(got), 385 | expect, typeKindString(expect), 386 | )) 387 | } 388 | } 389 | return ErrFail(fmt.Errorf( 390 | "\nFAIL (%s)\n got: %v%s\nexpected: %v%s", 391 | comparator, 392 | got, typeKindString(got), 393 | expect, typeKindString(expect), 394 | )) 395 | } 396 | return ErrFail(fmt.Errorf( 397 | "\nFAIL (%s)\n got: %v%s\nexpected: %s %v%s", 398 | comparator, 399 | got, typeKindString(got), 400 | comparator, expect, typeKindString(expect), 401 | )) 402 | } 403 | 404 | return nil 405 | } 406 | 407 | func typeKindString(value interface{}) string { 408 | reflectValue := reflect.ValueOf(value) 409 | kind := reflectValue.Kind().String() 410 | result := fmt.Sprintf("%T", value) 411 | if kind == result { 412 | if kind == "string" { 413 | return "" 414 | } 415 | return fmt.Sprintf(" (%T)", value) 416 | } 417 | return fmt.Sprintf(" (%T=%s)", value, kind) 418 | } 419 | 420 | func (scope *_scope) reset() { 421 | scope.name = "" 422 | scope.output = scope.output[:] 423 | scope.start = time.Time{} 424 | scope.duration = 0 425 | } 426 | 427 | // Terst creates a testing scope, where Is can be called and errors will be reported 428 | // according to the top-level location of the comparison, and not where the Is call 429 | // actually takes place. For example: 430 | // 431 | // func test(value int) { 432 | // Is(value, 5) // <--- This failure is reported below. 433 | // } 434 | // 435 | // Terst(t, func(){ 436 | // 437 | // Is(2, ">", 3) // <--- An error is reported here. 438 | // 439 | // test(5) // <--- An error is reported here. 440 | // 441 | // }) 442 | // 443 | func Terst(t *testing.T, arguments ...func()) { 444 | scope := &_scope{ 445 | t: t, 446 | } 447 | 448 | pc, _, _, ok := runtime.Caller(1) // TODO Associate with the Test... func 449 | if !ok { 450 | panic("Here be dragons.") 451 | } 452 | 453 | _, scope.testFunc = findTestFunc() 454 | 455 | registerScope(pc, scope) 456 | 457 | for _, fn := range arguments { 458 | func() { 459 | scope.reset() 460 | name := scope.testFunc.Name() 461 | index := strings.LastIndex(scope.testFunc.Name(), ".") 462 | if index >= 0 { 463 | name = name[index+1:] + "(Terst)" 464 | } else { 465 | name = "(Terst)" 466 | } 467 | name = "(Terst)" 468 | scope.name = name 469 | scope.start = time.Now() 470 | defer func() { 471 | scope.duration = time.Now().Sub(scope.start) 472 | if err := recover(); err != nil { 473 | scope.t.Fail() 474 | scope.report() 475 | panic(err) 476 | } 477 | scope.report() 478 | }() 479 | fn() 480 | }() 481 | } 482 | } 483 | 484 | // From "testing" 485 | func (scope *_scope) report() { 486 | format := "~~~ %s: (Terst)\n%s" 487 | if scope.t.Failed() { 488 | fmt.Printf(format, "FAIL", scope.output) 489 | } else if testing.Verbose() && len(scope.output) > 0 { 490 | fmt.Printf(format, "PASS", scope.output) 491 | } 492 | } 493 | 494 | func (scope *_scope) log(call _entry, str string) { 495 | scope.mu.Lock() 496 | defer scope.mu.Unlock() 497 | scope.output = append(scope.output, decorate(call, str)...) 498 | } 499 | 500 | // decorate prefixes the string with the file and line of the call site 501 | // and inserts the final newline if needed and indentation tabs for formascing. 502 | func decorate(call _entry, s string) string { 503 | 504 | file, line := call.File, call.Line 505 | if call.PC > 0 { 506 | // Truncate file name at last file name separator. 507 | if index := strings.LastIndex(file, "/"); index >= 0 { 508 | file = file[index+1:] 509 | } else if index = strings.LastIndex(file, "\\"); index >= 0 { 510 | file = file[index+1:] 511 | } 512 | } else { 513 | file = "???" 514 | line = 1 515 | } 516 | buf := new(bytes.Buffer) 517 | // Every line is indented at least one tab. 518 | buf.WriteByte('\t') 519 | fmt.Fprintf(buf, "%s:%d: ", file, line) 520 | lines := strings.Split(s, "\n") 521 | if l := len(lines); l > 1 && lines[l-1] == "" { 522 | lines = lines[:l-1] 523 | } 524 | for i, line := range lines { 525 | if i > 0 { 526 | // Second and subsequent lines are indented an extra tab. 527 | buf.WriteString("\n\t\t") 528 | } 529 | buf.WriteString(line) 530 | } 531 | buf.WriteByte('\n') 532 | return buf.String() 533 | } 534 | 535 | func findScope() (*_scope, _entry) { 536 | registry.lock.RLock() 537 | defer registry.lock.RUnlock() 538 | table := registry.table 539 | depth := 2 // Starting depth 540 | call := _entry{} 541 | for { 542 | pc, _, _, ok := runtime.Caller(depth) 543 | if !ok { 544 | break 545 | } 546 | if scope, exists := table[pc]; exists { 547 | pc, file, line, _ := runtime.Caller(depth - 3) // Terst(...) + func(){}() + fn() => ???() 548 | call.PC = pc 549 | call.File = file 550 | call.Line = line 551 | return scope, call 552 | } 553 | depth++ 554 | } 555 | return nil, _entry{} 556 | } 557 | 558 | // Call is a reference to a line immediately under a Terst testing scope. 559 | type Call struct { 560 | scope *_scope 561 | entry _entry 562 | } 563 | 564 | // Caller will search the stack, looking for a Terst testing scope. If a scope 565 | // is found, then Caller returns a Call for logging errors, accessing testing.T, etc. 566 | // If no scope is found, Caller returns nil. 567 | func Caller() *Call { 568 | scope, entry := findScope() 569 | if scope == nil { 570 | return nil 571 | } 572 | return &Call{ 573 | scope: scope, 574 | entry: entry, 575 | } 576 | } 577 | 578 | // TestFunc returns the *runtime.Func entry for the top-level Test...(t testing.T) 579 | // function. 580 | func (cl *Call) TestFunc() *runtime.Func { 581 | return cl.scope.testFunc 582 | } 583 | 584 | // T returns the original testing.T passed to Terst(...) 585 | func (cl *Call) T() *testing.T { 586 | return cl.scope.t 587 | } 588 | 589 | // Log is the terst version of `testing.T.Log` 590 | func (cl *Call) Log(arguments ...interface{}) { 591 | cl.scope.log(cl.entry, fmt.Sprintln(arguments...)) 592 | } 593 | 594 | // Logf is the terst version of `testing.T.Logf` 595 | func (cl *Call) Logf(format string, arguments ...interface{}) { 596 | cl.scope.log(cl.entry, fmt.Sprintf(format, arguments...)) 597 | } 598 | 599 | // Error is the terst version of `testing.T.Error` 600 | func (cl *Call) Error(arguments ...interface{}) { 601 | cl.scope.log(cl.entry, fmt.Sprintln(arguments...)) 602 | cl.scope.t.Fail() 603 | } 604 | 605 | // Errorf is the terst version of `testing.T.Errorf` 606 | func (cl *Call) Errorf(format string, arguments ...interface{}) { 607 | cl.scope.log(cl.entry, fmt.Sprintf(format, arguments...)) 608 | cl.scope.t.Fail() 609 | } 610 | 611 | // Skip is the terst version of `testing.T.Skip` 612 | func (cl *Call) Skip(arguments ...interface{}) { 613 | cl.scope.log(cl.entry, fmt.Sprintln(arguments...)) 614 | cl.scope.t.SkipNow() 615 | } 616 | 617 | // Skipf is the terst version of `testing.T.Skipf` 618 | func (cl *Call) Skipf(format string, arguments ...interface{}) { 619 | cl.scope.log(cl.entry, fmt.Sprintf(format, arguments...)) 620 | cl.scope.t.SkipNow() 621 | } 622 | 623 | type _scope struct { 624 | t *testing.T 625 | testFunc *runtime.Func 626 | name string 627 | mu sync.RWMutex 628 | output []byte 629 | start time.Time 630 | duration time.Duration 631 | } 632 | 633 | type _entry struct { 634 | PC uintptr 635 | File string 636 | Line int 637 | Func *runtime.Func 638 | } 639 | 640 | func _findFunc(match string) (_entry, *runtime.Func) { 641 | depth := 2 // Starting depth 642 | for { 643 | pc, file, line, ok := runtime.Caller(depth) 644 | if !ok { 645 | break 646 | } 647 | fn := runtime.FuncForPC(pc) 648 | name := fn.Name() 649 | if index := strings.LastIndex(name, match); index >= 0 { 650 | // Assume we have an instance of TestXyzzy in a _test file 651 | return _entry{ 652 | PC: pc, 653 | File: file, 654 | Line: line, 655 | Func: fn, 656 | }, fn 657 | } 658 | depth++ 659 | } 660 | return _entry{}, nil 661 | } 662 | 663 | func findTestFunc() (_entry, *runtime.Func) { 664 | return _findFunc(".Test") 665 | } 666 | 667 | func findTerstFunc() (_entry, *runtime.Func) { 668 | return _findFunc(".Terst") 669 | } 670 | --------------------------------------------------------------------------------