packages feed

document-build-0.2.0.0: test/OutputTests.hs

{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wall #-}

-- | Properties of the output tree, its JSON, 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 Data.Tree (Forest, Tree (..), flatten)
import Document.Build
import Gens (genGitDetails, genImageInfo, genOutputFile, genPath, genWord)
import Hedgehog
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import Laws (Laws)

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 genFile = Gen.element (fmap (\n -> "public/f" <> show n <> ".pdf") [1 .. 12 :: Int])
   in Gen.list (Range.linear 0 10) ((,) <$> genFile <*> Gen.list (Range.linear 0 3) genFile)

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)
  ]

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