o-clock-1.4.0.1: test/Test/Time/Property.hs
-- SPDX-FileCopyrightText: 2019 Serokell <https://serokell.io>
--
-- SPDX-License-Identifier: MPL-2.0
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module Test.Time.Property
( hedgehogTestTrees
) where
import GHC.Natural (Natural)
import GHC.Real ((%))
import Hedgehog (MonadGen, MonadTest, Property, PropertyT, forAll, property, (===))
import Test.Tasty (TestTree)
import Test.Tasty.Hedgehog (testProperty)
import Time (Day, Fortnight, Hour, KnownRat, KnownRatName, Microsecond,
Millisecond, Minute, Nanosecond, Picosecond, Rat, RatioNat, Second,
Time (..), Week, toUnit, unitsF, unitsP)
import Time (withRuntimeDivRat)
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
hedgehogTestTrees :: [TestTree]
hedgehogTestTrees = [readShowTestTree, toUnitTestTree, seriesTestTree]
readShowTestTree :: TestTree
readShowTestTree = testProperty "Hedgehog read . show == id" prop_readShowUnit
toUnitTestTree :: TestTree
toUnitTestTree = testProperty "Hedgehog toUnit @to @from . toUnit @from @to ≡ id' property" prop_toUnit
seriesTestTree :: TestTree
seriesTestTree = testProperty "Hedgehog unitsP . unitsF ≡ id" prop_series
-- | Existential data type for 'Unit's.
data AnyTime = forall (unit :: Rat) . (KnownRatName unit)
=> MkAnyTime (Time unit)
instance Show AnyTime where
show (MkAnyTime t) = show t
-- | Returns random 'AnyTime'.
unitChooser :: (MonadGen m) => RatioNat -> m AnyTime
unitChooser t = Gen.element
[ MkAnyTime (Time @Second t)
, MkAnyTime (Time @Millisecond t)
, MkAnyTime (Time @Microsecond t)
, MkAnyTime (Time @Nanosecond t)
, MkAnyTime (Time @Picosecond t)
, MkAnyTime (Time @Minute t)
, MkAnyTime (Time @Hour t)
, MkAnyTime (Time @Day t)
, MkAnyTime (Time @Week t)
, MkAnyTime (Time @Fortnight t)
]
-- | Verifier for 'AnyTime' @read . show = id@.
verifyAnyTime :: (MonadTest m) => AnyTime -> m ()
verifyAnyTime (MkAnyTime t) = read (show t) === t
-- | Verifier for 'toUnit'.
verifyToUnit :: forall m . (MonadTest m) => AnyTime -> AnyTime -> m ()
verifyToUnit (MkAnyTime t1) (MkAnyTime t2) = checkToUnit t1 t2
where
checkToUnit :: forall (unitFrom :: Rat) (unitTo :: Rat) .
(KnownRatName unitFrom, KnownRat unitTo)
=> Time unitFrom
-> Time unitTo
-> m ()
checkToUnit t _ =
withRuntimeDivRat @unitTo @unitFrom $
withRuntimeDivRat @unitFrom @unitTo $
toUnit (toUnit @unitTo t) === t
-- | Verifier for @ seriesP . seriesF @.
verifySeries :: forall m . (MonadTest m) => AnyTime -> m ()
verifySeries (MkAnyTime anyT) = checkSeries anyT
where
checkSeries :: forall (unit :: Rat) . KnownRatName unit
=> Time unit -> m ()
checkSeries t = unitsP @unit (unitsF t) === Just t
-- | Generates random natural number up to 10^20.
-- it receives the lower bound so that it wouldn't be possible
-- to get 0 for denominator.
natural :: (MonadGen m) => Natural -> m Natural
natural n = Gen.integral (Range.constant n $ 10 ^ (20 :: Int))
-- | Generates random rational number.
rationalNum :: (MonadGen m) => m RatioNat
rationalNum = do
numeratorVal <- natural 0
isOne <- Gen.bool
denomVal <- if isOne then pure 1
else natural 1
return $ numeratorVal % denomVal
anyTime :: (MonadGen m) => m AnyTime
anyTime = rationalNum >>= unitChooser
genAnyTime :: Monad m => PropertyT m AnyTime
genAnyTime = forAll anyTime
-- | Property test.
prop_readShowUnit :: Property
prop_readShowUnit = property $ genAnyTime >>= verifyAnyTime
prop_toUnit :: Property
prop_toUnit = property $ do
t1 <- genAnyTime
t2 <- genAnyTime
verifyToUnit t1 t2
prop_series :: Property
prop_series = property $ genAnyTime >>= verifySeries