dojang-0.2.1: test/Dojang/Commands/HookSpec.hs
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
module Dojang.Commands.HookSpec (spec) where
import Control.Exception (bracket_)
import Control.Monad (when)
import System.IO.Unsafe (unsafePerformIO)
import Data.HashMap.Strict (empty)
import qualified Data.Map.Strict as Map
import Data.Text (Text, pack)
import System.Environment
( getArgs
, getExecutablePath
, lookupEnv
, setEnv
, unsetEnv
)
import System.OsPath (OsPath, encodeFS, (</>))
import Test.Hspec (Spec, describe, it, sequential, shouldBe)
import Dojang.App (AppEnv (..), runAppWithoutLogging)
import Dojang.Commands.Hook
( HookEnv (..)
, executeHooks
, mergeHookEnvironment
, shouldRunHook
)
import Dojang.TestUtils (withTempDir)
import Dojang.Types.Context (Context (..))
import Dojang.Types.Environment (Environment (..), Kernel (..))
import Dojang.Types.EnvironmentPredicate (EnvironmentPredicate (..))
import Dojang.Types.Hook (Hook (..), HookType (PreApply))
import Dojang.Types.Manifest (Manifest (..))
import Dojang.Types.MonikerMap (MonikerMap)
import Dojang.Types.MonikerName (parseMonikerName)
import Dojang.Types.Repository (Repository (..))
encodePath :: String -> OsPath
encodePath = unsafePerformIO . encodeFS
spec :: Spec
spec = sequential $ do
describe "mergeHookEnvironment" $ do
let dojangEnv = [("DOJANG_OS", "linux")]
let parentEnv = [("Dojang_OS", "stale"), ("PATH", "/bin")]
it "replaces differently cased variable names on Windows" $ do
mergeHookEnvironment "mingw32" dojangEnv parentEnv
`shouldBe` [("DOJANG_OS", "linux"), ("PATH", "/bin")]
it "preserves differently cased variable names on POSIX" $ do
mergeHookEnvironment "linux" dojangEnv parentEnv
`shouldBe` dojangEnv ++ parentEnv
describe "shouldRunHook" $ do
let env = Environment "linux" "x86_64" $ Kernel "Linux" "6.0.0"
it "returns True for Always condition" $ do
let hook =
Hook
{ command = encodePath "/bin/echo"
, args = ["hello"]
, condition = Always
, workingDirectory = Nothing
, ignoreFailure = False
}
shouldRunHook empty env hook `shouldBe` True
it "returns True when OS matches" $ do
let hook =
Hook
{ command = encodePath "/bin/echo"
, args = []
, condition = OperatingSystem "linux"
, workingDirectory = Nothing
, ignoreFailure = False
}
shouldRunHook empty env hook `shouldBe` True
it "returns False when OS does not match" $ do
let hook =
Hook
{ command = encodePath "/bin/echo"
, args = []
, condition = OperatingSystem "windows"
, workingDirectory = Nothing
, ignoreFailure = False
}
shouldRunHook empty env hook `shouldBe` False
it "returns True when moniker matches" $ do
let Right monikerName = parseMonikerName "linux"
let monikers = [(monikerName, OperatingSystem "linux")] :: MonikerMap
let hook =
Hook
{ command = encodePath "/bin/echo"
, args = []
, condition = Moniker monikerName
, workingDirectory = Nothing
, ignoreFailure = False
}
shouldRunHook monikers env hook `shouldBe` True
it "returns False when moniker does not match" $ do
let Right linuxMoniker = parseMonikerName "linux"
let Right windowsMoniker = parseMonikerName "windows"
let monikers =
[ (linuxMoniker, OperatingSystem "linux")
, (windowsMoniker, OperatingSystem "windows")
]
:: MonikerMap
let hook =
Hook
{ command = encodePath "/bin/echo"
, args = []
, condition = Moniker windowsMoniker
, workingDirectory = Nothing
, ignoreFailure = False
}
shouldRunHook monikers env hook `shouldBe` False
describe "executeHooks" $ do
it "inherits the parent environment and overrides Dojang variables" $
withTempDir $ \tmpDir _ -> do
command <- hookCommand
manifestFilename <- encodeFS "dojang.toml"
intermediateDir <- encodeFS ".dojang"
envFilename <- encodeFS "dojang-env.toml"
let hook =
Hook
{ command = fst command
, args = snd command
, condition = Always
, workingDirectory = Nothing
, ignoreFailure = False
}
let manifest' =
Manifest empty Map.empty Map.empty $ Map.singleton PreApply [hook]
let repository = Repository tmpDir (tmpDir </> intermediateDir) manifest'
let environment = Environment "linux" "x86_64" $ Kernel "Linux" "6.0.0"
let context = Context repository environment (const $ pure Nothing)
let appEnv =
AppEnv
tmpDir
intermediateDir
manifestFilename
envFilename
False
False
let hookEnv =
HookEnv
tmpDir
(tmpDir </> manifestFilename)
False
"hook-test-os"
"x86_64"
withEnvVars
[ ("DOJANG_TEST_PARENT", Just "inherited")
, ("DOJANG_OS", Just "stale")
]
$ runAppWithoutLogging appEnv
$ executeHooks hookEnv context PreApply
it "hook environment probe" $ do
arguments <- getArgs
when (probePattern `elem` arguments && probeSeed `elem` arguments) $ do
lookupEnv "DOJANG_TEST_PARENT" >>= (`shouldBe` Just "inherited")
lookupEnv "DOJANG_OS" >>= (`shouldBe` Just "hook-test-os")
hookCommand :: IO (OsPath, [Text])
hookCommand = do
command <- getExecutablePath >>= encodeFS
pure
( command
,
[ "--match"
, pack probePattern
, "--seed"
, pack probeSeed
]
)
probePattern :: String
probePattern = "/Dojang.Commands.Hook/executeHooks/hook environment probe/"
probeSeed :: String
probeSeed = "250025"
withEnvVars :: [(String, Maybe String)] -> IO a -> IO a
withEnvVars [] action = action
withEnvVars ((name, value) : rest) action = do
oldValue <- lookupEnv name
bracket_
(setOrUnset value)
(setOrUnset oldValue)
(withEnvVars rest action)
where
setOrUnset (Just value') = setEnv name value'
setOrUnset Nothing = unsetEnv name