document-build-0.1.0.0: test/OutputTests.hs
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wall #-}
-- | Properties of the output tree, its JSON, the optics of the context, and placeholders.
module OutputTests (
outputTests,
) where
import Data.Aeson (fromJSON, toJSON)
import qualified Data.Aeson as Aeson
import Data.Aeson.Types (parseEither)
import Data.Containers.ListUtils (nubOrd)
import Data.Foldable (toList)
import Data.List (sort)
import qualified Data.Map as Map
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import Data.Tree (Forest, Tree (..), flatten)
import Document.Build
import Gens (genPath)
import Hedgehog
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import Laws (Laws, lensLaws, prismLaws)
genWord :: Gen String
genWord =
Gen.string (Range.linear 1 12) Gen.alphaNum
genGitDetails :: Gen GitDetails
genGitDetails =
let time = posixSecondsToUTCTime . fromIntegral <$> Gen.int (Range.linear 0 2000000000)
in GitDetails <$> genWord <*> genWord <*> genWord <*> genWord <*> time <*> genWord <*> genWord <*> time <*> genWord <*> Gen.list (Range.linear 0 3) genWord
genImageInfo :: Gen ImageInfo
genImageInfo =
ImageInfo
<$> genWord
<*> Gen.int (Range.linear 1 5000)
<*> Gen.int (Range.linear 1 5000)
<*> (fromIntegral <$> Gen.int (Range.linear 1 600))
<*> (fromIntegral <$> Gen.int (Range.linear 1 600))
<*> genWord
<*> genWord
<*> Gen.int (Range.linear 1 16)
<*> Gen.int (Range.linear 1 10)
genOutputFile :: Gen OutputFile
genOutputFile =
OutputFile <$> genPath <*> genWord <*> Gen.maybe genGitDetails <*> Gen.maybe (Gen.int (Range.linear 1 500)) <*> Gen.maybe genImageInfo
genForest :: Gen (Forest OutputFile)
genForest =
Gen.list (Range.linear 0 3) (Gen.recursive Gen.choice [Node <$> genOutputFile <*> pure []] [Node <$> genOutputFile <*> Gen.list (Range.linear 0 3) (Node <$> genOutputFile <*> pure [])])
{- | Edges between a small set of file paths, so that some outputs have more
than one input.
-}
genEdges :: Gen Edges
genEdges =
let file = Gen.element (fmap (\n -> "public/f" <> show n <> ".pdf") [1 .. 12 :: Int])
in Gen.list (Range.linear 0 10) ((,) <$> file <*> Gen.list (Range.linear 0 3) file)
outputTests :: Laws
outputTests =
[ ("edgeForest has every file once", prop_everyFileOnce)
, ("edgeForest puts an output under its first input", prop_firstInput)
, ("OutputFile JSON round trip", prop_outputFileJson)
, ("output forest JSON round trip", prop_forestJson)
, ("GitDetails JSON round trip", prop_gitDetailsJson)
, ("ImageInfo JSON round trip", prop_imageInfoJson)
, ("withPlaceholder then placeholderValue gives the value", prop_withPlaceholder)
, ("placeholderAliases gives the value of the other placeholder", prop_placeholderAliases)
]
<> concat
[ lensLaws "outputDirectory" outputDirectory genDirectories genPath
, lensLaws "inputDirectories" inputDirectories genDirectories (Gen.list (Range.linear 0 3) genPath)
, lensLaws "fontsDirectory" fontsDirectory genDirectories (Gen.maybe genPath)
, lensLaws "contextDirectories" contextDirectories genContext genDirectories
, lensLaws "contextProject" contextProject genContext genWord
, lensLaws "contextTarget" contextTarget genContext genTarget
, prismLaws "_TargetFile" _TargetFile genTarget (pure ())
, prismLaws "_TargetDirectory" _TargetDirectory genTarget (pure ())
, prismLaws "_TargetComplete" _TargetComplete genTarget genKnownFiles
]
genDirectories :: Gen Directories
genDirectories =
Directories <$> genPath <*> Gen.list (Range.linear 0 3) genPath <*> Gen.maybe genPath
genKnownFiles :: Gen KnownFiles
genKnownFiles =
Map.fromList <$> Gen.list (Range.linear 0 3) ((,) <$> genPath <*> (Trail <$> Gen.list (Range.linear 0 3) (Step <$> genWord <*> genPath)))
genTarget :: Gen Target
genTarget =
Gen.choice [pure TargetFile, pure TargetDirectory, TargetComplete <$> genKnownFiles]
genContext :: Gen Context
genContext =
Context <$> genDirectories <*> genWord <*> genTarget
genPlaceholders :: Gen Placeholders
genPlaceholders =
Gen.list (Range.linear 0 5) ((,) <$> fmap (\n -> "${" <> n <> "}") genWord <*> genWord)
prop_withPlaceholder :: Property
prop_withPlaceholder =
property $
do
ph <- forAll genPlaceholders
n <- forAll genWord
v <- forAll genWord
placeholderValue (withPlaceholder n v ph) n === v
prop_placeholderAliases :: Property
prop_placeholderAliases =
property $
do
ph <- forAll genPlaceholders
n <- forAll genWord
v <- forAll genWord
placeholderValue (placeholderAliases [(n, v)] ph) n === placeholderValue ph v
prop_everyFileOnce :: Property
prop_everyFileOnce =
property $
do
es <- forAll genEdges
others <- forAll (Gen.list (Range.linear 0 3) genPath)
let files = concatMap flatten (edgeForest es others)
expected = sort (nubOrd (concatMap (uncurry (:)) es <> others))
sort files === expected
prop_firstInput :: Property
prop_firstInput =
property $
do
es <- forAll genEdges
let forest = edgeForest es []
parent o = lookup o (concatMap pairs forest)
pairs (Node p cs) = fmap (\(Node c _) -> (c, p)) cs <> concatMap pairs cs
firstInput o = lookup o [(x, i) | (i, os) <- es, x <- os, x /= i]
for' (nubOrd (concatMap snd es)) $ \o ->
case parent o of
Nothing -> pure ()
Just p -> Just p === firstInput o
where
for' xs f = mapM_ f xs
prop_outputFileJson :: Property
prop_outputFileJson =
property $
do
o <- forAll genOutputFile
fromJSON (toJSON o) === Aeson.Success o
prop_forestJson :: Property
prop_forestJson =
property $
do
f <- forAll genForest
parseEither outputForestFromJSON (outputForestToJSON f) === Right f
length (concatMap toList f) === length (concatMap flatten f)
prop_gitDetailsJson :: Property
prop_gitDetailsJson =
property $
do
g <- forAll genGitDetails
fromJSON (toJSON g) === Aeson.Success g
prop_imageInfoJson :: Property
prop_imageInfoJson =
property $
do
i <- forAll genImageInfo
fromJSON (toJSON i) === Aeson.Success i