fresnel-0.0.0.0: test/Test/Group.hs
{-# LANGUAGE DisambiguateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE TemplateHaskell #-}
module Test.Group
( Entry(..)
, mkGroup
, deriveGroup
, mkProp
, entryName
, Loc(..)
, here
, zero
, Semiring(..)
, Unital(..)
, horizontal
, sumWidths
, vertical
, maxWidths
, Width(..)
, HasWidth(..)
-- * Statuses
, stat
, Status(..)
-- * Tallies
, hasFailures
, hasSuccesses
, unit
, Tally(..)
) where
import Data.Char (isSpace)
import Fresnel.Tropical
import GHC.Stack
import Language.Haskell.TH.Lib
import Language.Haskell.TH.Syntax (Module(..), modString)
import Numeric (readDec)
import Test.QuickCheck (Property, allProperties)
data Entry
= Group String [Entry]
| Prop String Loc Property
mkGroup :: String -> [(String, Property)] -> Entry
mkGroup name = Group name . map (uncurry mkProp)
deriveGroup :: ExpQ
deriveGroup = [e| mkGroup $(thisModule >>= \ (Module _ name) -> stringE (modString name)) $allProperties |]
mkProp :: String -> Property -> Entry
mkProp s = Prop name Loc{ path, lineNumber }
where
(name, path, lineNumber) = case breaks [isSpace, not . isSpace, isSpace, not . isSpace, (== ':'), (/= ':')] s of
[n, _, _, _, p, _, l] -> (unwords (filter (\ s -> s /= "_" && s /= "prop") (breakAll (== '_') n)), p, fst (head (readDec l)))
_ -> ("", "", 0)
entryName :: Entry -> String
entryName = \case
Group name _ -> name
Prop name _ _ -> name
data Loc = Loc { path :: FilePath, lineNumber :: Int }
here :: HasCallStack => Loc
here = Loc{ path = srcLocFile srcLoc, lineNumber = srcLocStartLine srcLoc }
where
(_, srcLoc) = head (getCallStack callStack)
breaks :: [a -> Bool] -> [a] -> [[a]]
breaks ps as = case ps of
[] -> [as]
p:ps -> let (h, t) = break p as in h : breaks ps t
breakAll :: (a -> Bool) -> [a] -> [[a]]
breakAll p = go False where
go b = \case
[] -> []
as -> let (h, t) = break (if b then not . p else p) as in h : go (not b) t
zero :: Monoid s => s
zero = mempty
class Semigroup s => Semiring s where
(><) :: s -> s -> s
infixr 7 ><
instance (Semigroup a, Ord a) => Semiring (Tropical a) where
Tropical a1 >< Tropical a2 = Tropical ((<>) <$> a1 <*> a2)
class (Monoid s, Semiring s) => Unital s where
one :: s
instance (Monoid a, Ord a) => Unital (Tropical a) where
one = finite zero
horizontal :: (Foldable t, Unital r) => (a -> r) -> t a -> r
horizontal f = foldr ((><) . f) one
sumWidths :: (Foldable t, HasWidth a) => t a -> Tropical Width
sumWidths = horizontal maxWidth
vertical :: (Foldable t, Semiring r, Monoid r) => (a -> r) -> t a -> r
vertical f = foldr ((<>) . f) zero
maxWidths :: (Foldable t, HasWidth a) => t a -> Tropical Width
maxWidths = vertical maxWidth
newtype Width = Width { width :: Int }
deriving (Eq, Ord)
instance Semigroup Width where
Width a1 <> Width a2 = Width (a1 + a2)
instance Monoid Width where
mempty = Width 0
instance Semiring Width where
Width a1 >< Width a2 = Width (a1 * a2)
instance Unital Width where
one = Width 1
class HasWidth t where
maxWidth :: t -> Tropical Width
instance HasWidth Char where
maxWidth _ = finite one
instance HasWidth Entry where
maxWidth = \case
Group groupName entries -> sumWidths groupName <> maxWidths entries
Prop name _ _ -> sumWidths name
-- Statuses
stat :: a -> a -> Status -> a
stat pass fail = \case{ Pass -> pass ; Fail -> fail }
data Status = Pass | Fail
deriving (Eq)
-- Tallies
hasSuccesses :: Tally -> Bool
hasSuccesses = (/= 0) . successes
hasFailures :: Tally -> Bool
hasFailures = (/= 0) . failures
unit :: Status -> Tally
unit = stat (Tally 1 0) (Tally 0 1)
data Tally = Tally { successes :: Int, failures :: Int }
instance Semigroup Tally where
Tally s1 f1 <> Tally s2 f2 = Tally (s1 + s2) (f1 + f2)
instance Monoid Tally where
mempty = Tally 0 0