packages feed

dhrun-1.0.1: src/Dhrun/Pure.hs

{-# language RecordWildCards #-}
{-# language DeriveGeneric #-}

{-|
Module      : Dhrun.Run
Description : runner
Copyright   : (c) Valentin Reis, 2018
License     : MIT
Maintainer  : fre@freux.fr
-}

module Dhrun.Pure
  ( ProcessWas(..)
  , CmdResult(..)
  , MonitoringResult(..)
  , Std(..)
  , envVars
  , concludeCmd
  , finalizeCmd
  , getWdFilename
  , stdToS
  , noWants
  , with3
  , with2
  , mapTuple
  )
where

import           Dhrun.Types.Cfg
import           Protolude
import qualified Data.Map.Lazy                                     as DM
                   ( fromList
                   , toList
                   )
import qualified Data.Map.Merge.Lazy                               as DMM
                   ( merge
                   , preserveMissing
                   , zipWithMatched
                   )
import qualified Data.Text                                         as T
                   ( lines )
import           Control.Arrow
                   ( (***) )

data CmdResult =
    Timeout Cmd -- | if the command died with a timeout
  | DiedLegal Cmd -- | if the command wasnt expected to do anything and died 1
  | ThrewException Cmd Text
  | DiedFailure Cmd Int
  | DiedExpected Cmd -- | if the command was expected in exitcode x and died x
  | DiedUnExpected Cmd ExitCode -- | command expected in exitcode x and died y!=x
  | FoundAll Cmd
  | FoundIllegal Cmd Text Std
  | OutputLacking Cmd Std
  | ConduitException Cmd Std
  deriving (Show,Generic)

data MonitoringResult =
    ThrowFoundAllWants
  | ThrowFoundAnAvoid Text
  deriving (Show, Typeable)
instance Exception MonitoringResult

data ProcessWas = Died ExitCode | Killed

data Std = Out | Err deriving (Show, Generic)

-- | concludeCmd hadNoWants cmdresult returns dhrun's conclusion based on whether
-- there were any "wants" in the template.
concludeCmd :: Bool -> CmdResult -> Either [Text] Text
concludeCmd True  (DiedLegal _) = Right "All commands exited successfully."
concludeCmd False (DiedLegal c) = Left
  [ "command exited:"
      <> mconcat (intersperse "\n" (T.lines (toS $ encodeCmd c)))
  ]
concludeCmd _ (Timeout c) =
  Left $ "The following command timed out:" : T.lines (toS $ encodeCmd c)
concludeCmd _ (DiedFailure c n) =
  Left
    $  "The following command died with exit code "
    <> show n
    <> " :"
    :  T.lines (toS $ encodeCmd c)
concludeCmd _ (FoundAll c) = Right
  ("All searched patterns in the following command were found. Killing all processes.\n "
  <> mconcat (intersperse "\n" (T.lines (toS $ encodeCmd c)))
  )
concludeCmd _ (FoundIllegal c t e) =
  Left
    $  "The illegal pattern "
    <> t
    <> " was found in the output of this process' "
    <> stdToS e
    <> ":"
    :  T.lines (toS $ encodeCmd c)
concludeCmd _ (OutputLacking c e) =
  Left
    $  "This process' "
    <> stdToS e
    <> " was found to be lacking pattern(s):"
    :  T.lines (toS $ encodeCmd c)
concludeCmd _ (ConduitException c e) =
  Left $ "This process ended with a conduit exception:" <> stdToS e : T.lines
    (toS $ encodeCmd c)
concludeCmd _ (ThrewException c e) =
  Left $ "This process' execution ended with an exception: " <> e : T.lines
    (toS $ encodeCmd c)
concludeCmd _ (DiedUnExpected c n) =
  Left $ "process exited with inadequate exit code " <> show n <> ": " : T.lines
    (toS $ encodeCmd c)
concludeCmd _ (DiedExpected c) =
  Right $ "process exited with adequate exit code " <> ": " <> toS (encodeCmd c)

stdToS :: Std -> Text
stdToS Out = "stdout"
stdToS Err = "stderr"

noWants :: Cmd -> Bool
noWants Cmd {..} = null (wants $ filecheck out) && null (wants $ filecheck err)

envVars :: [EnvVar] -> [VarName] -> [(Text, Text)] -> [(Text, Text)]
envVars internEnv passVars externEnv = DM.toList $ DMM.merge
  DMM.preserveMissing
  DMM.preserveMissing
  (DMM.zipWithMatched (\_ x _ -> x))
  (DM.fromList (forcedEnvVars internEnv))
  (DM.fromList (externEnvVars passVars externEnv))

externEnvVars :: [VarName] -> [(Text, Text)] -> [(Text, Text)]
externEnvVars passvars = filter (\(k, _) -> k `elem` (toS <$> passvars))

forcedEnvVars :: [EnvVar] -> [(Text, Text)]
forcedEnvVars vs = (\EnvVar {..} -> (toS varname, toS value)) <$> vs

mapTuple :: (a -> b) -> (a, a) -> (b, b)
mapTuple = join (***)

getWdFilename :: Text -> FileCheck a -> FilePath
getWdFilename wdT fc = toS $ wdT <> "/" <> toS (filename fc)

finalizeCmd
  :: Cmd
  -> Either (Either SomeException ()) (Either SomeException ())
  -> ProcessWas
  -> CmdResult
finalizeCmd c _ (Died ec) = case exitcode c of
  Nothing -> DiedLegal c
  Just ecExpect ->
    if ecExpect == ec then DiedExpected c else DiedUnExpected c ec
finalizeCmd c ei _ = rightFinalizer ei
 where
  rightFinalizer (Left  (Right ())) = legalOrLacking Out
  rightFinalizer (Right (Right ())) = legalOrLacking Err
  rightFinalizer (Left  (Left  e )) = unpackE e Out
  rightFinalizer (Right (Left  e )) = unpackE e Err
  unpackE e r = case fromException e of
    Just (ThrowFoundAnAvoid t) -> FoundIllegal c t r
    Just ThrowFoundAllWants    -> FoundAll c
    Nothing                    -> ConduitException c r
  legalOrLacking r = if noWants c then DiedLegal c else OutputLacking c r

with3
  :: ((t1 -> t2) -> t3)
  -> ((t4 -> t5) -> t2)
  -> ((t6 -> t7) -> t5)
  -> (t1 -> t4 -> t6 -> t7)
  -> t3
with3 w1 w2 w3 f = w1 $ \v1 -> w2 $ \v2 -> w3 $ \v3 -> f v1 v2 v3

with2 :: ((t1 -> t2) -> t3) -> ((t4 -> t5) -> t2) -> (t1 -> t4 -> t5) -> t3
with2 w1 w2 f = w1 $ \v1 -> w2 $ \v2 -> f v1 v2