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