PurelyFunctional.tv Newsletter 329: Tool: cljdoc

Issue 329 - June 03, 2019 · Archives · Subscribe

Clojure Tool 🔨


cljdoc is awesome. It can generate documentation for any Clojure project. Do try it on a test project you or someone else has created and published to Clojars. It generates beautiful documentation. Besides the documentation, the site has lots of helpful suggestions for how to make it generate better docs for your project. If you're a library author, it's invaluable.

You can watch a talk by the creator, Martin Klepsch, about how it works at IN/Clojure.

Clojure Media 🍿

:clojureD videos! You can watch them here (YouTube).

Clojure Puzzle 🤔

Last week's puzzle

The puzzle in Issue 328 was a redo of the previous maxcat puzzle.

You can see the submissions here. The testbed for this puzzle is here.

Rather than use example-based tests, the testbed uses test.check to generate random tests and check that they obey certain properties. What properties are included?

The first test is that all digits are included in the output number, and no digit is left out. Basically, the number maxcat returns has to use all of the digits in its argument. This is a cheap test to run, even for long lists of big integers.

The second test makes sure that the output is in fact a concatenation. Imagine if you could sort the digits instead of sorting the integers in the list. That would mean you could pull all the 9's to the left, which would make the final output bigger, but not really follow the spec. This one is slower, since you have to gen erate all possible permutations, so we can only run it on short lists.

The combination of the first two tests (one fast that works for long lists, one slow that works for short lists) (we hope) make sure that you are returning a concatenation. Using two tests like this is a common pattern in property-based testing.

The third and fourth tests also follow this pattern. The third test generates lists of integers and checks that the return of maxcat is larger than all other concatenations. This test is slow, because we have to generate all possible permutations. So we only run it for small lists by setting a max-size of 10. Unfortunately, the max-size of 10 also limits the size of the integers.

To cover larger integers, we need to run a very similar test, but limited to lists of length 2. This is what the fourth test does. It generates two integers that can be very large, so that they have many digits. It then checks that the return of maxcat is the largest.

With these last two property-based tests, we cover long lists with small integers and short lists with big integers. These cover everything except for long lists of big integers. Is this a problem? It seems unlikely, but I could be wrong.

Finally, there are a few example-based tests just to have some well-known test cases. Val identified (maxcat [1 10]) as a case that many of the previous submissions didn't pass. This one is probably covered by tests 3 & 4, but just to make sure, here it is. (maxcat [Integer/MAX_VALUE Long/MAX_VALUE]) was suggested by Steve because it catches implementations that don't use BigIntegers and instead rely on Long/parseLong or read-string. Why not include these? They don't take long to test and have been shown to exist in practice.

I tested all of the previous submissions using the testbed. I copied the ones that did pass to the new submission gist. I also modified a couple that were not passing simply due to using read-string. I replac ed read-string with BigInteger. to make them work, and they passed the tests. I also got a few new submissions.

Every submission in the gist has passed the test suite on my machine.

I'm going to continue doing this for puzzles in the future.

This week's challenge

email generator for test.check and clojure.spec

Email addresses are a common use for strings. And although a clojure.spec using a regex might be good for validation, it's not good for generating random emails addresses for property-based testing.

The challenge this week is to create a test.check generator for email addresses. This is both an implementation challenge and a design challenge. How completely does your generator cover the space of emails? How can it use the size parameter? Does the generator capture useful edge cases? What resources (for instance, documentation) can you use to make sure you are generating correct emails?

As usual, please send me your implementations. I'll share them all in next week's issue. If you send me one, but you don't want me to share it publicly, please let me know.

Rock on!
Eric Normand