Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stateful generators and combinators #51

Open
alfert opened this issue Oct 15, 2017 · 13 comments
Open

Stateful generators and combinators #51

alfert opened this issue Oct 15, 2017 · 13 comments

Comments

@alfert
Copy link
Contributor

alfert commented Oct 15, 2017

I would like to see more combinators to create more complex data, in particular a reducer to create data depending on the already generated elements.

Background:

The reducers could be a nice solution for generating command sequences to support symbolic state machines for property testing of stateful systems. Here we start with an initial state and generate commands depending on the current state and producing a new state. A reduce or unfold function could be a solution for that problem.

If this goes along your development direction of StreamData, I can work on PRs for that.

@whatyouhide whatyouhide changed the title Statefull generator combinators Stateful generators and combinators Oct 15, 2017
@josevalim
Copy link
Contributor

@alfert this sounds very exciting but I am having some difficulty in seeing some of the use cases. Could you please expand on that area? Thank you!

@alfert
Copy link
Contributor Author

alfert commented Oct 15, 2017

@josevalim sure: property testing of stateful systems in Quickcheck, Proper, and others work with a state machine as the abstraction of the system under test (SUT) and a command generator, which generates a list of commands against the SUT. Pre- and postconditions on the state machine define valid command sequences, state transition of the state machine and connect SUT values with their state machine counter parts. There are two phases in stateful system testing: the first phase generate valid command sequences depending on the preconditions of the state machine, the second phase uses the generated command sequences and executes it against the SUT checking the postcondition of the state machine is the same as the results of the SUT. A failing property results in a shrinked command list to get the minimal sequence of commands that exhibit the error.

Use cases for this approach are property based testing of all kinds of stateful (sub) systems, such as GenServers, processes, but also data structures (see "breaking erlang maps" below). Another interesting approach is to run a long sequence of commands against a system, e.g. a sequence of 3.000 or more commands. These are tests which you would never write as a unit test and the property testing approach guarantees that each command execution sequence is different.

Some documentation and examples are here:

@josevalim
Copy link
Contributor

Thanks @alfert. I thought you were hinting at something else when you said about relying on reduce. :) Do you have a proposal about the API? Maybe something you already leverage in propcheck?

@alfert
Copy link
Contributor Author

alfert commented Oct 15, 2017

@josevalim Depends on what kind of API you are thinking about.

For the property testing, I think about following either QuickCheck or PropEr closely - this is work in progress and will be the foundational API. To make the state machine definition easier, I would like to define a macro based DSL which shall be nicer and more idiomatic than QuickCheck's new state machine DSL (relying on function name postfixes and a parse transform).

Regarding the use of StreamData, I am in the state of an early implementation but hit a wall when realizing that the combinators have no state (i.e. map, bind and filter). Reduce is for me the essential combinator, where you can deal with state (or time - as in Elm's foldp). But thinking about the infinite streams in StreamState, an unfold might be even more helpful. The basic approach in pseudo code is:

def command_generator(initial_state, command_list) do
  unfold(initial_state, fn state -> 
   cmd = command_list 
   |> Enum.filter(& StateMachine.precondition(state, &1))
   |> one_of()
   new_state = StateMachine.transition(state, cmd)
  {new_state, cmd}
  end) 
end

Until now, I avoided a deep dive into the inner workings of the LazyTree. But if an unfold like in Streams can be defined on top of LazyTree, I assume a proper command generator where each new element depends on the elements generated before should be do-able.

The implementation in propcheck is mostly a layer on top of PropEr, so I cannot take any implementation with me, since the generation mechanism and the internal data structure are completely different compared to StreamData. PropEr is a complex Erlang library and its internals are not for the faint-hearted.

@josevalim
Copy link
Contributor

This is ultimately @whatyouhide's call to make but maybe we could provide an unfold/reduce operation and leave the rest of the stateful generators up to libraries so we have a bit of experimentation before sticking with a final implementation.

I will also copy @fishcakez since I know he has some thoughts on the matter too.

@whatyouhide
Copy link
Owner

I think that we might be underestimating the changes that stateful generators would bring (or I didn't understand the scope of this discussion). Stateful generators were avoided from the start (after I tried really hard to make them work) because essentially

bind(gen1, fn term ->
  gen2
end)

invalidates the state of gen2 everytime we generate a new term from gen1. The only semi-solution I can see is to have a single generator that generates the next command and which is passed a list of previous commands (so that it can generate a valid command based on the previous ones). This would be a stateless generator.

Can you tell me if I'm on the right track and if what I'm proposing makes sense? Otherwise, how can we avoid the bind problem?

@josevalim how would unfold work?

@alfert
Copy link
Contributor Author

alfert commented Oct 17, 2017

I am currently working on a recursive translation of PropEr's command sequence generator. You more or less encouraged me to look more closely into their implementation, since they don't have such combinators. But I will progress slowly, since I can do this in my spare time only and weekend is over. I will come back to you, with either success or failure. So, do not invest too much time on this topic just to serve me.

@fishcakez
Copy link
Collaborator

I was able to get a statem generator working with an older version of this code, so it should still be possible. I haven't looked at the proper code but I used a body recursive call I think and shrinking worked okay. Unfortunately the laptop is in storage and I didn't take advantage of the distributed part of a DVCS.

I think that using a different DSL is a great idea, I was working on using native Elixir features to replace the awkward virtual state and to create a reproducible method that did not use seeds but generated test case files. Hopefully @alfert can make some progress as I doubt I will return to it any time soon.

@tmbb
Copy link

tmbb commented Oct 20, 2017

I have a question regarding stateful testing in general. As long as you're not messing with the scheduler, it's just a question of generating valid command sequences (according to the rules you give it) and executing them in order, right? Can't the stream_data generatos do it already? Or is there something I'm not seeing?

@fishcakez
Copy link
Collaborator

fishcakez commented Oct 20, 2017 via email

@alfert
Copy link
Contributor Author

alfert commented Dec 27, 2017

Back, after a long time....

I tried to translate the recursive command generators of proper, but I alway ran against the wall since list construction is not allowed by call (only atoms, tuples and StreamData structs). Interestingly the approach of @fishcakez in https://github.com/fishcakez/stream_code works, but to be honest I do not understand really what the difference is that makes it work.

I modified @fishcakez's approach such that it does not generate macro code but symbolic calls to come closer to the usual state-machine definition. Works nicely. But when I introduced failing properties to see how shrinking works, the generated list of commands did not shrink but only tried various elements in the list's head.

My next approach was to modify the list_of generator to allow for an accumulator and data generator working together with the accumulator, similar to Stream.unfold in its arguments. This works and I will publish a PR for that soon introducing an unfold function. It requires only a small modification of call_n_times and reuses everything else from list_of. With that in place, list_of itself can be expressed as the following

    def list_of(data, options \\ []) do
       unfold(nil, fn _ -> {nil, data} end, options)
    end 

@keathley
Copy link
Contributor

keathley commented Feb 4, 2018

I wanted to see if there was any progress that had been made on this issue and if I might be able to help contribute in any way. This is something that our team is very interested in since eqc is currently prohibitively expensive and Proper's licensing makes it a non-starter for our organization.

@alfert
Copy link
Contributor Author

alfert commented Apr 2, 2018

As a funny co-incidence of @keathley' new issue #94, I finally had a good idea to implement the PropEr API for stateful testing including shrinking on top of stream_data. You can find this in repo https://github.com/alfert/counter. I needed to copy some of the private functions of stream_data to make it work. I put it into a separate repo to research various strategies including some tests with demo systems and a comparison to PropEr's approach. We can later merge it into stream_data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants