{-# 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) #if ( __GLASGOW_HASKELL__ >= 804 ) import Time (withRuntimeDivRat) #endif 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 _ = #if ( __GLASGOW_HASKELL__ >= 804 ) withRuntimeDivRat @unitTo @unitFrom $ withRuntimeDivRat @unitFrom @unitTo $ #endif 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