summaryrefslogtreecommitdiff
path: root/lambda-calcul/haskell/test
diff options
context:
space:
mode:
authorArnaud Bailly <arnaud@pankzsoft.com>2026-04-01 16:18:01 +0200
committerArnaud Bailly <arnaud@pankzsoft.com>2026-04-01 16:22:02 +0200
commitf6dde1041a52da4f2e7ccb77eed3c577f9d07798 (patch)
treeee99b8a606b7dcb378744f62245209a7d4d8eda4 /lambda-calcul/haskell/test
parentd820a635afb69c44f736e941bbe094ad6a838073 (diff)
downloadlambda-nantes-f6dde1041a52da4f2e7ccb77eed3c577f9d07798.tar.gz
Remove answers from sent challenges
Also fix integration test
Diffstat (limited to 'lambda-calcul/haskell/test')
-rw-r--r--lambda-calcul/haskell/test/Minilang/IntegrationSpec.hs17
-rw-r--r--lambda-calcul/haskell/test/Minilang/Lambda/ParserSpec.hs15
-rw-r--r--lambda-calcul/haskell/test/Minilang/REPL/NetSpec.hs45
-rw-r--r--lambda-calcul/haskell/test/Minilang/REPLSpec.hs68
-rw-r--r--lambda-calcul/haskell/test/Minilang/ServerSpec.hs12
5 files changed, 82 insertions, 75 deletions
diff --git a/lambda-calcul/haskell/test/Minilang/IntegrationSpec.hs b/lambda-calcul/haskell/test/Minilang/IntegrationSpec.hs
index ef05e20..d0265ac 100644
--- a/lambda-calcul/haskell/test/Minilang/IntegrationSpec.hs
+++ b/lambda-calcul/haskell/test/Minilang/IntegrationSpec.hs
@@ -6,8 +6,12 @@ import Control.Exception (bracket)
import Data.Aeson (eitherDecodeFileStrict)
import Data.Proxy (Proxy (..))
import Data.Text (Text)
+import qualified Data.Text as Text
import Minilang.API
-import Minilang.REPL.Types (Evaluator (..))
+import Minilang.Lambda.Parser (pretty)
+import Minilang.REPL (withInput, withInputs)
+import Minilang.REPL.Purer (PurerEnv (..))
+import Minilang.REPL.Types (Command (Load), Content (..), Evaluator (..), In (Com), Out (..), initialREPL)
import Minilang.Server (Server (..), startServer, stopServer)
import Network.HTTP.Client (defaultManagerSettings, newManager)
import Servant.API ((:<|>) (..))
@@ -89,12 +93,19 @@ spec =
-- | Fetch the current challenge for the token, post the known answer, and
-- assert the server accepted it (200 OK / no exception).
solveChallenge :: ClientEnv -> Token -> Challenge -> IO ()
-solveChallenge env token Challenge{level, answer} = do
+solveChallenge env token Challenge{level, expr} = do
-- Confirm the server gives us the challenge we expect at this grade
Challenge{level = serverLevel} <- runC env $ challengeClient (Just token)
serverLevel `shouldBe` level
-- Post the correct answer
- runC env $ answerClient (Just token) answer
+ let PurerEnv{outputs} = withInputs initialREPL [Com (Load (Data expr))]
+ case outputs of
+ -- NOTE: Outputs are in reverse order, so the first output is the last one produced by the REPL.
+ (Bye : Evaluated ast _ _ : _) -> do
+ runC env $ answerClient (Just token) (pretty ast)
+ [Bye] ->
+ runC env $ answerClient (Just token) ""
+ answer -> fail $ "Unexpected answer format: " <> show answer
-- ---------------------------------------------------------------------------
-- Server lifecycle
diff --git a/lambda-calcul/haskell/test/Minilang/Lambda/ParserSpec.hs b/lambda-calcul/haskell/test/Minilang/Lambda/ParserSpec.hs
index 40866bb..0050973 100644
--- a/lambda-calcul/haskell/test/Minilang/Lambda/ParserSpec.hs
+++ b/lambda-calcul/haskell/test/Minilang/Lambda/ParserSpec.hs
@@ -8,10 +8,9 @@ import Data.Either (isLeft, isRight)
import Data.Function ((&))
import Data.Text (Text, pack)
import qualified Data.Text as Text
-import Minilang.Lambda.Eval (Term (Lam))
+import Minilang.Lambda.Eval (Term (Lam), normalise)
import qualified Minilang.Lambda.Eval as Eval
-import Minilang.Lambda.Parser (AST (..), desugar, initialChars, parse, parseProgram, pretty, restChars, escapeString)
-import Minilang.Lambda.Eval (normalise)
+import Minilang.Lambda.Parser (AST (..), desugar, escapeString, initialChars, parse, parseProgram, pretty, restChars)
import Test.Hspec (Spec, describe, it, parallel, shouldBe, shouldSatisfy)
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck (
@@ -91,13 +90,13 @@ spec = parallel $ do
<> Text.concat
( map
( \(Identifier v, t) ->
- "(" <> v <> " " <> pretty t <> ")"
+ "(" <> v <> " " <> pretty t <> ")\n"
)
bound
)
- <> ") "
+ <> ")\n "
<> pretty body
- <> ")"
+ <> ")\n"
)
`shouldSatisfy` isRight
@@ -370,8 +369,8 @@ genAst depth =
-- logic is tested separately.
genStrLit :: Gen AST
genStrLit = StrLit . pack <$> listOf (elements safeChars)
- where
- safeChars = filter (\c -> c /= '"' && c /= '\\') [' '..'~']
+ where
+ safeChars = filter (\c -> c /= '"' && c /= '\\') [' ' .. '~']
genDef :: Int -> Gen AST
genDef n = Def . unIdent <$> arbitrary <*> genAst n
diff --git a/lambda-calcul/haskell/test/Minilang/REPL/NetSpec.hs b/lambda-calcul/haskell/test/Minilang/REPL/NetSpec.hs
index 6a2b1ab..2f8880a 100644
--- a/lambda-calcul/haskell/test/Minilang/REPL/NetSpec.hs
+++ b/lambda-calcul/haskell/test/Minilang/REPL/NetSpec.hs
@@ -1,6 +1,5 @@
module Minilang.REPL.NetSpec where
--- import Control.Concurrent (threadDelay)
import Control.Concurrent.Async
import Control.Concurrent.STM.TVar
import Control.Exception (bracket)
@@ -46,48 +45,48 @@ withServer =
runTestClient :: Int -> String -> [In] -> IO [Either String Out]
runTestClient port envId inputs =
runClient "127.0.0.1" port ("/repl/" <> envId) client
- where
- client cnx = do
- outs <- forM inputs $ \inp -> do
- WS.sendBinaryData cnx (encode inp)
- eitherDecode <$> WS.receiveData cnx
- WS.sendBinaryData cnx (encode EOF)
- WS.sendClose cnx (encode ("" :: String))
- pure outs
+ where
+ client cnx = do
+ outs <- forM inputs $ \inp -> do
+ WS.sendBinaryData cnx (encode inp)
+ eitherDecode <$> WS.receiveData cnx
+ WS.sendBinaryData cnx (encode EOF)
+ WS.sendClose cnx (encode ("" :: String))
+ pure outs
temporaryFile :: (FilePath -> IO c) -> IO c
temporaryFile = bracket newTempFile removeFile
- where
- newTempFile = do
- dir <- getTemporaryDirectory
- (f, h) <- mkstemp (dir </> "repl.test")
- hClose h
- pure f
+ where
+ newTempFile = do
+ dir <- getTemporaryDirectory
+ (f, h) <- mkstemp (dir </> "repl.test")
+ hClose h
+ pure f
spec :: Spec
spec = around withServer $
describe "MiniLang Network REPL" $ do
- it "evaluates expression and returns evaluation result" $ \Server {serverPort} -> do
+ it "evaluates expression and returns evaluation result" $ \Server{serverPort} -> do
let inp = [Com (Set (SelectLanguage Typed)), In "((lam (x) x) y)"]
expectedOutput =
- [ Right (Msg "Language set to Typed"),
- Right $ Evaluated (AST.Sym "y") (Var "y") (TyVar "a")
+ [ Right (Msg "Language set to Typed")
+ , Right $ Evaluated (AST.Sym "y") (Var "y") (Just $ TyVar "a")
]
res <- runTestClient serverPort ".newenv1" inp
res `shouldBe` expectedOutput
- it "allows retrieving empty initial env" $ \Server {serverPort} -> do
+ it "allows retrieving empty initial env" $ \Server{serverPort} -> do
let inp = [Com (Set (SelectLanguage Typed)), Com DumpEnv]
expectedOutput =
- [ Right (Msg "Language set to Typed"),
- Right $ CurrentEnv newEnv newContext Typed
+ [ Right (Msg "Language set to Typed")
+ , Right $ CurrentEnv newEnv newContext Typed
]
res <- runTestClient serverPort ".newenv2" inp
res `shouldBe` expectedOutput
- it "can reconnect to existing environment" $ \Server {serverPort} -> do
+ it "can reconnect to existing environment" $ \Server{serverPort} -> do
let inp = [In "Unit : U = Sum(tt)"]
_ <- runTestClient serverPort ".newenv3" inp
res <- runTestClient serverPort ".newenv3" [In "Unit"]
@@ -95,5 +94,5 @@ spec = around withServer $
res `shouldSatisfy` isEvaluated
isEvaluated :: [Either a Out] -> Bool
-isEvaluated [Right (Evaluated _ _ _)] = True
+isEvaluated [Right Evaluated{}] = True
isEvaluated _ = False
diff --git a/lambda-calcul/haskell/test/Minilang/REPLSpec.hs b/lambda-calcul/haskell/test/Minilang/REPLSpec.hs
index e382df4..1754cdb 100644
--- a/lambda-calcul/haskell/test/Minilang/REPLSpec.hs
+++ b/lambda-calcul/haskell/test/Minilang/REPLSpec.hs
@@ -1,11 +1,9 @@
module Minilang.REPLSpec where
import Control.Exception (bracket)
-import Data.Either (isRight)
import Data.Text.IO
import Minilang.Lambda.Eval (Term (Lam, Var))
import Minilang.Lambda.Parser (AST (..))
-import Minilang.Lambda.Unify (Type (TyVar))
import Minilang.REPL
import Minilang.REPL.Purer (PurerEnv (..))
import Minilang.REPL.Run (loadProgramInEnv)
@@ -26,8 +24,8 @@ spec = parallel $
["((lam (x) x) y)"]
out
- `shouldBe` [ "Bye!",
- "y : <?>"
+ `shouldBe` [ "Bye!"
+ , "y"
]
it "evaluates untyped MiniLang terms with definitions" $ do
@@ -36,24 +34,24 @@ spec = parallel $
["(def id (lam (x) x))", "(id y)"]
out
- `shouldBe` [ "Bye!",
- "y : <?>",
- "id : <?>"
+ `shouldBe` [ "Bye!"
+ , "y"
+ , "id"
]
it "evaluates typed MiniLang terms with definitions" $ do
let out =
withInput
- [ ":lang Typed",
- "(def id (lam (x) x))",
- "(id y)"
+ [ ":lang Typed"
+ , "(def id (lam (x) x))"
+ , "(id y)"
]
out
- `shouldBe` [ "Bye!",
- "y : a",
- "id : a -> a",
- "Language set to Typed"
+ `shouldBe` [ "Bye!"
+ , "y : a"
+ , "id : a -> a"
+ , "Language set to Typed"
]
it "defaults untyped terms" $ do
@@ -65,58 +63,58 @@ spec = parallel $
]
out
- `shouldBe` [ "Bye!",
- "(lam (z s) (s (lam (z s) (s (lam (z s) z))))) : <?>"
+ `shouldBe` [ "Bye!"
+ , "(lam (z s) (s (lam (z s) (s (lam (z s) z)))))"
]
it "do typecheck term when language is set to 'Typed'" $ do
let out =
withInput
- [ ":lang Typed",
- "(let ((Succ (lam (n z s) (s n))) \
+ [ ":lang Typed"
+ , "(let ((Succ (lam (n z s) (s n))) \
\ (Zero (lam (z s) z))) \
\ (Succ (Succ Zero)))"
]
out
- `shouldBe` [ "Bye!",
- "(lam (z s) (s (lam (z s) (s (lam (z s) z))))) : a -> ((c -> ((e -> f -> e) -> g) -> g) -> h) -> h",
- "Language set to Typed"
+ `shouldBe` [ "Bye!"
+ , "(lam (z s) (s (lam (z s) (s (lam (z s) z))))) : a -> ((c -> ((e -> f -> e) -> g) -> g) -> h) -> h"
+ , "Language set to Typed"
]
it "shows error when trying to load a non-existent file" $ do
withInput [":load foo"]
- `shouldBe` [ "Bye!",
- "REPLError \"cannot load file foo in Pure interpreter\""
+ `shouldBe` [ "Bye!"
+ , "REPLError \"cannot load file foo in Pure interpreter\""
]
it "can load 'bool-prog.lisp'" $ do
boolProg <- readFile "bool-prog.lisp"
- let PurerEnv {outputs} = runProgram (loadProgramInEnv boolProg)
+ let PurerEnv{outputs} = runProgram (loadProgramInEnv boolProg)
outputs
- `shouldBe` [ Evaluated (Abs ["f", "t"] (Sym "t")) (Lam "f" (Lam "t" (Var "t"))) (TyVar "<?>"),
- Evaluated (Abs ["f", "t"] (Sym "f")) (Lam "f" (Lam "t" (Var "f"))) (TyVar "<?>"),
- Evaluated (Abs ["f", "t"] (Sym "f")) (Lam "f" (Lam "t" (Var "f"))) (TyVar "<?>")
+ `shouldBe` [ Evaluated (Abs ["f", "t"] (Sym "t")) (Lam "f" (Lam "t" (Var "t"))) Nothing
+ , Evaluated (Abs ["f", "t"] (Sym "f")) (Lam "f" (Lam "t" (Var "f"))) Nothing
+ , Evaluated (Abs ["f", "t"] (Sym "f")) (Lam "f" (Lam "t" (Var "f"))) Nothing
]
it "can load 'bool.lisp'" $ do
boolProg <- readFile "bool.lisp"
- let PurerEnv {outputs} = runProgram (loadProgramInEnv boolProg)
+ let PurerEnv{outputs} = runProgram (loadProgramInEnv boolProg)
outputs
`shouldBe` [ Evaluated
(Abs ["f", "t"] (Sym "f"))
(Lam "f" (Lam "t" (Var "f")))
- (TyVar "<?>")
+ Nothing
]
it "can load 'fac.lisp'" $ do
boolProg <- readFile "fac.lisp"
- let PurerEnv {outputs} = runProgram (loadProgramInEnv boolProg)
+ let PurerEnv{outputs} = runProgram (loadProgramInEnv boolProg)
outputs
`shouldBe` [ Evaluated
(Abs ["f", "t"] (Sym "t"))
(Lam "f" (Lam "t" (Var "t")))
- (TyVar "<?>")
+ Nothing
]
around withTempFile $
@@ -136,11 +134,11 @@ spec = parallel $
out <- readFile outputFileName
out
- `shouldBe` "\955\928> (lam (z s) (s (lam (z s) z))) : <?>\n\955\928> Bye!\n"
+ `shouldBe` "\955\928> (lam (z s) (s (lam (z s) z)))\n\955\928> Bye!\n"
withTempFile :: (String -> IO ()) -> IO ()
withTempFile =
bracket mkTempFile rmTempFile
- where
- mkTempFile = getTemporaryDirectory >>= \dir -> mkstemp (dir </> "test-repl") >>= \(fp, h) -> hClose h >> pure fp
- rmTempFile fn = removeFile fn >> removeFile (fn <.> "out")
+ where
+ mkTempFile = getTemporaryDirectory >>= \dir -> mkstemp (dir </> "test-repl") >>= \(fp, h) -> hClose h >> pure fp
+ rmTempFile fn = removeFile fn >> removeFile (fn <.> "out")
diff --git a/lambda-calcul/haskell/test/Minilang/ServerSpec.hs b/lambda-calcul/haskell/test/Minilang/ServerSpec.hs
index 2860c40..8d0b182 100644
--- a/lambda-calcul/haskell/test/Minilang/ServerSpec.hs
+++ b/lambda-calcul/haskell/test/Minilang/ServerSpec.hs
@@ -51,7 +51,7 @@ spec = with (app testSeed) $ do
it "GET /api/leaderboard returns empty list of scores at start" $ do
get "/api/leaderboard"
- `shouldRespondWith` 200 {matchBody = arrayOfLengthOf (Proxy @Score) 0}
+ `shouldRespondWith` 200{matchBody = arrayOfLengthOf (Proxy @Score) 0}
it "POST /api/register returns registration token given user is not registered" $ do
postJSON "/api/register" alice
@@ -66,7 +66,7 @@ spec = with (app testSeed) $ do
it "GET /api/leaderboard returns list of one element after user registers" $ do
void $ postJSON "/api/register" alice
get "/api/leaderboard"
- `shouldRespondWith` 200 {matchBody = arrayOfLengthOf (Proxy @Score) 1}
+ `shouldRespondWith` 200{matchBody = arrayOfLengthOf (Proxy @Score) 1}
it "POST /api/register returns 400 given user is already registered" $ do
void $ postJSON "/api/register" alice
@@ -84,7 +84,7 @@ spec = with (app testSeed) $ do
`shouldRespondWith` 404
it "GET /api/challenge returns next challenge given user is registered" $ do
- let expected = LT.unpack $ decodeUtf8 $ encode $ Challenge {level = 0, expr = "x", description = "A lone free variable. What does x reduce to?", answer = "x"}
+ let expected = LT.unpack $ decodeUtf8 $ encode $ Challenge{level = 0, expr = "x", description = "A lone free variable. What does x reduce to?"}
postJSON "/api/register" alice
`shouldRespondWith` 201
@@ -123,7 +123,7 @@ spec = with (app testSeed) $ do
`shouldRespondWith` 400
it "GET /api/challenge returns next level challenge after successful POST" $ do
- let expected = LT.unpack $ decodeUtf8 $ encode $ Challenge {level = 1, expr = "y", description = "Another free variable. What does y reduce to?", answer = "y"}
+ let expected = LT.unpack $ decodeUtf8 $ encode $ Challenge{level = 1, expr = "y", description = "Another free variable. What does y reduce to?"}
void $ postJSON "/api/register" alice
void $ postJSON ("/api/challenge?token=" <> LBS.toStrict token) "\"x\""
@@ -147,7 +147,7 @@ spec = with (app testSeed) $ do
{ matchBody = MatchBody $ \_ actual ->
case eitherDecode actual of
Right (score :: Score) ->
- if score == Score {user = User "alice", grade = 0, lastTest = Nothing, lastResult = Nothing}
+ if score == Score{user = User "alice", grade = 0, lastTest = Nothing, lastResult = Nothing}
then Nothing
else Just $ "Expected score with grade 0, but got " ++ show score
Left err -> Just $ "Failed to decode JSON: " ++ err
@@ -190,7 +190,7 @@ spec = with (app testSeed) $ do
postJSON :: BS.ByteString -> ByteString -> WaiSession () SResponse
postJSON url = request "POST" url [("Content-Type", "application/json")]
-arrayOfLengthOf :: forall elem proxy. (FromJSON elem) => proxy elem -> Int -> MatchBody
+arrayOfLengthOf :: forall elem proxy. FromJSON elem => proxy elem -> Int -> MatchBody
arrayOfLengthOf _ len = MatchBody $ \_ actual ->
case eitherDecode actual of
Right (scores :: [elem]) ->