test-check 2022-03-08

maybe I'm not looking in the right places - but there seems to be not much information about writing customer generators?

Not knowing that much about custom generators, after auditing some repos I have a trivial stateful generator for producing commands to drive some test

it works by recursion by feeding the augmented state to produce the next command

by doing so then it becomes easy to make the command sequence more meaningful, by i.e. decreasing the frequency that the last command will be chosen, etc. etc.

but before proceeding any further I did a quick check (ba-dum) and of course it did not shrink

instead of generating a single command generate a list of commands, and statefully process each list, with a fresh state for each list

basically you want to generate programs, instead of generating a single command and building programs out of the generated command

if you generate a whole program then the whole program can shrink

the recursive thing does indeed produce a list of commands - but that is what doesn't shrink

ah, I thought you where doing the recursive thing outside of generating

looking at the built in generators - it appears that shrinking might be a bit manual

like you have to make the shrink tree yourself?

I've never had to manually do shrinking

I usually generate programs then feed them into some interpreter, and that interpret might no-op some commands in certain states, instead of feeding the state through the generator

hrm really, in the (repo docs) the first thing that it says is that if you build a stateful generator via bind - it will not shrink very well

oh yeah, I've had to massage things around to get good shrinking

because bind is opaque

I feel like @dnolen described this to me at a conference like five years ago

I think anything other than bind being a simple maping kind of operation (take a vector of numbers and produce a java.util.Date object) isn't going to shrink well

@gfredericks haha we probably talked about it - but now I actually have a reason to use this approach at work

I feel like maybe the best thought I had on this was that it'd be useful to have a general framework for this style of generator Something that could be implemented once against the lower level primitives

only tangentially related but I found this interesting paper - https://proper-testing.github.io/papers/icst2018.pdf

I assume the style is that you have a model of the state of the system at a given time, and can generate commands based on the current state, and each command transforms the current state to the next state?

PropEr is a open source Erlang framework for testing stateful systems, and they have been implementing things to make it easier to test stateful system

both by providing a framework, but also integrated simulated annealing to control the search

🤔 1

I've done that sort of thing, but without feeding the state through command generation, and then ignoring invalid commands in the interpreter

I don't really care one way or another about whether test-check provides such affordances

but rather some guidance on writing custom generators that might be influenced by above tactics

so if I recur on this generator, do I need to manually populate the rose tree w/ children for the previous state

so it can shrink later?

Custom shrinks aren't available at all in the main user facing api I don't think

yes I know that 🙂

but the non-doc'ed but not private internal helpers can be used as far as I can tell (to accomplish the goal, not future-proof)

By "manually popular the rose tree" do you mean something like the haskell quickcheck api where you provide a function X -> [X] X being a list of commands

(oh yeah, re: using test-check, I think that was about testing stateful UIs - barely remember that so long ago)

@gfredericks can you not just gen-fmap and create new rose w/ what you know should be the children?

Alex Miller (Clojure team) 2022-03-08T19:58:59.137579Z

if you build out of fmap and bind, it will shrink

Alex Miller (Clojure team) 2022-03-08T20:00:47.574349Z

right? looks at gfredericks

This kind of thing can definitely shrink, but often it's degenerate

If you generate 200 commands

And each is logically dependent on all the earlier ones

Then shrinking command #7 invalidates the other 293 of them

So they have to be replaced with freshly generated commands based on the new state after the new command 7

Because TC can't do better than that generically

But dnolen with his domain knowledge can describe more particularly when it's valid to shrink or delete intermediate commands

And so can get much better results

Alex Miller (Clojure team) 2022-03-08T20:04:07.088739Z

👍

So the choice is A) add a new feature for this situation that lets users express that domain knowledge B) dnolen does this particular case by hand

Writing your own shrink tree can be laborious because it's hard to take advantage of the built in shrinking for the atomic types you're using

I also had an idea for a user-enhanced shrinking, but not sure that's a comprehensive solution here

@gfredericks design is hard ... I'm curious as to why the shrinking stuff that's there is not just exposed and documented

it seemed like one of the novel things about test-check was that shrinking was decoupled - but then somehow not a part of the api

I don't think that was too intentional I mean you could ask Reid if you want historical context I largely took the approach of not changing things without a really strong reason So I think it's totally valid to suggest exposing it

I think the hope was that the built in shrinking was high enough quality that users wouldn't have to think about it Probably not realistic, but it works much better than the QuickCheck approach I think

I think you don't really hit this problem until you want to write stateful generators

then the domain starts to dominate and the defaults just aren't that useful - because it's mostly junk

I will continue poke around at what's there for now - still barely know how to do anything so will probably say marginally more intelligent things later 😉

Oh hey, I wrote something about this

yes already read that 🙂

several times

it is iterate (unfold) for generators

@dnolen does the idea there under "general problem" seem like it would cover your use?

not entirely sure, but it seems like https://github.com/advancedtelematic/quickcheck-state-machine takes the approach of generating events without regard to state, but has you define preconditions which say if some event + state is well formed

(I can tell the precondition is a thing, but my haskell is not good enough to immediate tell if the precondition stuff is somehow used as part of generation or just as a filter afterwards)

basically the problem that I've encountered so far is as @gfredericks alluded - steps depend on previous steps

> The pre-conditions are used while generating programs (lists of actions).

as the command grows expecting to randomly line anything up is unlikely

instead we want to look at the previous state to make intelligent but not totally predetermined steps

at the same time, if something is found, we want to it to shrink

ok manually managing the rose tree seems like it leads to better results here

@gfredericks I do see that one irritation w/ the rose tree versions is that mixing things (on accident) leads to very cryptic problems

Another option would be, given a state machine description S, generate a vector of numbers, and then in a map bind, from your start state, take the sorted possible transitions, mod the first integer and take the nth, then iterate that for each state and integer

Which would shrink based on vectors and int generators, which might not be the most intelligent shrinking, but avoids recursive generation passing the state through

hrm, the problem has more constraints then it may appear, so I don't see that approach as being less cumbersome?

in truth what I'm interested in is simulation generation

that is there is some actor A, and then they do something

well then maybe there is corresponding action for actor B

and then also a corresponding action for actor C

Sure, and I guess you'd get shrinking, but the behavior would be very odd

because frequencies is dynamic - I'm also interested in using the state at each step to dynamically bind new likelihoods

so the tools in test.check at the moment seem sufficient to accomplish what I want, without going elsewhere

I think probably only the command choice function (which generates the liklihoods and the command arguments) will be of any real interest

the rest will probably be boring

Alex Miller (Clojure team) 2022-03-08T21:10:04.325279Z

sounds like you are really doing simulation testing. we did several projects like this at Cognitect with Simulant (and other Simulant like things). there are a couple other open source things we made for those

but we are not actually testing a whole system - we don't care about time

and we're going to drive anything that would be external

we don't even care about concurrency

we really don't care about time

we have a purely functional system - for what we want to test - no externalities are of interest

value to value only - including all storage of interest (Datomic and custom data structures for cryptographic purposes)

thus test.check is attractive

sure, but if you have a succession of states, that is a ticking clock (how things like event clocks work)

but it's not of any interest at all

in fact what we are interested is only the cryptographic results at the end of sequence

they must hold - nothing else really matters

the key is actually we are in fact looking for the shortest sequences that would break some invariant - I don't think the simulation stuff can do that @alexmiller ?

Alex Miller (Clojure team) 2022-03-08T21:25:39.577379Z

no, that's different use case