Skip to content

Commit

Permalink
Merge pull request #18 from maxatome/CmpPanic
Browse files Browse the repository at this point in the history
Add CmpPanic & CmpNoPanic functions + same T methods
  • Loading branch information
maxatome committed Jun 17, 2018
2 parents a9a3532 + 90a00fa commit da61dc8
Show file tree
Hide file tree
Showing 6 changed files with 323 additions and 0 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,28 @@ Golang package `testdeep` allows extremely flexible deep comparison,
built for testing.


## Latest news

- 2018/06/17: new
[`CmpPanic`](https://godoc.org/github.com/maxatome/go-testdeep#CmpPanic)
&
[`CmpNoPanic`](https://godoc.org/github.com/maxatome/go-testdeep#CmpNoPanic)
functions and
[`T.CmpPanic`](https://godoc.org/github.com/maxatome/go-testdeep#T.CmpPanic)
&
[`T.CmpNoPanic`](https://godoc.org/github.com/maxatome/go-testdeep#T.CmpNoPanic)
methods
- 2018/06/15: new
[`Smuggle`](https://godoc.org/github.com/maxatome/go-testdeep#Smuggle)
operator (and its friends
[`CmpSmuggle`](https://godoc.org/github.com/maxatome/go-testdeep#CmpSmuggle)
&
[`T.CmpSmuggle`](https://godoc.org/github.com/maxatome/go-testdeep#T.CmpSmuggle));
- 2018/06/11: `DefaultContextConfig.MaxErrors` defaults to 10 (was 1);
- see [commits history](https://github.com/maxatome/go-testdeep/commits/master)
for other/older changes.


## Synopsis

```go
Expand Down
113 changes: 113 additions & 0 deletions cmp_funcs_misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

package testdeep

import "runtime"

// CmpTrue is a shortcut for:
//
// CmpDeeply(t, got, true, args...)
Expand Down Expand Up @@ -101,3 +103,114 @@ func CmpNoError(t TestingT, got error, args ...interface{}) bool {
t.Helper()
return cmpNoError(NewContext(), t, got, args...)
}

func cmpPanic(ctx Context, t TestingT, fn func(), expected interface{}, args ...interface{}) bool {
t.Helper()

if ctx.path == contextDefaultRootName {
ctx.path = contextPanicRootName
}

var (
panicked bool
panicParam interface{}
)

func() {
defer func() { panicParam = recover() }()
panicked = true
fn()
panicked = false
}()

if !panicked {
formatError(t,
&Error{
Context: ctx,
Message: "should have panicked",
Summary: rawString("did not panic"),
},
args...)
return false
}

return cmpDeeply(ctx.AddDepth("→panic()"), t, panicParam, expected, args...)
}

func cmpNotPanic(ctx Context, t TestingT, fn func(), args ...interface{}) bool {
var (
panicked bool
stackTrace rawString
)

func() {
defer func() {
panicParam := recover()
if panicked {
buf := make([]byte, 8192)
n := runtime.Stack(buf, false)
for ; n > 0; n-- {
if buf[n-1] != '\n' {
break
}
}
stackTrace = rawString("panic: " + toString(panicParam) + "\n\n" +
string(buf[:n]))
}
}()
panicked = true
fn()
panicked = false
}()

if !panicked {
return true
}

t.Helper()

if ctx.path == contextDefaultRootName {
ctx.path = contextPanicRootName
}

formatError(t,
&Error{
Context: ctx,
Message: "should NOT have panicked",
Got: stackTrace,
Expected: rawString("not panicking at all"),
})
return false
}

// CmpPanic calls "fn" and checks a panic() occurred with the
// "expectedPanic" parameter. It returns true only if both conditions
// are fulfilled.
//
// Note that calling panic(nil) in "fn" body is detected as a panic
// (in this case "expectedPanic" has to be nil.)
//
// "args..." are optional and allow to name the test. This name is
// logged as is in case of failure. If len(args) > 1 and the first
// item of args is a string and contains a '%' rune then fmt.Fprintf
// is used to compose the name, else args are passed to fmt.Fprint.
func CmpPanic(t TestingT, fn func(), expectedPanic interface{},
args ...interface{}) bool {
t.Helper()
return cmpPanic(NewContext(), t, fn, expectedPanic, args...)
}

// CmpNotPanic calls "fn" and checks no panic() occurred. If a panic()
// occurred false is returned then the panic() parameter and the stack
// trace appear in the test report.
//
// Note that calling panic(nil) in "fn" body is detected as a panic.
//
// "args..." are optional and allow to name the test. This name is
// logged as is in case of failure. If len(args) > 1 and the first
// item of args is a string and contains a '%' rune then fmt.Fprintf
// is used to compose the name, else args are passed to fmt.Fprint.
func CmpNotPanic(t TestingT, fn func(), args ...interface{}) bool {
t.Helper()
return cmpNotPanic(NewContext(), t, fn, args...)
}
79 changes: 79 additions & 0 deletions cmp_funcs_misc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,82 @@ func ExampleCmpNoError() {
// false
// true
}

func ExampleCmpPanic() {
t := &testing.T{}

ok := CmpPanic(t,
func() { panic("I am panicking!") }, "I am panicking!",
"Checks for panic")
fmt.Println("checks exact panic() string:", ok)

// Can use TestDeep operator too
ok = CmpPanic(t,
func() { panic("I am panicking!") }, Contains("panicking!"),
"Checks for panic")
fmt.Println("checks panic() sub-string:", ok)

// Can detect panic(nil)
ok = CmpPanic(t, func() { panic(nil) }, nil, "Checks for panic(nil)")
fmt.Println("checks for panic(nil):", ok)

// As well as structured data panic
type PanicStruct struct {
Error string
Code int
}

ok = CmpPanic(t,
func() {
panic(PanicStruct{Error: "Memory violation", Code: 11})
},
PanicStruct{
Error: "Memory violation",
Code: 11,
})
fmt.Println("checks exact panic() struct:", ok)

// or combined with TestDeep operators too
ok = CmpPanic(t,
func() {
panic(PanicStruct{Error: "Memory violation", Code: 11})
},
Struct(PanicStruct{}, StructFields{
"Code": Between(10, 20),
}))
fmt.Println("checks panic() struct against TestDeep operators:", ok)

// Of course, do not panic = test failure, even for expected nil
// panic parameter
ok = CmpPanic(t, func() {}, nil)
fmt.Println("checks a panic occurred:", ok)

// Output:
// checks exact panic() string: true
// checks panic() sub-string: true
// checks for panic(nil): true
// checks exact panic() struct: true
// checks panic() struct against TestDeep operators: true
// checks a panic occurred: false
}

func ExampleCmpNotPanic() {
t := &testing.T{}

ok := CmpNotPanic(t, func() {}, nil)
fmt.Println("checks a panic DID NOT occur:", ok)

// Classic panic
ok = CmpNotPanic(t, func() { panic("I am panicking!") },
"Hope it does not panic!")
fmt.Println("still no panic?", ok)

// Can detect panic(nil)
ok = CmpNotPanic(t, func() { panic(nil) }, "Checks for panic(nil)")
fmt.Println("last no panic?", ok)

// Output:
// checks a panic DID NOT occur: true
// still no panic? false
// last no panic? false
}
1 change: 1 addition & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type ContextConfig struct {

const (
contextDefaultRootName = "DATA"
contextPanicRootName = "FUNCTION"
envMaxErrors = "TESTDEEP_MAX_ERRORS"
)

Expand Down
31 changes: 31 additions & 0 deletions t_struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,37 @@ func (t *T) CmpNoError(got error, args ...interface{}) bool {
return cmpNoError(NewContextWithConfig(t.Config), t.TestingFT, got, args...)
}

// CmpPanic calls "fn" and checks a panic() occurred with the
// "expectedPanic" parameter. It returns true only if both conditions
// are fulfilled.
//
// Note that calling panic(nil) in "fn" body is detected as a panic
// (in this case "expectedPanic" has to be nil.)
//
// "args..." are optional and allow to name the test. This name is
// logged as is in case of failure. If len(args) > 1 and the first
// item of args is a string and contains a '%' rune then fmt.Fprintf
// is used to compose the name, else args are passed to fmt.Fprint.
func (t *T) CmpPanic(fn func(), expected interface{}, args ...interface{}) bool {
t.Helper()
return cmpPanic(NewContextWithConfig(t.Config), t, fn, expected, args...)
}

// CmpNotPanic calls "fn" and checks no panic() occurred. If a panic()
// occurred false is returned then the panic() parameter and the stack
// trace appear in the test report.
//
// Note that calling panic(nil) in "fn" body is detected as a panic.
//
// "args..." are optional and allow to name the test. This name is
// logged as is in case of failure. If len(args) > 1 and the first
// item of args is a string and contains a '%' rune then fmt.Fprintf
// is used to compose the name, else args are passed to fmt.Fprint.
func (t *T) CmpNotPanic(fn func(), args ...interface{}) bool {
t.Helper()
return cmpNotPanic(NewContextWithConfig(t.Config), t, fn, args...)
}

// Run runs "f" as a subtest of t called "name". It runs "f" in a separate
// goroutine and blocks until "f" returns or calls t.Parallel to become
// a parallel test. Run reports whether "f" succeeded (or at least did
Expand Down
77 changes: 77 additions & 0 deletions t_struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,83 @@ func ExampleT_CmpNoError() {
// true
}

func ExampleT_CmpPanic() {
t := NewT(&testing.T{})

ok := t.CmpPanic(func() { panic("I am panicking!") }, "I am panicking!",
"Checks for panic")
fmt.Println("checks exact panic() string:", ok)

// Can use TestDeep operator too
ok = t.CmpPanic(func() { panic("I am panicking!") }, Contains("panicking!"),
"Checks for panic")
fmt.Println("checks panic() sub-string:", ok)

// Can detect panic(nil)
ok = t.CmpPanic(func() { panic(nil) }, nil, "Checks for panic(nil)")
fmt.Println("checks for panic(nil):", ok)

// As well as structured data panic
type PanicStruct struct {
Error string
Code int
}

ok = t.CmpPanic(
func() {
panic(PanicStruct{Error: "Memory violation", Code: 11})
},
PanicStruct{
Error: "Memory violation",
Code: 11,
})
fmt.Println("checks exact panic() struct:", ok)

// or combined with TestDeep operators too
ok = t.CmpPanic(
func() {
panic(PanicStruct{Error: "Memory violation", Code: 11})
},
Struct(PanicStruct{}, StructFields{
"Code": Between(10, 20),
}))
fmt.Println("checks panic() struct against TestDeep operators:", ok)

// Of course, do not panic = test failure, even for expected nil
// panic parameter
ok = t.CmpPanic(func() {}, nil)
fmt.Println("checks a panic occurred:", ok)

// Output:
// checks exact panic() string: true
// checks panic() sub-string: true
// checks for panic(nil): true
// checks exact panic() struct: true
// checks panic() struct against TestDeep operators: true
// checks a panic occurred: false
}

func ExampleT_CmpNotPanic() {
t := NewT(&testing.T{})

ok := t.CmpNotPanic(func() {}, nil)
fmt.Println("checks a panic DID NOT occur:", ok)

// Classic panic
ok = t.CmpNotPanic(func() { panic("I am panicking!") },
"Hope it does not panic!")
fmt.Println("still no panic?", ok)

// Can detect panic(nil)
ok = t.CmpNotPanic(func() { panic(nil) }, "Checks for panic(nil)")
fmt.Println("last no panic?", ok)

// Output:
// checks a panic DID NOT occur: true
// still no panic? false
// last no panic? false
}

func TestT(tt *testing.T) {
t := NewT(tt)
CmpDeeply(tt, t.Config, DefaultContextConfig)
Expand Down

0 comments on commit da61dc8

Please sign in to comment.