diff --git a/ChangeLog.md b/ChangeLog.md
deleted file mode 100644
--- a/ChangeLog.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# Changelog
-  
-## [Unreleased]
-
-## [0.0.0.2] - 2019-05-15
-
-### Changed
-
-* Exposed 'readLooperEnvironment'
-* Doc fix
-
-## [0.0.0.1] - 2019-05-15
-
-### Changed
-
-* Better docs
-
-## [0.0.0.0] - 2019-05-15
-
-Initial version
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright Tom Sydney Kerckhove (c) 2019
+Copyright Tom Sydney Kerckhove (c) 2019-2022
 
 All rights reserved.
 
diff --git a/README.md b/README.md
deleted file mode 100644
--- a/README.md
+++ /dev/null
@@ -1,1 +0,0 @@
-# looper
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
--- a/Setup.hs
+++ /dev/null
@@ -1,2 +0,0 @@
-import Distribution.Simple
-main = defaultMain
diff --git a/looper.cabal b/looper.cabal
--- a/looper.cabal
+++ b/looper.cabal
@@ -1,25 +1,22 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.31.1.
+-- This file has been generated from package.yaml by hpack version 0.34.7.
 --
 -- see: https://github.com/sol/hpack
---
--- hash: 89647c3d09f7fa4d71e44a70470be2d4892fa2d52a50236d39c23d195fe84eae
 
 name:           looper
-version:        0.0.0.2
-description:    Please see the README on GitHub at <https://github.com/NorfairKing/looper#readme>
+version:        0.2.0.1
+description:    Configure and run recurring jobs indefinitely
 homepage:       https://github.com/NorfairKing/looper#readme
 bug-reports:    https://github.com/NorfairKing/looper/issues
 author:         Tom Sydney Kerckhove
 maintainer:     syd@cs-syd.eu
-copyright:      Copyright: (c) 2019 Tom Sydney Kerckhove
-license:        BSD3
+copyright:      Copyright: (c) 2019-2022 Tom Sydney Kerckhove
+license:        MIT
 license-file:   LICENSE
 build-type:     Simple
 extra-source-files:
-    README.md
-    ChangeLog.md
+    test_resources/configuration.txt
 
 source-repository head
   type: git
@@ -34,7 +31,9 @@
       src
   build-depends:
       aeson
+    , autodocodec
     , base >=4.7 && <5
+    , envparse
     , optparse-applicative
     , text
     , time
@@ -50,13 +49,13 @@
   hs-source-dirs:
       test
   ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  build-tool-depends:
+      sydtest-discover:sydtest-discover
   build-depends:
-      aeson
+      autodocodec-yaml
     , base >=4.7 && <5
-    , hspec
     , looper
     , optparse-applicative
-    , text
-    , time
+    , sydtest
     , unliftio
   default-language: Haskell2010
diff --git a/src/Looper.hs b/src/Looper.hs
--- a/src/Looper.hs
+++ b/src/Looper.hs
@@ -1,55 +1,57 @@
-{-# LANGUAGE RecordWildCards #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
 
 module Looper
-  ( LooperDef(..)
-  , seconds
-  , minutes
-  , hours
-  , LooperFlags(..)
-  , getLooperFlags
-  , LooperEnvironment(..)
-  , getLooperEnvironment
-  , readLooperEnvironment
-  , LooperConfiguration(..)
-  , LooperSettings(..)
-  , deriveLooperSettings
-  , mkLooperDef
-  , runLoopers
-  , runLoopersIgnoreOverrun
-  , runLoopersRaw
-  , waitNominalDiffTime
-  ) where
-
-import GHC.Generics (Generic)
+  ( LooperDef (..),
+    seconds,
+    minutes,
+    hours,
+    LooperFlags (..),
+    getLooperFlags,
+    LooperEnvironment (..),
+    getLooperEnvironment,
+    readLooperEnvironment,
+    looperEnvironmentParser,
+    LooperConfiguration (..),
+    LooperSettings (..),
+    deriveLooperSettings,
+    mkLooperDef,
+    runLoopers,
+    runLoopersIgnoreOverrun,
+    runLoopersRaw,
+    waitNominalDiffTime,
+  )
+where
 
+import Autodocodec
 import Control.Applicative
 import Control.Monad
-
-import qualified System.Environment as System (getEnvironment)
-
-import Data.Aeson
+import Data.Aeson (FromJSON, ToJSON)
 import Data.Maybe
-import Data.String
 import Data.Text (Text)
 import Data.Time
-
-import Options.Applicative
-import Text.Read
-
+import qualified Env
+import GHC.Generics (Generic)
+import Options.Applicative as OptParse
+import qualified System.Environment as System (getEnvironment)
 import UnliftIO
 import UnliftIO.Concurrent
 
 -- | A looper definition
-data LooperDef m =
-  LooperDef
-    { looperDefName :: Text -- ^ The name of the looper, can be useful for logging
-    , looperDefEnabled :: Bool -- ^ Whether this looper is enabled
-    , looperDefPeriod :: NominalDiffTime -- ^ The time between the start of each run
-    , looperDefPhase :: NominalDiffTime -- ^ The time before the first run
-    , looperDefFunc :: m () -- ^ The function to run
-    }
+data LooperDef m = LooperDef
+  { -- | The name of the looper, can be useful for logging
+    looperDefName :: Text,
+    -- | Whether this looper is enabled
+    looperDefEnabled :: Bool,
+    -- | The time between the start of each run
+    looperDefPeriod :: NominalDiffTime,
+    -- | The time before the first run
+    looperDefPhase :: NominalDiffTime,
+    -- | The function to run
+    looperDefFunc :: m ()
+  }
   deriving (Generic)
 
 -- | Construct a 'NominalDiffTime' from a number of seconds
@@ -65,147 +67,167 @@
 hours = minutes . (* 60)
 
 -- | A structure to parse command-line flags for a looper into
-data LooperFlags =
-  LooperFlags
-    { looperFlagEnabled :: Maybe Bool
-    , looperFlagPhase :: Maybe Int -- Seconds
-    , looperFlagPeriod :: Maybe Int -- Seconds
-    }
+data LooperFlags = LooperFlags
+  { looperFlagEnabled :: Maybe Bool,
+    looperFlagPhase :: Maybe Word, -- Seconds
+    looperFlagPeriod :: Maybe Word -- Seconds
+  }
   deriving (Show, Eq, Generic)
 
 -- | An optparse applicative parser for 'LooperFlags'
 getLooperFlags ::
-     String -- ^ The name of the looper (best to make this all-lowercase)
-  -> Parser LooperFlags
+  -- | The name of the looper (best to make this all-lowercase)
+  String ->
+  OptParse.Parser LooperFlags
 getLooperFlags name =
-  LooperFlags <$> doubleSwitch name (unwords ["enable the", name, "looper"]) mempty <*>
-  option
-    (Just <$> auto)
-    (mconcat
-       [ long $ name <> "-phase"
-       , metavar "SECONDS"
-       , value Nothing
-       , help $ unwords ["the phase for the", name, "looper in seconsd"]
-       ]) <*>
-  option
-    (Just <$> auto)
-    (mconcat
-       [ long $ name <> "-period"
-       , metavar "SECONDS"
-       , value Nothing
-       , help $ unwords ["the period for the", name, "looper in seconds"]
-       ])
+  LooperFlags <$> doubleSwitch name (unwords ["enable the", name, "looper"]) mempty
+    <*> option
+      (Just <$> auto)
+      ( mconcat
+          [ long $ name <> "-phase",
+            metavar "SECONDS",
+            value Nothing,
+            help $ unwords ["the phase for the", name, "looper in seconsd"]
+          ]
+      )
+    <*> option
+      (Just <$> auto)
+      ( mconcat
+          [ long $ name <> "-period",
+            metavar "SECONDS",
+            value Nothing,
+            help $ unwords ["the period for the", name, "looper in seconds"]
+          ]
+      )
 
-doubleSwitch :: String -> String -> Mod FlagFields Bool -> Parser (Maybe Bool)
+doubleSwitch :: String -> String -> Mod FlagFields Bool -> OptParse.Parser (Maybe Bool)
 doubleSwitch name helpText mods =
   let enabledValue = True
       disabledValue = False
       defaultValue = True
-   in ((last . map Just) <$>
-       some
-         ((flag'
-             enabledValue
-             (hidden <> internal <> long ("enable-" ++ name) <> help helpText <> mods) <|>
-           flag'
-             disabledValue
-             (hidden <> internal <> long ("disable-" ++ name) <> help helpText <> mods)) <|>
-          flag'
-            disabledValue
-            (long ("(enable|disable)-" ++ name) <>
-             help ("Enable/disable " ++ helpText ++ " (default: " ++ show defaultValue ++ ")") <>
-             mods))) <|>
-      pure Nothing
+   in ( last . map Just
+          <$> some
+            ( ( flag'
+                  enabledValue
+                  (hidden <> internal <> long ("enable-" ++ name) <> help helpText <> mods)
+                  <|> flag'
+                    disabledValue
+                    (hidden <> internal <> long ("disable-" ++ name) <> help helpText <> mods)
+              )
+                <|> flag'
+                  disabledValue
+                  ( long ("(enable|disable)-" ++ name)
+                      <> help ("Enable/disable " ++ helpText ++ " (default: " ++ show defaultValue ++ ")")
+                      <> mods
+                  )
+            )
+      )
+        <|> pure Nothing
 
 -- | A structure to parse environment variables for a looper into
-data LooperEnvironment =
-  LooperEnvironment
-    { looperEnvEnabled :: Maybe Bool
-    , looperEnvPhase :: Maybe Int -- Seconds
-    , looperEnvPeriod :: Maybe Int -- Seconds
-    }
+data LooperEnvironment = LooperEnvironment
+  { looperEnvEnabled :: Maybe Bool,
+    looperEnvPhase :: Maybe Word, -- Seconds
+    looperEnvPeriod :: Maybe Word -- Seconds
+  }
   deriving (Show, Eq, Generic)
 
 -- | Get a 'LooperEnvironment' from the environment
 getLooperEnvironment ::
-     String -- ^ Prefix for each variable (best to make this all-caps)
-  -> String -- ^ Name of the looper (best to make this all-caps too)
-  -> IO LooperEnvironment
+  -- | Prefix for each variable (best to make this all-caps)
+  String ->
+  -- | Name of the looper (best to make this all-caps too)
+  String ->
+  IO LooperEnvironment
 getLooperEnvironment prefix name = readLooperEnvironment prefix name <$> System.getEnvironment
 
 -- | Get a 'LooperEnvironment' from a pure environment
 readLooperEnvironment ::
-     String -- ^ Prefix for each variable (best to make this all-caps)
-  -> String -- ^ Name of the looper (best to make this all-caps too)
-  -> [(String, String)]
-  -> LooperEnvironment
-readLooperEnvironment prefix name env =
-  let v :: IsString s => String -> Maybe s
-      v k = fromString <$> lookup (prefix <> k) env
-      r :: Read a => String -> Maybe a
-      r k = v k >>= readMaybe
-      lr :: Read a => String -> Maybe a
-      lr k = r $ name <> "_" <> k
-   in LooperEnvironment
-        { looperEnvEnabled = lr "ENABLED"
-        , looperEnvPhase = lr "PHASE"
-        , looperEnvPeriod = lr "PERIOD"
-        }
+  -- | Prefix for each variable (best to make this all-caps)
+  String ->
+  -- | Name of the looper (best to make this all-caps too)
+  String ->
+  [(String, String)] ->
+  LooperEnvironment
+readLooperEnvironment prefix name env = case Env.parsePure (Env.prefixed prefix $ looperEnvironmentParser name) env of
+  Left _ -> error "This indicates a bug in looper because all environment variables are optional."
+  Right r -> r
 
+-- | An 'envparse' parser for a 'LooperEnvironment'
+looperEnvironmentParser ::
+  -- | Name of the looper (best to make this all-caps)
+  String ->
+  Env.Parser Env.Error LooperEnvironment
+looperEnvironmentParser name =
+  Env.prefixed (name <> "_") $
+    LooperEnvironment
+      <$> Env.var (fmap Just . Env.auto) "ENABLED" (Env.def Nothing <> Env.help "Whether to enable this looper")
+      <*> Env.var (fmap Just . Env.auto) "PHASE" (Env.def Nothing <> Env.help "The amount of time to wait before starting the looper the first time, in seconds")
+      <*> Env.var (fmap Just . Env.auto) "PERIOD" (Env.def Nothing <> Env.help "The amount of time to wait between runs of the looper, in seconds")
+
 -- | A structure to configuration for a looper into
-data LooperConfiguration =
-  LooperConfiguration
-    { looperConfEnabled :: Maybe Bool
-    , looperConfPhase :: Maybe Int
-    , looperConfPeriod :: Maybe Int
-    }
-  deriving (Show, Eq, Generic)
+data LooperConfiguration = LooperConfiguration
+  { looperConfEnabled :: Maybe Bool,
+    looperConfPhase :: Maybe Word,
+    looperConfPeriod :: Maybe Word
+  }
+  deriving stock (Show, Eq, Generic)
+  deriving (FromJSON, ToJSON) via (Autodocodec LooperConfiguration)
 
--- | You can parse Data.Aeson's JSON or Data.Yaml's YAML to parse a 'LooperConfiguration'.
--- You can also use Data.Yaml.Config.
-instance FromJSON LooperConfiguration where
-  parseJSON =
-    withObject "LooperConfiguration" $ \o ->
-      LooperConfiguration <$> o .:? "enabled" <*> o .:? "phase" <*> o .: "period"
+instance HasCodec LooperConfiguration where
+  codec =
+    named "LooperConfiguration" $
+      object "LooperConfiguration" $
+        LooperConfiguration
+          <$> parseAlternative
+            (optionalFieldOrNull "enable" "Enable this looper")
+            (optionalFieldOrNull "enabled" "Enable this looper")
+            .= looperConfEnabled
+          <*> optionalFieldOrNull "phase" "The amount of time to wait before starting the looper the first time, in seconds" .= looperConfPhase
+          <*> optionalFieldOrNull "period" "The amount of time to wait between runs of the looper, in seconds" .= looperConfPeriod
 
 -- | Settings that you might want to pass into a looper using 'mkLooperDef'
-data LooperSettings =
-  LooperSettings
-    { looperSetEnabled :: Bool
-    , looperSetPhase :: NominalDiffTime
-    , looperSetPeriod :: NominalDiffTime
-    }
+data LooperSettings = LooperSettings
+  { looperSetEnabled :: Bool,
+    looperSetPhase :: NominalDiffTime,
+    looperSetPeriod :: NominalDiffTime
+  }
   deriving (Show, Eq, Generic)
 
 deriveLooperSettings ::
-     NominalDiffTime -- ^ Default phase
-  -> NominalDiffTime -- ^ Default period
-  -> LooperFlags
-  -> LooperEnvironment
-  -> Maybe LooperConfiguration
-  -> LooperSettings
+  -- | Default phase
+  NominalDiffTime ->
+  -- | Default period
+  NominalDiffTime ->
+  LooperFlags ->
+  LooperEnvironment ->
+  Maybe LooperConfiguration ->
+  LooperSettings
 deriveLooperSettings defaultPhase defaultPeriod LooperFlags {..} LooperEnvironment {..} mlc =
   let looperSetEnabled =
         fromMaybe True $ looperFlagEnabled <|> looperEnvEnabled <|> (mlc >>= looperConfEnabled)
       looperSetPhase =
         maybe defaultPhase fromIntegral $
-        looperFlagPhase <|> looperEnvPhase <|> (mlc >>= looperConfPhase)
+          looperFlagPhase <|> looperEnvPhase <|> (mlc >>= looperConfPhase)
       looperSetPeriod =
         maybe defaultPeriod fromIntegral $
-        looperFlagPeriod <|> looperEnvPeriod <|> (mlc >>= looperConfPeriod)
+          looperFlagPeriod <|> looperEnvPeriod <|> (mlc >>= looperConfPeriod)
    in LooperSettings {..}
 
 mkLooperDef ::
-     Text -- ^ Name
-  -> LooperSettings
-  -> m () -- ^ The function to loop
-  -> LooperDef m
+  -- | Name
+  Text ->
+  LooperSettings ->
+  -- | The function to loop
+  m () ->
+  LooperDef m
 mkLooperDef name LooperSettings {..} func =
   LooperDef
-    { looperDefName = name
-    , looperDefEnabled = looperSetEnabled
-    , looperDefPeriod = looperSetPeriod
-    , looperDefPhase = looperSetPhase
-    , looperDefFunc = func
+    { looperDefName = name,
+      looperDefEnabled = looperSetEnabled,
+      looperDefPeriod = looperSetPeriod,
+      looperDefPhase = looperSetPhase,
+      looperDefFunc = func
     }
 
 -- | Simply run loopers
@@ -226,11 +248,13 @@
 --
 -- Note that this function will loop forever, you need to wrap it using 'async' yourself.
 runLoopersIgnoreOverrun ::
-     MonadUnliftIO m
-  => (LooperDef m -> m ()) -- ^ Custom runner
-  -> [LooperDef m] -- ^ Loopers
-  -> m ()
-runLoopersIgnoreOverrun = runLoopersRaw (pure ())
+  (MonadUnliftIO m, MonadUnliftIO n) =>
+  -- | Custom runner
+  (LooperDef m -> n ()) ->
+  -- | Loopers
+  [LooperDef m] ->
+  n ()
+runLoopersIgnoreOverrun = runLoopersRaw (const $ pure ())
 
 -- | Run loopers, with a custom runner and overrun handler
 --
@@ -244,23 +268,26 @@
 --
 -- Note that this function will loop forever, you need to wrap it using 'async' yourself.
 runLoopersRaw ::
-     MonadUnliftIO m
-  => m () -- ^ Overrun handler
-  -> (LooperDef m -> m ()) -- ^ Runner
-  -> [LooperDef m] -- ^ Loopers
-  -> m ()
+  (MonadUnliftIO m, MonadUnliftIO n) =>
+  -- | Overrun handler
+  (LooperDef m -> n ()) ->
+  -- | Runner
+  (LooperDef m -> n ()) ->
+  -- | Loopers
+  [LooperDef m] ->
+  n ()
 runLoopersRaw onOverrun runLooper =
   mapConcurrently_ $ \ld@LooperDef {..} ->
     when looperDefEnabled $ do
       waitNominalDiffTime looperDefPhase
       let loop = do
-            start <- liftIO $ getCurrentTime
+            start <- liftIO getCurrentTime
             runLooper ld
-            end <- liftIO $ getCurrentTime
+            end <- liftIO getCurrentTime
             let elapsed = diffUTCTime end start
             let nextWait = looperDefPeriod - elapsed
-            if (nextWait < 0)
-              then onOverrun
+            if nextWait < 0
+              then onOverrun ld
               else waitNominalDiffTime nextWait
             loop
       loop
diff --git a/test/LooperSpec.hs b/test/LooperSpec.hs
--- a/test/LooperSpec.hs
+++ b/test/LooperSpec.hs
@@ -1,16 +1,17 @@
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeApplications #-}
 
 module LooperSpec
-  ( spec
-  ) where
-
-import Test.Hspec
+  ( spec,
+  )
+where
 
+import Autodocodec.Yaml
+import Looper
 import Options.Applicative as AP
+import Test.Syd
 import UnliftIO
 
-import Looper
-
 spec :: Spec
 spec = do
   describe "getLooperFlags" $ do
@@ -18,33 +19,50 @@
       parserSucceedsWith
         (getLooperFlags "test")
         []
-        (LooperFlags
-           {looperFlagEnabled = Nothing, looperFlagPhase = Nothing, looperFlagPeriod = Nothing})
+        ( LooperFlags
+            { looperFlagEnabled = Nothing,
+              looperFlagPhase = Nothing,
+              looperFlagPeriod = Nothing
+            }
+        )
     it "parses an enable flag correctly" $ do
       parserSucceedsWith
         (getLooperFlags "test")
         ["--enable-test"]
-        (LooperFlags
-           {looperFlagEnabled = Just True, looperFlagPhase = Nothing, looperFlagPeriod = Nothing})
+        ( LooperFlags
+            { looperFlagEnabled = Just True,
+              looperFlagPhase = Nothing,
+              looperFlagPeriod = Nothing
+            }
+        )
     it "parses an disable flag correctly" $ do
       parserSucceedsWith
         (getLooperFlags "test")
         ["--disable-test"]
-        (LooperFlags
-           {looperFlagEnabled = Just False, looperFlagPhase = Nothing, looperFlagPeriod = Nothing})
+        ( LooperFlags
+            { looperFlagEnabled = Just False,
+              looperFlagPhase = Nothing,
+              looperFlagPeriod = Nothing
+            }
+        )
+
+  describe "Configuration" $ do
+    it "has the same schema as before" $
+      pureGoldenByteStringFile "test_resources/configuration.txt" (renderColouredSchemaViaCodec @LooperConfiguration)
+
   describe "runLoopers" $ do
     it "runs one looper as intended" $ do
       v <- newTVarIO (0 :: Int)
       let l =
             LooperDef
-              { looperDefName = "l1"
-              , looperDefEnabled = True
-              , looperDefPeriod = seconds 0.01
-              , looperDefPhase = seconds 0
-              , looperDefFunc = atomically $ modifyTVar' v succ
+              { looperDefName = "l1",
+                looperDefEnabled = True,
+                looperDefPeriod = seconds 1,
+                looperDefPhase = seconds 0,
+                looperDefFunc = atomically $ modifyTVar' v succ
               }
       a <- async $ runLoopers [l]
-      waitNominalDiffTime $ seconds 0.015
+      waitNominalDiffTime $ seconds 1.5
       cancel a
       r <- readTVarIO v
       r `shouldBe` 2
@@ -52,14 +70,14 @@
       v <- newTVarIO (0 :: Int)
       let l =
             LooperDef
-              { looperDefName = "l"
-              , looperDefEnabled = True
-              , looperDefPeriod = seconds 0.01
-              , looperDefPhase = seconds 0.02
-              , looperDefFunc = atomically $ modifyTVar' v succ
+              { looperDefName = "l",
+                looperDefEnabled = True,
+                looperDefPeriod = seconds 1,
+                looperDefPhase = seconds 2,
+                looperDefFunc = atomically $ modifyTVar' v succ
               }
       a <- async $ runLoopers [l]
-      waitNominalDiffTime $ seconds 0.015
+      waitNominalDiffTime $ seconds 1.5
       cancel a
       r <- readTVarIO v
       r `shouldBe` 0
@@ -69,28 +87,28 @@
       v3 <- newTVarIO (0 :: Int)
       let l1 =
             LooperDef
-              { looperDefName = "l1"
-              , looperDefEnabled = True
-              , looperDefPeriod = seconds 0.01
-              , looperDefPhase = seconds 0
-              , looperDefFunc =
+              { looperDefName = "l1",
+                looperDefEnabled = True,
+                looperDefPeriod = seconds 1,
+                looperDefPhase = seconds 0,
+                looperDefFunc =
                   atomically $ do
                     modifyTVar' v1 succ
                     modifyTVar' v2 succ
               }
       let l2 =
             LooperDef
-              { looperDefName = "l2"
-              , looperDefEnabled = True
-              , looperDefPeriod = seconds 0.005
-              , looperDefPhase = seconds 0.005
-              , looperDefFunc =
+              { looperDefName = "l2",
+                looperDefEnabled = True,
+                looperDefPeriod = seconds 0.5,
+                looperDefPhase = seconds 0.5,
+                looperDefFunc =
                   atomically $ do
                     modifyTVar' v2 succ
                     modifyTVar' v3 succ
               }
       a <- async $ runLoopers [l1, l2]
-      waitNominalDiffTime $ seconds 0.0225
+      waitNominalDiffTime $ seconds 2.25
       cancel a
       r1 <- readTVarIO v1
       r2 <- readTVarIO v2
@@ -104,14 +122,14 @@
           inc2 = atomically $ modifyTVar' v2 succ
       let l =
             LooperDef
-              { looperDefName = "l"
-              , looperDefEnabled = True
-              , looperDefPeriod = seconds 0.01
-              , looperDefPhase = seconds 0
-              , looperDefFunc = inc1
+              { looperDefName = "l",
+                looperDefEnabled = True,
+                looperDefPeriod = seconds 1,
+                looperDefPhase = seconds 0,
+                looperDefFunc = inc1
               }
-      a <- async $ runLoopersRaw (pure ()) (\ld -> looperDefFunc ld >> inc2) [l]
-      waitNominalDiffTime $ seconds 0.015
+      a <- async $ runLoopersRaw (const $ pure ()) (\ld -> looperDefFunc ld >> inc2) [l]
+      waitNominalDiffTime $ seconds 1.5
       cancel a
       r1 <- readTVarIO v1
       r2 <- readTVarIO v2
@@ -123,14 +141,14 @@
           inc2 = atomically $ modifyTVar' v2 succ
       let l =
             LooperDef
-              { looperDefName = "l"
-              , looperDefEnabled = True
-              , looperDefPeriod = seconds 0.01
-              , looperDefPhase = seconds 0
-              , looperDefFunc = inc1 >> waitNominalDiffTime (seconds 0.015)
+              { looperDefName = "l",
+                looperDefEnabled = True,
+                looperDefPeriod = seconds 1,
+                looperDefPhase = seconds 0,
+                looperDefFunc = inc1 >> waitNominalDiffTime (seconds 1.5)
               }
-      a <- async $ runLoopersRaw inc2 looperDefFunc [l]
-      waitNominalDiffTime $ seconds 0.035
+      a <- async $ runLoopersRaw (const inc2) looperDefFunc [l]
+      waitNominalDiffTime $ seconds 3.5
       cancel a
       r1 <- readTVarIO v1
       r2 <- readTVarIO v2
@@ -143,16 +161,12 @@
     AP.Failure fp ->
       let (err, ec) = renderFailure fp "test"
        in expectationFailure $
-          unlines ["Failed to parse:", err, "would have resulted in exit code", show ec]
+            unlines ["Failed to parse:", err, "would have resulted in exit code", show ec]
     AP.CompletionInvoked _ -> expectationFailure "Tried to invoke a completion, should not happen"
   where
     parserPrefs :: ParserPrefs
     parserPrefs =
-      ParserPrefs
-        { prefMultiSuffix = ""
-        , prefDisambiguate = True
-        , prefShowHelpOnError = True
-        , prefShowHelpOnEmpty = True
-        , prefBacktrack = True
-        , prefColumns = 80
+      defaultPrefs
+        { prefShowHelpOnError = True,
+          prefShowHelpOnEmpty = True
         }
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -1,1 +1,1 @@
-{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+{-# OPTIONS_GHC -F -pgmF sydtest-discover #-}
diff --git a/test_resources/configuration.txt b/test_resources/configuration.txt
new file mode 100644
--- /dev/null
+++ b/test_resources/configuration.txt
@@ -0,0 +1,20 @@
+[36mdef: LooperConfiguration[m
+# LooperConfiguration
+# [32many of[m
+[ [37menable[m: # [34moptional[m
+    # Enable this looper
+    # [32mor null[m
+    [33m<boolean>[m
+, [37menabled[m: # [34moptional[m
+    # Enable this looper
+    # [32mor null[m
+    [33m<boolean>[m
+]
+[37mphase[m: # [34moptional[m
+  # The amount of time to wait before starting the looper the first time, in seconds
+  # [32mor null[m
+  [33m<number>[m # between [32m0[m and [32m18446744073709551615[m
+[37mperiod[m: # [34moptional[m
+  # The amount of time to wait between runs of the looper, in seconds
+  # [32mor null[m
+  [33m<number>[m # between [32m0[m and [32m18446744073709551615[m
