{-# LANGUAGE BlockArguments #-} module Hercules.CNix.ExprSpec (spec) where import qualified Data.Aeson as A import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BL import Data.List ((\\)) import qualified Data.Map as M import qualified Data.Text as T import qualified Data.Text.IO as T import Hercules.CNix.Expr import qualified Hercules.CNix.Expr.Typed as Typed import Protolude hiding (evalState) import qualified SingleState import System.FilePath (()) import System.IO.Temp (withSystemTempDirectory) import System.Process (waitForProcess) import qualified System.Process import Test.Hspec setup :: (Ptr EvalState -> IO a) -> IO a setup f = f SingleState.evalState checkWithNix :: Ptr EvalState -> ByteString -> RawValue -> IO () checkWithNix evalState s a = do f <- valueFromExpressionString evalState s "/home/someuser/src/dummy-project" r <- apply f a match evalState r >>= \case Right (IsBool b) -> do bl <- getBool b if bl then pass else panic $ "Value did not satisfy <<" <> decodeUtf8With lenientDecode s <> ">>" Right _ -> panic "wrong type" Left e -> throwIO e spec :: Spec spec = do describe "getLocalFlake" $ it "gets a trivial flake" $ do setup \evalState -> do v <- getLocalFlake evalState "test/data/simple-flake" >>= assertType evalState lib <- getAttr evalState v "lib" libAttrs <- for lib (Typed.match evalState) >>= \case Just (Right (Typed.IsAttrs as)) -> pure as Just (Left e) -> panic $ "lib error " <> show e Just _ -> panic "lib must be attrs" Nothing -> panic "no lib???" greeting <- getAttr evalState libAttrs "greeting" greetingBytes <- for greeting (Typed.match evalState) >>= \case Just (Right (Typed.IsString s)) -> getStringIgnoreContext s Just (Left e) -> panic $ "lib.greeting error " <> show e Just _ -> panic "lib.greeting must be string" Nothing -> panic "no lib.greeting???" greetingBytes `shouldBe` "hello flake" describe "valueFromExpressionString" do it "parses true" do setup \evalState -> do v <- valueFromExpressionString evalState "true" "/" match evalState v >>= \case Right (IsBool b) -> do r <- getBool b r `shouldBe` True Right _ -> panic "wrong type" Left e -> throwIO e describe "toRawValue" do it "converts True" $ setup \evalState -> do a <- toRawValue evalState True a & checkWithNix evalState "a: a == true" it "converts False" $ setup \evalState -> do a <- toRawValue evalState False a & checkWithNix evalState "a: a == false" it "converts 0" $ setup \evalState -> do a <- toRawValue evalState (0 :: Int64) a & checkWithNix evalState "a: a == 0" it "converts 1" $ setup \evalState -> do a <- toRawValue evalState (1 :: Int64) a & checkWithNix evalState "a: a == 1" it "converts 42" $ setup \evalState -> do a <- toRawValue evalState (42 :: Int64) a & checkWithNix evalState "a: a == 42" -- Oddly, Nix does not support -9223372036854775808, which would be the -- most negative int64 it "converts -9223372036854775807" $ setup \evalState -> do a <- toRawValue evalState (-9223372036854775807 :: Int64) a & checkWithNix evalState "a: a == (-9223372036854775807)" it "converts 9223372036854775807" $ setup \evalState -> do a <- toRawValue evalState (9223372036854775807 :: Int64) a & checkWithNix evalState "a: a == 9223372036854775807" it "converts Map.empty" $ setup \evalState -> do a <- toRawValue evalState (mempty :: Map ByteString Int64) a & checkWithNix evalState "a: a == {}" it "converts Map.singleton" $ setup \evalState -> do a <- toRawValue evalState (M.singleton "foo" 1 :: Map ByteString Int64) a & checkWithNix evalState "a: a == { foo = 1; }" it "converts empty bytes" $ setup \evalState -> do a <- toRawValue evalState ("" :: ByteString) a & checkWithNix evalState "a: a == ''''" it "converts empty text" $ setup \evalState -> do a <- toRawValue evalState ("" :: Text) a & checkWithNix evalState "a: a == ''''" it "converts bytes" $ setup \evalState -> do a <- toRawValue evalState ("hi" :: ByteString) a & checkWithNix evalState "a: a == ''hi''" it "converts text" $ setup \evalState -> do a <- toRawValue evalState ("hi" :: Text) a & checkWithNix evalState "a: a == ''hi''" it "converts empty list" $ setup \evalState -> do a <- toRawValue evalState ([] :: [Int]) a & checkWithNix evalState "a: a == []" it "converts singleton list" $ setup \evalState -> do a <- toRawValue evalState ["hi" :: Text] a & checkWithNix evalState "a: a == [''hi'']" it "converts list" $ setup \evalState -> do a <- toRawValue evalState =<< sequenceA [toRawValue evalState ("hi" :: Text), toRawValue evalState True, toRawValue evalState (1 :: Int), toRawValue evalState (mempty :: Map ByteString Int)] a & checkWithNix evalState "a: a == [''hi'' true 1 {}]" it "converts json" $ setup \evalState -> do jsonExample <- BS.readFile "test/data/sample.json" jsonValue <- case A.eitherDecode (BL.fromStrict jsonExample) of Left e -> panic (toS e) Right r -> pure (r :: A.Value) a <- toRawValue evalState jsonValue a & checkWithNix evalState ("a: a == builtins.fromJSON ''" <> jsonExample <> "''") describe "getFlakeFromGit" do it "works" $ setup \evalState -> do withSystemTempDirectory "cnix-expr-test" \dir -> do let run exe args = do -- putErrText $ "Running " <> show exe <> " " <> show args r <- System.Process.withCreateProcess ((System.Process.proc exe args) {System.Process.cwd = Just dir}) \_ _ _ -> waitForProcess case r of ExitFailure n -> panic ("command " <> show exe <> " " <> show args <> " failed with exit code " <> show n) ExitSuccess -> pass readProc exe args = do -- putErrText $ "Reading " <> show exe <> " " <> show args (t, r) <- System.Process.withCreateProcess ((System.Process.proc exe args) {System.Process.cwd = Just dir, System.Process.std_out = System.Process.CreatePipe}) \_ stdom _ p -> do stdo <- maybe (panic "no stdout") pure stdom t <- T.hGetContents stdo (t,) <$> waitForProcess p case r of ExitFailure n -> panic ("command " <> show exe <> " " <> show args <> " failed with exit code " <> show n) ExitSuccess -> pass pure t run "git" ["-c", "init.defaultBranch=main", "init"] writeFile (dir "flake.nix") "{ outputs = { ... }: { hello = ''world''; }; }" run "git" ["add", "flake.nix"] run "git" ["-c", "user.name=Tester", "-c", "user.email=test@example.com", "commit", "-m", "go go go", "flake.nix"] -- man git check-ref-format let ref = (['!' .. 'A'] ++ ['Z' .. 'a'] ++ "Ü" ++ ['z' .. '}']) \\ "\\~^?*[:" run "git" ["branch", ref] rev <- T.strip . toS <$> readProc "git" ["show", "--format=format:%H", "--quiet"] r <- getFlakeFromGit evalState ("file://" <> toS dir) (toS ref) rev r & checkWithNix evalState "r: r.hello == ''world''" r & checkWithNix evalState ("r: r.sourceInfo.rev == ''" <> encodeUtf8 rev <> "''")