File size: 8,310 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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
{-# LANGUAGE OverloadedStrings #-}

module Test.Backend.CodeGen
  ( tests
  ) where

import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck as QC
import qualified Data.Text as T
import Data.Text (Text)

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

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

tests :: TestTree
tests = testGroup "Assertica.Backend.CodeGen"
  [ testGroup "Simple Expression Compilation"
      [ testCase "Compile integer literal" testIntLiteral
      , testCase "Compile boolean literal" testBoolLiteral
      , testCase "Compile unit constant" testUnitConstant
      , testCase "Compile string literal" testStringLiteral
      ]
  , testGroup "Variable Compilation"
      [ testCase "Compile simple variable" testSimpleVar
      , testCase "Compile qualified variable" testQualifiedVar
      ]
  , testGroup "Lambda and Application"
      [ testCase "Compile simple lambda" testSimpleLambda
      , testCase "Compile application" testApplication
      , testCase "Compile nested application" testNestedApp
      ]
  , testGroup "Type Compilation"
      [ testCase "Compile function type" testFunctionType
      , testCase "Compile type variable" testTypeVar
      , testCase "Compile type constructor" testTypeConstructor
      ]
  , testGroup "Error Handling"
      [ testCase "Reject invalid qualified name" testInvalidQName
      ]
  , testGroup "Configuration"
      [ testCase "Default config has comments enabled" testDefaultConfig
      , testCase "Config affects output" testConfigAffectsOutput
      ]
  ]

-- ============================================================================
-- UNIT TESTS
-- ============================================================================

testIntLiteral :: Assertion
testIntLiteral = do
  let term = Const (IntLit 42)
  let result = compileExpression defaultConfig term
  case result of
    Left err -> assertFailure $ "Unexpected error: " ++ err
    Right expr -> do
      let code = heExpr expr
      assertBool "Should contain '42'" ("42" `T.isInfixOf` code)

testBoolLiteral :: Assertion
testBoolLiteral = do
  let term = Const (BoolLit True)
  let result = compileExpression defaultConfig term
  case result of
    Left err -> assertFailure $ "Unexpected error: " ++ err
    Right expr -> do
      let code = heExpr expr
      assertBool "Should contain 'True'" ("True" `T.isInfixOf` code)

testUnitConstant :: Assertion
testUnitConstant = do
  let term = Const UnitConst
  let result = compileExpression defaultConfig term
  case result of
    Left err -> assertFailure $ "Unexpected error: " ++ err
    Right expr -> do
      let code = heExpr expr
      assertEqual "Should be unit" "()" code

testStringLiteral :: Assertion
testStringLiteral = do
  let term = Const (StringLit "hello")
  let result = compileExpression defaultConfig term
  case result of
    Left err -> assertFailure $ "Unexpected error: " ++ err
    Right expr -> do
      let code = heExpr expr
      assertBool "Should contain string" ("hello" `T.isInfixOf` code)

testSimpleVar :: Assertion
testSimpleVar = do
  let var = Var (Var "x" 0)
  let result = compileExpression defaultConfig var
  case result of
    Left err -> assertFailure $ "Unexpected error: " ++ err
    Right expr -> do
      let code = heExpr expr
      assertEqual "Should be variable name" "x" code

testQualifiedVar :: Assertion
testQualifiedVar = do
  let var = Var (Var "map" 0)
  let result = compileExpression defaultConfig var
  case result of
    Left err -> assertFailure $ "Unexpected error: " ++ err
    Right expr -> do
      let code = heExpr expr
      assertBool "Should contain variable" ("map" `T.isInfixOf` code)

testSimpleLambda :: Assertion
testSimpleLambda = do
  let lambda = Lam (Binder (Var "x" 0) Nothing) (Var (Var "x" 0))
  let result = compileExpression defaultConfig lambda
  case result of
    Left err -> assertFailure $ "Unexpected error: " ++ err
    Right expr -> do
      let code = heExpr expr
      assertBool "Should contain lambda" ("\\" `T.isInfixOf` code)
      assertBool "Should contain variable" ("x" `T.isInfixOf` code)

testApplication :: Assertion
testApplication = do
  let f = Var (Var "f" 0)
  let x = Var (Var "x" 0)
  let app = App f x
  let result = compileExpression defaultConfig app
  case result of
    Left err -> assertFailure $ "Unexpected error: " ++ err
    Right expr -> do
      let code = heExpr expr
      assertBool "Should contain f" ("f" `T.isInfixOf` code)
      assertBool "Should contain x" ("x" `T.isInfixOf` code)

testNestedApp :: Assertion
testNestedApp = do
  let f = Var (Var "f" 0)
  let x = Var (Var "x" 0)
  let y = Var (Var "y" 0)
  let app1 = App f x
  let app2 = App app1 y
  let result = compileExpression defaultConfig app2
  case result of
    Left err -> assertFailure $ "Unexpected error: " ++ err
    Right expr -> do
      let code = heExpr expr
      assertBool "Should be valid expression" (not (T.null code))

testFunctionType :: Assertion
testFunctionType = do
  let tyInt = TyConst (QName [] "Int") []
  let tyBool = TyConst (QName [] "Bool") []
  let funType = TyFun tyInt tyBool
  let result = compileType defaultConfig funType
  case result of
    Left err -> assertFailure $ "Unexpected error: " ++ err
    Right expr -> do
      let code = heExpr expr
      assertBool "Should contain arrow" ("->" `T.isInfixOf` code)

testTypeVar :: Assertion
testTypeVar = do
  let tv = TyVar (Var "a" 0)
  let result = compileType defaultConfig tv
  case result of
    Left err -> assertFailure $ "Unexpected error: " ++ err
    Right expr -> do
      let code = heExpr expr
      assertEqual "Should be type variable" "a" code

testTypeConstructor :: Assertion
testTypeConstructor = do
  let intType = TyConst (QName [] "Int") []
  let result = compileType defaultConfig intType
  case result of
    Left err -> assertFailure $ "Unexpected error: " ++ err
    Right expr -> do
      let code = heExpr expr
      assertEqual "Should be Int" "Int" code

testInvalidQName :: Assertion
testInvalidQName = do
  -- This test checks that invalid qualified names are handled
  -- For now, most constructs compile, but this is a placeholder
  let qname = QName ["Assertica", "Core"] "someType"
  let ty = TyConst qname []
  let result = compileType defaultConfig ty
  case result of
    Left _ -> return ()  -- Error is acceptable
    Right expr -> do
      let code = heExpr expr
      assertBool "Should produce valid code" (not (T.null code))

testDefaultConfig :: Assertion
testDefaultConfig = do
  assertEqual "Add comments enabled" True (cgcAddComments defaultConfig)
  assertEqual "Add type signatures enabled" True (cgcAddTypeSignatures defaultConfig)

testConfigAffectsOutput :: Assertion
testConfigAffectsOutput = do
  let config1 = defaultConfig { cgcAddComments = True }
  let config2 = defaultConfig { cgcAddComments = False }

  let term = Const (IntLit 42)
  let result1 = compileExpression config1 term
  let result2 = compileExpression config2 term

  case (result1, result2) of
    (Right e1, Right e2) ->
      -- Both should produce valid expressions
      assertBool "First result valid" (not (T.null (heExpr e1)))
      assertBool "Second result valid" (not (T.null (heExpr e2)))
    (Left e, _) -> assertFailure $ "Unexpected error: " ++ e
    (_, Left e) -> assertFailure $ "Unexpected error: " ++ e

-- ============================================================================
-- PROPERTY TESTS
-- ============================================================================

-- Property: Code generation is deterministic
propDeterministic :: Term -> Bool
propDeterministic term =
  let result1 = compileExpression defaultConfig term
      result2 = compileExpression defaultConfig term
  in case (result1, result2) of
    (Right e1, Right e2) -> heExpr e1 == heExpr e2
    (Left err1, Left err2) -> err1 == err2
    _ -> False

-- Property: Simple constants compile without errors
propSimpleConstantsCompile :: Constant -> Bool
propSimpleConstantsCompile c =
  let term = Const c
      result = compileExpression defaultConfig term
  in case result of
    Right _ -> True
    Left _ -> False