packages feed

cron 0.3.1 → 0.3.2

raw patch · 6 files changed

+548/−1 lines, 6 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- System.Cron: [command] :: CrontabEntry -> Text
- System.Cron: [dayOfMonth] :: CronSchedule -> DayOfMonthSpec
- System.Cron: [dayOfWeek] :: CronSchedule -> DayOfWeekSpec
- System.Cron: [hour] :: CronSchedule -> HourSpec
- System.Cron: [minute] :: CronSchedule -> MinuteSpec
- System.Cron: [month] :: CronSchedule -> MonthSpec
- System.Cron: [schedule] :: CrontabEntry -> CronSchedule
- System.Cron: [varName] :: CrontabEntry -> Text
- System.Cron: [varValue] :: CrontabEntry -> Text
- System.Cron: instance GHC.Classes.Eq System.Cron.CronField
- System.Cron: instance GHC.Classes.Eq System.Cron.CronSchedule
- System.Cron: instance GHC.Classes.Eq System.Cron.CronUnit
- System.Cron: instance GHC.Classes.Eq System.Cron.Crontab
- System.Cron: instance GHC.Classes.Eq System.Cron.CrontabEntry
- System.Cron: instance GHC.Classes.Eq System.Cron.DayOfMonthSpec
- System.Cron: instance GHC.Classes.Eq System.Cron.DayOfWeekSpec
- System.Cron: instance GHC.Classes.Eq System.Cron.HourSpec
- System.Cron: instance GHC.Classes.Eq System.Cron.MinuteSpec
- System.Cron: instance GHC.Classes.Eq System.Cron.MonthSpec
- System.Cron: instance GHC.Show.Show System.Cron.CronField
- System.Cron: instance GHC.Show.Show System.Cron.CronSchedule
- System.Cron: instance GHC.Show.Show System.Cron.CronUnit
- System.Cron: instance GHC.Show.Show System.Cron.Crontab
- System.Cron: instance GHC.Show.Show System.Cron.CrontabEntry
- System.Cron: instance GHC.Show.Show System.Cron.DayOfMonthSpec
- System.Cron: instance GHC.Show.Show System.Cron.DayOfWeekSpec
- System.Cron: instance GHC.Show.Show System.Cron.HourSpec
- System.Cron: instance GHC.Show.Show System.Cron.MinuteSpec
- System.Cron: instance GHC.Show.Show System.Cron.MonthSpec
- System.Cron.Schedule: [unSchedule] :: ScheduleT m a -> StateT Jobs (ExceptT ScheduleError m) a
- System.Cron.Schedule: instance GHC.Base.Functor m => GHC.Base.Functor (System.Cron.Schedule.ScheduleT m)
- System.Cron.Schedule: instance GHC.Base.Monad m => Control.Monad.Error.Class.MonadError System.Cron.Schedule.ScheduleError (System.Cron.Schedule.ScheduleT m)
- System.Cron.Schedule: instance GHC.Base.Monad m => Control.Monad.State.Class.MonadState System.Cron.Schedule.Jobs (System.Cron.Schedule.ScheduleT m)
- System.Cron.Schedule: instance GHC.Base.Monad m => GHC.Base.Applicative (System.Cron.Schedule.ScheduleT m)
- System.Cron.Schedule: instance GHC.Base.Monad m => GHC.Base.Monad (System.Cron.Schedule.ScheduleT m)
- System.Cron.Schedule: instance GHC.Base.Monad m => System.Cron.Schedule.MonadSchedule (System.Cron.Schedule.ScheduleT m)
- System.Cron.Schedule: instance GHC.Show.Show System.Cron.Schedule.Job
- System.Cron.Schedule: instance GHC.Show.Show System.Cron.Schedule.ScheduleError
+ System.Cron: command :: CrontabEntry -> Text
+ System.Cron: dayOfMonth :: CronSchedule -> DayOfMonthSpec
+ System.Cron: dayOfWeek :: CronSchedule -> DayOfWeekSpec
+ System.Cron: hour :: CronSchedule -> HourSpec
+ System.Cron: instance Eq CronField
+ System.Cron: instance Eq CronSchedule
+ System.Cron: instance Eq CronUnit
+ System.Cron: instance Eq Crontab
+ System.Cron: instance Eq CrontabEntry
+ System.Cron: instance Eq DayOfMonthSpec
+ System.Cron: instance Eq DayOfWeekSpec
+ System.Cron: instance Eq HourSpec
+ System.Cron: instance Eq MinuteSpec
+ System.Cron: instance Eq MonthSpec
+ System.Cron: instance Show CronField
+ System.Cron: instance Show CronSchedule
+ System.Cron: instance Show CronUnit
+ System.Cron: instance Show Crontab
+ System.Cron: instance Show CrontabEntry
+ System.Cron: instance Show DayOfMonthSpec
+ System.Cron: instance Show DayOfWeekSpec
+ System.Cron: instance Show HourSpec
+ System.Cron: instance Show MinuteSpec
+ System.Cron: instance Show MonthSpec
+ System.Cron: minute :: CronSchedule -> MinuteSpec
+ System.Cron: month :: CronSchedule -> MonthSpec
+ System.Cron: schedule :: CrontabEntry -> CronSchedule
+ System.Cron: varName :: CrontabEntry -> Text
+ System.Cron: varValue :: CrontabEntry -> Text
+ System.Cron.Schedule: instance (Monad m, Functor m) => Applicative (ScheduleT m)
+ System.Cron.Schedule: instance Functor m => Functor (ScheduleT m)
+ System.Cron.Schedule: instance Monad m => Monad (ScheduleT m)
+ System.Cron.Schedule: instance Monad m => MonadError ScheduleError (ScheduleT m)
+ System.Cron.Schedule: instance Monad m => MonadSchedule (ScheduleT m)
+ System.Cron.Schedule: instance Monad m => MonadState Jobs (ScheduleT m)
+ System.Cron.Schedule: instance Show Job
+ System.Cron.Schedule: instance Show ScheduleError
+ System.Cron.Schedule: unSchedule :: ScheduleT m a -> StateT Jobs (ExceptT ScheduleError m) a

Files

changelog view
@@ -1,3 +1,5 @@+# 0.3.2+* Add test files to dist # 0.3.1 * Fix bug that caused some range schedules to not fire at the correct time. See https://github.com/MichaelXavier/cron/issues/18 # 0.3.0
cron.cabal view
@@ -1,5 +1,5 @@ Name:                cron-Version:             0.3.1+Version:             0.3.2 Description:   Cron data structure and Attoparsec parser. The idea is to embed it in larger   systems which want to roll their own scheduled tasks in a format that people@@ -29,6 +29,11 @@   README.md   LICENSE   changelog+  test/Main.hs+  test/SpecHelper.hs+  test/System/Test/Cron.hs+  test/System/Test/Cron/Parser.hs+  test/System/Test/Cron/Schedule.hs Homepage:           http://github.com/michaelxavier/cron Bug-Reports:        http://github.com/michaelxavier/cron/issues 
+ test/SpecHelper.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE TemplateHaskell #-}+module SpecHelper+    ( module X+    , isLeft+    ) where+++-------------------------------------------------------------------------------+import           Control.Applicative   as X+import           Data.Attoparsec.Text  as X+import           Data.DeriveTH+import           Data.Time.Calendar    as X+import           Data.Time.Clock       as X+import           Data.Time.LocalTime   as X+import           Debug.Trace           as X+import           Test.Tasty            as X+import           Test.Tasty.HUnit      as X+import           Test.Tasty.QuickCheck as X+-------------------------------------------------------------------------------+import           System.Cron           as X+import           System.Cron.Parser    as X+-------------------------------------------------------------------------------+++instance Arbitrary UTCTime where+  arbitrary = do+    d <- ModifiedJulianDay . fromInteger . getPositive <$> arbitrary+    t <- fromInteger . getPositive <$> arbitrary+    return $ UTCTime d t++$(derive makeArbitrary ''CronField)+$(derive makeArbitrary ''MinuteSpec)+$(derive makeArbitrary ''HourSpec)+$(derive makeArbitrary ''DayOfWeekSpec)+$(derive makeArbitrary ''DayOfMonthSpec)+$(derive makeArbitrary ''MonthSpec)+$(derive makeArbitrary ''CronSchedule)+++-------------------------------------------------------------------------------+isLeft :: Either a b -> Bool+isLeft (Left _) = True+isLeft _        = False
+ test/System/Test/Cron.hs view
@@ -0,0 +1,252 @@+{-# LANGUAGE OverloadedStrings #-}+module System.Test.Cron (tests) where++-------------------------------------------------------------------------------+import           SpecHelper+-------------------------------------------------------------------------------+++tests :: TestTree+tests = testGroup "System.Cron"+  [ describeScheduleMatches+  , describeCronScheduleShow+  , describeCrontabEntryShow+  , describeCrontabShow+  ]++---- Specs+describeScheduleMatches :: TestTree+describeScheduleMatches = testGroup "ScheduleMatches"+  [+      testCase "matches a catch-all" $+      scheduleMatches stars (day 5 25 1 2) @?= True++    , testCase "matches a specific field" $+      scheduleMatches stars { hour = Hours (SpecificField 1)}+                      (day 5 25 1 2) @?= True++    , testCase "matches a range" $+      scheduleMatches stars { dayOfMonth = DaysOfMonth (RangeField 3 5)}+                      (day 5 4 1 2) @?= True++    , testCase "does not match invalid range" $+      scheduleMatches stars { dayOfMonth = DaysOfMonth (RangeField 5 3)}+                      (day 5 4 1 2) @?= False++    , testCase "matches a list" $+      scheduleMatches stars { month = Months (ListField [SpecificField 1,+                                                         SpecificField 2,+                                                         SpecificField 3])}+                      (day 2 3 1 2) @?= True++    , testCase "matches a step field" $+       scheduleMatches stars { dayOfMonth = DaysOfMonth (StepField (RangeField 10 16) 2)}+                       (day 5 12 1 2) @?= True++    , testCase "does not match something missing the step field" $+      scheduleMatches stars { dayOfMonth = DaysOfMonth (StepField (RangeField 10 16) 2)}+                      (day 5 13 1 2) @?= False++    , testCase "matches starred stepped fields" $+      scheduleMatches stars { minute = Minutes (StepField Star 2)}+                            (day 5 13 1 4) @?= True++    , testCase "does not match fields that miss starred stepped fields" $+      scheduleMatches stars { minute = Minutes (StepField Star 2)}+                      (day 5 13 1 5) @?= False++    , testCase "matches multiple fields at once" $+      scheduleMatches stars { minute     = Minutes (StepField Star 2),+                              dayOfMonth = DaysOfMonth (SpecificField 3),+                              hour       = Hours (RangeField 10 14) }+                      (day 5 3 13 2) @?= True++    , testCase "matches a monday as 1" $+      scheduleMatches stars { dayOfWeek  = DaysOfWeek (SpecificField 1) }+                      (UTCTime (fromGregorian 2014 3 17) 0) @?= True++    , testCase "matches a sunday as 0" $+      scheduleMatches stars { dayOfWeek  = DaysOfWeek (SpecificField 0) }+                      (UTCTime (fromGregorian 2014 3 16) 0) @?= True++    , testCase "matches a sunday as 7" $+      scheduleMatches stars { dayOfWeek  = DaysOfWeek (SpecificField 7) }+                      (UTCTime (fromGregorian 2014 3 16) 0) @?= True++    , testCase "matches weekly on a sunday at 0:00" $+      scheduleMatches weekly (UTCTime (fromGregorian 2014 4 6) 0) @?= True++    , testCase "does not match weekly on a sunday at some time past midnight" $+      scheduleMatches weekly (UTCTime (fromGregorian 2014 6 4) 600) @?= False++    , testCase "does not match weekly on another day at midnight" $+      scheduleMatches weekly (UTCTime (fromGregorian 2014 6 5) 600) @?= False++    , testCase "only needs weekday or monthday to match" $+      scheduleMatches stars { dayOfWeek = DaysOfWeek (SpecificField 1),+                              dayOfMonth = DaysOfMonth (SpecificField 1) }+                      (UTCTime (fromGregorian 2014 11 1) 600) @?= True+    -- https://github.com/MichaelXavier/cron/issues/18+    , testCase "correctly schedules steps and ranges" $ do+      let Right oddMinute = parseOnly cronSchedule "1-59/2 * * * *"+      let Right evenMinute = parseOnly cronSchedule "0-59/2 * * * *"+      let t1 = mkTime 2015 7 17 15 17 0+      let t2 = mkTime 2015 7 17 15 18 0+      scheduleMatches oddMinute t1 @?= True+      scheduleMatches oddMinute t2 @?= False+      scheduleMatches evenMinute t1 @?= False+      scheduleMatches evenMinute t2 @?= True++    , testProperty "star matches everything" $ \t ->+            scheduleMatches stars t++    , testProperty "exact time matches" $ \t ->+      let (_, m, d, h, mn) = timeComponents t+          sched = CronSchedule (Minutes $ SpecificField mn)+                               (Hours $ SpecificField h)+                               (DaysOfMonth $ SpecificField d)+                               (Months $ SpecificField m)+                               (DaysOfWeek Star)+      in scheduleMatches sched t++    , testProperty "any time with the same minute as n * * * * matches" $ arbitraryTimeFields $ \y m d h mn ->+      let sched = stars { minute = Minutes $ SpecificField mn }+          t     = day' y m d h mn+      in scheduleMatches sched t++    , testProperty "any time with the diff minute as n * * * * does not match" $ arbitraryTimeFields $ \y m d h mn ->+      let sched = stars { minute = Minutes $ SpecificField $ stepMax 59 mn }+          t     = day' y m d h mn+      in not $ scheduleMatches sched t++    , testProperty "any time with the same hour as * n * * * matches" $ arbitraryTimeFields $ \y m d h mn ->+      let sched = stars { hour = Hours $ SpecificField h }+          t     = day' y m d h mn+      in scheduleMatches sched t++    , testProperty "any time with the diff hour as * n * * * does not match" $ arbitraryTimeFields $ \y m d h mn ->+      let sched = stars { hour = Hours $ SpecificField $ stepMax 23 h }+          t     = day' y m d h mn+      in not $ scheduleMatches sched t++    , testProperty "any time with the same day as * * n * * matches" $ \t ->+      let (_, m, d, h, mn) = timeComponents t+          sched = CronSchedule (Minutes $ SpecificField mn)+                               (Hours $ SpecificField h)+                               (DaysOfMonth $ SpecificField d)+                               (Months $ SpecificField m)+                               (DaysOfWeek Star)+      in scheduleMatches sched t++    , testProperty "any time with the diff day as * * n * * does not match" $ arbitraryTimeFields $ \y m d h mn ->+      let sched = stars { dayOfMonth = DaysOfMonth $ SpecificField $ stepMax 31 d }+          t     = day' y m d h mn+      in not $ scheduleMatches sched t++  ]++  where day = day' 2012+        day' y m d h mn = UTCTime (fromGregorian y m d) (diffTime h mn)+        diffTime h mn = timeOfDayToTime $ TimeOfDay h mn 1++arbitraryTimeFields f y m d h mn = f (getPositive y)+                                     (min 12 $ getPositive m)+                                     (min 28 $ getPositive d)+                                     (min 23 $ getPositive h)+                                     (min 59 $ getPositive mn)++hoursMins uTime = (hr, mn)+  where+    TimeOfDay { todHour = hr,+                todMin  = mn} = timeToTimeOfDay uTime+++stepMax :: (Enum a, Ord a) => a -> a -> a+stepMax mx n | n < mx    = succ n+             | otherwise = pred n+++describeCronScheduleShow :: TestTree+describeCronScheduleShow = testGroup "CronSchedule show"+  [+    testCase "formats stars" $+    show stars @?= "CronSchedule * * * * *"++  , testCase "formats specific numbers" $+    show stars { dayOfWeek = DaysOfWeek (SpecificField 3)} @?=+         "CronSchedule * * * * 3"++  , testCase "formats lists" $+    show stars { minute = Minutes (ListField [SpecificField 1,+                                   SpecificField 2,+                                   SpecificField 3])} @?=+         "CronSchedule 1,2,3 * * * *"++  , testCase "formats ranges" $+    show stars { hour = Hours (RangeField 7 10)} @?=+         "CronSchedule * 7-10 * * *"++  , testCase "formats steps" $+    show stars { dayOfMonth = DaysOfMonth (StepField (ListField [SpecificField 3, SpecificField 5]) 2)} @?=+         "CronSchedule * * 3,5/2 * *"++  , testCase "formats @yearly" $+    show yearly @?= "CronSchedule 0 0 1 1 *"++  , testCase "formats @monthly" $+    show monthly @?= "CronSchedule 0 0 1 * *"++  , testCase "formats @weekly" $+    show weekly @?= "CronSchedule 0 0 * * 0"++  , testCase "formats @daily" $+    show daily @?= "CronSchedule 0 0 * * *"++  , testCase "formats @hourly" $+    show hourly @?= "CronSchedule 0 * * * *"++  , testCase "formats everyMinute" $+    show everyMinute @?= "CronSchedule * * * * *"+  ]++describeCrontabShow :: TestTree+describeCrontabShow = testGroup "Crontab Show"+  [+    testCase "prints nothing for an empty crontab" $+    show (Crontab []) @?= ""+  ]++describeCrontabEntryShow :: TestTree+describeCrontabEntryShow = testGroup "CrontabEntry Show"+  [+   testCase "formats environment variable sets" $+   show envSet @?= "FOO=BAR"++  , testCase "formats command entries" $+    show entry @?= "* * * * * do stuff"+  ]+++envSet :: CrontabEntry+envSet = EnvVariable "FOO" "BAR"++entry :: CrontabEntry+entry = CommandEntry stars "do stuff"++stars :: CronSchedule+stars = CronSchedule (Minutes Star)+                     (Hours Star)+                     (DaysOfMonth Star)+                     (Months Star)+                     (DaysOfWeek Star)++timeComponents :: UTCTime -> (Integer, Int, Int, Int, Int)+timeComponents (UTCTime dy dt) = (y, m, d, h, mn)+  where+    (y, m, d) = toGregorian dy+    (h, mn)   = hoursMins dt+++mkTime y m d hr mn s = UTCTime day time+  where day = fromGregorian y m d+        time = s + 60 * mn + 60 * 60 * hr
+ test/System/Test/Cron/Parser.hs view
@@ -0,0 +1,195 @@+{-# LANGUAGE OverloadedStrings #-}+module System.Test.Cron.Parser (tests) where+-- TODO: this *should* just work with {-# OPTIONS_GHC -F -pgmF hspec-discover #-}+++-------------------------------------------------------------------------------+import           Data.Attoparsec.Text (Parser, parseOnly)+import           Data.Text            (Text)+-------------------------------------------------------------------------------+import           SpecHelper+import           System.Cron+import           System.Cron.Parser+-------------------------------------------------------------------------------+++tests :: TestTree+tests = testGroup "System.Cron.Parser"+  [ describeCronSchedule+  , describeCronScheduleLoose+  , describeCrontab+  , describeCrontabEntry+  ]++describeCronSchedule :: TestTree+describeCronSchedule = testGroup "cronSchedule"+  [+    testCase "parses all stars" $+    assertSuccessfulParse "* * * * *"+                          stars++  , testCase "parses specific values" $+    assertSuccessfulParse "1 2 3 * *"+                           stars { minute      = Minutes (SpecificField 1),+                                   hour        = Hours (SpecificField 2),+                                   dayOfMonth  = DaysOfMonth (SpecificField 3) }++  , testCase "parses list values" $+    assertSuccessfulParse "* * 3,4 * *"+                           stars { dayOfMonth  = DaysOfMonth (ListField [SpecificField 3,+                                                                         SpecificField 4]) }++  , testCase "parses range values" $+    assertSuccessfulParse "* * 3-4 * *"+                           stars { dayOfMonth  = DaysOfMonth (RangeField 3 4) }++  , testCase "parses step values" $+    assertSuccessfulParse "*/2 * 2-10/4 * *"+                          stars { minute     = Minutes (StepField Star 2),+                                  dayOfMonth  = DaysOfMonth (StepField (RangeField 2 10) 4) }++  , testCase "refuses to parse recursive steps" $+    assertFailedParse "*/2/3 * * * *"++  , testCase "refuses to parse sparse lists" $+    assertFailedParse "1,,2 * * * *"++  , testCase "refuses too few fields" $+    assertFailedParse "* * * *"++  , testCase "refuses too many fields" $+    assertFailedParse "* * * * * *"++  , testCase "refuses extraneous input" $+    assertFailedParse "* * * * *    wat is this"++  , testCase "parses @hourly" $+    assertSuccessfulParse "@hourly" hourly++  , testCase "parses @daily" $+    assertSuccessfulParse "@daily" daily++  , testCase "parses @monthly" $+    assertSuccessfulParse "@monthly" monthly++  , testCase "parses @yearly" $+    assertSuccessfulParse "@yearly" yearly++  , testCase "parses ranges at the last field" $+    assertSuccessfulParse "* * * * 3-4"+                           stars { dayOfWeek  = DaysOfWeek (RangeField 3 4) }+  , testCase "parses lists at the last field" $+    assertSuccessfulParse "* * * * 3,4"+                           stars { dayOfWeek  = DaysOfWeek (ListField [SpecificField 3,+                                                                       SpecificField 4]) }+  , testCase "parses steps at the last field" $+    assertSuccessfulParse "* * * * */4"+                           stars { dayOfWeek  = DaysOfWeek (StepField Star 4) }+  , testCase "parses a sunday as 7" $+    assertSuccessfulParse "* * * * 7"+                           stars { dayOfWeek  = DaysOfWeek (SpecificField 7) }+  , testCase "parses a sunday as 0" $+    assertSuccessfulParse "* * * * 0"+                           stars { dayOfWeek  = DaysOfWeek (SpecificField 0) }+  , testCase "parses another example" $+    assertSuccessfulParse "1-59/2 * * * *"+                          stars { minute     = Minutes (StepField (RangeField 1 59) 2) }++  ]+  where assertSuccessfulParse = assertParse cronSchedule+        assertFailedParse = assertNoParse cronSchedule++describeCronScheduleLoose :: TestTree+describeCronScheduleLoose = testGroup "cronScheduleLoose"+  [+    testCase "is okay with extaneous input" $+    assertSuccessfulParse "* * * * * *"+                          stars+  ]+  where assertSuccessfulParse = assertParse cronScheduleLoose++describeCrontab :: TestTree+describeCrontab = testGroup "crontab"+  [+    testCase "parses an empty input" $+    assertSuccessfulParse ""+                          (Crontab [])++  , testCase "parses whitespace" $+    assertSuccessfulParse "        "+                          (Crontab [])++  , testCase "parses a single line" $+    assertSuccessfulParse "        "+                          (Crontab [])++  , testCase "ignores comments" $+    assertSuccessfulParse "# comment"+                          (Crontab [])++  , testCase "ignores comments with leading whitespace" $+    assertSuccessfulParse "    # comment"+                          (Crontab [])++  , testCase "parses comments interspersed with actual commands" $+    assertSuccessfulParse "#comment here\nFOO=BAR\n  #another\n* * * * * do stuff"+                          (Crontab [envSet, entry])+  ]+  where assertSuccessfulParse = assertParse crontab++describeCrontabEntry :: TestTree+describeCrontabEntry = testGroup "crontabEntry"+  [+    testCase "parses an environment variable assignment" $+    assertSuccessfulParse "FOO=BAR"+                          envSet++  , testCase "parses an environment variable with whitespace at the front" $+    assertSuccessfulParse "  FOO=BAR"+                          envSet++  , testCase "parses an environment variable with whitespace in the middle" $+    assertSuccessfulParse "  FOO =   BAR"+                          envSet++  , testCase "parses a command" $+    assertSuccessfulParse "* * * * * do stuff"+                          entry++  , testCase "parses a command with any amount of whitespace inbetween" $+    assertSuccessfulParse "* * * * *      do stuff"+                          entry++  , testCase "parses a command with whitespace at the front" $+    assertSuccessfulParse "  * * * * *      do stuff"+                          entry+  ]+  where assertSuccessfulParse = assertParse crontabEntry++assertParse :: (Eq a, Show a)+               => Parser a+               -> Text+               -> a+               -> Assertion+assertParse parser txt expected = parsed @?= Right expected+  where parsed = parseOnly parser txt++--assertNoParse :: Parser a -> Text -> b+assertNoParse :: (Eq a, Show a)+                 => Parser a+                 -> Text+                 -> Assertion+assertNoParse parser txt = isLeft (parseOnly parser txt) @?= True++envSet :: CrontabEntry+envSet = EnvVariable "FOO" "BAR"++entry :: CrontabEntry+entry = CommandEntry stars "do stuff"++stars :: CronSchedule+stars = CronSchedule (Minutes Star)+                     (Hours Star)+                     (DaysOfMonth Star)+                     (Months Star)+                     (DaysOfWeek Star)
+ test/System/Test/Cron/Schedule.hs view
@@ -0,0 +1,50 @@+module System.Test.Cron.Schedule+    ( tests+    ) where++-------------------------------------------------------------------------------+import           Control.Concurrent+-------------------------------------------------------------------------------+import           SpecHelper+import           System.Cron.Schedule+-------------------------------------------------------------------------------+++tests :: TestTree+tests = testGroup "System.Cron.Schedule"+  [ describeMonadSchedule+  , describeExecSchedule+  ]++flipMVar :: MVar String -> IO ()+flipMVar v = putMVar v "dost thou even hoist"++describeMonadSchedule :: TestTree+describeMonadSchedule = testGroup "MonadSchedule"+  [+    testCase "should place the first job as the last job stated." $+      let (Job x _) = (s !! 0)+       in show x @?= "CronSchedule 0 0 * * *"+  , testCase "should place the last job as the first job stated." $+      let (Job x _) = (s !! 2)+       in show x @?= "CronSchedule * * * * *"+  , testCase "should read all three jobs." $+      length s @?= 3+  ]+  where Right ((), s) = runSchedule $ do addJob empty "* * * * *"+                                         addJob empty "0 * * * *"+                                         addJob empty "0 0 * * *"++describeExecSchedule :: TestTree+describeExecSchedule = testGroup "execSchedule"+  [+     testCase "should set an mvar each minute" $+       fireAndWait >>= (@?= "dost thou even hoist")+  ]+    where fireAndWait = do+            v    <- newEmptyMVar+            tids <- execSchedule $ do+                addJob (flipMVar v) "* * * * *"+            threadDelay (1000000 * 60)+            mapM_ killThread tids+            takeMVar v