log4hs-0.7.0.0: test/LoggingTest/TypesSpec.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeApplications #-}
module LoggingTest.TypesSpec ( spec ) where
import Control.Concurrent.MVar
import Control.Lens hiding (Level (..))
import Control.Monad
import Data.Default (def)
import Data.Generics.Product.Typed
import Data.IORef
import Data.List (isPrefixOf)
import Data.String (fromString)
import Data.Time.Clock
import Data.Time.Clock.POSIX
import Data.Time.LocalTime
import Prelude hiding (filter)
import System.Directory
import System.FilePath
import System.IO
import System.Process (createPipe)
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck
import Test.QuickCheck.Monadic
import Text.Format
import Logging.Class
import Logging.Filter
import Logging.Handler.FileHandler
import Logging.Handler.RotatingFileHandler
import Logging.Handler.StreamHandler
import Logging.Handler.TimeRotatingFileHandler
import Logging.Level
import Logging.Logger
import Logging.Prelude
import Logging.Record
import Logging.Sink
import LoggingTest.Prelude
spec :: Spec
spec = do
levelSpec
filterSpec
handlerSpec
levelSpec :: Spec
levelSpec = describe "Level" $ do
prop "read & show" $ \v ->
let level = Level v in read (show level) == level
prop "overload string" $ \v ->
let level = Level v in fromString (show level) == level
filterSpec :: Spec
filterSpec = describe "Filter" $ do
prop "pass" $ \rcd@LogRecord{..} ->
let filterer = map fromString $
[logger, parent logger, parent $ parent logger, ""]
in filter (filterer :: Filterer) rcd
prop "reject" $ \rcd@LogRecord{..} ->
let filters = map (fromString . (++ "Test")) $
[logger, parent logger, parent $ parent logger, ""]
in all (== False) [filter (f :: Filter) rcd | f <- filters]
handlerSpec :: Spec
handlerSpec = describe "Handler" $ do
(read, write) <- runIO createPipe
fileRef <- runIO $ newIORef undefined
rotateFileMVar <- runIO newEmptyMVar
timeRotateRef <- runIO $ newIORef undefined
timeFileMVar <- runIO newEmptyMVar
runIO $ hSetEncoding read utf8 >> hSetEncoding write utf8
let handler = toHandler $ StreamHandler def [] "{message}" write
fileHandler = toHandler $
FileHandler def [] "{message}" "/tmp/log4hs/file.log" utf8 fileRef
rotateHandler = toHandler $
RotatingFileHandler def [] "{message}" "/tmp/log4hs/rotate.log" utf8
1000 5 rotateFileMVar
timeHandler = toHandler $
TimeRotatingFileHandler def [] "{message}" "/tmp/log4hs/time.log" utf8
utc (Minute 5) 5 timeRotateRef timeFileMVar
runIO $ open handler >> open fileHandler >> open rotateHandler
prop "filter" $ \(rcd@LogRecord{..}) -> monadicIO $ do
let loggers = [logger, parent logger, parent $ parent logger, ""]
-- level reject
forM_ logger $ \logger -> do
let handler' = handler & typed @Level .~ (succ level)
res <- run $ handle handler' rcd
assert $ not res
-- filterer reject
forM_ loggers $ \logger -> do
let handler' = handler & typed @Filterer .~ [fromString $ logger ++ "Test"]
res <- run $ handle handler' rcd
assert $ not res
-- filterer pass
forM_ loggers $ \logger -> do
let handler' = handler & typed @Filterer .~ [fromString logger]
res <- run $ handle handler' rcd
msg <- run $ hTryGetLine read
assert res
assert (msg == message)
-- empty filterer
res <- run $ handle handler rcd
msg <- run $ hTryGetLine read
assert res
assert (msg == message)
prop "file" $ \(rcd@LogRecord{..}) -> monadicIO $ do
file <- run $ readIORef fileRef
run $ hSetFileSize file 0 >> hSeek file AbsoluteSeek 0
run $ handle fileHandler rcd
run $ hSeek file AbsoluteSeek 0
msg <- run $ hTryGetLine file
assert (msg == message)
prop "rotate" $ \(rcd, Alphabet c) -> monadicIO $ do
file <- run $ readMVar rotateFileMVar
run $ hSetFileSize file 0 >> hSeek file AbsoluteSeek 0
let message = replicate 99 c
rcd' = rcd { message = message }
-- not rollover yet
run $ forM [1..9] $ \_ -> handle rotateHandler rcd'
run $ hSeek file AbsoluteSeek 0
msgs <- run $ forM [1..9] $ \_ -> hTryGetLine file
assert $ all (== message) msgs
-- rollover
run $ handle rotateHandler rcd'
closed <- run $ hIsClosed file
assert closed
pos <- run $ readMVar rotateFileMVar >>= hTell
assert (pos == 0)
-- rotate.1.log
file1 <- run $ openLogFile "/tmp/log4hs/rotate.1.log" utf8
run $ hSeek file1 AbsoluteSeek 0
msgs1 <- run $ forM [1..10] $ \_ -> hTryGetLine file1
run $ hClose file1
assert $ all (== message) msgs1
-- rollover again
file' <- run $ readMVar rotateFileMVar
run $ forM [1..10] $ \_ -> handle rotateHandler rcd'
closed1 <- run $ hIsClosed file'
assert closed1
-- rotate.2.log
file2 <- run $ openLogFile "/tmp/log4hs/rotate.2.log" utf8
run $ hSeek file2 AbsoluteSeek 0
msgs2 <- run $ forM [1..10] $ \_ -> hTryGetLine file2
run $ hClose file2
-- rotate.2.log
assert $ all (== message) msgs2
prop "time" $ \(Positive n, rcd) -> monadicIO $ do
-- open
now <- run $ getCurrentTime
let mtime = addUTCTime (negate $ 310 * fromInteger n) now
run $ do
createFile "/tmp/log4hs/time.log"
setModificationTime "/tmp/log4hs/time.log" mtime
open timeHandler
rt <- run $ readIORef timeRotateRef
let ts1 = fromInteger $ 300 * (1 + truncate ((utcTimeToPOSIXSeconds mtime) / 300))
assert (ts1 == utcTimeToPOSIXSeconds rt)
-- rotate
file <- run $ readMVar timeFileMVar
run $ hSetFileSize file 0 >> hSeek file AbsoluteSeek 0
run $ handle timeHandler $ rcd {utctime = now}
closed <- run $ hIsClosed file
assert closed
-- contents of new file
file1 <- run $ readMVar timeFileMVar
run $ hSeek file1 AbsoluteSeek 0
msg <- run $ hTryGetLine file1
assert $ msg == message rcd
-- latest backup
exist <- run $ doesFileExist $ appendBaseName "/tmp/log4hs/time.log" $
format ".{:%Y-%m-%d_%H-%M}" $ addUTCTime (negate 300) rt
assert exist
-- max backups
files <- run $ listDirectory "/tmp/log4hs/"
assert $ 6 >= length [f | f <- files, isPrefixOf "/tmp/log4hs/time." f]
run $ close timeHandler