cleveland-0.1.0: src/Test/Cleveland/Doc/Common.hs
-- SPDX-FileCopyrightText: 2020 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ
-- | Utilities for testing documentations sanity.
--
-- These tests serve to ensure that documentation generation is not broken and
-- that user follows sane documentation structure (e.g. contract should be
-- named, some entities require description, e.t.c).
module Test.Cleveland.Doc.Common
( DocTest (..)
, mkDocTest
, excludeDocTest
, excludeDocTests
, runDocTests
, expectDocTestFailure
-- * Utilities
, allContractLayers
, allContractDocItems
-- * Misc
, buildDocTest
, buildMarkdownDocTest
) where
import qualified Data.List as L
import Fmt (Buildable(..), pretty)
import GHC.Stack (SrcLoc)
import Test.HUnit (Assertion, assertFailure)
import Test.HUnit.Lang (HUnitFailure(..))
import Test.Tasty (TestTree)
import Test.Tasty.HUnit (testCase)
import qualified Text.Show
import Morley.Michelson.Doc
----------------------------------------------------------------------------
-- Misc test helpers
----------------------------------------------------------------------------
-- | Like 'buildDoc', but does not require documentation to be finalized.
buildDocTest :: ContainsDoc a => a -> ContractDoc
buildDocTest = buildDocUnfinalized
-- | Like 'buildMarkdownDoc', but does not require documentation to be finalized.
buildMarkdownDocTest :: ContainsDoc a => a -> LText
buildMarkdownDocTest = contractDocToMarkdown . buildDocUnfinalized
----------------------------------------------------------------------------
-- Framework
----------------------------------------------------------------------------
-- | Test case for contract documentation.
data DocTest = DocTest
{ dtDeclLoc :: SrcLoc
-- ^ Declaration location, used to distinguish different test predicates.
-- This is primarily used in 'excludeDocTest'.
, dtDesc :: String
-- ^ Description of predicate, which you put to 'testCase'.
, dtSuite :: HasCallStack => ContractDoc -> Assertion
-- ^ Test itself.
}
instance Eq DocTest where
(==) = (==) `on` dtDeclLoc
instance Show DocTest where
show = pretty
instance Buildable DocTest where
build DocTest{..} = "Doc test '" <> build dtDesc <> "'"
-- | Construct 'DocTest'.
--
-- Note: you should not declare helpers with this function rather use it
-- directly in every test suite.
mkDocTest
:: HasCallStack
=> String
-> (HasCallStack => ContractDoc -> Assertion)
-> DocTest
mkDocTest dtDesc dtSuite = DocTest{..}
where
(_, dtDeclLoc) = case getCallStack callStack of
[] -> error "Callstacks operate in a weird way, excluding doc tests won't work"
layer : _ -> layer
-- | Exclude given test suite.
excludeDocTest :: HasCallStack => DocTest -> [DocTest] -> [DocTest]
excludeDocTest toExclude tests =
case L.partition (== toExclude) tests of
([], _) ->
error $ "Not in the list of doc items: " <> pretty toExclude
(_ : _ : _, _) ->
-- This is e.g. possible if someone abused 'mkDocTest' and created a function
-- which calls it and this function is used to create multiple predicates
error "Multiple test predicates were considered equal.\n\
\Either list of tests contains identical test predicates, \
\or used predicates were constructed incorrectly."
([_], notExcluded) ->
notExcluded
-- | Calling @excludeDocTests tests toExclude@ returns all test suites from
-- @tests@ which are not present in @toExclude@.
excludeDocTests :: [DocTest] -> [DocTest] -> [DocTest]
excludeDocTests = foldr excludeDocTest
-- | Finalize test suites.
runDocTests :: (ContainsDoc code, HasCallStack) => [DocTest] -> code -> [TestTree]
runDocTests tests (buildDocTest -> contractDoc) =
tests <&> \DocTest{..} ->
testCase dtDesc (dtSuite contractDoc)
-- | Ensure that 'DocTest' check fires on given contract.
-- Used in tests on this module.
expectDocTestFailure :: ContainsDoc code => DocTest -> code -> Assertion
expectDocTestFailure DocTest{..} (buildDocTest -> contractDoc) = do
passed <- (dtSuite contractDoc $> True) `catch` \HUnitFailure{} -> pure False
when passed $ assertFailure "Test didn't fail unexpectedly"
----------------------------------------------------------------------------
-- Test predicates
----------------------------------------------------------------------------
-- Utilities
----------------------------------------------------------------------------
-- | Collect all doc groups (created with @DocGroup@ instruction)
-- recursively.
-- The result will contain grouping doc item itself and its subcontents.
allLayers :: DocBlock -> [(SomeDocItem, DocBlock)]
allLayers block = do
DocSection docElems <- toList block
DocElem{..} <- toList docElems
Just (SubDoc sub) <- pure deSub
(SomeDocItem deItem, sub) : allLayers sub
-- | Collect all doc groups (created with @DocGroup@ instruction)
-- within a contract recursively.
-- The result will contain grouping doc item itself (unless we are at root)
-- and its subcontents.
allContractLayers :: ContractDoc -> [(Maybe SomeDocItem, DocBlock)]
allContractLayers contract =
let contents = cdContents contract
in (Nothing, contents)
: (first Just <$> allLayers contents)
-- | Collect all doc items (atomic and grouping) recursively.
allContractDocItems :: DocItem d => ContractDoc -> [d]
allContractDocItems contract =
fold $ allContractLayers contract <&> \(_, block) ->
maybe [] toList $ lookupDocBlockSection block