@@ -0,0 +1,152 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
2+module Control.DebounceSpec (spec) where
4+import Control.Concurrent
5+import Control.Debounce
6+import qualified Control.Debounce.Internal as DI
8+import Control.Monad.Catch
9+import Control.Monad.IO.Class
12+import Test.HUnit.Lang
16+spec = describe "mkDebounce" $ do
17+ describe "Leading edge" $ do
18+ it "works for a single event" $ do
19+ (ref, debounced, baton, returnFromWait) <- getDebounce Leading
22+ waitUntil 5 $ readIORef ref >>= (`shouldBe` 1)
26+ readIORef ref >>= (`shouldBe` 1)
30+ waitUntil 5 $ readIORef ref >>= (`shouldBe` 2)
34+ readIORef ref >>= (`shouldBe` 2)
36+ it "works for multiple events" $ do
37+ (ref, debounced, baton, returnFromWait) <- getDebounce Leading
40+ waitForBatonToBeTaken baton
43+ waitUntil 5 $ readIORef ref >>= (`shouldBe` 1)
47+ readIORef ref >>= (`shouldBe` 2)
49+ describe "Trailing edge" $ do
50+ it "works for a single event" $ do
51+ (ref, debounced, baton, returnFromWait) <- getDebounce Trailing
55+ waitUntil 5 $ readIORef ref >>= (`shouldBe` 0)
58+ waitUntil 5 $ readIORef ref >>= (`shouldBe` 1)
63+ waitUntil 5 $ readIORef ref >>= (`shouldBe` 1)
66+ waitUntil 5 $ readIORef ref >>= (`shouldBe` 2)
68+ it "works for multiple events" $ do
69+ (ref, debounced, baton, returnFromWait) <- getDebounce Trailing
72+ waitForBatonToBeTaken baton
75+ waitUntil 5 $ readIORef ref >>= (`shouldBe` 0)
78+ waitUntil 5 $ readIORef ref >>= (`shouldBe` 1)
80+ describe "Leading or trailing edge" $ do
81+ it "works for a single event" $ do
82+ (ref, debounced, baton, returnFromWait) <- getDebounce LeadingAndTrailing
85+ waitUntil 5 $ readIORef ref >>= (`shouldBe` 1)
89+ readIORef ref >>= (`shouldBe` 1)
93+ waitUntil 5 $ readIORef ref >>= (`shouldBe` 2)
97+ readIORef ref >>= (`shouldBe` 2)
99+ it "works for multiple events" $ do
100+ (ref, debounced, baton, returnFromWait) <- getDebounce LeadingAndTrailing
103+ waitForBatonToBeTaken baton
106+ waitUntil 5 $ readIORef ref >>= (`shouldBe` 1)
109+ waitUntil 5 $ readIORef ref >>= (`shouldBe` 2)
112+-- | Make a controllable delay function
113+getWaitAction :: IO (p -> IO (), IO ())
115+ waitVar <- newEmptyMVar
116+ let waitAction _ = takeMVar waitVar
117+ let returnFromWait = putMVar waitVar ()
118+ return (waitAction, returnFromWait)
120+-- | Get a debounce system with access to the internals for testing
121+getDebounce :: DebounceEdge -> IO (IORef Int, IO (), MVar (), IO ())
122+getDebounce edge = do
123+ ref :: IORef Int <- newIORef 0
124+ let action = modifyIORef ref (+ 1)
126+ (waitAction, returnFromWait) <- getWaitAction
128+ baton <- newEmptyMVar
130+ debounced <- DI.mkDebounce baton waitAction defaultDebounceSettings {
131+ debounceFreq = 5000000 -- unused
132+ , debounceAction = action
133+ , debounceEdge = edge
136+ return (ref, debounced, baton, returnFromWait)
138+-- | Pause briefly (100ms)
140+pause = threadDelay 100000
142+waitForBatonToBeTaken :: MVar () -> IO ()
143+waitForBatonToBeTaken baton = waitUntil 5 $ tryReadMVar baton >>= (`shouldBe` Nothing)
145+-- | Wait up to n seconds for an actual to complete without throwing an HUnitFailure
146+waitUntil :: (MonadIO m, MonadMask m) => Int -> m a -> m ()
147+waitUntil n action = recovering policy [handler] (\_status -> void action)
148+ where policy = constantDelay 1000 <> limitRetries (n * 1000) -- 1ms * n * 1000 tries = n seconds
149+ handler _status = Handler (\(HUnitFailure {}) -> return True)