QuickCheck is an awesome library, so I decided to play with it a bit and also refresh my memory of Haskell.

## Intro

To, warm up, let’s remind ourselves how insane floating point arithmetic is. Let’s test if addition of floating point numbers is commutative (`x + y = y + x`).

Ok, so far so good. What about associativity (`(x + y) + z == x + (y + z))`?

Nice! Let’s try a few more.

This is one more reminder not to use double and float types to represent money. If you have X dollars and then someone gives you Y dollars and then changes their mind and takes these Y dollars back, you can end up with 0 dollars in your pocket, even though X was greater than zero. Before taking money from someone, make sure they don’t use doubles for money.

## Testing Date and Time

Not only floating-pointer numbers are insane. Date and time are extremely weird as well. All these time zones, daylight saving time, leap years, leap seconds. Pure madness. Many of our assumptions about time are wrong.

In order to make my life more complicated, I decided to check whether `ActiveSupport::Duration` from Rails is associative, but I want to use Haskell’s QuickCheck to do that.

Here are a few instances of the `ActiveSupport::Duration` class:

In which case the time to wait is longer: when you first wait `D1` and then `D2`, or when you first wait `D2` and then `D1`? Common sense suggests that it’s the same, but let’s check it.

Let’s get started.

There are a few predefined tests in `test/Spec.hs`, let’s remove them all. Now, testing pure properties is easy. This time, however, we need to communicate with Ruby, thus we need quickcheck with IO. After 15 seconds of searching, we can find this. Looks like this is exactly what we need. Let’s adapt it to our needs.

Simpru, right?

Let’s run it:

And check it in Ruby:

Wow, 3 days difference! Not bad. This is because adding the actual duration of adding months depends on the timestamp we are adding these months to.

Now, what if we remove months out of the equation? Let’s comment out `pure Months` from `Arbitrary` instance definition for `Duration` and rerun the test.

Let’s have a look.

1 day difference. This time, it’s a leap year problem: if we travel from 23 December 2016 to 49 weeks in past, we will get to 15 January (because 2016 is a leap year and 29 February is a thing). Then 17 years to the future - it’s 15 Jan 2033.

On the other hand, if we first go to the future (23 December 2033), and then 49 weeks back, then we’ll get to 14 January (because the 2033 year is not leap and Feb 29 doesn’t exit).

So the problem now is very similar to the previous problem we had with `month` durations. `year` durations depend on the year of the date they are applied to. Let’s comment out `pure Years` and rerun tests.

Hmm, looks promising. But maybe I overlooked something? Let’s increase the number of tests and run it again.

What is this?

1 hour difference. DST? Most likely. If you run `stack test` a few times, you’ll see that `x` and `y` will be around mid March or mid November. This confirms our suspicion. Well, the problem is the same: `1.day` doesn’t actually have a constant length. It can be 23 or 25 hours long, if we jump over DST point.

Let’s exclude `days` and `weeks` and rerun tests.

I ran `stack test` with the new settings multiple times and didn’t get any new failures. It doesn’t mean that the commutative property holds for `second`/`minute`/`hour` durations. It just means that QuickCheck didn’t find any counterexamples.

## Conclusion

Current implementation of the test is very inefficient: it starts a process for each single check. My initial implementation was more ugly and a lot faster. Its source code is there. I checked a few more properties there. In that implementation, the Ruby process is started once and it communicates with Haskell via pipe.