leanovate / gopter

GOlang Property TestER
MIT License
599 stars 40 forks source link

Nested structs with Ptr of usage panics #40

Closed jhedev closed 6 years ago

jhedev commented 6 years ago

The following code panics sometimes:

package main

import (
    "github.com/leanovate/gopter"
    "github.com/leanovate/gopter/gen"
    "github.com/leanovate/gopter/prop"
)

type A struct {
    Value *float64
}

func genA() gopter.Gen {
    return gopter.DeriveGen(
        func(f *float64) A {
            return A{Value: f}
        },
        func(a A) *float64 {
            return a.Value
        },
        gen.PtrOf(gen.Float64()),
    )
}

type B struct {
    A1 A
}

func genB() gopter.Gen {
    return gopter.DeriveGen(
        func(a1 A) B {
            return B{a1}
        }, func(b B) A {
            return b.A1
        },
        genA(),
    )
}

func main() {
    properties := gopter.NewProperties(nil)

    properties.Property("should not panic", prop.ForAll(
        func(b B) bool {
            return false
        },
        genB(),
    ))

    properties.Run(gopter.ConsoleReporter(false))
}

with the output being:

! should not panic: Error on property evaluation after 0 passed tests:
   Check paniced: reflect: call of reflect.Value.Interface on zero Value
   goroutine 1 [running]:
runtime/debug.Stack(0xc4200556b0, 0x10f6840, 0xc42000aa80)
    /usr/local/Cellar/go/1.10.3/libexec/src/runtime/debug/stack.go:24 +0xa7
github.com/leanovate/gopter.SaveProp.func1.1(0xc420055d30)
    /Users/joel/code/go/src/github.com/leanovate/gopter/prop.go:19 +0x6e
panic(0x10f6840, 0xc42000aa80)
    /usr/local/Cellar/go/1.10.3/libexec/src/runtime/panic.go:502 +0x229
reflect.valueInterface(0x0, 0x0, 0x0, 0x1, 0x10e49e0, 0x0)
    /usr/local/Cellar/go/1.10.3/libexec/src/reflect/value.go:953 +0x1a3
reflect.Value.Interface(0x0, 0x0, 0x0, 0x0, 0x0)
    /usr/local/Cellar/go/1.10.3/libexec/src/reflect/value.go:948 +0x44
github.com/leanovate/gopter/gen.PtrShrinker.func1(0x10e49e0, 0x0, 0x1)
    /Users/joel/code/go/src/github.com/leanovate/gopter/gen/ptr_shrink.go:28
   +0xc1
github.com/leanovate/gopter.CombineShrinker.func1(0x10e8c80, 0xc42000aa60,
   0x10e8c80)
    /Users/joel/code/go/src/github.com/leanovate/gopter/shrink.go:168 +0x139
github.com/leanovate/gopter.(*derivedGen).Shrinker(0xc420094500,
   0x10fc000, 0x0, 0xc42000c0e0)
    /Users/joel/code/go/src/github.com/leanovate/gopter/derived_gen.go:81
   +0xe3
github.com/leanovate/gopter.(*derivedGen).Shrinker-fm(0x10fc000, 0x0, 0x1)
    /Users/joel/code/go/src/github.com/leanovate/gopter/derived_gen.go:31
   +0x3e
github.com/leanovate/gopter.CombineShrinker.func1(0x10e8c80, 0xc42000aa00,
   0x10e8c80)
    /Users/joel/code/go/src/github.com/leanovate/gopter/shrink.go:168 +0x139
github.com/leanovate/gopter.(*derivedGen).Shrinker(0xc420094780,
   0x10fc080, 0x0, 0xc420055b90)
    /Users/joel/code/go/src/github.com/leanovate/gopter/derived_gen.go:81
   +0xe3
github.com/leanovate/gopter.(*derivedGen).Shrinker-fm(0x10fc080, 0x0,
   0x10fc080)
    /Users/joel/code/go/src/github.com/leanovate/gopter/derived_gen.go:31
   +0x3e
github.com/leanovate/gopter/prop.shrinkValue(0x3e8, 0xc420094a00,
   0x10fc080, 0x0, 0xc420094a50, 0xc420055cb8, 0x10c4dbc, 0x10fc980,
   0xc42000e480)
    /Users/joel/code/go/src/github.com/leanovate/gopter/prop/forall.go:99
   +0x4e
github.com/leanovate/gopter/prop.ForAll.func1(0xc42000a620, 0x1122320)
    /Users/joel/code/go/src/github.com/leanovate/gopter/prop/forall.go:47
   +0x532
github.com/leanovate/gopter.SaveProp.func1(0xc42000a620, 0x0)
    /Users/joel/code/go/src/github.com/leanovate/gopter/prop.go:24 +0x6c
github.com/leanovate/gopter.Prop.Check.func1(0x0, 0xc42000e4c0, 0x11c69a0)
    /Users/joel/code/go/src/github.com/leanovate/gopter/prop.go:52 +0x15c
github.com/leanovate/gopter.(*runner).runWorkers(0xc420088570, 0x0)
    /Users/joel/code/go/src/github.com/leanovate/gopter/runner.go:49 +0x2d9
github.com/leanovate/gopter.Prop.Check(0xc42000e4a0, 0xc42001e6c0,
   0x111b6c4)
    /Users/joel/code/go/src/github.com/leanovate/gopter/prop.go:110 +0x1af
github.com/leanovate/gopter.(*Properties).Run(0xc4200882d0, 0x1131560,
   0xc42000a5e0, 0x10)
    /Users/joel/code/go/src/github.com/leanovate/gopter/properties.go:37 +0xc9
main.main()
    /Users/joel/code/gopter_sample.go:50 +0x1b0

It seems to only happen if the shrinker is used. If I change, for example, the return false in the property to return true it does never happen.

Somehow it requires these two nested structs. Using just struct A does not result in a panic... Not sure why or if I was just unlucky when reproducing...

It looks like the problem is in the Ptr shrink implementation where the call to Interface panics on a nil value.

I'm happy to help to fix this but I need a bit of guidance :)

jhedev commented 6 years ago

I've now found a similar problem when using slices: reflect: call of reflect.Value.Set on zero Value

untoldwind commented 6 years ago

Just patched the ptr shrinker to avoid this kind of problem, please check if it really solves it as it seems to be pretty erratic.

Can you also give me a hint about the problem with slices (i.e. stacktrack or example)

jhedev commented 6 years ago

Thanks so much for the quick fix!

The slice thing happens when I adapt the example above like this:

package main

import (
    "github.com/leanovate/gopter"
    "github.com/leanovate/gopter/gen"
    "github.com/leanovate/gopter/prop"
)

type A struct {
    Value *float64
}

func genA() gopter.Gen {
    return gopter.DeriveGen(
        func(f *float64) A {
            return A{Value: f}
        },
        func(a A) *float64 {
            return a.Value
        },
        gen.PtrOf(gen.Float64()),
    )
}

type B struct {
    A1 A
}

func genB() gopter.Gen {
    return gopter.DeriveGen(
        func(a1 A) B {
            return B{a1}
        }, func(b B) A {
            return b.A1
        },
        genA(),
    )
}

func main() {
    properties := gopter.NewProperties(nil)

    properties.Property("should not panic", prop.ForAll(
        func(b []*B) bool {
            if len(b) == 0 {
                return true
            }
            return false
        },
        gen.SliceOf(gen.PtrOf(genB())),
    ))

    properties.Run(gopter.ConsoleReporter(false))
}

Output is:

! should not panic: Error on property evaluation after 2 passed tests:
   Check paniced: reflect: call of reflect.Value.Set on zero Value goroutine
   1 [running]:
runtime/debug.Stack(0xc420055800, 0x10f7100, 0xc42000b600)
    /usr/local/Cellar/go/1.10.3/libexec/src/runtime/debug/stack.go:24 +0xa7
github.com/leanovate/gopter.SaveProp.func1.1(0xc420055d30)
    /Users/joel/code/go/src/github.com/leanovate/gopter/prop.go:19 +0x6e
panic(0x10f7100, 0xc42000b600)
    /usr/local/Cellar/go/1.10.3/libexec/src/runtime/panic.go:502 +0x229
reflect.flag.mustBeExported(0x0)
    /usr/local/Cellar/go/1.10.3/libexec/src/reflect/value.go:215 +0xae
reflect.Value.Set(0x10e5920, 0xc42000c1a8, 0x196, 0x0, 0x0, 0x0)
    /usr/local/Cellar/go/1.10.3/libexec/src/reflect/value.go:1368 +0x40
github.com/leanovate/gopter/gen.(*sliceShrinkOne).Next(0xc4200889c0, 0x0,
   0x0, 0x100e800)
    /Users/joel/code/go/src/github.com/leanovate/gopter/gen/slice_shrink.go:23
   +0x21a
github.com/leanovate/gopter/gen.(*sliceShrinkOne).Next-fm(0x0, 0x0,
   0xc42000c100)
    /Users/joel/code/go/src/github.com/leanovate/gopter/gen/slice_shrink.go:45
   +0x2a
github.com/leanovate/gopter.(*concatedShrink).Next(0xc42000b5a0,
   0xc42000b5c0, 0x0, 0x11c80e0)
    /Users/joel/code/go/src/github.com/leanovate/gopter/shrink.go:76 +0x4b
github.com/leanovate/gopter.(*concatedShrink).Next-fm(0x0, 0xc4200889c0,
   0xc4200888a0)
    /Users/joel/code/go/src/github.com/leanovate/gopter/shrink.go:91 +0x2a
github.com/leanovate/gopter.Shrink.Filter.func1(0xc42000b5c0,
   0xc420055b48, 0x10cb17b)
    /Users/joel/code/go/src/github.com/leanovate/gopter/shrink.go:24 +0x3b
github.com/leanovate/gopter/prop.firstFailure(0xc42000b5c0, 0xc420055cb8,
   0xc42000b5c0, 0xc42000b140, 0xc420055bd0)
    /Users/joel/code/go/src/github.com/leanovate/gopter/prop/forall.go:114
   +0x2b
github.com/leanovate/gopter/prop.shrinkValue(0x3e8, 0xc420095450,
   0x10e8c40, 0xc42000b140, 0xc4200954a0, 0xc420055cb8, 0xc42001e740,
   0x10fd240, 0xc42000e4a0)
    /Users/joel/code/go/src/github.com/leanovate/gopter/prop/forall.go:100
   +0x88
github.com/leanovate/gopter/prop.ForAll.func1(0xc42000ada0, 0x1122c18)
    /Users/joel/code/go/src/github.com/leanovate/gopter/prop/forall.go:47
   +0x532
github.com/leanovate/gopter.SaveProp.func1(0xc42000ada0, 0x0)
    /Users/joel/code/go/src/github.com/leanovate/gopter/prop.go:24 +0x6c
github.com/leanovate/gopter.Prop.Check.func1(0x0, 0xc42000e500, 0x11c79a0)
    /Users/joel/code/go/src/github.com/leanovate/gopter/prop.go:52 +0x15c
github.com/leanovate/gopter.(*runner).runWorkers(0xc420088570, 0x0)
    /Users/joel/code/go/src/github.com/leanovate/gopter/runner.go:49 +0x2d9
github.com/leanovate/gopter.Prop.Check(0xc42000e4e0, 0xc42001e6c0,
   0x111bf84)
    /Users/joel/code/go/src/github.com/leanovate/gopter/prop.go:110 +0x1af
github.com/leanovate/gopter.(*Properties).Run(0xc4200882d0, 0x1131ee0,
   0xc42000a620, 0x10)
    /Users/joel/code/go/src/github.com/leanovate/gopter/properties.go:37 +0xc9
main.main()
    /Users/joel/code/gopter_sample.go:53 +0x1c4
untoldwind commented 6 years ago

The panic message was a bit misleading in this case.

The patch should fix this problem (at least I was not able to reproduce the panic any more)

jhedev commented 6 years ago

@untoldwind Thanks again for your quick help. I'll try it with our test suite asap

jhedev commented 6 years ago

I ran the test suite again and the error did not occur anymore.