packages feed

coquina-0.1.0.0: test/unit-tests.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}

module Main where

import Control.Concurrent.STM
import Control.Lens (_1, _2, (%~))
import Control.Monad.IO.Class (liftIO)
import Data.Function ((&))
import Data.IORef (atomicModifyIORef', newIORef, readIORef)
import Data.Text (Text)
import qualified Data.Text.IO as T
import qualified Data.Text.Encoding as T
import System.IO (hFlush)
import System.IO.Temp (withSystemTempFile)
import System.Process (proc)
import System.Timeout (timeout)
import System.Which (staticWhich)
import Test.Hspec (describe, hspec, it, shouldBe)

import Coquina (StreamingProcess (..), runShell, shellStreamableProcess)

main :: IO ()
main = hspec $
  describe "shellStreamableProcess" $
    it "reads stdout incrementally" $ do
      (allStdout, allStderr, result :: Either Int ([Text], [Text])) <- withSystemTempFile @IO "temp" $ \path h -> runShell $ do
        stdoutListeners <- liftIO $ newTVarIO (0 :: Int)
        stderrListeners <- liftIO $ newTVarIO (0 :: Int)

        procOuts <- liftIO $ newIORef ([], [])
        let
          waitForStdout = hFlush h *> waitForListeners stdoutListeners 1

          recordOutput listener setter x = do
            atomicModifyIORef' procOuts $ \vs ->
              (vs & setter %~ (T.decodeUtf8 x :), ())
            proceed listener

        sp <- shellStreamableProcess
          (recordOutput stdoutListeners _1)
          (recordOutput stderrListeners _2)
          (proc $(staticWhich "tail") ["-f", path])

        (maybe (error "Test timed out") pure =<<) $ liftIO $ timeout (5*10^(6::Int)) $ do
          T.hPutStrLn h "line1" *> waitForStdout
          T.hPutStrLn h "line2" *> waitForStdout
          T.hPutStrLn h "line3" *> waitForStdout

        _streamingProcess_terminateProcess sp

        liftIO $ readIORef procOuts

      allStdout `shouldBe` "line1\nline2\nline3\n"
      allStderr `shouldBe` ""
      result `shouldBe` Right (["line3\n", "line2\n", "line1\n"], [])
  where
    -- Help synchronize test with threads
    proceed var = atomically $ modifyTVar var (+1)

    waitForListeners var n = atomically $ do
      newStep <- readTVar var
      if newStep < n
        then retry
        else writeTVar var 0