|
| 1 | +package liquidjava.integration; |
| 2 | + |
| 3 | +import static org.junit.jupiter.api.Assertions.*; |
| 4 | + |
| 5 | +import java.util.List; |
| 6 | + |
| 7 | +import liquidjava.processor.context.*; |
| 8 | +import liquidjava.rj_language.Predicate; |
| 9 | +import liquidjava.rj_language.ast.*; |
| 10 | +import org.junit.jupiter.api.BeforeEach; |
| 11 | +import org.junit.jupiter.api.Test; |
| 12 | + |
| 13 | +import spoon.Launcher; |
| 14 | +import spoon.reflect.factory.Factory; |
| 15 | +import spoon.reflect.reference.CtTypeReference; |
| 16 | + |
| 17 | +/** |
| 18 | + * Integration tests for Context management with variables, functions, ghosts, and refinements |
| 19 | + * These tests verify that multiple components work together correctly in realistic scenarios |
| 20 | + */ |
| 21 | +class ContextIntegrationTest { |
| 22 | + |
| 23 | + private Context context; |
| 24 | + private Factory factory; |
| 25 | + |
| 26 | + @BeforeEach |
| 27 | + void setUp() { |
| 28 | + Launcher launcher = new Launcher(); |
| 29 | + factory = launcher.getFactory(); |
| 30 | + context = Context.getInstance(); |
| 31 | + context.reinitializeAllContext(); |
| 32 | + } |
| 33 | + |
| 34 | + @Test |
| 35 | + void testCompleteVariableLifecycle() { |
| 36 | + // Scenario: Create variables, enter/exit contexts, track refinements |
| 37 | + CtTypeReference<Integer> intType = factory.Type().integerPrimitiveType(); |
| 38 | + Predicate initialPred = Predicate.createOperation( |
| 39 | + Predicate.createVar("x"), |
| 40 | + ">", |
| 41 | + Predicate.createLit("0", "int") |
| 42 | + ); |
| 43 | + |
| 44 | + // Add variable in global scope |
| 45 | + context.addVarToContext("x", intType, initialPred, null); |
| 46 | + assertTrue(context.hasVariable("x"), "Variable should exist in global scope"); |
| 47 | + |
| 48 | + // Enter new scope and add local variable |
| 49 | + context.enterContext(); |
| 50 | + Predicate localPred = Predicate.createOperation( |
| 51 | + Predicate.createVar("y"), |
| 52 | + "<", |
| 53 | + Predicate.createLit("100", "int") |
| 54 | + ); |
| 55 | + context.addVarToContext("y", intType, localPred, null); |
| 56 | + |
| 57 | + assertTrue(context.hasVariable("x"), "Global variable accessible in nested scope"); |
| 58 | + assertTrue(context.hasVariable("y"), "Local variable exists"); |
| 59 | + assertEquals(2, context.getAllVariables().size(), "Should have 2 variables"); |
| 60 | + |
| 61 | + // Update refinement for x |
| 62 | + Predicate newPred = Predicate.createOperation( |
| 63 | + Predicate.createVar("x"), |
| 64 | + ">=", |
| 65 | + Predicate.createLit("5", "int") |
| 66 | + ); |
| 67 | + context.newRefinementToVariableInContext("x", newPred); |
| 68 | + assertEquals(newPred.toString(), context.getVariableRefinements("x").toString()); |
| 69 | + |
| 70 | + // Exit scope |
| 71 | + context.exitContext(); |
| 72 | + assertTrue(context.hasVariable("x"), "Global variable still exists"); |
| 73 | + assertFalse(context.hasVariable("y"), "Local variable removed"); |
| 74 | + assertEquals(1, context.getAllVariables().size(), "Only global variable remains"); |
| 75 | + } |
| 76 | + |
| 77 | + @Test |
| 78 | + void testFunctionRegistrationAndRetrieval() { |
| 79 | + // Scenario: Register functions with refinements and retrieve them |
| 80 | + CtTypeReference<Integer> intType = factory.Type().integerPrimitiveType(); |
| 81 | + CtTypeReference<String> stringType = factory.Type().stringType(); |
| 82 | + |
| 83 | + // Create function with arguments and return refinement |
| 84 | + RefinedFunction func = new RefinedFunction("calculate", "MathUtils", intType, new Predicate()); |
| 85 | + func.addArgRefinements("x", intType, Predicate.createOperation( |
| 86 | + Predicate.createVar("x"), ">", Predicate.createLit("0", "int") |
| 87 | + )); |
| 88 | + func.addArgRefinements("y", intType, Predicate.createOperation( |
| 89 | + Predicate.createVar("y"), ">", Predicate.createLit("0", "int") |
| 90 | + )); |
| 91 | + func.setRefReturn(Predicate.createOperation( |
| 92 | + Predicate.createVar("result"), ">", Predicate.createLit("0", "int") |
| 93 | + )); |
| 94 | + |
| 95 | + context.addFunctionToContext(func); |
| 96 | + |
| 97 | + // Retrieve and verify |
| 98 | + RefinedFunction retrieved = context.getFunction("calculate", "MathUtils", 2); |
| 99 | + assertNotNull(retrieved, "Function should be found by name, class, and arity"); |
| 100 | + assertEquals(2, retrieved.getArguments().size(), "Should have 2 arguments"); |
| 101 | + |
| 102 | + // Add another function with same name but different arity |
| 103 | + RefinedFunction func2 = new RefinedFunction("calculate", "MathUtils", intType, new Predicate()); |
| 104 | + func2.addArgRefinements("x", intType, new Predicate()); |
| 105 | + context.addFunctionToContext(func2); |
| 106 | + |
| 107 | + List<RefinedFunction> overloads = context.getAllMethodsWithNameSize("calculate", 1); |
| 108 | + assertEquals(1, overloads.size(), "Should find function with arity 1"); |
| 109 | + |
| 110 | + overloads = context.getAllMethodsWithNameSize("calculate", 2); |
| 111 | + assertEquals(1, overloads.size(), "Should find function with arity 2"); |
| 112 | + } |
| 113 | + |
| 114 | + @Test |
| 115 | + void testGhostFunctionsAndStates() { |
| 116 | + // Scenario: Register ghost functions and states, verify hierarchy |
| 117 | + GhostFunction ghost1 = new GhostFunction("ghostPredicate", List.of(), factory.Type().booleanPrimitiveType(), "TestClass"); |
| 118 | + context.addGhostFunction(ghost1); |
| 119 | + assertTrue(context.hasGhost("TestClass.ghostPredicate"), "Should find ghost by qualified name"); |
| 120 | + assertTrue(context.hasGhost("ghostPredicate"), "Should find ghost by simple name"); |
| 121 | + |
| 122 | + // Add ghost class with states |
| 123 | + context.addGhostClass("StateManager"); |
| 124 | + GhostState state1 = new GhostState("StateManager", "initialized", null, null); |
| 125 | + state1.setRefinement(Predicate.createLit("true", "boolean")); |
| 126 | + context.addToGhostClass("StateManager", state1); |
| 127 | + |
| 128 | + GhostState state2 = new GhostState("StateManager", "ready", null, null); |
| 129 | + state2.setRefinement(Predicate.createVar("initialized")); |
| 130 | + context.addToGhostClass("StateManager", state2); |
| 131 | + |
| 132 | + List<GhostState> states = context.getGhostState("StateManager"); |
| 133 | + assertEquals(2, states.size(), "Should have 2 states"); |
| 134 | + |
| 135 | + // Verify state refinements |
| 136 | + assertTrue(states.get(0).getRefinement().toString().contains("true")); |
| 137 | + assertTrue(states.get(1).getRefinement().toString().contains("initialized")); |
| 138 | + } |
| 139 | + |
| 140 | + @Test |
| 141 | + void testAliasManagement() { |
| 142 | + // Scenario: Register and use aliases for complex predicates |
| 143 | + Predicate complexPred = Predicate.createOperation( |
| 144 | + Predicate.createOperation( |
| 145 | + Predicate.createVar("x"), |
| 146 | + "*", |
| 147 | + Predicate.createVar("x") |
| 148 | + ), |
| 149 | + "+", |
| 150 | + Predicate.createOperation( |
| 151 | + Predicate.createVar("y"), |
| 152 | + "*", |
| 153 | + Predicate.createVar("y") |
| 154 | + ) |
| 155 | + ); |
| 156 | + |
| 157 | + AliasWrapper alias = new AliasWrapper("distanceSquared", complexPred, |
| 158 | + List.of("x", "y"), List.of("int", "int")); |
| 159 | + context.addAlias(alias); |
| 160 | + |
| 161 | + List<AliasWrapper> aliases = context.getAlias(); |
| 162 | + assertEquals(1, aliases.size(), "Should have 1 alias"); |
| 163 | + assertEquals("distanceSquared", aliases.get(0).getName()); |
| 164 | + |
| 165 | + // Create new variables for substitution |
| 166 | + List<String> newVars = alias.getNewVariables(context); |
| 167 | + assertEquals(2, newVars.size(), "Should generate 2 new variable names"); |
| 168 | + assertTrue(newVars.get(0).contains("alias_x"), "Generated name should contain original"); |
| 169 | + } |
| 170 | + |
| 171 | + @Test |
| 172 | + void testVariableInstanceTracking() { |
| 173 | + // Scenario: Track variable instances through assignments and control flow |
| 174 | + CtTypeReference<Integer> intType = factory.Type().integerPrimitiveType(); |
| 175 | + Predicate initialRefinement = Predicate.createVar("x"); |
| 176 | + |
| 177 | + Variable var = new Variable("x", intType, initialRefinement); |
| 178 | + context.addVarToContext(var); |
| 179 | + |
| 180 | + // Simulate assignment: x = 5 |
| 181 | + VariableInstance instance1 = new VariableInstance("x_1", intType, |
| 182 | + Predicate.createEquals(Predicate.createVar("x_1"), Predicate.createLit("5", "int"))); |
| 183 | + var.addInstance(instance1); |
| 184 | + context.addSpecificVariable(instance1); |
| 185 | + context.addRefinementInstanceToVariable("x", "x_1"); |
| 186 | + |
| 187 | + assertTrue(var.getLastInstance().isPresent(), "Should have instance"); |
| 188 | + assertEquals("x_1", var.getLastInstance().get().getName()); |
| 189 | + |
| 190 | + // Simulate second assignment: x = x + 1 |
| 191 | + VariableInstance instance2 = new VariableInstance("x_2", intType, |
| 192 | + Predicate.createEquals(Predicate.createVar("x_2"), |
| 193 | + Predicate.createOperation(Predicate.createVar("x_1"), "+", Predicate.createLit("1", "int")))); |
| 194 | + var.addInstance(instance2); |
| 195 | + context.addSpecificVariable(instance2); |
| 196 | + context.addRefinementInstanceToVariable("x", "x_2"); |
| 197 | + |
| 198 | + assertEquals("x_2", var.getLastInstance().get().getName(), "Latest instance should be x_2"); |
| 199 | + } |
| 200 | + |
| 201 | + @Test |
| 202 | + void testIfBranchCombination() { |
| 203 | + // Scenario: Track variables through if-then-else branches |
| 204 | + CtTypeReference<Integer> intType = factory.Type().integerPrimitiveType(); |
| 205 | + |
| 206 | + Variable var = new Variable("x", intType, Predicate.createVar("x")); |
| 207 | + context.addVarToContext(var); |
| 208 | + |
| 209 | + // Before if |
| 210 | + context.variablesSetBeforeIf(); |
| 211 | + |
| 212 | + // Then branch: x = 10 |
| 213 | + VariableInstance thenInstance = new VariableInstance("x_then", intType, |
| 214 | + Predicate.createEquals(Predicate.createVar("x_then"), Predicate.createLit("10", "int"))); |
| 215 | + var.addInstance(thenInstance); |
| 216 | + context.variablesSetThenIf(); |
| 217 | + |
| 218 | + // Else branch: x = 20 |
| 219 | + VariableInstance elseInstance = new VariableInstance("x_else", intType, |
| 220 | + Predicate.createEquals(Predicate.createVar("x_else"), Predicate.createLit("20", "int"))); |
| 221 | + var.addInstance(elseInstance); |
| 222 | + context.variablesSetElseIf(); |
| 223 | + |
| 224 | + // Combine branches |
| 225 | + Predicate condition = Predicate.createVar("condition"); |
| 226 | + context.variablesNewIfCombination(); |
| 227 | + context.variablesCombineFromIf(condition); |
| 228 | + context.variablesFinishIfCombination(); |
| 229 | + |
| 230 | + // Should create combined instance with ITE predicate |
| 231 | + assertTrue(context.hasVariable("x"), "Variable x should still exist"); |
| 232 | + } |
| 233 | + |
| 234 | + @Test |
| 235 | + void testComplexScenarioWithMultipleComponents() { |
| 236 | + // Realistic scenario: Function with refinements, variables, and ghosts |
| 237 | + CtTypeReference<Integer> intType = factory.Type().integerPrimitiveType(); |
| 238 | + |
| 239 | + // Register a ghost function for validation |
| 240 | + GhostFunction validationGhost = new GhostFunction("isValid", |
| 241 | + List.of(intType), factory.Type().booleanPrimitiveType(), "Validator"); |
| 242 | + context.addGhostFunction(validationGhost); |
| 243 | + |
| 244 | + // Create function with precondition using ghost |
| 245 | + RefinedFunction processFunc = new RefinedFunction("process", "Processor", intType, new Predicate()); |
| 246 | + Predicate precondition = Predicate.createInvocation("Validator.isValid", Predicate.createVar("input")); |
| 247 | + processFunc.addArgRefinements("input", intType, precondition); |
| 248 | + |
| 249 | + Predicate postcondition = Predicate.createOperation( |
| 250 | + Predicate.createVar("result"), ">=", Predicate.createVar("input") |
| 251 | + ); |
| 252 | + processFunc.setRefReturn(postcondition); |
| 253 | + |
| 254 | + context.addFunctionToContext(processFunc); |
| 255 | + |
| 256 | + // Add variables with refinements |
| 257 | + context.addVarToContext("input", intType, precondition, null); |
| 258 | + context.addVarToContext("result", intType, postcondition, null); |
| 259 | + |
| 260 | + // Verify everything is integrated |
| 261 | + assertTrue(context.hasGhost("Validator.isValid"), "Ghost function registered"); |
| 262 | + assertNotNull(context.getFunction("process", "Processor"), "Function registered"); |
| 263 | + assertTrue(context.hasVariable("input"), "Input variable exists"); |
| 264 | + assertTrue(context.hasVariable("result"), "Result variable exists"); |
| 265 | + |
| 266 | + // Get all variables with supertypes (for subtyping checks) |
| 267 | + List<RefinedVariable> varsWithSupertypes = context.getAllVariablesWithSupertypes(); |
| 268 | + assertNotNull(varsWithSupertypes, "Should return variables list"); |
| 269 | + } |
| 270 | + |
| 271 | + @Test |
| 272 | + void testGlobalVariableManagement() { |
| 273 | + // Scenario: Global variables persist across context resets |
| 274 | + CtTypeReference<Integer> intType = factory.Type().integerPrimitiveType(); |
| 275 | + |
| 276 | + context.addGlobalVariableToContext("GLOBAL_CONST", intType, |
| 277 | + Predicate.createEquals(Predicate.createVar("GLOBAL_CONST"), Predicate.createLit("42", "int"))); |
| 278 | + |
| 279 | + assertTrue(context.hasVariable("GLOBAL_CONST"), "Global variable should exist"); |
| 280 | + |
| 281 | + // Add local variable |
| 282 | + context.addVarToContext("local", intType, new Predicate(), null); |
| 283 | + assertEquals(2, context.getAllVariables().size(), "Should have both global and local"); |
| 284 | + |
| 285 | + // Reinitialize context (not all) |
| 286 | + context.reinitializeContext(); |
| 287 | + |
| 288 | + assertTrue(context.hasVariable("GLOBAL_CONST"), "Global variable persists"); |
| 289 | + assertFalse(context.hasVariable("local"), "Local variable removed"); |
| 290 | + } |
| 291 | + |
| 292 | + @Test |
| 293 | + void testCounterIncrement() { |
| 294 | + // Verify counter is used for unique variable generation |
| 295 | + int counter1 = context.getCounter(); |
| 296 | + int counter2 = context.getCounter(); |
| 297 | + int counter3 = context.getCounter(); |
| 298 | + |
| 299 | + assertTrue(counter2 > counter1, "Counter should increment"); |
| 300 | + assertTrue(counter3 > counter2, "Counter should continue incrementing"); |
| 301 | + assertEquals(1, counter2 - counter1, "Should increment by 1"); |
| 302 | + } |
| 303 | +} |
0 commit comments