Re-centering with ?SHRINK

Learn how the ?SHRINK macro works, and when to use it to better understand the failing cases of programs.

We'll cover the following

?SHRINK

?SHRINK is conceptually the simplest of the two macros that can be used to impact shrinking. It is best used to pick a custom zero-point toward which PropEr will try to shrink data. We can do this mainly by giving the framework a normal generator for normal cases, and then suggesting it uses other simpler generators whenever an error is discovered.

The macro takes the form ?SHRINK(DefaultGenerator, [AlternativeGenerators]) in Erlang. The DefaultGenerator will be used for all passing tests. Once a property fails, however, ?SHRINK lets us tell PropEr that any of the alternative generators in the list are interesting ways to get simpler relevant data. We can give hints about how the framework should search for failures. And if the alternative generators are not fruitful, so be it. The shrinking will continue in other ways until no progress can be made.

To make things practical, if we’re generating timestamps or dates, we may be interested in including years from 0 to 9999 to make sure we cover all kinds of unusual cases. But if we know that the underlying implementation of our system uses Unix timestamps, then we should consider that its epoch, or starting time, is on January 1, 1970. Since January 1, 1970, is the underlying system’s zero value, picking 1970 as a shrinking target makes more sense than the literal year 0.

Example

Let’s take a look at the following set of generators used to create strings of the form "1997-08-04T12:02:18-05:00", in accordance with the ISO 8601 standard. This set of generators will center its shrinking efforts toward January 1, 1970. Let’s start with the overall structure, and see what we can do with years. These are available in the code below between line 8 and line 19.

The year generator uses the ?SHRINK macro as its default generator. This covers all the thousand or so years we are interested in. The alternative generators for the macro are:

  1. range(1970,2000)
  2. range(1900,2100)

This means that if some generated year causes a property to fail, rather than trying years such as 73 or 8763, PropEr will try years closer to the epoch, like 1988 or 2040. The ?SHRINK macro helps us narrow PropEr’s search space down significantly to get relevant results faster. Let’s look at the rest of the generators in the code below. They are defined between line 21 and line 38.

One thing to note is that the standard is somewhat lax and doesn’t forbid the notation of a timezone that is +99:76, lagging about four days behind standard time, even if that is nonsensical from a human perspective.

Similarly, our timezone() generator will look for values between 0 and 99, but in any failure case, we’ll try to settle between 0 and 14 when possible, which are ranges that we humans find more reasonable. Similarly, the minute’s offsets will try to match currently standard offsets of 0, 15, 30, or 45 minutes.

As we can see, generators that use ?SHRINK can be used like any other. The macro adds some metadata to the underlying structure representing a generator, so they remain possible to compose. From anybody else’s point of view, it’s a generator like any other.

Note: We can take a look at the shrinking action in the shell.

The command is proper_gen:sampleshrink(prop_shrink:strdatetime()).

Get hands-on with 1200+ tech skills courses.