Download test/Test/Core/Termination.hs from Snapkitty/assertica: direct link, hf CLI and curl.
- Browser
- Download file 11.4 kB
-
https://huggingface.co/Snapkitty/assertica/resolve/main/test/Test/Core/Termination.hs
- Command line
-
hf download hf://Snapkitty/assertica/test/Test/Core/Termination.hs
-
curl -L -o Termination.hs https://huggingface.co/Snapkitty/assertica/resolve/main/test/Test/Core/Termination.hs
11.4 kB
| {-| | |
| Module : Test.Core.Termination | |
| Description : Tests for termination checking (Agent 4B) | |
| -} | |
| module Test.Core.Termination | |
| ( tests | |
| ) where | |
| import Test.Tasty | |
| import Test.Tasty.HUnit | |
| import qualified Data.Text as T | |
| import Assertica.Core.AST | |
| import Assertica.Core.Termination | |
| import Assertica.Core.TypeEnv | |
| -- ============================================================================ | |
| -- TEST SUITE | |
| -- ============================================================================ | |
| tests :: TestTree | |
| tests = testGroup "Assertica.Core.Termination" | |
| [ testGroup "Recursion Detection" | |
| [ testCase "No recursion detected in simple term" testNoRecursion | |
| , testCase "Direct recursion detected" testDirectRecursion | |
| , testCase "Recursion in application" testRecursionInApp | |
| , testCase "Recursion in case expression" testRecursionInCase | |
| ] | |
| , testGroup "Simple Recursion Patterns" | |
| [ testCase "Simple structural recursion accepted" testStructuralRecursion | |
| , testCase "Non-terminating recursion rejected" testNonTerminating | |
| , testCase "Guarded recursion accepted" testGuardedRecursion | |
| ] | |
| , testGroup "Recursive Call Analysis" | |
| [ testCase "Find direct recursive call" testFindDirectCall | |
| , testCase "Analyze structural call pattern" testStructuralCallPattern | |
| , testCase "Detect unsupported recursion pattern" testUnsupportedPattern | |
| ] | |
| , testGroup "Mutual Recursion" | |
| [ testCase "Mutual recursion with progress" testMutualRecursionProgress | |
| , testCase "Infinite mutual recursion rejected" testInfiniteMutualRecursion | |
| , testCase "Call cycle detection" testCallCycleDetection | |
| ] | |
| , testGroup "Integration Tests" | |
| [ testCase "Length function on List" testLengthFunction | |
| , testCase "Fibonacci with guards" testFibonacciFunction | |
| , testCase "Map over list" testMapFunction | |
| ] | |
| , testGroup "Error Messages" | |
| [ testCase "Non-terminating error message" testNonTerminatingError | |
| , testCase "Unsupported pattern error message" testUnsupportedPatternError | |
| , testCase "Cycle detection error message" testCycleDetectionError | |
| ] | |
| ] | |
| -- ============================================================================ | |
| -- HELPER FUNCTIONS | |
| -- ============================================================================ | |
| {-| Create a simple variable. -} | |
| var :: Text -> Var | |
| var name = Var name 0 | |
| {-| Create a simple qualified name. -} | |
| qn :: Text -> QName | |
| qn name = QName [] name | |
| {-| Create a binder. -} | |
| binder :: Text -> Maybe Type -> Binder | |
| binder name mty = Binder (var name) mty | |
| {-| Create a pattern for a variable. -} | |
| patVar :: Text -> Pattern | |
| patVar name = PatVar (var name) | |
| {-| Create a clause. -} | |
| clause :: Pattern -> Term -> Clause | |
| clause = Clause | |
| -- ============================================================================ | |
| -- RECURSION DETECTION TESTS | |
| -- ============================================================================ | |
| testNoRecursion :: Assertion | |
| testNoRecursion = do | |
| -- A simple non-recursive term | |
| let term = Const (IntLit 42) | |
| assertBool "Non-recursive term should not have recursion" $ | |
| not (hasRecursion term) | |
| testDirectRecursion :: Assertion | |
| testDirectRecursion = do | |
| -- A term that calls itself: f x | |
| let term = App (Var (var "f")) (Var (var "x")) | |
| assertBool "Application of f should be detected as having recursion" $ | |
| hasRecursion term | |
| testRecursionInApp :: Assertion | |
| testRecursionInApp = do | |
| -- f (g x) | |
| let term = App (Var (var "f")) (App (Var (var "g")) (Var (var "x"))) | |
| assertBool "Recursion in nested application should be detected" $ | |
| hasRecursion term | |
| testRecursionInCase :: Assertion | |
| testRecursionInCase = do | |
| -- case x of P -> f y | |
| let scrutinee = Var (var "x") | |
| c1 = clause (patVar "y") (App (Var (var "f")) (Var (var "y"))) | |
| term = Case scrutinee [c1] Nothing | |
| assertBool "Recursion in case body should be detected" $ | |
| hasRecursion term | |
| -- ============================================================================ | |
| -- SIMPLE RECURSION PATTERN TESTS | |
| -- ============================================================================ | |
| testStructuralRecursion :: Assertion | |
| testStructuralRecursion = do | |
| -- length (Cons h t) = 1 + length t | |
| -- Simplified: just the recursive call part | |
| let funcName = "length" | |
| -- App of length to a term | |
| term = App (Var (var "length")) (Var (var "t")) | |
| case checkTermination emptyEnv funcName term of | |
| Right () -> return () -- Should succeed | |
| Left err -> assertFailure $ "Structural recursion should pass: " ++ prettyTermError err | |
| testNonTerminating :: Assertion | |
| testNonTerminating = do | |
| -- f x = f (x + 1) | |
| let funcName = "f" | |
| -- The recursive call f (x + 1) | |
| successor = App (Const (PrimOp OpAdd)) (Var (var "x")) | |
| term = App (Var (var "f")) successor | |
| case checkTermination emptyEnv funcName term of | |
| Left (NonTerminatingRecursion _) -> return () -- Should fail | |
| Left err -> assertFailure $ "Wrong error type: " ++ show err | |
| Right () -> assertFailure "Non-terminating recursion should be rejected" | |
| testGuardedRecursion :: Assertion | |
| testGuardedRecursion = do | |
| -- Simplified guarded recursion | |
| let funcName = "countdown" | |
| -- countdown n = if n <= 0 then ... else countdown (n - 1) | |
| -- Just the recursive part | |
| term = App (Var (var "countdown")) (Var (var "n")) | |
| case checkTermination emptyEnv funcName term of | |
| Right () -> return () -- Should succeed (structural pattern) | |
| Left err -> assertFailure $ "Guarded recursion should pass: " ++ prettyTermError err | |
| -- ============================================================================ | |
| -- RECURSIVE CALL ANALYSIS TESTS | |
| -- ============================================================================ | |
| testFindDirectCall :: Assertion | |
| testFindDirectCall = do | |
| let funcName = "length" | |
| term = App (Var (var "length")) (Var (var "t")) | |
| case analyzeRecursiveCalls funcName term of | |
| Right calls -> | |
| case calls of | |
| [RecursiveCall name _ _] -> | |
| assertEqual "Should find call to length" name "length" | |
| [] -> assertFailure "Should find at least one recursive call" | |
| _ -> assertFailure "Should find exactly one recursive call" | |
| Left err -> assertFailure $ "Analysis should succeed: " ++ prettyTermError err | |
| testStructuralCallPattern :: Assertion | |
| testStructuralCallPattern = do | |
| let funcName = "walk" | |
| -- walk (tree) | |
| term = App (Var (var "walk")) (Constr (qn "Leaf") [Var (var "x")]) | |
| case analyzeRecursiveCalls funcName term of | |
| Right [RecursiveCall _ pattern _] -> | |
| assertEqual "Should detect structural call pattern" pattern (StructuralCall "constr") | |
| _ -> assertFailure "Should find structural call pattern" | |
| testUnsupportedPattern :: Assertion | |
| testUnsupportedPattern = do | |
| let funcName = "strange" | |
| -- A recursive call with unknown pattern | |
| unhandledOp = App (Const (PrimOp OpDiv)) (Var (var "x")) | |
| term = App (Var (var "strange")) unhandledOp | |
| case analyzeRecursiveCalls funcName term of | |
| Right calls -> | |
| case calls of | |
| [RecursiveCall _ UnknownPattern _] -> return () | |
| _ -> assertFailure "Should detect unknown pattern" | |
| Left err -> assertFailure $ "Analysis should still work: " ++ prettyTermError err | |
| -- ============================================================================ | |
| -- MUTUAL RECURSION TESTS | |
| -- ============================================================================ | |
| testMutualRecursionProgress :: Assertion | |
| testMutualRecursionProgress = do | |
| -- even n = if n == 0 then true else odd (n - 1) | |
| -- odd n = if n == 0 then false else even (n - 1) | |
| let functions = [("even", Var (var "x")), ("odd", Var (var "y"))] | |
| case checkTerminationMulti emptyEnv functions of | |
| Right () -> return () -- Should succeed | |
| Left err -> assertFailure $ "Mutual recursion with progress should pass: " ++ prettyTermError err | |
| testInfiniteMutualRecursion :: Assertion | |
| testInfiniteMutualRecursion = do | |
| -- f x = g x | |
| -- g x = f x | |
| -- No progress on any argument | |
| let functions = [("f", Var (var "x")), ("g", Var (var "x"))] | |
| case checkTerminationMulti emptyEnv functions of | |
| Left (InfiniteCallCycle _) -> return () -- Should detect cycle | |
| Left err -> assertFailure $ "Wrong error type: " ++ show err | |
| Right () -> assertFailure "Infinite cycle should be rejected" | |
| testCallCycleDetection :: Assertion | |
| testCallCycleDetection = do | |
| -- a calls b, b calls c, c calls a | |
| let functions = [("a", Var (var "b")), ("b", Var (var "c")), ("c", Var (var "a"))] | |
| case checkTerminationMulti emptyEnv functions of | |
| Left (InfiniteCallCycle cycle_path) -> | |
| assertBool "Cycle should be detected" (length cycle_path > 0) | |
| _ -> assertFailure "Should detect cycle in call graph" | |
| -- ============================================================================ | |
| -- INTEGRATION TESTS | |
| -- ============================================================================ | |
| testLengthFunction :: Assertion | |
| testLengthFunction = do | |
| -- Simulate length :: List a -> Nat | |
| -- length Nil = 0 | |
| -- length (Cons _ t) = 1 + length t | |
| let funcName = "length" | |
| -- The recursive case: length t | |
| consCase = Constr (qn "Cons") [Var (var "h"), Var (var "t")] | |
| recursiveCall = App (Var (var "length")) (Var (var "t")) | |
| case checkTermination emptyEnv funcName recursiveCall of | |
| Right () -> return () | |
| Left err -> assertFailure $ "length should terminate: " ++ prettyTermError err | |
| testFibonacciFunction :: Assertion | |
| testFibonacciFunction = do | |
| -- fib n = if n <= 1 then n else fib(n-1) + fib(n-2) | |
| -- For simplicity, just check the recursive call part | |
| let funcName = "fib" | |
| -- fib (n - 1) | |
| term = App (Var (var "fib")) (Var (var "n")) | |
| case checkTermination emptyEnv funcName term of | |
| Right () -> return () | |
| Left err -> assertFailure $ "fib should pass initial check: " ++ prettyTermError err | |
| testMapFunction :: Assertion | |
| testMapFunction = do | |
| -- map :: (a -> b) -> List a -> List b | |
| -- map f [] = [] | |
| -- map f (x:xs) = f x : map f xs | |
| let funcName = "map" | |
| -- map f xs | |
| recursiveCall = App (Var (var "map")) (Var (var "xs")) | |
| case checkTermination emptyEnv funcName recursiveCall of | |
| Right () -> return () | |
| Left err -> assertFailure $ "map should terminate: " ++ prettyTermError err | |
| -- ============================================================================ | |
| -- ERROR MESSAGE TESTS | |
| -- ============================================================================ | |
| testNonTerminatingError :: Assertion | |
| testNonTerminatingError = do | |
| let err = NonTerminatingRecursion "recursive call does not decrease" | |
| let msg = prettyTermError err | |
| assertBool "Error message should mention non-termination" $ | |
| "does not terminate" `elem` words msg | |
| testUnsupportedPatternError :: Assertion | |
| testUnsupportedPatternError = do | |
| let err = UnsupportedRecursionPattern "unknown pattern found" | |
| let msg = prettyTermError err | |
| assertBool "Error message should mention unsupported pattern" $ | |
| "pattern" `elem` words msg | |
| testCycleDetectionError :: Assertion | |
| testCycleDetectionError = do | |
| let cycle_path = ["f", "g", "f"] | |
| let err = InfiniteCallCycle cycle_path | |
| let msg = prettyTermError err | |
| assertBool "Error message should mention cycle" $ | |
| "cycle" `elem` words msg | |