ribosome-0.2.1.0: lib/Ribosome/Test/Embed.hs
module Ribosome.Test.Embed(
defaultTestConfig,
defaultTestConfigWith,
TestConfig (..),
Vars(..),
unsafeEmbeddedSpec,
setVars,
setupPluginEnv,
quitNvim,
) where
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Reader (runReaderT)
import Control.Monad.Trans.Resource (runResourceT)
import Data.Default (Default(def))
import Data.Foldable (traverse_)
import Data.Functor (void)
import Data.Maybe (fromMaybe)
import GHC.IO.Handle (Handle)
import Neovim (Neovim, Object, vim_set_var', vim_command)
import qualified Neovim.Context.Internal as Internal(
Neovim(Neovim),
Config,
newConfig,
retypeConfig,
mkFunctionMap,
pluginSettings,
globalFunctionMap,
)
import Neovim.RPC.Common (RPCConfig, newRPCConfig)
import Neovim.RPC.EventHandler (runEventHandler)
import Neovim.RPC.SocketReader (runSocketReader)
import System.Directory (makeAbsolute)
import System.Exit (ExitCode)
import qualified System.Posix.Signals as Signal (signalProcess, killProcess)
import System.Process (getPid)
import System.Process.Typed (
ProcessConfig,
Process,
withProcess,
proc,
setStdin,
setStdout,
getStdin,
getStdout,
unsafeProcessHandle,
createPipe,
getExitCode,
)
import UnliftIO.Async (async, cancel, race)
import UnliftIO.Exception (tryAny, bracket)
import UnliftIO.STM (atomically, putTMVar)
import Ribosome.Api.Option (rtpCat)
import Ribosome.Control.Ribo (Ribo)
import Ribosome.Control.Ribosome (Ribosome(Ribosome), newInternalTVar)
import Ribosome.Data.Time (sleep, sleepW)
type Runner env = TestConfig -> Neovim env () -> Neovim env ()
newtype Vars = Vars [(String, Object)]
data TestConfig =
TestConfig {
tcPluginName :: String,
tcExtraRtp :: String,
tcLogPath :: FilePath,
tcTimeout :: Word,
tcCmdline :: Maybe [String],
tcCmdArgs :: [String],
tcVariables :: Vars
}
instance Default TestConfig where
def = TestConfig "ribosome" "test/f/fixtures/rtp" "test/f/temp/log" 5 def def (Vars [])
defaultTestConfigWith :: String -> Vars -> TestConfig
defaultTestConfigWith name = TestConfig name "test/f/fixtures/rtp" "test/f/temp/log" 5 def def
defaultTestConfig :: String -> TestConfig
defaultTestConfig name = defaultTestConfigWith name (Vars [])
setVars :: Vars -> Neovim e ()
setVars (Vars vars) =
traverse_ (uncurry vim_set_var') vars
setupPluginEnv :: TestConfig -> Neovim e ()
setupPluginEnv (TestConfig _ rtp _ _ _ _ vars) = do
absRtp <- liftIO $ makeAbsolute rtp
rtpCat absRtp
setVars vars
killPid :: Integral a => a -> IO ()
killPid =
void . tryAny . Signal.signalProcess Signal.killProcess . fromIntegral
killProcess :: Process i o e -> IO ()
killProcess prc = do
let handle = unsafeProcessHandle prc
mayPid <- getPid handle
traverse_ killPid mayPid
testNvimProcessConfig :: TestConfig -> ProcessConfig Handle Handle ()
testNvimProcessConfig TestConfig {..} =
setStdin createPipe $ setStdout createPipe $ proc "nvim" $ args ++ tcCmdArgs
where
args = fromMaybe defaultArgs tcCmdline
defaultArgs = ["--embed", "-n", "-u", "NONE", "-i", "NONE"]
startHandlers :: NvimProc -> TestConfig -> Internal.Config RPCConfig -> IO (IO ())
startHandlers prc TestConfig{..} nvimConf = do
socketReader <- run runSocketReader getStdout
eventHandler <- run runEventHandler getStdin
atomically $ putTMVar (Internal.globalFunctionMap nvimConf) (Internal.mkFunctionMap [])
let stopEventHandlers = traverse_ cancel [socketReader, eventHandler]
return stopEventHandlers
where
run runner stream = async . void $ runner (stream prc) emptyConf
emptyConf = nvimConf { Internal.pluginSettings = Nothing }
runNeovimThunk :: Internal.Config e -> Neovim e a -> IO ()
runNeovimThunk cfg (Internal.Neovim thunk) =
void $ runReaderT (runResourceT thunk) cfg
type NvimProc = Process Handle Handle ()
waitQuit :: NvimProc -> IO (Maybe ExitCode)
waitQuit prc =
wait 30
where
wait :: Int -> IO (Maybe ExitCode)
wait 0 = return Nothing
wait count = do
code <- getExitCode prc
case code of
Just a -> return $ Just a
Nothing -> do
sleep 0.1
wait $ count - 1
quitNvim :: Internal.Config e -> NvimProc -> IO ()
quitNvim testCfg prc = do
quitThread <- async $ runNeovimThunk testCfg quit
result <- waitQuit prc
case result of
Just _ -> return ()
Nothing -> killProcess prc
cancel quitThread
where
quit = vim_command "qall!"
shutdownNvim :: Internal.Config e -> NvimProc -> IO () -> IO ()
shutdownNvim _ prc stopEventHandlers = do
stopEventHandlers
killProcess prc
-- quitNvim testCfg prc
runTest :: TestConfig -> Internal.Config (Ribosome e) -> Ribo e () -> IO () -> IO ()
runTest TestConfig{..} testCfg thunk _ = do
result <- race (sleepW tcTimeout) (runNeovimThunk testCfg thunk)
case result of
Right _ -> return ()
Left _ -> fail $ "test exceeded timeout of " ++ show tcTimeout ++ " seconds"
runEmbeddedNvim :: TestConfig -> Ribosome e -> Ribo e () -> NvimProc -> IO ()
runEmbeddedNvim conf ribo thunk prc = do
nvimConf <- Internal.newConfig (pure Nothing) newRPCConfig
let testCfg = Internal.retypeConfig ribo nvimConf
bracket (startHandlers prc conf nvimConf) (shutdownNvim testCfg prc) (runTest conf testCfg thunk)
runEmbedded :: TestConfig -> Ribosome e -> Ribo e () -> IO ()
runEmbedded conf ribo thunk = do
let pc = testNvimProcessConfig conf
withProcess pc $ runEmbeddedNvim conf ribo thunk
unsafeEmbeddedSpec :: Runner (Ribosome e) -> TestConfig -> e -> Ribo e () -> IO ()
unsafeEmbeddedSpec runner conf env spec = do
internal <- newInternalTVar
let ribo = Ribosome (tcPluginName conf) internal env
runEmbedded conf ribo $ runner conf spec