libnix-0.4.1.0: tests/TestShellout.hs
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE LambdaCase #-}
module TestShellout where
import qualified Data.Text as T
import System.IO (openTempFile, hClose)
import System.Directory (getTemporaryDirectory)
import Test.Tasty
import Test.Tasty.HUnit
import Foreign.Nix.Shellout
import Data.Text (Text)
import Control.Monad.IO.Class (liftIO)
import Control.Monad ((>=>))
import Control.Exception (SomeException, try, Exception (displayException))
import Data.Function ((&))
import qualified Data.List as List
shelloutTests :: TestTree
shelloutTests = testGroup "shellout tests"
[ testGroup "parsing"
[ syntaxError ]
, testGroup "evaluating"
[ infiniteRecursion
, notADerivation
, someDerivation ]
, testGroup "realizing"
[ nixpkgsExists
, multilineErrors
, helloWorld
, copyTempfileToStore ]
, testGroup "executable paths"
[ badInstantiateFilePath ]
]
syntaxError :: TestTree
syntaxError = testCase "syntax error"
$ parseNixExpr ";" `isENoFail`
Left ("", SyntaxError "unexpected ';', at (string):1:1")
infiniteRecursion :: TestTree
infiniteRecursion = testCase "infinite recursion"
$ parseInst "let a = a; in a"
`isE` Left ( "error: infinite recursion encountered, at (string):1:10"
, UnknownInstantiateError)
notADerivation :: TestTree
notADerivation = testCase "not a derivation"
$ parseInst "42"
`isE` Left ("", NotADerivation)
someDerivation :: TestTree
someDerivation = testCase "a basic derivation"
$ assertNoFailure $ parseInst
"derivation { name = \"foo\"; builder = \" \"; system = \" \"; }"
nixpkgsExists :: TestTree
nixpkgsExists = testCase "nixpkgs is accessible"
$ assertNoFailure $ parseEval "import <nixpkgs> {}"
multilineErrors :: TestTree
multilineErrors = testCase "nixpkgs multiline stderr it parsed"
$ parseEval "builtins.abort ''wow\nsuch error''"
`isE` Left
( "error: evaluation aborted with the following error \
\message: 'wow\nsuch error'"
, UnknownInstantiateError)
helloWorld :: TestTree
helloWorld = testCase "build the GNU hello package"
$ assertNoFailure $ parseInstRealize "with import <nixpkgs> {}; hello"
copyTempfileToStore :: TestTree
copyTempfileToStore = testCase "copy a temporary file to store"
$ assertNoFailure $ do
(fp, h) <- liftIO $
getTemporaryDirectory >>= flip openTempFile "store-test"
liftIO $ hClose h
addToStore fp
badInstantiateFilePath :: TestTree
badInstantiateFilePath = testCase "not a derivation"
$ isExcBad "No such file or directory" (parseInst "42")
where
isExcBad = isException (defaultRunOptions {
executables = defaultExecutables {
exeNixInstantiate = Just "/path/does/not/exist"
}
})
--------------------------------------------------------------------
-- Helpers
parseInst :: Text -> NixAction InstantiateError IO (StorePath Derivation)
parseInst = parseInstLike instantiate
parseEval :: Text -> NixAction InstantiateError IO ()
parseEval = parseInstLike eval
parseInstLike :: (NixExpr -> NixAction InstantiateError IO a)
-> Text
-> NixAction InstantiateError IO a
parseInstLike like =
mapActionError (\_ -> UnknownInstantiateError)
. parseNixExpr >=> like
isE :: (Eq a, Eq e, Show a, Show e)
=> NixAction e IO a
-> Either (Text, e) a
-- ^ Left (subset of stdout, error)
-> Assertion
isE = isE' defaultRunOptions
isE' :: (Eq a, Eq e, Show a, Show e)
=> RunOptions IO
-> NixAction e IO a
-> Either (Text, e) a
-- ^ Left (subset of stdout, error)
-> Assertion
isE' runOptions na match = runNixAction runOptions na >>= \res ->
case (match, res) of
(Right a, Right b) -> a @=? b
(match', res') -> check match' res'
isException :: RunOptions IO -> [Char] -> NixAction e IO a -> IO ()
isException runOptions matchText na = try @SomeException (runNixAction runOptions na) >>= \case
Left exc -> do
let text = exc & displayException
assertBool ("threw exception, but did not match the given substring\nshould have contained: " <> matchText <> "\nbut was: " <> text)
$ matchText `List.isInfixOf` text
Right _a -> assertFailure "runNixAction should have thrown an exception"
isENoFail :: (Eq e, Show a, Show e)
=> NixAction e IO a
-> Either (Text, e) a
-- ^ Left (subset of stdout, error)
-> Assertion
isENoFail na match = runNixAction defaultRunOptions na >>= check match
check :: (Eq a, Show a)
=> Either (Text, a) _x
-> Either (NixActionError a) _x
-> Assertion
check (Left (outMatch, err))
(Left NixActionError
{ actionStderr = out
, actionError = err' }) = do
assertBool "stderr not matched" (outMatch `T.isInfixOf` out)
err @=? err'
check (Right _) (Left _) =
assertFailure "output should have succeeded, but it failed"
check (Left _) (Right _) =
assertFailure "output should have failed, but it succeeded"
check _ _ = error "handled by isE"
assertNoFailure :: Show e => NixAction e IO a -> Assertion
assertNoFailure na = do
ei <- runNixAction defaultRunOptions na
case ei of
(Left naErr) -> assertFailure $ show $ actionError naErr
(Right _) -> pure ()