Welcome! Please see the About page for a little more info on how this works.

0 votes
in test.check by

There are a lot of generators that return a sequence (list, vector, tuple, map, string, etc). Sometimes it us useful to set size limits, either min or max, on the generated sequence. It would be nice to have a generator that would accept a sequence generator and ensure that the sequences are of a certain length. Three examples would be:
(of-length min max gen)
(of-max-length max gen) => (of-length 0 max gen)
(of-min-length min gen) => (of-length min nil gen)

of-length check the length of the generated sequence. If it is too small, use (take min (cycle s)) to extend the length of the sequence.
If it is too long, use (take max s) to return the max length
It will need to be careful to return the same type it received.
If it does not receive a sequence, treat it as though it was one element sequence.
If min is not 0, use such-that not nil to ensure a proper seq is generated.

22 Answers

0 votes
by

Comment made by: gfredericks

Most of the collection generators already accept options for controlling their size; is there a use case you have in mind where those controls are not sufficient?

0 votes
by

Comment made by: m0smith

I completely missed those that take the :min and :max options

In particular I was looking at the string based generators as I need to populate database tables and so the strings have to match the column definition like not null and varchar (15). As I was thinking about the problem it seemed like writing a composable generator to enforce length restraints might make more sense than trying to retro fit all the generators that produce sequence-like structures.

A short check of generators.cljc shows list, hash-map and even the array based ones could use min and max options.

0 votes
by

Comment made by: m0smith

If I try to update string from a def to a defn in an attempt to allow multiple arguments for sizes like

(defn string ([] (fmap clojure.string/join (vector char))) ([size] (fmap clojure.string/join (vector char size))) ([min max] (fmap clojure.string/join (vector char min max))))

Then (sample string) no longer works but (sample (string)) (sample (string 5)) (sample (string 3 7)) all work

Is there a trick to getting a just "string" to work? Or is there a better way to modify string to accept multiple arugments?

0 votes
by
_Comment made by: gfredericks_

I don't think there's any clean approach to solving the problem you noted, which is one reason that I don't see an obvious solution here.

I faced this issue when creating the {{large-integer}} and {{double}} generators, and I decided to create two generators: {{large-integer}} is a generator with default behavior, and {{large-integer*}} is a function that takes options and returns a generator.

In hindsight I don't know if this was the best choice, since it's confusing. I asked David MacIver about how he handles this in hypothesis (a similar python library) and he said he doesn't have *any* raw generators, just functions that return generators (sometimes with 0 arguments). I like the uniformity of that approach, but it would obviously be a big breaking change for test.check (though there are some hacky tricks that could preserve backwards compatibility).

That issue is a bit bigger than this ticket. With respect to the code you showed, I think I'd prefer an API closer to what {{gen/set}} has, with an options map rather than positional args.

Now that you've got me thinking about this, I'm starting to get fixated on the idea of a big breaking API change that uses hacks to preserve backwards compatibility for a few versions, for the sake of cleaning up a lot of inconsistencies. But again, that's bigger than this ticket.

If we can't come up with a clean short-term approach, my standards for accepting things are much more relaxed at [test.chuck|https://github.com/gfredericks/test.chuck] ☺.

0 votes
by

Comment made by: m0smith

Which do you prefer? adding something like of-length to limit existing seq generators or create (def string ...) and (defn string* ...)? I am just finishing up the Unicode implementation for TCHECK-97 as well so I'll follow which ever pattern we decide.

0 votes
by

Comment made by: gfredericks

Don't we have at least three generators for strings? Are you thinking of having alternatives to each one?

0 votes
by
_Comment made by: m0smith_

My very thought for making this request.  Do we really want to double the number of string generators?  Not to mention other things like arrays etc.

Another thought would be to add a single new generator, to-string, that can take a different character generator and use it to make strings based on an element generator:


 (defn to-string
   ([element-gen] (to-string element-gen {})))
   ([element-gen [{:keys [num-elements min-elements max-elements max-tries ] :or {num-elements nil min-elements 0 max-elements nil max-retries 10}}]
                   (fmap clojure.string/join (vector char-gen ==apply-options==))))


I left out the code to handle the options properly but hopefully you get the idea.

It would be called like  (to-string char {:min-elements 5 :max-elements 10})  or (to-string char-ascii {:num-elements 5})

This way it would be possible to add new character generators that could easily be converted to strings.
0 votes
by
_Comment made by: m0smith_

Looking at it, coll-distinct-by almost does exactly what I need, except the allow-dups? flag does not do what I was expecting.

(sample (coll-distinct-by [] identity true  false (elements [\a \b \c \d \e ]) {:num-elements 4})) always return distinct elements even though allow-dups? is true
0 votes
by

Comment made by: gfredericks

{{coll-distinct-by}} is pretty specialized and probably not relevant here.

I think you could translate (link: num-elements min-elements max-elements) to the args taken by gen/vector pretty easily. max-retries shouldn't apply.

Something like {{to-string}} might be good, especially if it fits with whatever you're thinking about for TCHECK-97 (e.g., TCHECK-97 provides args to use with {{to-string}}).

0 votes
by

Comment made by: m0smith

A patch generated with git --no-pager diff master feature/TCHECK-99 on https://github.com/m0smith/test.check.git

0 votes
by

Comment made by: m0smith

Ignore that patch. I am way out of date with master. New patch on its way

0 votes
by

Comment made by: m0smith

TCHECK-99-2.patch was generated after syncing test.check with my fork. Should be the latest.

0 votes
by
_Comment made by: m0smith_

I also had a thought on how to allow a function to be treated like a generator.   Add a :generator-factory meta data to a function like

(defn to-string
  "Generates strings of element-gen, default to char"
   {:generator-factory to-string}
   ([] (to-string char {})))
   ([element-gen] (to-string element-gen {})))
   ([element-gen [{:keys [num-elements min-elements max-elements]}]
                   (fmap clojure.string/join (vector char-gen ==apply-options==))))


The generator? could then be updated to accept anything with a :generator-factory meta data
call-gen could be updated to look for the new meta data and if there, call the associated function and use the result as the Generator.

0 votes
by

Comment made by: gfredericks

yeah, that's basically the idea I had for preserving backward compatibility -- the metadata generator would have some sort of deprecation warning attached somehow.

0 votes
by
_Comment made by: m0smith_

Also, on the to-string, clojure.string/join supports a seperator which we be easily added as an additional option like:

(to-string int {:sep ","})

If you are ok with it, I will update the patch.
...