{-# LANGUAGE CPP #-}
module TestMode (
testMode,
) where
import Control.Monad.Except
import Control.Monad.Reader
import Control.Monad.State
import Data.Bifunctor
import Data.List
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.IO qualified as T
import System.IO.Error
import Text.Megaparsec.Error
import Text.Megaparsec.Pos
import Output
import Parser
import Run
import Script.Expr
import Script.Module
import Test
data TestModeInput = TestModeInput
{ tmiOutput :: Output
, tmiParams :: [ Text ]
}
data TestModeState = TestModeState
{ tmsModules :: [ Module ]
, tmsGlobals :: GlobalDefs
}
initTestModeState :: TestModeState
initTestModeState = TestModeState
{ tmsModules = mempty
, tmsGlobals = mempty
}
testMode :: IO ()
testMode = do
out <- startOutput OutputStyleTest False
let testLoop = getLineMb >>= \case
Just line -> do
case T.words line of
cname : params
| Just (CommandM cmd) <- lookup cname commands -> do
runReaderT cmd $ TestModeInput out params
| otherwise -> fail $ "Unknown command '" ++ T.unpack cname ++ "'"
[] -> return ()
testLoop
Nothing -> return ()
runExceptT (evalStateT testLoop initTestModeState) >>= \case
Left err -> flip runReaderT out $ outLine OutputError Nothing $ T.pack err
Right () -> return ()
getLineMb :: MonadIO m => m (Maybe Text)
getLineMb = liftIO $ catchIOError (Just <$> T.getLine) (\e -> if isEOFError e then return Nothing else ioError e)
cmdOut :: Text -> Command
cmdOut line = do
out <- asks tmiOutput
flip runReaderT out $ outLine OutputTestRaw Nothing line
newtype CommandM a = CommandM (ReaderT TestModeInput (StateT TestModeState (ExceptT String IO)) a)
deriving
( Functor, Applicative, Monad, MonadIO
, MonadReader TestModeInput, MonadState TestModeState, MonadError String
)
instance MonadFail CommandM where
fail = throwError
type Command = CommandM ()
commands :: [ ( Text, Command ) ]
commands =
[ ( "load", cmdLoad )
, ( "run", cmdRun )
]
cmdLoad :: Command
cmdLoad = do
[ path ] <- asks tmiParams
liftIO (parseTestFiles [ T.unpack path ]) >>= \case
Right ( modules, allModules ) -> do
let globalDefs = evalGlobalDefs $ concatMap (\m -> map (first ( moduleName m, )) $ moduleDefinitions m) allModules
modify $ \s -> s
{ tmsModules = modules
, tmsGlobals = globalDefs
}
cmdOut "load-done"
Left (ModuleNotFound moduleName) -> do
cmdOut $ "load-failed module-not-found" <> textModuleName moduleName
Left (FileNotFound notFoundPath) -> do
cmdOut $ "load-failed file-not-found " <> T.pack notFoundPath
Left (ImportModuleError bundle) -> do
#if MIN_VERSION_megaparsec(9,7,0)
mapM_ (cmdOut . T.pack) $ lines $ errorBundlePrettyWith showParseError bundle
#endif
cmdOut $ "load-failed parse-error"
where
showParseError _ SourcePos {..} _ = concat
[ "parse-error"
, " ", sourceName
, ":", show $ unPos sourceLine
, ":", show $ unPos sourceColumn
]
cmdRun :: Command
cmdRun = do
[ name ] <- asks tmiParams
TestModeState {..} <- get
case find ((name ==) . testName) $ concatMap moduleTests tmsModules of
Nothing -> cmdOut "run-not-found"
Just test -> do
out <- asks tmiOutput
liftIO (runTest out defaultTestOptions tmsGlobals test) >>= \case
True -> cmdOut "run-done"
False -> cmdOut "run-failed"