packages feed

katip-0.8.7.1: test/Katip/Tests/Scribes/Handle.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}

module Katip.Tests.Scribes.Handle
  ( tests,
  )
where

-------------------------------------------------------------------------------
import Control.Monad
import Data.Aeson
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy as BL
import Data.Monoid as M
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Text.Lazy as LT
import qualified Data.Text.Lazy.Builder as LT
import Data.Time
-------------------------------------------------------------------------------
import Katip
import Language.Haskell.TH.Syntax (Loc (..))
import Lens.Micro ((.~))
import System.Directory
import System.IO
import Test.Tasty
import Test.Tasty.Golden
import Test.Tasty.HUnit
import Text.Regex.TDFA

-------------------------------------------------------------------------------

tests :: TestTree
tests =
  testGroup
    "Katip.Scribes.Handle"
    [ withResource setup teardown $ \setupScribe -> testCase "logs the correct data" $ do
        (path, h, fin, le) <- setupScribe
        runKatipT le $ logItem dummyLogItem "test" Nothing InfoS "test message"
        fin
        runKatipT le $ logItem dummyLogItem "test" Nothing InfoS "wont make it in"
        hClose h
        res <- readFile path
        let pat = "\\[[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}\\]\\[katip-test.test\\]\\[Info\\]\\[.+\\]\\[[PID [:digit:]]+\\]\\[ThreadId [[:digit:]]+\\]\\[note.deep:some note\\] test message" :: String
        let matches = res =~ pat
        assertBool (show res M.<> " did not match") matches,
      withResource setupFile (const (return ())) $ \setupScribe -> testCase "logs correct data to a file" $ do
        (path, fin, le) <- setupScribe
        runKatipT le $ logItem dummyLogItem "test" Nothing InfoS "test message"
        fin
        runKatipT le $ logItem dummyLogItem "test" Nothing InfoS "wont make it in"
        res <- readFile path
        let pat = "\\[[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}\\]\\[katip-test.test\\]\\[Info\\]\\[.+\\]\\[[PID [:digit:]]+\\]\\[ThreadId [[:digit:]]+\\]\\[note.deep:some note\\] test message" :: String
        let matches = res =~ pat
        assertBool (show res <> " did not match") matches,
      withResource setupTempFile teardownTempFile $ \setupFn ->
        goldenVsString
          "Text-golden"
          goldenTextPath
          (setupFn >>= writeTextLog),
      withResource setupTempFile teardownTempFile $ \setupFn ->
        goldenVsString
          "Json-golden"
          goldenJsonPath
          (setupFn >>= writeJsonLog)
    ]

#if MIN_VERSION_aeson(2, 0, 0)
-- keys get reordered, hence different output
goldenTextPath :: FilePath
goldenTextPath = "test/Katip/Tests/Scribes/Handle-text-aeson2.golden"
goldenJsonPath :: FilePath
goldenJsonPath = "test/Katip/Tests/Scribes/Handle-json-aeson2.golden"
#elif MIN_VERSION_unordered_containers(0, 2, 16)
goldenTextPath :: FilePath
goldenTextPath = "test/Katip/Tests/Scribes/Handle-text-aeson2.golden"
goldenJsonPath :: FilePath
goldenJsonPath = "test/Katip/Tests/Scribes/Handle-json-aeson2.golden"
#else
goldenTextPath :: FilePath
goldenTextPath = "test/Katip/Tests/Scribes/Handle-text.golden"
goldenJsonPath :: FilePath
goldenJsonPath = "test/Katip/Tests/Scribes/Handle-json.golden"
#endif

-------------------------------------------------------------------------------
data DummyLogItem = DummyLogItem
  { dliNote :: Text
  }

instance ToJSON DummyLogItem where
  toJSON dli =
    object
      [ "note"
          .= object
            [ "deep" .= dliNote dli
            ]
      ]

instance ToObject DummyLogItem

instance LogItem DummyLogItem where
  payloadKeys _ _ = AllKeys

-------------------------------------------------------------------------------
dummyLogItem :: DummyLogItem
dummyLogItem = DummyLogItem "some note"

-------------------------------------------------------------------------------
setup :: IO (FilePath, Handle, IO (), LogEnv)
setup = do
  tempDir <- getTemporaryDirectory
  (fp, h) <- openTempFile tempDir "katip.log"
  s <- mkHandleScribe (ColorLog False) h (permitItem DebugS) V3
  le <- initLogEnv "katip-test" "test"
  le' <- registerScribe "handle" s defaultScribeSettings le
  return (fp, h, void (closeScribes le'), le')

-------------------------------------------------------------------------------
teardown :: (a, Handle, b, c) -> IO ()
teardown (_, h, _, _) = do
  chk <- hIsOpen h
  when chk $ hClose h

-------------------------------------------------------------------------------
setupFile :: IO (FilePath, IO (), LogEnv)
setupFile = do
  tempDir <- getTemporaryDirectory
  (fp, h) <- openTempFile tempDir "katip.log"
  hClose h
  s <- mkFileScribe fp (permitItem DebugS) V3
  le <- initLogEnv "katip-test" "test"
  le' <- registerScribe "handle" s defaultScribeSettings le
  return (fp, void (closeScribes le'), le')

-- Following code tests Handle scribe output against a golden file.
-- This test will fail on non utf8 locales because golden file is in utf-8.
-- It generates all meaningfull variations of Item, and also tests
-- writing of payload of different Aeson constructors
--
-- Note: currently Handle scribe does not write Array items at all
-------------------------------------------------------------------------------
data AllTypesLogItem = AllTypesLogItem
  { atlText :: Text,
    atlNum :: Int,
    atlFloat :: Float,
    atlList :: [Text],
    atlSub :: Maybe DummyLogItem
  }

instance ToJSON AllTypesLogItem where
  toJSON it =
    object
      [ "text" .= atlText it,
        "num" .= atlNum it,
        "float" .= atlFloat it,
        "list" .= atlList it,
        "sub" .= atlSub it
      ]

instance ToObject AllTypesLogItem

instance LogItem AllTypesLogItem where
  payloadKeys _ _ = AllKeys

-------------------------------------------------------------------------------
theItem :: Item DummyLogItem
theItem =
  Item
    (Namespace ["app"])
    (Environment "production")
    (InfoS)
    (ThreadIdText "1337")
    "example"
    7331
    dummyLogItem
    "message"
    (mkUTCTime 2016 6 12 12 34 56)
    (Namespace ["foo"])
    Nothing

genItems :: [Item DummyLogItem]
genItems =
  concat $
    [ [ itemSeverity .~ s $ theItem
        | s <- [minBound .. maxBound]
      ],
      [ itemThread .~ (ThreadIdText . T.pack $ show t) $ theItem
        | t <- [0 :: Int, 1, 1337, 2147483647]
      ],
      [ itemHost .~ h $ theItem
        | h <- ["example", "www.example.com", "127.0.0.1"]
      ],
      [ itemProcess .~ p $ theItem
        | p <- [0, 1, 1337, 2147483647]
      ],
      [ itemMessage .~ LogStr m $ theItem
        | m <-
            [ "message",
              "message\nwith newline",
              LT.fromLazyText (LT.replicate 40 " a really long message"),
              "сообщение",
              "哈囉世界"
            ]
      ],
      [ itemTime .~ t $ theItem
        | t <- genDates
      ],
      [ itemNamespace .~ Namespace ns $ theItem
        | ns <-
            [ ["foo"],
              ["foo", "bar"],
              ["фу", "бар"],
              ["with\nnewline"]
            ]
      ],
      [ itemLoc .~ l $ theItem | l <- genLocs
      ]
    ]

genDates :: [UTCTime]
genDates =
  [ mkUTCTime 2000 1 1 0 0 0.0,
    mkUTCTime 2123 12 31 23 59 59.999999999999,
    mkUTCTime 2016 10 10 1 1 5.0,
    mkUTCTime 2100 12 31 12 59 10.1,
    mkUTCTime 1982 1 1 12 30 0.000000000001
  ]

genLocs :: [Maybe Loc]
genLocs =
  [ Nothing,
    Just $
      Loc
        "path/Some/Module.hs"
        "main"
        "Some.Module"
        (30, 1)
        (30, 14),
    Just $
      Loc
        "путь/Some/Module.hs"
        "main"
        "Some.Module"
        (3000, 9000)
        (4000, 1)
  ]

genTypedItems :: [Item AllTypesLogItem]
genTypedItems =
  [ itemPayload .~ p $ theItem
    | p <-
        [ AllTypesLogItem "" 0 0.0 [] Nothing,
          AllTypesLogItem
            "note"
            10
            5.5
            ["one", "two", "three"]
            (Just dummyLogItem)
        ]
  ]

mkUTCTime :: Integer -> Int -> Int -> DiffTime -> DiffTime -> DiffTime -> UTCTime
mkUTCTime y mt d h mn s = UTCTime day dt
  where
    day = fromGregorian y mt d
    dt = h * 60 * 60 + mn * 60 + s

-------------------------------------------------------------------------------
writeTextLog :: (FilePath, Handle) -> IO (BL.ByteString)
writeTextLog = writeFormattedLog bracketFormat

writeJsonLog :: (FilePath, Handle) -> IO (BL.ByteString)
writeJsonLog = writeFormattedLog jsonFormat

writeFormattedLog :: (forall a. LogItem a => ItemFormatter a) -> (FilePath, Handle) -> IO (BL.ByteString)
writeFormattedLog format (path, h) = do
  mapM_ (put . formatOne) genItems
  mapM_ (put . formatOne) genTypedItems
  hClose h
  BL.readFile path
  where
    formatOne :: LogItem a => Item a -> Text
    formatOne = LT.toStrict . LT.toLazyText . format False V3
    put = B.hPutStrLn h . T.encodeUtf8

setupTempFile :: IO (FilePath, Handle)
setupTempFile = do
  tempDir <- getTemporaryDirectory
  (fp, h) <- openBinaryTempFile tempDir "katip.log"
  return (fp, h)

teardownTempFile :: (FilePath, Handle) -> IO ()
teardownTempFile (_, h) = do
  chk <- hIsOpen h
  when chk $ hClose h