packages feed

nix-eval-0.3.3.0: tests/Main.hs

{-# LANGUAGE OverloadedStrings #-}

{-
nix-eval: Dependency-injecting Haskell evaluator

Copyright (C) 2015 Chris Warburton

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
-}

module Main where

import Data.List
import Data.Maybe
import Language.Eval
import Language.Eval.Internal
import Test.QuickCheck.Monadic
import Test.Tasty (defaultMain, testGroup)
import Test.Tasty.QuickCheck

-- | Only test if `nix-shell` is available. In particular, when Nix builds and
--   tests this package, it uses a pure environment where nix-shell isn't
--   available
main = do nix <- haveNix
          defaultMain $ testGroup "All tests" $ if nix then [
                testProperty "Can eval unit"       unitEval
              , testProperty "Can eval sums"       sumEval
              , testProperty "Can import modules"  modulesImport
              , testProperty "Can import packages" packagesImport
              , testProperty "$$ precedence"       precedence
              , testProperty "Preamble added"      preambleAdded
              , testProperty "Flags work"          checkFlags
              , testProperty "Nesting works"       checkNesting
              ]
            else []

unitEval = checkIO "()" (Just "()")

sumEval :: [Int] -> Property
sumEval s = checkIO ("sum" $$ asString s)
                    (Just (show (sum s)))

modulesImport =
  checkIO (qualified "Data.Maybe" "isNothing" $$ "Nothing")
          (Just "True")

packagesImport :: String -> Property
packagesImport s = let expr = len $$ (pack $$ asString s)
                       len  = qualified "Data.Text" "length"
                       pack = qualified "Data.Text" "pack"
  in checkIO (withPkgs ["text"] expr)
             (Just (show (length s)))

precedence :: String -> Property
precedence s =
  checkIO (withPkgs ["text"] (qualified "Data.Text" "unpack" $$
                              qualified "Data.Text" "pack"   $$
                              asString s))
          (Just (show s))

preambleAdded :: Int -> Property
preambleAdded i =
  checkIO (withPreamble ("foo = " ++ show i) "foo * foo")
          (Just (show (i * i)))

checkFlags :: Int -> Property
checkFlags i = checkIO e (Just (show i))
  where e = withFlags ["-XTemplateHaskell"] . raw $
                "$([|" ++ show i ++ "|])"

-- Run nix-shells inside nix-shells, each with different Haskell packages
-- available, to make sure the environments are set up correctly
checkNesting = checkIO' id expr (Just "True")
  where expr   = unsafe $$ runHaskell nested
        -- Runs a String of Haskell in a sub-process
        runHaskell s = ((process $$ asString "runhaskell") $$ "[]") $$ asString s
        process    = withPkgs ["process"] $ qualified "System.Process" "readProcess"
        -- A String of Haskell code, which should invoke several `runhaskell`
        -- instances inside `nix-shell`s with different elements of `pkgs`
        -- available. The inner layer executes `base`, the others just propagate
        -- the result back up to us
        nested = recursiveHaskell pkgs base
        -- A selection of non-default packages/modules
        pkgs = [(Pkg "text",       Mod "Data.Text"),
                (Pkg "containers", Mod "Data.Graph"),
                (Pkg "parsec",     Mod "Text.Parsec")]
        -- This value should be propagated up through each putStrLn
        base = "main = putStr \"True\""

-- Helpers

-- | `checkIO' gen x s` runs `gen x` to get an `Expr`, evaluates it, and asserts
--   that the result equals `s`
checkIO' :: Show a => (a -> Expr) -> a -> Maybe String -> Property
checkIO' pre i o = once $ monadicIO $ do
  dbg (("expression", pre i),
       ("expected",   o))
  result <- run $ eval (pre i)
  dbg ("result", result)
  assert (result == o)

checkIO :: Expr -> Maybe String -> Property
checkIO = checkIO' ("show" $$)

mkHaskell :: Pkg -> Mod -> String -> String
mkHaskell p (Mod m) str = unlines [
          "import System.Process",
          "main = System.Process.readProcess",
          indent (show cmd),
          indent args',
          indent (show inner ++ " >>= putStrLn")]
  where inner = "import " ++ m ++ "\n" ++ str
        args' = "[" ++ intercalate "," (map show args) ++ "]"
        indent = ("  " ++)
        (cmd, args) = mkCmd expr
        expr = withPkgs [p] . withMods [Mod m] $ "undefined"

pkgString :: [Pkg] -> String
pkgString [Pkg p] = "haskellPackages.ghcWithPackages (h: [ h." ++ p ++ "])"

recursiveHaskell :: [(Pkg, Mod)] -> String -> String
recursiveHaskell []          base = base
recursiveHaskell ((p, m):xs) base = mkHaskell p m (recursiveHaskell xs base)

unsafe = qualified "System.IO.Unsafe" "unsafePerformIO"

dbg :: (Monad m, Show a) => a -> PropertyM m ()
dbg = monitor . counterexample . show