statsdi-0.1.0.0: test/Spec.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
import Control.Concurrent
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Stats
import Data.ByteString (ByteString)
import qualified Data.ByteString as ByteString
import qualified Data.ByteString.Char8 as Char8
import Data.Char (isNumber)
import Data.Proxy
import Data.Time.Clock.POSIX
import Harness
import Test.Hspec
import Test.Tasty
import Test.Tasty.Hspec
import Test.Tasty.Options (OptionDescription (..))
import Test.Tasty.Runners (NumThreads (..))
import Debug.Trace (traceShowId)
-- these tests effectively test the TH for sanity
-- otherwise the test suite wouldnt even compile
defineCounter "ctr.hello.world" []
defineCounter "ctr.tagged" [("env","test")]
defineGauge "gau.testing.things" []
defineTimer "time.test" []
defineHistogram "hist.stuff.things" [] 1.0
defineSet "set.of.people" []
defineServiceCheck "svc.haskell" []
ourStatsTConfig :: StatsTConfig
ourStatsTConfig = defaultStatsTConfig { flushInterval = 250 }
st :: (MonadIO m) => Int -> StatsT m a -> m ([ByteString], a)
st = st' ourStatsTConfig
st' :: (MonadIO m) => StatsTConfig -> Int -> StatsT m a -> m ([ByteString], a)
st' = runStatsTCapturingOutput
sleepMs :: MonadIO m => Int -> m ()
sleepMs = liftIO . threadDelay . (1000 *)
main :: IO ()
main = do
putStrLn ""
let toRun = [sillyTests, noStatsTTest, counterTests, gaugeTests, timerTests]
forM toRun testSpecs >>= defaultMain . withTests
withTests :: [[TestTree]] -> TestTree
withTests = testGroup "Statsdi" . concat
sillyTests :: Spec
sillyTests = describe "The test harness" $ do
it "should run and capture something (1s linger)" $ do
(capture, _) <- st 1000 $ tick ctr_hello_world
capture `shouldSatisfy` (not . null)
it "should run and capture with a delay before the tick (250ms flushInterval / 1s linger / 500ms delay)" $ do
(capture, _) <- st 1000 $ do
sleepMs 500
tick ctr_hello_world
capture `shouldSatisfy` (not . null)
it "should linger after the test runs and get everything (100ms linger / 5000ms+ test)" $ do
(capture, _) <- st 100 $ do
sleepMs 5000
tick ctr_hello_world
capture `shouldSatisfy` (not . null)
counterTests :: Spec
counterTests = describe "A Counter" $ do
it "should have a kind tag of |c" $ do
(capture, _) <- st 1000 $ setCounter 0 ctr_hello_world
capture `shouldSatisfy` (not . null)
capture `shouldSatisfy` (ByteString.isPrefixOf "ctr.hello.world:0|c" . head)
it "should increment by one when calling `tick`" $ do
(capture, _) <- st 1000 $ tick ctr_hello_world
capture `shouldSatisfy` (not . null)
capture `shouldSatisfy` (ByteString.isPrefixOf "ctr.hello.world:1|c" . head)
it "should send a multi-event with a zeroing-out before being set to a negative number" $ do
(capture, _) <- st 1000 $ setCounter (-20) ctr_hello_world
capture `shouldSatisfy` (not . null)
capture `shouldSatisfy` (ByteString.isPrefixOf "ctr.hello.world:0|c\nctr.hello.world:-20|c" . head)
gaugeTests :: Spec
gaugeTests = describe "A Gauge" $ do
it "should have a kind tag of |g" $ do
(capture, _) <- st 1000 $ setGauge 0 gau_testing_things
capture `shouldSatisfy` (not . null)
capture `shouldSatisfy` (ByteString.isPrefixOf "gau.testing.things:0|g" . head)
it "should send a multi-event with a zeroing-out before being set to a negative number" $ do
(capture, _) <- st 1000 $ setGauge (-20) gau_testing_things
capture `shouldSatisfy` (not . null)
capture `shouldSatisfy` (ByteString.isPrefixOf "gau.testing.things:0|g\ngau.testing.things:-20|g" . head)
timerTests :: Spec
timerTests = describe "A Timer" $ do
it "should have a kind tag of |ms" $ do
(capture, _) <- st 1000 $ time 0 time_test
capture `shouldSatisfy` (not . null)
capture `shouldSatisfy` (ByteString.isPrefixOf "time.test:0|ms" . head)
it "should report in milliseconds" $ do
(capture, _) <- st 1000 $ time 1.0 time_test
capture `shouldSatisfy` (not . null)
capture `shouldSatisfy` (ByteString.isPrefixOf "time.test:1000|ms" . head)
it "should handle DiffTimes appropriately" $ do
(capture, _) <- st 1000 $ do
now <- liftIO getPOSIXTime
sleepMs 250
then' <- liftIO getPOSIXTime
time (then' - now) time_test
capture `shouldSatisfy` (not . null)
capture `shouldSatisfy` (isRoughlyMillis 250 10 . head)
where isRoughlyMillis target leeway bs = abs (actual - target) <= leeway
where actual = read pluckedTime
pluckedTime = takeWhile isNumber nameStripped
nameStripped = drop (ByteString.length (timerName time_test) + 1) (Char8.unpack bs)
noStatsTTest :: Spec
noStatsTTest = describe "runNoStatsT" $ do
it "should successfully run its inner monad without any funny hiccups" $
runNoStatsT (return ()) >>= shouldBe ()
describe "should successfully run its inner monad even it performs Counter metrics" $ do
it "works with tick" $
runNoStatsT (tick ctr_hello_world) >>= shouldBe ()
it "works with tickBy" $
runNoStatsT (tickBy 2 ctr_hello_world) >>= shouldBe ()
describe "should successfully run its inner monad even it performs Gauge metrics" $
it "works with setGauge" $
runNoStatsT (setGauge 20 gau_testing_things) >>= shouldBe ()
describe "should successfully run its inner monad even it performs Timer metrics" $
it "works with time" $ do
ret <- runNoStatsT $ do
now <- liftIO getPOSIXTime
sleepMs 250
then' <- liftIO getPOSIXTime
time (then' - now) time_test
ret `shouldBe` ()
describe "should successfully run its inner monad even it performs Histogram metrics" $ do
it "works with histoSample" $
runNoStatsT (histoSample 3 hist_stuff_things) >>= shouldBe ()
it "works with multiple calls to histoSample" $ do
ret <- runNoStatsT $ do
histoSample 18 hist_stuff_things
histoSample 19 hist_stuff_things
ret `shouldBe` ()
describe "should successfully run its inner monad even it performs Set metrics" $ do
it "works with addSetMember" $
runNoStatsT (addSetMember 12 set_of_people) >>= shouldBe ()
it "works with multiple calls to addSetMember" $ do
ret <- runNoStatsT $ do
addSetMember 12 set_of_people
addSetMember 24 set_of_people
ret `shouldBe` ()