packages feed

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` ()