packages feed

tail-0.2.0.0: test/System/TailSpec.hs

module System.TailSpec where

import Control.Concurrent (forkIO, threadDelay)
import Control.Concurrent.STM.CircularBuffer (CircularBuffer)
import Control.Concurrent.STM.CircularBuffer qualified as CB
import GHC.IO.Handle (hClose)
import Paths_tail (getDataFileName)
import System.IO.Temp (withSystemTempFile)
import System.Process (system)
import System.Tail qualified as Tail
import Test.Hspec

-- Helper to read all items from CircularBuffer until closed
drainAll :: CircularBuffer a -> IO [a]
drainAll q = do
  let go acc = do
        maybeItems <- atomically $ CB.drain q
        case maybeItems of
          Nothing -> pure acc -- Queue is closed
          Just items -> go (acc ++ toList items)
  go []

spec :: Spec
spec = describe "System.Tail" $ do
  it "hello" $ do
    t <- Tail.tailFile 100 "/dev/null"
    Tail.tailStop t
  it "streams a static file" $ do
    testFile <- getDataFileName "test-fixture.txt"
    t <- Tail.tailFile 100 testFile
    q <- Tail.tailSubscribe t
    threadDelay 1_000_000 >> Tail.tailStop t
    ls <- drainAll q
    viaNonEmpty head ls `shouldBe` Just "Line 1: This is a test fixture file"
    -- Find the last line from the lines we got
    let lastLine = viaNonEmpty last ls
    lastLine `shouldBe` Just "Line 5: End of test fixture"
  it "streams a log file being appended to by another process" $ do
    -- Create a file under a temp directory. Then spawn an external process that writes to it lines over time.
    withSystemTempFile "tail-spec" $ \tempFile h -> do
      hClose h
      void $ system $ "echo 'Hello' >> " <> tempFile
      t <- Tail.tailFile 100 tempFile
      q <- Tail.tailSubscribe t
      void $ forkIO $ do
        void $ system $ "echo 'World' >> " <> tempFile
        threadDelay 1_000_000 >> Tail.tailStop t
      -- Read everything and compare output
      ls <- drainAll q
      ls `shouldBe` ["Hello", "World"]
  it "ring buffer provides last lines to new subscribers" $ do
    withSystemTempFile "ring-buffer-spec" $ \tempFile h -> do
      hClose h
      void $ system $ "echo 'Line1' >> " <> tempFile
      t <- Tail.tailFile 100 tempFile
      q <- Tail.tailSubscribe t
      void $ system $ "echo 'Line2' >> " <> tempFile -- Write *after* subscribing
      threadDelay 1_000_000 >> Tail.tailStop t
      ls <- drainAll q
      ls `shouldBe` ["Line1", "Line2"]

-- End of file.