{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE CPP #-} module IHaskell.Test.Eval (testEval) where import Prelude import Data.List (stripPrefix) import Control.Monad (when, forM_) import Data.IORef (newIORef, modifyIORef, readIORef) import System.Directory (getTemporaryDirectory, setCurrentDirectory) import Data.String.Here (hereLit) import qualified GHC.Paths import Test.Hspec import IHaskell.Test.Util (strip) import IHaskell.Eval.Evaluate (interpret, evaluate) import IHaskell.Types (EvaluationResult(..), defaultKernelState, KernelState(..), LintStatus(..), Display(..), extractPlain) eval :: String -> IO ([Display], String) eval string = do outputAccum <- newIORef [] pagerAccum <- newIORef [] let publish evalResult = case evalResult of IntermediateResult{} -> return () FinalResult outs page [] -> do modifyIORef outputAccum (outs :) modifyIORef pagerAccum (page :) noWidgetHandling s _ = return s getTemporaryDirectory >>= setCurrentDirectory let state = defaultKernelState { getLintStatus = LintOff } interpret GHC.Paths.libdir False $ const $ IHaskell.Eval.Evaluate.evaluate state string publish noWidgetHandling out <- readIORef outputAccum pagerOut <- readIORef pagerAccum return (reverse out, unlines . map extractPlain . reverse $ pagerOut) becomes :: String -> [String] -> IO () becomes string expected = evaluationComparing comparison string where comparison :: ([Display], String) -> IO () comparison (results, pageOut) = do when (length results /= length expected) $ expectationFailure $ "Expected result to have " ++ show (length expected) ++ " results. Got " ++ show results forM_ (zip results expected) $ \(ManyDisplay [Display result], expected) -> case extractPlain result of "" -> expectationFailure $ "No plain-text output in " ++ show result ++ "\nExpected: " ++ expected str -> str `shouldBe` expected evaluationComparing :: (([Display], String) -> IO b) -> String -> IO b evaluationComparing comparison string = do let indent (' ':x) = 1 + indent x indent _ = 0 empty = null . strip stringLines = filter (not . empty) $ lines string minIndent = minimum (map indent stringLines) newString = unlines $ map (drop minIndent) stringLines eval newString >>= comparison pages :: String -> [String] -> IO () pages string expected = evaluationComparing comparison string where comparison (results, pageOut) = strip (stripHtml pageOut) `shouldBe` strip (fixQuotes $ unlines expected) -- A very, very hacky method for removing HTML stripHtml str = go str where go ('<':str) = case stripPrefix "script" str of Nothing -> go' str Just str -> dropScriptTag str go (x:xs) = x : go xs go [] = [] go' ('>':str) = go str go' (x:xs) = go' xs go' [] = error $ "Unending bracket html tag in string " ++ str dropScriptTag str = case stripPrefix "" str of Just str -> go str Nothing -> dropScriptTag $ tail str fixQuotes :: String -> String #if MIN_VERSION_ghc(7, 8, 0) fixQuotes = id #else fixQuotes = map $ \char -> case char of '\8216' -> '`' '\8217' -> '\'' c -> c #endif testEval :: Spec testEval = describe "Code Evaluation" $ do it "gets rid of the test failure with Nix" $ let throwAway string _ = evaluationComparing (const $ shouldBe True True) string in throwAway "True" ["True"] it "evaluates expressions" $ do "3" `becomes` ["3"] "3+5" `becomes` ["8"] "print 3" `becomes` ["3"] [hereLit| let x = 11 z = 10 in x+z |] `becomes` ["21"] it "evaluates flags" $ do ":set -package hello" `becomes` ["Warning: -package not supported yet"] ":set -XNoImplicitPrelude" `becomes` [] it "evaluates multiline expressions" $ do [hereLit| import Control.Monad forM_ [1, 2, 3] $ \x -> print x |] `becomes` ["1\n2\n3"] it "evaluates function declarations silently" $ do [hereLit| fun :: [Int] -> Int fun [] = 3 fun (x:xs) = 10 fun [1, 2] |] `becomes` ["10"] it "evaluates data declarations" $ do [hereLit| data X = Y Int | Z String deriving (Show, Eq) print [Y 3, Z "No"] print (Y 3 == Z "No") |] `becomes` ["[Y 3,Z \"No\"]", "False"] it "evaluates do blocks in expressions" $ do [hereLit| show (show (do Just 10 Nothing Just 100)) |] `becomes` ["\"\\\"Nothing\\\"\""] it "is silent for imports" $ do "import Control.Monad" `becomes` [] "import qualified Control.Monad" `becomes` [] "import qualified Control.Monad as CM" `becomes` [] "import Control.Monad (when)" `becomes` [] it "prints Unicode characters correctly" $ do "putStrLn \"Héllö, Üñiço∂e!\"" `becomes` ["Héllö, Üñiço∂e!"] "putStrLn \"Привет!\"" `becomes` ["Привет!"] it "evaluates directives" $ do #if MIN_VERSION_ghc(8,2,0) -- It's `p` instead of `t` for some reason ":typ 3" `becomes` ["3 :: forall p. Num p => p"] #else ":typ 3" `becomes` ["3 :: forall t. Num t => t"] #endif ":k Maybe" `becomes` ["Maybe :: * -> *"] ":in String" `pages` ["type String = [Char] \t-- Defined in \8216GHC.Base\8217"]