leanovate / gopter

GOlang Property TestER
MIT License
599 stars 40 forks source link

GenCommand: Generating Commands Based On The Current State #69

Closed jrife closed 4 years ago

jrife commented 4 years ago

I have a scenario where I'd like to generate commands for a system under test based on the current state of the system. For example, let's say my system under test is an on-disk set of strings and I've modeled it with an in-memory set. It supports two operations: Add and Has:

Add() string adds a random string to the set and returns this string Has(s string) bool returns true if the given string is in the set

To test this system I might have a generator for Add and Has commands and my test would apply the commands to each system and check that the results match. When I generate my Has commands it would be useful if the current in-memory state could inform my generator so that I ensure that the string I'm passing to Has actually was added to the set. Would the state parameter of the GenCommandFunc function allow me to do something like this?

Here's the general idea I had for how I might structure the generators:

type MySet struct {
    s map[string]bool
}

func (s *MySet) Add() string {
    r := randomString()
    s.s[r] = true
    return r
}

func (s *MySet) Has(s string) bool {
    _, ok := s.s[s]
    return ok
}

func AddCommand() gopter.Gen {
    return gen.Const(&addCommand{})
}

func HasCommand(state *MySet) gopter.Gen {
    return gen.Int().Map(func(i int) commands.Command {
        if len(state.s) == 0 {
            return hasCommand("")
        }

        i = i % len(state.s)

        for _, str := range {
            if i == 0 {
                return hasCommand(str)
            }

            i--
        }
    })
}

In a scenario like this it's difficult because passing any random string to Has() would probably result in it returning false. It's very unlikely that a randomly generated string would match one returned by the system under test. What is the intended use of that parameter in the GenCommandFunc function? Is there a better way to construct generators for commands where the parameters of one command might rely on the result of another?

jrife commented 4 years ago

So I think I figured it out. It looks like in my case I wasn't advancing the state in one of the command's NextState calls so it messed up some of my logic. I fixed that and it's working as expected with commands being generated based on the model's state.