"I am the law!" ⸻Judge Dredd
This is a GHC plugin that, during compilation, automatically builds a hedgehog-classes test suite for the type class instances defined in each module.
You need to add this plugin to any library you want to test, then you have to add a corresponding test suite. Here is a cabal example:
name: my-library
library
exposed-modules: My.Test.Mod
...
ghc-options: -fplugin Dredd
library my-library-testing -- where `Gen`s and `Laws` are defined
exposed-modules: My.Test.Mod.Gen,
My.Test.Mod.Laws
...
test-suite my-test
build-depends: base,
hedgehog-classes,
my-library,
my-library-testing
build-tool-depends: dredd:lawmaster
default-language: Haskell2010
hs-source-dirs: dredd
main-is: Main.hs -- you have to write this one
other-modules: Judge.Dredd.My.Test.Mod -- this module is autogenerated
type: exitcode-stdio-1.0
Main.hs
should look like
{-# options_ghc -F -pgmF lawmaster #-}
The test output is just that from hedgehog-classes, but here you get all those property tests from a couple small modules.
Currently, it expects things to be named in a particular way:
Gen
andLaws
need to be defined in a separate library, so they don't get recursively processed, looking for more laws to check;Gen
s need to be namedgen<Type>
and live in<defining module>.Gen
, and must expect anm a
for each type parameter;Laws
need to be named<Class>Laws
and live in<defining module>.Laws
;- the test suite needs to have
hs-source-dirs: dredd
(since that's where the generated modules will be put); - the test suite needs to have
main-is: Main.hs
("dredd/Main.hs" needs to contain theoptions_ghc
pragma);
Here is a brief example:
module MyModule where
instance MyTypeClass MyType
will generate a test module that looks like
module Judge.Dredd.MyModule (dreddLaws) where
import Hedgehog.Classes
import MyModule.Gen
dreddLaws :: IO Bool
dreddLaws =
= lawsCheckMany
[("MyType", [myTypeClassLaws genMyType]),
...]
and the generated driver looks something like
module Main (main) where
import Hedgehog.Classes (lawsCheckmMany)
import Hedgehog.Main (defaultMain)
import qualified Judge.Dredd.MyModule.Gen
main :: IO ()
main = defaultMain [Judge.Dredd.MyModule.Gen.dreddLaws, ...]
- omit tests that aren't satisfied (e.g., most
Laws
requireEq
andShow
instances, and we shouldn't generate tests for types that are missing those instances (or maybe create orphan standalone deriving instances in the generated test suite) - support parameterized instances (e.g.,
Monoid a => Monoid (Maybe a)
), perhaps using Exemplar - allow configuration of names and locations for
Laws
andGen
s (similarly, support other test frameworks).