File size: 7,087 Bytes
a10b189
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
{-# LANGUAGE OverloadedStrings #-}

module Test.Backend.Integration
  ( tests
  ) where

import Test.Tasty
import Test.Tasty.HUnit
import qualified Data.Text as T
import qualified Data.Map.Strict as Map

import Assertica.Core.AST
import Assertica.Core.Module
import Assertica.Backend.CodeGen
import Assertica.Backend.Emit

-- ============================================================================
-- TEST SUITE
-- ============================================================================

tests :: TestTree
tests = testGroup "Assertica.Backend.Integration"
  [ testGroup "End-to-End Compilation"
      [ testCase "Compile simple lambda to Haskell" testSimpleLambdaE2E
      , testCase "Compile let binding to Haskell" testLetBindingE2E
      , testCase "Compile case expression to Haskell" testCaseExpressionE2E
      ]
  , testGroup "Round-Trip Properties"
      [ testCase "Deterministic: AST -> Haskell is idempotent" testDeterministicCompilation
      ]
  , testGroup "Error Handling"
      [ testCase "Invalid input generates error" testErrorHandling
      ]
  , testGroup "Haskell Output Validity"
      [ testCase "Generated module has correct syntax" testModuleSyntax
      , testCase "Generated definitions are syntactically valid" testDefinitionSyntax
      ]
  ]

-- ============================================================================
-- END-TO-END TESTS
-- ============================================================================

testSimpleLambdaE2E :: Assertion
testSimpleLambdaE2E = do
  -- Compile: λx . x
  let lambda = Lam (Binder (Var "x" 0) Nothing) (Var (Var "x" 0))
  result <- case compileExpression defaultConfig lambda of
    Right expr -> return (heExpr expr)
    Left err -> assertFailure $ "Compilation failed: " ++ err

  -- Generated code should be valid Haskell
  assertBool "Should be non-empty" (not (T.null result))
  assertBool "Should contain lambda operator" ("\\" `T.isInfixOf` result)
  assertBool "Should contain variable" ("x" `T.isInfixOf` result)

testLetBindingE2E :: Assertion
testLetBindingE2E = do
  -- Compile: let x = 5 in x
  let letExpr = Let
        (Binder (Var "x" 0) Nothing)
        (Const (IntLit 5))
        (Var (Var "x" 0))

  result <- case compileExpression defaultConfig letExpr of
    Right expr -> return (heExpr expr)
    Left err -> assertFailure $ "Compilation failed: " ++ err

  assertBool "Should be non-empty" (not (T.null result))
  assertBool "Should contain let" ("let" `T.isInfixOf` result)
  assertBool "Should contain 5" ("5" `T.isInfixOf` result)
  assertBool "Should contain in" ("in" `T.isInfixOf` result)

testCaseExpressionE2E :: Assertion
testCaseExpressionE2E = do
  -- Compile: case x of { True -> 1; False -> 0 }
  let trueClause = Clause (PatConstr (QName [] "True") []) (Const (IntLit 1))
  let falseClause = Clause (PatConstr (QName [] "False") []) (Const (IntLit 0))
  let caseExpr = Case (Var (Var "x" 0)) [trueClause, falseClause] Nothing

  result <- case compileExpression defaultConfig caseExpr of
    Right expr -> return (heExpr expr)
    Left err -> assertFailure $ "Compilation failed: " ++ err

  assertBool "Should be non-empty" (not (T.null result))
  assertBool "Should contain case" ("case" `T.isInfixOf` result)
  assertBool "Should contain 1" ("1" `T.isInfixOf` result)
  assertBool "Should contain 0" ("0" `T.isInfixOf` result)

-- ============================================================================
-- ROUND-TRIP AND IDEMPOTENCY TESTS
-- ============================================================================

testDeterministicCompilation :: Assertion
testDeterministicCompilation = do
  let lambda = Lam (Binder (Var "x" 0) Nothing) (Var (Var "x" 0))

  -- Compile three times
  let result1 = compileExpression defaultConfig lambda
  let result2 = compileExpression defaultConfig lambda
  let result3 = compileExpression defaultConfig lambda

  case (result1, result2, result3) of
    (Right e1, Right e2, Right e3) -> do
      let r1 = heExpr e1
      let r2 = heExpr e2
      let r3 = heExpr e3
      assertEqual "First and second should match" r1 r2
      assertEqual "Second and third should match" r2 r3
    (Left e, _, _) -> assertFailure $ "Compilation failed: " ++ e
    (_, Left e, _) -> assertFailure $ "Compilation failed: " ++ e
    (_, _, Left e) -> assertFailure $ "Compilation failed: " ++ e

-- ============================================================================
-- ERROR HANDLING
-- ============================================================================

testErrorHandling :: Assertion
testErrorHandling = do
  -- Test that various error conditions are handled gracefully
  -- For now, most constructs compile, so test basic error path existence
  let x = Var (Var "x" 0)
  let result = compileExpression defaultConfig x
  case result of
    Right _ -> return ()  -- Success is also valid
    Left _ -> return ()   -- Error is acceptable

-- ============================================================================
-- HASKELL OUTPUT VALIDITY
-- ============================================================================

testModuleSyntax :: Assertion
testModuleSyntax = do
  let modName = ["Test", "Module"]
  let def = HaskellDefinition
        { hdName = "main"
        , hdTypeSignature = Just "IO ()"
        , hdBody = "putStrLn \"Hello\""
        , hdSourceLocation = Nothing
        }
  let mod = HaskellModule
        { hmName = modName
        , hmImports = ["Prelude"]
        , hmDefinitions = [def]
        , hmComments = ["Test module"]
        }

  let haskellCode = emitHaskellModule defaultEmitConfig mod

  -- Check basic Haskell syntax
  assertBool "Should start with comment or module" $
    "-- " `T.isPrefixOf` haskellCode || "module" `T.isPrefixOf` haskellCode

  assertBool "Should contain module declaration" $
    "module" `T.isInfixOf` haskellCode

  assertBool "Should contain where" $
    "where" `T.isInfixOf` haskellCode

  -- Count the lines to ensure structure
  let lineCount = length (T.lines haskellCode)
  assertBool "Should have multiple lines" (lineCount > 3)

testDefinitionSyntax :: Assertion
testDefinitionSyntax = do
  let def1 = HaskellDefinition
        { hdName = "id"
        , hdTypeSignature = Just "a -> a"
        , hdBody = "\\x -> x"
        , hdSourceLocation = Just "identity function"
        }

  let def2 = HaskellDefinition
        { hdName = "const"
        , hdTypeSignature = Just "a -> b -> a"
        , hdBody = "\\x y -> x"
        , hdSourceLocation = Nothing
        }

  let code1 = emitHaskellDefinition defaultEmitConfig def1
  let code2 = emitHaskellDefinition defaultEmitConfig def2

  -- Both should have valid structure
  assertBool "def1 should contain name" ("id" `T.isInfixOf` code1)
  assertBool "def1 should contain type" ("a" `T.isInfixOf` code1)
  assertBool "def1 should contain arrow" ("->" `T.isInfixOf` code1)
  assertBool "def1 should contain comment" ("--" `T.isInfixOf` code1)

  assertBool "def2 should contain name" ("const" `T.isInfixOf` code2)
  assertBool "def2 should contain type" ("a" `T.isInfixOf` code2)