packages feed

time-machine-0.1.0: test/Control/Monad/TimeMachine/EngineSpec.hs

module Control.Monad.TimeMachine.EngineSpec ( spec ) where

import Test.Hspec
import Test.HUnit ( Assertion )

import Control.Monad.TimeMachine.Engine

import           Control.Concurrent  ( threadDelay )
import           Control.Monad.Trans ( MonadIO, liftIO )
import qualified Data.Time           as T
import qualified Data.Time.Zones     as TZ

spec :: Spec
spec = do
    describe "travelTo" $ do
        it "switches the current time" $ do
            let itinerary = halt $ do
                    travelTo (Absolute $ fromOrigin 0) $ do
                        getCurrentTime
            itinerary `shouldReturn` (fromOrigin 0)
        it "overwrites the time if you have explicit dates" $ do
            let itinerary = halt $ do
                    travelTo (Absolute $ fromOrigin 0) $ do
                        travelTo (Absolute $ fromOrigin 1) $ do
                            getCurrentTime
            itinerary `shouldReturn` (fromOrigin 1)
        it "determines the destination according to the current TZ" $ do
            let itinerary = halt $ do
                    shanghai <- jumpTo "Asia/Shanghai" $ do
                        travelTo (Zoned localOrigin) $ do
                            getCurrentTime
                    tokyo <- jumpTo "Asia/Tokyo" $ do
                        travelTo (Zoned localOrigin) $ do
                            getCurrentTime
                    return $ T.diffUTCTime shanghai tokyo
            itinerary `shouldReturn` (1 * 60 * 60)
        it "calculates the destination if you have relative dates" $ do
            let itinerary = halt $ do
                    travelTo (Absolute $ fromOrigin 0) $ do
                        travelTo (Relative $ Days 2) $ do
                            travelTo (Relative $ Days (-1)) $ do
                                getCurrentTime
            itinerary `shouldReturn` (fromOrigin 1)

    describe "jumpTo" $ do
        it "switches the current TZ" $ do
            let itinerary = jumpTo "Europe/Paris" $ do
                    getCurrentTZ
            tz <- TZ.loadTZFromDB "Europe/Paris"
            itinerary `shouldReturn` tz
        it "overwrites the TZ of outer contexts" $ do
            let itinerary = jumpTo "Europe/Paris" $ do
                    jumpTo "Europe/London" $ do
                        getCurrentTZ
            tz <- TZ.loadTZFromDB "Europe/London"
            itinerary `shouldReturn` tz
        it "inherits the outer TZ if the TZ name is not found" $ do
            let itinerary = jumpTo "Europe/Paris" $ do
                    jumpTo "Not/Found" $ do
                        getCurrentTZ
            tz <- TZ.loadTZFromDB "Europe/Paris"
            itinerary `shouldReturn` tz

    describe "accelerate" $ do
        it "rescales the speed of time" $ do
            let itinerary = accelerate (Velocity 60) $ do
                    measureSeconds 1
            itinerary `shouldReturnBetweenInMinutes` (1, 2)
        it "overwrites the outer scale if you have explicit velocity" $ do
            let itinerary = accelerate (Velocity 60) $ do
                    accelerate (Velocity 120) $ do
                        measureSeconds 1
            itinerary `shouldReturnBetweenInMinutes` (2, 4)
        it "multiplies the scale if you have an acceleration factor" $ do
            let itinerary = accelerate (Velocity 60) $ do
                    accelerate (Factor 4) $ do
                        measureSeconds 1
            itinerary `shouldReturnBetweenInMinutes` (4, 8)

    describe "halt" $ do
        it "stops the time to advance in its context" $ do
            let itinerary = halt $ do
                    measureSeconds 1
            itinerary `shouldReturn` 0

fromOrigin :: Integer -> T.UTCTime
fromOrigin n = T.UTCTime (T.ModifiedJulianDay n) 0

localOrigin :: T.LocalTime
localOrigin = T.LocalTime (T.fromGregorian 1970 1 1) (T.TimeOfDay 0 0 0)

measureSeconds :: (MonadIO m, MonadTime m) => Int -> m T.NominalDiffTime
measureSeconds n = do
    t0 <- getCurrentTime
    liftIO . threadDelay $ 1000 * 1000 * n
    t1 <- getCurrentTime
    return $ T.diffUTCTime t1 t0

shouldReturnBetweenInMinutes :: IO T.NominalDiffTime -> (Int, Int) -> Assertion
shouldReturnBetweenInMinutes action (lo, hi) =
    action >>= (`shouldSatisfy` (\d -> loDiff <= d && d <= hiDiff))
        where
            loDiff = fromIntegral $ 60 * lo
            hiDiff = fromIntegral $ 60 * hi