Jacob McCann
7 years ago
committed by
GitHub
16 changed files with 811 additions and 52 deletions
@ -0,0 +1,19 @@ |
|||||
|
Copyright (c) 2013 Marcos Lilljedahl and Jonathan Leibiusky |
||||
|
|
||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
|
of this software and associated documentation files (the "Software"), to deal |
||||
|
in the Software without restriction, including without limitation the rights |
||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
|
copies of the Software, and to permit persons to whom the Software is |
||||
|
furnished to do so, subject to the following conditions: |
||||
|
|
||||
|
The above copyright notice and this permission notice shall be included in |
||||
|
all copies or substantial portions of the Software. |
||||
|
|
||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||
|
THE SOFTWARE. |
@ -0,0 +1,3 @@ |
|||||
|
export GOPATH=$(shell pwd) |
||||
|
test: |
||||
|
go test -v |
@ -0,0 +1,141 @@ |
|||||
|
[![Build Status](https://travis-ci.org/franela/goblin.png?branch=master)](https://travis-ci.org/franela/goblin) |
||||
|
Goblin |
||||
|
====== |
||||
|
|
||||
|
![](https://github.com/marcosnils/goblin/blob/master/goblin_logo.jpg?raw=true) |
||||
|
|
||||
|
A [Mocha](http://mochajs.org/) like BDD testing framework for Go |
||||
|
|
||||
|
No extensive documentation nor complicated steps to get it running |
||||
|
|
||||
|
Run tests as usual with `go test` |
||||
|
|
||||
|
Colorful reports and beautiful syntax |
||||
|
|
||||
|
|
||||
|
Why Goblin? |
||||
|
----------- |
||||
|
|
||||
|
Inspired by the flexibility and simplicity of Node BDD and frustrated by the |
||||
|
rigorousness of Go way of testing, we wanted to bring a new tool to |
||||
|
write self-describing and comprehensive code. |
||||
|
|
||||
|
|
||||
|
|
||||
|
What do I get with it? |
||||
|
---------------------- |
||||
|
|
||||
|
- Preserve the exact same syntax and behaviour as Node's Mocha |
||||
|
- Nest as many `Describe` and `It` blocks as you want |
||||
|
- Use `Before`, `BeforeEach`, `After` and `AfterEach` for setup and teardown your tests |
||||
|
- No need to remember confusing parameters in `Describe` and `It` blocks |
||||
|
- Use a declarative and expressive language to write your tests |
||||
|
- Plug different assertion libraries ([Gomega](https://github.com/onsi/gomega) supported so far) |
||||
|
- Skip your tests the same way as you would do in Mocha |
||||
|
- Automatic terminal support for colored outputs |
||||
|
- Two line setup is all you need to get up running |
||||
|
|
||||
|
|
||||
|
|
||||
|
How do I use it? |
||||
|
---------------- |
||||
|
|
||||
|
Since ```go test``` is not currently extensive, you will have to hook Goblin to it. You do that by |
||||
|
adding a single test method in your test file. All your goblin tests will be implemented inside this function. |
||||
|
|
||||
|
```go |
||||
|
package foobar |
||||
|
|
||||
|
import ( |
||||
|
"testing" |
||||
|
. "github.com/franela/goblin" |
||||
|
) |
||||
|
|
||||
|
func Test(t *testing.T) { |
||||
|
g := Goblin(t) |
||||
|
g.Describe("Numbers", func() { |
||||
|
g.It("Should add two numbers ", func() { |
||||
|
g.Assert(1+1).Equal(2) |
||||
|
}) |
||||
|
g.It("Should match equal numbers", func() { |
||||
|
g.Assert(2).Equal(4) |
||||
|
}) |
||||
|
g.It("Should substract two numbers") |
||||
|
}) |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Ouput will be something like: |
||||
|
|
||||
|
![](https://github.com/marcosnils/goblin/blob/master/goblin_output.png?raw=true) |
||||
|
|
||||
|
Nice and easy, right? |
||||
|
|
||||
|
Can I do asynchronous tests? |
||||
|
---------------------------- |
||||
|
|
||||
|
Yes! Goblin will help you to test asynchronous things, like goroutines, etc. You just need to add a ```done``` parameter to the handler function of your ```It```. This handler function should be called when your test passes. |
||||
|
|
||||
|
```go |
||||
|
... |
||||
|
g.Describe("Numbers", func() { |
||||
|
g.It("Should add two numbers asynchronously", func(done Done) { |
||||
|
go func() { |
||||
|
g.Assert(1+1).Equal(2) |
||||
|
done() |
||||
|
}() |
||||
|
}) |
||||
|
}) |
||||
|
... |
||||
|
``` |
||||
|
|
||||
|
Goblin will wait for the ```done``` call, a ```Fail``` call or any false assertion. |
||||
|
|
||||
|
How do I use it with Gomega? |
||||
|
---------------------------- |
||||
|
|
||||
|
Gomega is a nice assertion framework. But it doesn't provide a nice way to hook it to testing frameworks. It should just panic instead of requiring a fail function. There is an issue about that [here](https://github.com/onsi/gomega/issues/5). |
||||
|
While this is being discussed and hopefully fixed, the way to use Gomega with Goblin is: |
||||
|
|
||||
|
```go |
||||
|
package foobar |
||||
|
|
||||
|
import ( |
||||
|
"testing" |
||||
|
. "github.com/franela/goblin" |
||||
|
. "github.com/onsi/gomega" |
||||
|
) |
||||
|
|
||||
|
func Test(t *testing.T) { |
||||
|
g := Goblin(t) |
||||
|
|
||||
|
//special hook for gomega |
||||
|
RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) }) |
||||
|
|
||||
|
g.Describe("lala", func() { |
||||
|
g.It("lslslslsls", func() { |
||||
|
Expect(1).To(Equal(10)) |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
FAQ: |
||||
|
---- |
||||
|
|
||||
|
### How do I run specific tests? |
||||
|
|
||||
|
If `-goblin.run=$REGES` is supplied to the `go test` command then only tests that match the supplied regex will run |
||||
|
|
||||
|
|
||||
|
TODO: |
||||
|
----- |
||||
|
|
||||
|
We do have a couple of [issues](https://github.com/franela/goblin/issues) pending we'll be addressing soon. But feel free to |
||||
|
contribute and send us PRs (with tests please :smile:). |
||||
|
|
||||
|
Contributions: |
||||
|
------------ |
||||
|
|
||||
|
Special thanks to [Leandro Reox](https://github.com/leandroreox) (Leitan) for the goblin logo. |
@ -0,0 +1,59 @@ |
|||||
|
package goblin |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"reflect" |
||||
|
"strings" |
||||
|
) |
||||
|
|
||||
|
type Assertion struct { |
||||
|
src interface{} |
||||
|
fail func(interface{}) |
||||
|
} |
||||
|
|
||||
|
func objectsAreEqual(a, b interface{}) bool { |
||||
|
if reflect.TypeOf(a) != reflect.TypeOf(b) { |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
if reflect.DeepEqual(a, b) { |
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
if fmt.Sprintf("%#v", a) == fmt.Sprintf("%#v", b) { |
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
func formatMessages(messages ...string) string { |
||||
|
if len(messages) > 0 { |
||||
|
return ", " + strings.Join(messages, " ") |
||||
|
} |
||||
|
return "" |
||||
|
} |
||||
|
|
||||
|
func (a *Assertion) Eql(dst interface{}) { |
||||
|
a.Equal(dst) |
||||
|
} |
||||
|
|
||||
|
func (a *Assertion) Equal(dst interface{}) { |
||||
|
if !objectsAreEqual(a.src, dst) { |
||||
|
a.fail(fmt.Sprintf("%#v %s %#v", a.src, "does not equal", dst)) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (a *Assertion) IsTrue(messages ...string) { |
||||
|
if !objectsAreEqual(a.src, true) { |
||||
|
message := fmt.Sprintf("%v %s%s", a.src, "expected false to be truthy", formatMessages(messages...)) |
||||
|
a.fail(message) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (a *Assertion) IsFalse(messages ...string) { |
||||
|
if !objectsAreEqual(a.src, false) { |
||||
|
message := fmt.Sprintf("%v %s%s", a.src, "expected true to be falsey", formatMessages(messages...)) |
||||
|
a.fail(message) |
||||
|
} |
||||
|
} |
@ -0,0 +1,36 @@ |
|||||
|
snippet gd |
||||
|
g.Describe("${1:name}", func() { |
||||
|
${2} |
||||
|
}) |
||||
|
${0} |
||||
|
snippet git |
||||
|
g.It("${1:name}", func() { |
||||
|
${2} |
||||
|
}) |
||||
|
${0} |
||||
|
snippet gait |
||||
|
g.It("${1:name}", func(done Done) { |
||||
|
done() |
||||
|
${2} |
||||
|
}) |
||||
|
${0} |
||||
|
snippet gb |
||||
|
g.Before(func() { |
||||
|
${1} |
||||
|
}) |
||||
|
${0} |
||||
|
snippet gbe |
||||
|
g.BeforeEach(func() { |
||||
|
${1} |
||||
|
}) |
||||
|
${0} |
||||
|
snippet ga |
||||
|
g.After(func() { |
||||
|
${1} |
||||
|
}) |
||||
|
${0} |
||||
|
snippet gae |
||||
|
g.AfterEach(func() { |
||||
|
${1} |
||||
|
}) |
||||
|
${0} |
@ -0,0 +1,302 @@ |
|||||
|
package goblin |
||||
|
|
||||
|
import ( |
||||
|
"flag" |
||||
|
"fmt" |
||||
|
"regexp" |
||||
|
"runtime" |
||||
|
"sync" |
||||
|
"testing" |
||||
|
"time" |
||||
|
) |
||||
|
|
||||
|
type Done func(error ...interface{}) |
||||
|
|
||||
|
type Runnable interface { |
||||
|
run(*G) bool |
||||
|
} |
||||
|
|
||||
|
func (g *G) Describe(name string, h func()) { |
||||
|
d := &Describe{name: name, h: h, parent: g.parent} |
||||
|
|
||||
|
if d.parent != nil { |
||||
|
d.parent.children = append(d.parent.children, Runnable(d)) |
||||
|
} |
||||
|
|
||||
|
g.parent = d |
||||
|
|
||||
|
h() |
||||
|
|
||||
|
g.parent = d.parent |
||||
|
|
||||
|
if g.parent == nil && d.hasTests { |
||||
|
g.reporter.begin() |
||||
|
if d.run(g) { |
||||
|
g.t.Fail() |
||||
|
} |
||||
|
g.reporter.end() |
||||
|
} |
||||
|
} |
||||
|
func (g *G) Timeout(time time.Duration) { |
||||
|
g.timeout = time |
||||
|
g.timer.Reset(time) |
||||
|
} |
||||
|
|
||||
|
type Describe struct { |
||||
|
name string |
||||
|
h func() |
||||
|
children []Runnable |
||||
|
befores []func() |
||||
|
afters []func() |
||||
|
afterEach []func() |
||||
|
beforeEach []func() |
||||
|
hasTests bool |
||||
|
parent *Describe |
||||
|
} |
||||
|
|
||||
|
func (d *Describe) runBeforeEach() { |
||||
|
if d.parent != nil { |
||||
|
d.parent.runBeforeEach() |
||||
|
} |
||||
|
|
||||
|
for _, b := range d.beforeEach { |
||||
|
b() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (d *Describe) runAfterEach() { |
||||
|
|
||||
|
if d.parent != nil { |
||||
|
d.parent.runAfterEach() |
||||
|
} |
||||
|
|
||||
|
for _, a := range d.afterEach { |
||||
|
a() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (d *Describe) run(g *G) bool { |
||||
|
failed := false |
||||
|
if d.hasTests { |
||||
|
g.reporter.beginDescribe(d.name) |
||||
|
|
||||
|
for _, b := range d.befores { |
||||
|
b() |
||||
|
} |
||||
|
|
||||
|
for _, r := range d.children { |
||||
|
if r.run(g) { |
||||
|
failed = true |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for _, a := range d.afters { |
||||
|
a() |
||||
|
} |
||||
|
|
||||
|
g.reporter.endDescribe() |
||||
|
} |
||||
|
|
||||
|
return failed |
||||
|
} |
||||
|
|
||||
|
type Failure struct { |
||||
|
stack []string |
||||
|
testName string |
||||
|
message string |
||||
|
} |
||||
|
|
||||
|
type It struct { |
||||
|
h interface{} |
||||
|
name string |
||||
|
parent *Describe |
||||
|
failure *Failure |
||||
|
reporter Reporter |
||||
|
isAsync bool |
||||
|
} |
||||
|
|
||||
|
func (it *It) run(g *G) bool { |
||||
|
g.currentIt = it |
||||
|
|
||||
|
if it.h == nil { |
||||
|
g.reporter.itIsPending(it.name) |
||||
|
return false |
||||
|
} |
||||
|
//TODO: should handle errors for beforeEach
|
||||
|
it.parent.runBeforeEach() |
||||
|
|
||||
|
runIt(g, it.h) |
||||
|
|
||||
|
it.parent.runAfterEach() |
||||
|
|
||||
|
failed := false |
||||
|
if it.failure != nil { |
||||
|
failed = true |
||||
|
} |
||||
|
|
||||
|
if failed { |
||||
|
g.reporter.itFailed(it.name) |
||||
|
g.reporter.failure(it.failure) |
||||
|
} else { |
||||
|
g.reporter.itPassed(it.name) |
||||
|
} |
||||
|
return failed |
||||
|
} |
||||
|
|
||||
|
func (it *It) failed(msg string, stack []string) { |
||||
|
it.failure = &Failure{stack: stack, message: msg, testName: it.parent.name + " " + it.name} |
||||
|
} |
||||
|
|
||||
|
func parseFlags() { |
||||
|
//Flag parsing
|
||||
|
flag.Parse() |
||||
|
if *regexParam != "" { |
||||
|
runRegex = regexp.MustCompile(*regexParam) |
||||
|
} else { |
||||
|
runRegex = nil |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var timeout = flag.Duration("goblin.timeout", 5*time.Second, "Sets default timeouts for all tests") |
||||
|
var isTty = flag.Bool("goblin.tty", true, "Sets the default output format (color / monochrome)") |
||||
|
var regexParam = flag.String("goblin.run", "", "Runs only tests which match the supplied regex") |
||||
|
var runRegex *regexp.Regexp |
||||
|
|
||||
|
func init() { |
||||
|
parseFlags() |
||||
|
} |
||||
|
|
||||
|
func Goblin(t *testing.T, arguments ...string) *G { |
||||
|
g := &G{t: t, timeout: *timeout} |
||||
|
var fancy TextFancier |
||||
|
if *isTty { |
||||
|
fancy = &TerminalFancier{} |
||||
|
} else { |
||||
|
fancy = &Monochrome{} |
||||
|
} |
||||
|
|
||||
|
g.reporter = Reporter(&DetailedReporter{fancy: fancy}) |
||||
|
return g |
||||
|
} |
||||
|
|
||||
|
func runIt(g *G, h interface{}) { |
||||
|
defer timeTrack(time.Now(), g) |
||||
|
g.mutex.Lock() |
||||
|
g.timedOut = false |
||||
|
g.mutex.Unlock() |
||||
|
g.timer = time.NewTimer(g.timeout) |
||||
|
g.shouldContinue = make(chan bool) |
||||
|
if call, ok := h.(func()); ok { |
||||
|
// the test is synchronous
|
||||
|
go func(c chan bool) { call(); c <- true }(g.shouldContinue) |
||||
|
} else if call, ok := h.(func(Done)); ok { |
||||
|
doneCalled := 0 |
||||
|
go func(c chan bool) { |
||||
|
call(func(msg ...interface{}) { |
||||
|
if len(msg) > 0 { |
||||
|
g.Fail(msg) |
||||
|
} else { |
||||
|
doneCalled++ |
||||
|
if doneCalled > 1 { |
||||
|
g.Fail("Done called multiple times") |
||||
|
} |
||||
|
c <- true |
||||
|
} |
||||
|
}) |
||||
|
}(g.shouldContinue) |
||||
|
} else { |
||||
|
panic("Not implemented.") |
||||
|
} |
||||
|
select { |
||||
|
case <-g.shouldContinue: |
||||
|
case <-g.timer.C: |
||||
|
//Set to nil as it shouldn't continue
|
||||
|
g.shouldContinue = nil |
||||
|
g.timedOut = true |
||||
|
g.Fail("Test exceeded " + fmt.Sprintf("%s", g.timeout)) |
||||
|
} |
||||
|
// Reset timeout value
|
||||
|
g.timeout = *timeout |
||||
|
} |
||||
|
|
||||
|
type G struct { |
||||
|
t *testing.T |
||||
|
parent *Describe |
||||
|
currentIt *It |
||||
|
timeout time.Duration |
||||
|
reporter Reporter |
||||
|
timedOut bool |
||||
|
shouldContinue chan bool |
||||
|
mutex sync.Mutex |
||||
|
timer *time.Timer |
||||
|
} |
||||
|
|
||||
|
func (g *G) SetReporter(r Reporter) { |
||||
|
g.reporter = r |
||||
|
} |
||||
|
|
||||
|
func (g *G) It(name string, h ...interface{}) { |
||||
|
if matchesRegex(name) { |
||||
|
it := &It{name: name, parent: g.parent, reporter: g.reporter} |
||||
|
notifyParents(g.parent) |
||||
|
if len(h) > 0 { |
||||
|
it.h = h[0] |
||||
|
} |
||||
|
g.parent.children = append(g.parent.children, Runnable(it)) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func matchesRegex(value string) bool { |
||||
|
if runRegex != nil { |
||||
|
return runRegex.MatchString(value) |
||||
|
} |
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
func notifyParents(d *Describe) { |
||||
|
d.hasTests = true |
||||
|
if d.parent != nil { |
||||
|
notifyParents(d.parent) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (g *G) Before(h func()) { |
||||
|
g.parent.befores = append(g.parent.befores, h) |
||||
|
} |
||||
|
|
||||
|
func (g *G) BeforeEach(h func()) { |
||||
|
g.parent.beforeEach = append(g.parent.beforeEach, h) |
||||
|
} |
||||
|
|
||||
|
func (g *G) After(h func()) { |
||||
|
g.parent.afters = append(g.parent.afters, h) |
||||
|
} |
||||
|
|
||||
|
func (g *G) AfterEach(h func()) { |
||||
|
g.parent.afterEach = append(g.parent.afterEach, h) |
||||
|
} |
||||
|
|
||||
|
func (g *G) Assert(src interface{}) *Assertion { |
||||
|
return &Assertion{src: src, fail: g.Fail} |
||||
|
} |
||||
|
|
||||
|
func timeTrack(start time.Time, g *G) { |
||||
|
g.reporter.itTook(time.Since(start)) |
||||
|
} |
||||
|
|
||||
|
func (g *G) Fail(error interface{}) { |
||||
|
//Skips 7 stacks due to the functions between the stack and the test
|
||||
|
stack := ResolveStack(7) |
||||
|
message := fmt.Sprintf("%v", error) |
||||
|
g.currentIt.failed(message, stack) |
||||
|
if g.shouldContinue != nil { |
||||
|
g.shouldContinue <- true |
||||
|
} |
||||
|
g.mutex.Lock() |
||||
|
defer g.mutex.Unlock() |
||||
|
if !g.timedOut { |
||||
|
//Stop test function execution
|
||||
|
runtime.Goexit() |
||||
|
} |
||||
|
|
||||
|
} |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 18 KiB |
@ -0,0 +1,26 @@ |
|||||
|
package goblin |
||||
|
|
||||
|
import () |
||||
|
|
||||
|
type Monochrome struct { |
||||
|
} |
||||
|
|
||||
|
func (self *Monochrome) Red(text string) string { |
||||
|
return "!" + text |
||||
|
} |
||||
|
|
||||
|
func (self *Monochrome) Gray(text string) string { |
||||
|
return text |
||||
|
} |
||||
|
|
||||
|
func (self *Monochrome) Cyan(text string) string { |
||||
|
return text |
||||
|
} |
||||
|
|
||||
|
func (self *Monochrome) WithCheck(text string) string { |
||||
|
return ">>>" + text |
||||
|
} |
||||
|
|
||||
|
func (self *Monochrome) Green(text string) string { |
||||
|
return text |
||||
|
} |
@ -0,0 +1,137 @@ |
|||||
|
package goblin |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"strconv" |
||||
|
"strings" |
||||
|
"time" |
||||
|
) |
||||
|
|
||||
|
type Reporter interface { |
||||
|
beginDescribe(string) |
||||
|
endDescribe() |
||||
|
begin() |
||||
|
end() |
||||
|
failure(*Failure) |
||||
|
itTook(time.Duration) |
||||
|
itFailed(string) |
||||
|
itPassed(string) |
||||
|
itIsPending(string) |
||||
|
} |
||||
|
|
||||
|
type TextFancier interface { |
||||
|
Red(text string) string |
||||
|
Gray(text string) string |
||||
|
Cyan(text string) string |
||||
|
Green(text string) string |
||||
|
WithCheck(text string) string |
||||
|
} |
||||
|
|
||||
|
type DetailedReporter struct { |
||||
|
level, failed, passed, pending int |
||||
|
failures []*Failure |
||||
|
executionTime, totalExecutionTime time.Duration |
||||
|
fancy TextFancier |
||||
|
} |
||||
|
|
||||
|
func (r *DetailedReporter) SetTextFancier(f TextFancier) { |
||||
|
r.fancy = f |
||||
|
} |
||||
|
|
||||
|
type TerminalFancier struct { |
||||
|
} |
||||
|
|
||||
|
func (self *TerminalFancier) Red(text string) string { |
||||
|
return "\033[31m" + text + "\033[0m" |
||||
|
} |
||||
|
|
||||
|
func (self *TerminalFancier) Gray(text string) string { |
||||
|
return "\033[90m" + text + "\033[0m" |
||||
|
} |
||||
|
|
||||
|
func (self *TerminalFancier) Cyan(text string) string { |
||||
|
return "\033[36m" + text + "\033[0m" |
||||
|
} |
||||
|
|
||||
|
func (self *TerminalFancier) Green(text string) string { |
||||
|
return "\033[32m" + text + "\033[0m" |
||||
|
} |
||||
|
|
||||
|
func (self *TerminalFancier) WithCheck(text string) string { |
||||
|
return "\033[32m\u2713\033[0m " + text |
||||
|
} |
||||
|
|
||||
|
func (r *DetailedReporter) getSpace() string { |
||||
|
return strings.Repeat(" ", (r.level+1)*2) |
||||
|
} |
||||
|
|
||||
|
func (r *DetailedReporter) failure(failure *Failure) { |
||||
|
r.failures = append(r.failures, failure) |
||||
|
} |
||||
|
|
||||
|
func (r *DetailedReporter) print(text string) { |
||||
|
fmt.Printf("%v%v\n", r.getSpace(), text) |
||||
|
} |
||||
|
|
||||
|
func (r *DetailedReporter) printWithCheck(text string) { |
||||
|
fmt.Printf("%v%v\n", r.getSpace(), r.fancy.WithCheck(text)) |
||||
|
} |
||||
|
|
||||
|
func (r *DetailedReporter) beginDescribe(name string) { |
||||
|
fmt.Println("") |
||||
|
r.print(name) |
||||
|
r.level++ |
||||
|
} |
||||
|
|
||||
|
func (r *DetailedReporter) endDescribe() { |
||||
|
r.level-- |
||||
|
} |
||||
|
|
||||
|
func (r *DetailedReporter) itTook(duration time.Duration) { |
||||
|
r.executionTime = duration |
||||
|
r.totalExecutionTime += duration |
||||
|
} |
||||
|
|
||||
|
func (r *DetailedReporter) itFailed(name string) { |
||||
|
r.failed++ |
||||
|
r.print(r.fancy.Red(strconv.Itoa(r.failed) + ") " + name)) |
||||
|
} |
||||
|
|
||||
|
func (r *DetailedReporter) itPassed(name string) { |
||||
|
r.passed++ |
||||
|
r.printWithCheck(r.fancy.Gray(name)) |
||||
|
} |
||||
|
|
||||
|
func (r *DetailedReporter) itIsPending(name string) { |
||||
|
r.pending++ |
||||
|
r.print(r.fancy.Cyan("- " + name)) |
||||
|
} |
||||
|
|
||||
|
func (r *DetailedReporter) begin() { |
||||
|
} |
||||
|
|
||||
|
func (r *DetailedReporter) end() { |
||||
|
comp := fmt.Sprintf("%d tests complete", r.passed) |
||||
|
t := fmt.Sprintf("(%d ms)", r.totalExecutionTime/time.Millisecond) |
||||
|
|
||||
|
//fmt.Printf("\n\n \033[32m%d tests complete\033[0m \033[90m(%d ms)\033[0m\n", r.passed, r.totalExecutionTime/time.Millisecond)
|
||||
|
fmt.Printf("\n\n %v %v\n", r.fancy.Green(comp), r.fancy.Gray(t)) |
||||
|
|
||||
|
if r.pending > 0 { |
||||
|
pend := fmt.Sprintf("%d test(s) pending", r.pending) |
||||
|
fmt.Printf(" %v\n\n", r.fancy.Cyan(pend)) |
||||
|
} |
||||
|
|
||||
|
if len(r.failures) > 0 { |
||||
|
fmt.Printf("%s \n\n", r.fancy.Red(fmt.Sprintf(" %d tests failed:", len(r.failures)))) |
||||
|
|
||||
|
} |
||||
|
|
||||
|
for i, failure := range r.failures { |
||||
|
fmt.Printf(" %d) %s:\n\n", i+1, failure.testName) |
||||
|
fmt.Printf(" %s\n", r.fancy.Red(failure.message)) |
||||
|
for _, stackItem := range failure.stack { |
||||
|
fmt.Printf(" %s\n", r.fancy.Gray(stackItem)) |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,21 @@ |
|||||
|
package goblin |
||||
|
|
||||
|
import ( |
||||
|
"runtime/debug" |
||||
|
"strings" |
||||
|
) |
||||
|
|
||||
|
func ResolveStack(skip int) []string { |
||||
|
return cleanStack(debug.Stack(), skip) |
||||
|
} |
||||
|
|
||||
|
func cleanStack(stack []byte, skip int) []string { |
||||
|
arrayStack := strings.Split(string(stack), "\n") |
||||
|
var finalStack []string |
||||
|
for i := skip; i < len(arrayStack); i++ { |
||||
|
if strings.Contains(arrayStack[i], ".go") { |
||||
|
finalStack = append(finalStack, arrayStack[i]) |
||||
|
} |
||||
|
} |
||||
|
return finalStack |
||||
|
} |
Loading…
Reference in new issue