diff --git a/build.gradle b/build.gradle index 5d7dda9..6352ebd 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,8 @@ dependencies { // Test dependencies testImplementation 'org.junit.jupiter:junit-jupiter:5.11.4' + testImplementation 'org.mockito:mockito-core:5.14.2' + testImplementation 'org.mockito:mockito-junit-jupiter:5.14.2' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } diff --git a/src/test/java/catdata/apg/ApgInstanceTest.java b/src/test/java/catdata/apg/ApgInstanceTest.java new file mode 100644 index 0000000..41c5507 --- /dev/null +++ b/src/test/java/catdata/apg/ApgInstanceTest.java @@ -0,0 +1,250 @@ +package catdata.apg; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import catdata.Pair; +import catdata.cql.Kind; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ApgInstanceTest { + + private static final Map, Function>> INT_TYS = + Map.of("Int", new Pair<>(Integer.class, (Function) Integer::parseInt)); + + private static ApgTypeside emptyTypeside() { + return new ApgTypeside(Collections.emptyMap(), Collections.emptyMap()); + } + + private static ApgTypeside intTypeside() { + return new ApgTypeside(new HashMap<>(INT_TYS), Collections.emptyMap()); + } + + private static ApgSchema emptySchema() { + return new ApgSchema<>(emptyTypeside(), new HashMap<>()); + } + + private static ApgSchema personSchema() { + return new ApgSchema<>(intTypeside(), new HashMap<>(Map.of("person", ApgTy.ApgTyB("Int")))); + } + + @Nested + class ConstructorTest { + + @Test + void emptyInstanceCreatesSuccessfully() { + assertDoesNotThrow(() -> new ApgInstance<>(emptySchema(), Collections.emptyMap())); + } + + @Test + void instanceWithElementCreatesSuccessfully() { + ApgSchema schema = personSchema(); + Map>> es = + Map.of("e1", new Pair<>("person", ApgTerm.ApgTermV(42, "Int"))); + assertDoesNotThrow(() -> new ApgInstance<>(schema, es)); + } + + @Test + void constructorValidates() { + ApgSchema schema = emptySchema(); + Map>> es = + Map.of("e1", new Pair<>("missing", ApgTerm.ApgTermV(42, "Int"))); + assertThrows(RuntimeException.class, () -> new ApgInstance<>(schema, es)); + } + } + + @Nested + class KindAndSizeTest { + + @Test + void kindReturnsApgInstance() { + ApgInstance inst = new ApgInstance<>(emptySchema(), Collections.emptyMap()); + assertEquals(Kind.APG_instance, inst.kind()); + } + + @Test + void sizeReturnsSumOfEsAndLs() { + ApgSchema schema = personSchema(); + Map>> es = + Map.of("e1", new Pair<>("person", ApgTerm.ApgTermV(42, "Int"))); + ApgInstance inst = new ApgInstance<>(schema, es); + assertEquals(1 + 1, inst.size()); + } + } + + @Nested + class ValidateTest { + + @Test + void throwsForUnknownLabel() { + ApgSchema schema = personSchema(); + Map>> es = + Map.of("e1", new Pair<>("unknown", ApgTerm.ApgTermV(42, "Int"))); + assertThrows(RuntimeException.class, () -> new ApgInstance<>(schema, es)); + } + + @Test + void throwsForTypeMismatch() { + ApgSchema schema = personSchema(); + Map>> es = + Map.of("e1", new Pair<>("person", ApgTerm.ApgTermV("notAnInt", "Int"))); + assertThrows(RuntimeException.class, () -> new ApgInstance<>(schema, es)); + } + } + + @Nested + class TypeCheckTest { + + @Test + void typeChecksValueAgainstBaseType() { + ApgSchema schema = personSchema(); + ApgInstance inst = + new ApgInstance<>(schema, Map.of("e1", new Pair<>("person", ApgTerm.ApgTermV(42, "Int")))); + assertDoesNotThrow(() -> inst.type(ApgTerm.ApgTermV(99, "Int"), ApgTy.ApgTyB("Int"))); + } + + @Test + void typeChecksElementReference() { + ApgSchema schema = personSchema(); + Map>> es = + Map.of("e1", new Pair<>("person", ApgTerm.ApgTermV(42, "Int"))); + ApgInstance inst = new ApgInstance<>(schema, es); + assertDoesNotThrow( + () -> inst.type(ApgTerm.ApgTermE("e1"), ApgTy.ApgTyL("person"))); + } + + @Test + void typeChecksTupleAgainstProductType() { + ApgSchema schema = + new ApgSchema<>( + intTypeside(), + new HashMap<>( + Map.of("rec", ApgTy.ApgTyP(true, Map.of("age", ApgTy.ApgTyB("Int")))))); + Map>> es = + Map.of( + "e1", + new Pair<>( + "rec", + ApgTerm.ApgTermTuple(Map.of("age", ApgTerm.ApgTermV(30, "Int"))))); + ApgInstance inst = new ApgInstance<>(schema, es); + ApgTerm tuple = ApgTerm.ApgTermTuple(Map.of("age", ApgTerm.ApgTermV(25, "Int"))); + assertDoesNotThrow( + () -> inst.type(tuple, ApgTy.ApgTyP(true, Map.of("age", ApgTy.ApgTyB("Int"))))); + } + + @Test + void typeChecksInjectionAgainstSumType() { + ApgTy sumTy = ApgTy.ApgTyP(false, Map.of("left", ApgTy.ApgTyB("Int"))); + ApgSchema schema = + new ApgSchema<>(intTypeside(), new HashMap<>(Map.of("choice", sumTy))); + ApgTerm inj = + ApgTerm.ApgTermInj("left", ApgTerm.ApgTermV(1, "Int"), ApgTy.ApgTyB("Int")); + Map>> es = + Map.of("e1", new Pair<>("choice", inj)); + ApgInstance inst = new ApgInstance<>(schema, es); + ApgTerm inj2 = + ApgTerm.ApgTermInj("left", ApgTerm.ApgTermV(2, "Int"), ApgTy.ApgTyB("Int")); + assertDoesNotThrow(() -> inst.type(inj2, sumTy)); + } + + @Test + void throwsForVariableTerm() { + ApgSchema schema = personSchema(); + ApgInstance inst = new ApgInstance<>(schema, Collections.emptyMap()); + ApgTerm varTerm = ApgTerm.ApgTermVar("x"); + assertThrows( + RuntimeException.class, + () -> inst.type(varTerm, ApgTy.ApgTyB("Int"))); + } + + @Test + void throwsForProjectionTerm() { + ApgSchema schema = personSchema(); + ApgInstance inst = new ApgInstance<>(schema, Collections.emptyMap()); + ApgTerm projTerm = + ApgTerm.ApgTermProj("f", ApgTerm.ApgTermTuple(Collections.emptyMap())); + assertThrows( + RuntimeException.class, + () -> inst.type(projTerm, ApgTy.ApgTyB("Int"))); + } + } + + @Nested + class ElemsForTest { + + @Test + void throwsForBaseType() { + ApgSchema schema = personSchema(); + ApgInstance inst = new ApgInstance<>(schema, Collections.emptyMap()); + assertThrows(RuntimeException.class, () -> inst.elemsFor(ApgTy.ApgTyB("Int"))); + } + + @Test + void returnsElementsForLabelType() { + ApgSchema schema = personSchema(); + Map>> es = new HashMap<>(); + es.put("e1", new Pair<>("person", ApgTerm.ApgTermV(42, "Int"))); + es.put("e2", new Pair<>("person", ApgTerm.ApgTermV(99, "Int"))); + ApgInstance inst = new ApgInstance<>(schema, es); + List> result = inst.elemsFor(ApgTy.ApgTyL("person")); + assertEquals(2, result.size()); + } + + @Test + void returnsEmptyForLabelWithNoElements() { + ApgSchema schema = personSchema(); + ApgInstance inst = new ApgInstance<>(schema, Collections.emptyMap()); + List> result = inst.elemsFor(ApgTy.ApgTyL("person")); + assertTrue(result.isEmpty()); + } + } + + @Nested + class EqualsTest { + + @Test + void equalsSame() { + ApgSchema schema = personSchema(); + Map>> es = + Map.of("e1", new Pair<>("person", ApgTerm.ApgTermV(42, "Int"))); + ApgInstance i1 = new ApgInstance<>(schema, es); + ApgInstance i2 = new ApgInstance<>(schema, new HashMap<>(es)); + assertEquals(i1, i2); + } + + @Test + void notEqualsDifferent() { + ApgSchema schema = personSchema(); + ApgInstance i1 = + new ApgInstance<>(schema, Map.of("e1", new Pair<>("person", ApgTerm.ApgTermV(42, "Int")))); + ApgInstance i2 = + new ApgInstance<>(schema, Map.of("e1", new Pair<>("person", ApgTerm.ApgTermV(99, "Int")))); + assertNotEquals(i1, i2); + } + + @Test + void equalsReflexive() { + ApgInstance inst = new ApgInstance<>(emptySchema(), Collections.emptyMap()); + assertEquals(inst, inst); + } + + @Test + void hashCodeConsistent() { + ApgSchema schema = personSchema(); + Map>> es = + Map.of("e1", new Pair<>("person", ApgTerm.ApgTermV(42, "Int"))); + ApgInstance i1 = new ApgInstance<>(schema, es); + ApgInstance i2 = new ApgInstance<>(schema, new HashMap<>(es)); + assertEquals(i1.hashCode(), i2.hashCode()); + } + } +} diff --git a/src/test/java/catdata/apg/ApgOpsTest.java b/src/test/java/catdata/apg/ApgOpsTest.java new file mode 100644 index 0000000..6ab990e --- /dev/null +++ b/src/test/java/catdata/apg/ApgOpsTest.java @@ -0,0 +1,300 @@ +package catdata.apg; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import catdata.Chc; +import catdata.Pair; +import catdata.Unit; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ApgOpsTest { + + private ApgTypeside emptyTs; + private ApgTypeside intTs; + + @BeforeEach + void setUp() { + emptyTs = new ApgTypeside(Collections.emptyMap(), Collections.emptyMap()); + + Map, Function>> tys = new HashMap<>(); + tys.put("Int", new Pair<>(Integer.class, (Function) Integer::parseInt)); + intTs = new ApgTypeside(tys, Collections.emptyMap()); + } + + private ApgSchema singleLabelSchema(String label) { + Map> m = new HashMap<>(); + m.put(label, ApgTy.ApgTyB("Int")); + return new ApgSchema<>(intTs, m); + } + + private ApgInstance emptyInstance(ApgSchema schema) { + return new ApgInstance<>(schema, Collections.emptyMap()); + } + + private ApgInstance singleElementInstance(String label, String elemKey, + int value) { + ApgSchema schema = singleLabelSchema(label); + Map>> es = new HashMap<>(); + es.put(elemKey, new Pair<>(label, ApgTerm.ApgTermV(value, "Int"))); + return new ApgInstance<>(schema, es); + } + + @Nested + class IdTest { + + @Test + void idReturnsIdentityTransform() { + ApgInstance inst = singleElementInstance("person", "e1", 42); + ApgTransform id = ApgOps.id(inst); + assertNotNull(id); + assertEquals(inst, id.src); + assertEquals(inst, id.dst); + } + + @Test + void idMapsLabelsToSelf() { + ApgInstance inst = singleElementInstance("person", "e1", 42); + ApgTransform id = ApgOps.id(inst); + assertEquals("person", id.lMap.get("person")); + } + + @Test + void idMapsElementsToSelf() { + ApgInstance inst = singleElementInstance("person", "e1", 42); + ApgTransform id = ApgOps.id(inst); + assertEquals("e1", id.eMap.get("e1")); + } + } + + @Nested + class ComposeTest { + + @Test + void composeTwoIdentities() { + ApgInstance inst = singleElementInstance("person", "e1", 42); + ApgTransform id = ApgOps.id(inst); + ApgTransform composed = ApgOps.compose(id, id); + assertEquals("person", composed.lMap.get("person")); + assertEquals("e1", composed.eMap.get("e1")); + } + + @Test + void composeProducesCorrectMaps() { + ApgSchema schema = singleLabelSchema("person"); + Map>> es1 = new HashMap<>(); + es1.put("a", new Pair<>("person", ApgTerm.ApgTermV(1, "Int"))); + ApgInstance inst1 = new ApgInstance<>(schema, es1); + + Map>> es2 = new HashMap<>(); + es2.put("b", new Pair<>("person", ApgTerm.ApgTermV(2, "Int"))); + ApgInstance inst2 = new ApgInstance<>(schema, es2); + + Map>> es3 = new HashMap<>(); + es3.put("c", new Pair<>("person", ApgTerm.ApgTermV(3, "Int"))); + ApgInstance inst3 = new ApgInstance<>(schema, es3); + + Map lMap12 = new HashMap<>(); + lMap12.put("person", "person"); + Map eMap12 = new HashMap<>(); + eMap12.put("a", "b"); + ApgTransform t12 = + new ApgTransform<>(inst1, inst2, lMap12, eMap12); + + Map lMap23 = new HashMap<>(); + lMap23.put("person", "person"); + Map eMap23 = new HashMap<>(); + eMap23.put("b", "c"); + ApgTransform t23 = + new ApgTransform<>(inst2, inst3, lMap23, eMap23); + + ApgTransform composed = ApgOps.compose(t12, t23); + assertEquals(inst1, composed.src); + assertEquals(inst3, composed.dst); + assertEquals("person", composed.lMap.get("person")); + assertEquals("c", composed.eMap.get("a")); + } + } + + @Nested + class InitialTest { + + @Test + void initialSchemaReturnsEmptyInstance() { + ApgSchema schema = singleLabelSchema("person"); + ApgInstance init = ApgOps.initial(schema); + assertNotNull(init); + assertTrue(init.Es.isEmpty()); + } + + @Test + void initialTransformFromEmptyToInstance() { + ApgInstance inst = singleElementInstance("person", "e1", 42); + ApgTransform t = ApgOps.initial(inst); + assertNotNull(t); + assertTrue(t.src.Es.isEmpty()); + assertEquals(inst, t.dst); + } + } + + @Nested + class TerminalTest { + + @Test + void terminalSchemaHasSingleLabel() { + ApgSchema ts = ApgOps.terminalSchema(emptyTs); + assertEquals(1, ts.size()); + assertTrue(ts.containsKey(Unit.unit)); + } + + @Test + void terminalInstanceHasSingleElement() { + ApgInstance term = ApgOps.terminal(emptyTs); + assertEquals(1, term.Es.size()); + assertTrue(term.Es.containsKey(Unit.unit)); + } + + @Test + void terminalTransformMapsAllToUnit() { + ApgInstance inst = singleElementInstance("person", "e1", 42); + ApgTransform t = ApgOps.terminal(inst); + assertEquals(Unit.unit, t.lMap.get("person")); + assertEquals(Unit.unit, t.eMap.get("e1")); + } + } + + @Nested + class CoproductTest { + + @Test + void coproductOfEmptyInstances() { + ApgSchema schema = singleLabelSchema("person"); + ApgInstance empty1 = emptyInstance(schema); + ApgInstance empty2 = emptyInstance(schema); + ApgInstance, Chc> coprod = + ApgOps.coproduct(empty1, empty2); + assertNotNull(coprod); + assertTrue(coprod.Es.isEmpty()); + } + + @Test + void coproductCombinesElements() { + ApgInstance inst1 = singleElementInstance("person", "e1", 1); + ApgInstance inst2 = singleElementInstance("person", "e2", 2); + ApgInstance, Chc> coprod = + ApgOps.coproduct(inst1, inst2); + assertEquals(2, coprod.Es.size()); + assertTrue(coprod.Es.containsKey(Chc.inLeft("e1"))); + assertTrue(coprod.Es.containsKey(Chc.inRight("e2"))); + } + + @Test + void inlMapsToLeft() { + ApgInstance inst1 = singleElementInstance("person", "e1", 1); + ApgInstance inst2 = singleElementInstance("person", "e2", 2); + ApgTransform, Chc> inl = + ApgOps.inl(inst1, inst2); + assertEquals(Chc.inLeft("person"), inl.lMap.get("person")); + assertEquals(Chc.inLeft("e1"), inl.eMap.get("e1")); + } + + @Test + void inrMapsToRight() { + ApgInstance inst1 = singleElementInstance("person", "e1", 1); + ApgInstance inst2 = singleElementInstance("person", "e2", 2); + ApgTransform, Chc> inr = + ApgOps.inr(inst1, inst2); + assertEquals(Chc.inRight("person"), inr.lMap.get("person")); + assertEquals(Chc.inRight("e2"), inr.eMap.get("e2")); + } + } + + @Nested + class ProductTest { + + @Test + void productOfEmptyInstances() { + ApgSchema schema = singleLabelSchema("person"); + ApgInstance empty1 = emptyInstance(schema); + ApgInstance empty2 = emptyInstance(schema); + ApgInstance, Pair> prod = + ApgOps.product(empty1, empty2); + assertNotNull(prod); + assertTrue(prod.Es.isEmpty()); + } + + @Test + void productCombinesElements() { + ApgInstance inst1 = singleElementInstance("person", "e1", 1); + ApgInstance inst2 = singleElementInstance("person", "e2", 2); + ApgInstance, Pair> prod = + ApgOps.product(inst1, inst2); + assertEquals(1, prod.Es.size()); + assertTrue(prod.Es.containsKey(new Pair<>("e1", "e2"))); + } + + @Test + void fstProjectsToFirst() { + ApgInstance inst1 = singleElementInstance("person", "e1", 1); + ApgInstance inst2 = singleElementInstance("person", "e2", 2); + ApgTransform, Pair, String, String> fst = + ApgOps.fst(inst1, inst2); + assertEquals("person", fst.lMap.get(new Pair<>("person", "person"))); + assertEquals("e1", fst.eMap.get(new Pair<>("e1", "e2"))); + } + + @Test + void sndProjectsToSecond() { + ApgInstance inst1 = singleElementInstance("person", "e1", 1); + ApgInstance inst2 = singleElementInstance("person", "e2", 2); + ApgTransform, Pair, String, String> snd = + ApgOps.snd(inst1, inst2); + assertEquals("person", snd.lMap.get(new Pair<>("person", "person"))); + assertEquals("e2", snd.eMap.get(new Pair<>("e1", "e2"))); + } + } + + @Nested + class SchemaOpsTest { + + @Test + void initialSchemaIsEmpty() { + ApgSchema schema = ApgOps.initialSchema(intTs); + assertTrue(schema.isEmpty()); + assertEquals(intTs, schema.typeside); + } + + @Test + void terminalSchemaHasOneLabel() { + ApgSchema schema = ApgOps.terminalSchema(emptyTs); + assertEquals(1, schema.size()); + assertTrue(schema.containsKey(Unit.unit)); + } + + @Test + void coproductSchemaUnifiesLabels() { + ApgSchema s1 = singleLabelSchema("a"); + ApgSchema s2 = singleLabelSchema("b"); + ApgSchema> coprod = ApgOps.coproductSchema(s1, s2); + assertEquals(2, coprod.size()); + assertTrue(coprod.containsKey(Chc.inLeft("a"))); + assertTrue(coprod.containsKey(Chc.inRight("b"))); + } + + @Test + void productSchemaCrossesLabels() { + ApgSchema s1 = singleLabelSchema("a"); + ApgSchema s2 = singleLabelSchema("b"); + ApgSchema> prod = ApgOps.productSchema(s1, s2); + assertEquals(1, prod.size()); + assertTrue(prod.containsKey(new Pair<>("a", "b"))); + } + } +} diff --git a/src/test/java/catdata/apg/ApgPreTermTest.java b/src/test/java/catdata/apg/ApgPreTermTest.java new file mode 100644 index 0000000..60ba564 --- /dev/null +++ b/src/test/java/catdata/apg/ApgPreTermTest.java @@ -0,0 +1,192 @@ +package catdata.apg; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import catdata.Pair; +import java.util.List; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ApgPreTermTest { + + @Nested + class StrFactory { + + @Test + void strFactorySetsString() { + ApgPreTerm t = ApgPreTerm.ApgPreTermStr("hello"); + assertEquals("hello", t.str); + assertNull(t.fields); + assertNull(t.inj); + assertNull(t.ty); + } + + @Test + void strToString() { + ApgPreTerm t = ApgPreTerm.ApgPreTermStr("hello"); + assertEquals("hello", t.toString()); + } + } + + @Nested + class TupleFactory { + + @Test + void tupleFactorySetsFields() { + List> fields = List.of( + new Pair<>("a", ApgPreTerm.ApgPreTermStr("x")), + new Pair<>("b", ApgPreTerm.ApgPreTermStr("y"))); + ApgPreTerm t = ApgPreTerm.ApgPreTermTuple(fields); + assertNotNull(t.fields); + assertEquals(2, t.fields.size()); + assertNull(t.str); + } + + @Test + void tupleToStringContainsComma() { + List> fields = List.of( + new Pair<>("a", ApgPreTerm.ApgPreTermStr("x")), + new Pair<>("b", ApgPreTerm.ApgPreTermStr("y"))); + ApgPreTerm t = ApgPreTerm.ApgPreTermTuple(fields); + assertTrue(t.toString().contains(",")); + } + } + + @Nested + class InjFactory { + + @Test + void injFactorySetsFields() { + ApgPreTerm inner = ApgPreTerm.ApgPreTermStr("val"); + ApgPreTerm t = ApgPreTerm.ApgPreTermInj("tag", inner); + assertEquals("tag", t.inj); + assertEquals(inner, t.arg); + } + + @Test + void injToStringContainsAngleBrackets() { + ApgPreTerm t = ApgPreTerm.ApgPreTermInj("tag", ApgPreTerm.ApgPreTermStr("v")); + String s = t.toString(); + assertTrue(s.contains("<")); + assertTrue(s.contains(">")); + } + } + + @Nested + class BaseFactory { + + @Test + void baseFactorySetsStringAndType() { + ApgTy ty = ApgTy.ApgTyB("Int"); + ApgPreTerm t = ApgPreTerm.ApgPreTermBase("42", ty); + assertEquals("42", t.str); + assertEquals(ty, t.ty); + } + + @Test + void baseToStringContainsAtSign() { + ApgTy ty = ApgTy.ApgTyB("Int"); + ApgPreTerm t = ApgPreTerm.ApgPreTermBase("42", ty); + assertTrue(t.toString().contains("@")); + } + } + + @Nested + class ProjFactory { + + @Test + void projFactorySetsFields() { + ApgPreTerm inner = ApgPreTerm.ApgPreTermStr("x"); + ApgPreTerm t = ApgPreTerm.ApgPreTermProj("name", inner); + assertEquals("name", t.proj); + assertEquals(inner, t.arg); + } + + @Test + void projToStringContainsDot() { + ApgPreTerm t = ApgPreTerm.ApgPreTermProj("name", ApgPreTerm.ApgPreTermStr("x")); + assertTrue(t.toString().contains(".")); + } + } + + @Nested + class DerefFactory { + + @Test + void derefFactorySetsFields() { + ApgPreTerm inner = ApgPreTerm.ApgPreTermStr("x"); + ApgPreTerm t = ApgPreTerm.ApgPreTermDeref("ref", inner); + assertEquals("ref", t.deref); + assertEquals(inner, t.arg); + } + + @Test + void derefToStringContainsBang() { + ApgPreTerm t = ApgPreTerm.ApgPreTermDeref("ref", ApgPreTerm.ApgPreTermStr("x")); + assertTrue(t.toString().contains("!")); + } + } + + @Nested + class AppFactory { + + @Test + void appFactorySetsFields() { + List args = List.of( + ApgPreTerm.ApgPreTermStr("a"), + ApgPreTerm.ApgPreTermStr("b")); + ApgPreTerm t = ApgPreTerm.ApgPreTermApp("fn", args); + assertEquals("fn", t.head); + assertEquals(2, t.args.size()); + } + + @Test + void appToStringContainsParens() { + ApgPreTerm t = ApgPreTerm.ApgPreTermApp("fn", List.of(ApgPreTerm.ApgPreTermStr("a"))); + String s = t.toString(); + assertTrue(s.contains("(")); + assertTrue(s.contains(")")); + } + } + + @Nested + class Equality { + + @Test + void equalsSameStr() { + ApgPreTerm t1 = ApgPreTerm.ApgPreTermStr("hello"); + ApgPreTerm t2 = ApgPreTerm.ApgPreTermStr("hello"); + assertEquals(t1, t2); + } + + @Test + void notEqualsDifferentStr() { + ApgPreTerm t1 = ApgPreTerm.ApgPreTermStr("hello"); + ApgPreTerm t2 = ApgPreTerm.ApgPreTermStr("world"); + assertNotEquals(t1, t2); + } + + @Test + void equalsReflexive() { + ApgPreTerm t = ApgPreTerm.ApgPreTermStr("x"); + assertEquals(t, t); + } + + @Test + void notEqualsNull() { + ApgPreTerm t = ApgPreTerm.ApgPreTermStr("x"); + assertNotEquals(null, t); + } + + @Test + void hashCodeConsistent() { + ApgPreTerm t1 = ApgPreTerm.ApgPreTermStr("hello"); + ApgPreTerm t2 = ApgPreTerm.ApgPreTermStr("hello"); + assertEquals(t1.hashCode(), t2.hashCode()); + } + } +} diff --git a/src/test/java/catdata/apg/ApgSchemaTest.java b/src/test/java/catdata/apg/ApgSchemaTest.java new file mode 100644 index 0000000..6206035 --- /dev/null +++ b/src/test/java/catdata/apg/ApgSchemaTest.java @@ -0,0 +1,170 @@ +package catdata.apg; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import catdata.cql.Kind; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ApgSchemaTest { + + private static ApgTypeside emptyTypeside() { + return new ApgTypeside(Collections.emptyMap(), Collections.emptyMap()); + } + + private static ApgSchema emptySchema() { + return new ApgSchema<>(emptyTypeside(), new HashMap<>()); + } + + private static ApgSchema singleLabelSchema() { + Map> map = new HashMap<>(); + map.put("Person", ApgTy.ApgTyB("Int")); + return new ApgSchema<>(emptyTypeside(), map); + } + + @Nested + class Constructor { + + @Test + void constructorSetsFields() { + ApgTypeside ts = emptyTypeside(); + Map> map = new HashMap<>(); + ApgSchema schema = new ApgSchema<>(ts, map); + assertEquals(ts, schema.typeside); + assertEquals(map, schema.schema); + } + } + + @Nested + class SemanticsMethods { + + @Test + void kindReturnsApgSchema() { + assertEquals(Kind.APG_schema, emptySchema().kind()); + } + + @Test + void sizeMatchesSchemaSize() { + assertEquals(0, emptySchema().size()); + assertEquals(1, singleLabelSchema().size()); + } + } + + @Nested + class MapDelegation { + + @Test + void isEmptyWhenSchemaEmpty() { + assertTrue(emptySchema().isEmpty()); + assertFalse(singleLabelSchema().isEmpty()); + } + + @Test + void containsKeyDelegates() { + ApgSchema schema = singleLabelSchema(); + assertTrue(schema.containsKey("Person")); + assertFalse(schema.containsKey("Animal")); + } + + @Test + void getDelegates() { + ApgSchema schema = singleLabelSchema(); + ApgTy ty = schema.get("Person"); + assertEquals(ApgTy.ApgTyB("Int"), ty); + assertNull(schema.get("Missing")); + } + + @Test + void putDelegates() { + ApgSchema schema = emptySchema(); + schema.put("Name", ApgTy.ApgTyB("String")); + assertEquals(1, schema.size()); + assertTrue(schema.containsKey("Name")); + } + + @Test + void removeDelegates() { + ApgSchema schema = singleLabelSchema(); + schema.remove("Person"); + assertTrue(schema.isEmpty()); + } + + @Test + void keySetDelegates() { + ApgSchema schema = singleLabelSchema(); + assertTrue(schema.keySet().contains("Person")); + assertEquals(1, schema.keySet().size()); + } + } + + @Nested + class Equality { + + @Test + void equalsSame() { + ApgTypeside ts = emptyTypeside(); + Map> map = new HashMap<>(); + map.put("X", ApgTy.ApgTyB("Int")); + ApgSchema s1 = new ApgSchema<>(ts, map); + ApgSchema s2 = new ApgSchema<>(ts, new HashMap<>(map)); + assertEquals(s1, s2); + } + + @Test + void notEqualsDifferentSchema() { + ApgTypeside ts = emptyTypeside(); + Map> map1 = new HashMap<>(); + map1.put("X", ApgTy.ApgTyB("Int")); + Map> map2 = new HashMap<>(); + map2.put("Y", ApgTy.ApgTyB("Int")); + assertNotEquals(new ApgSchema<>(ts, map1), new ApgSchema<>(ts, map2)); + } + + @Test + void notEqualsDifferentTypeside() { + Map> map = new HashMap<>(); + ApgSchema s1 = new ApgSchema<>(emptyTypeside(), map); + ApgSchema s2 = new ApgSchema<>(null, map); + assertNotEquals(s1, s2); + } + + @Test + void equalsReflexive() { + ApgSchema s = singleLabelSchema(); + assertEquals(s, s); + } + + @Test + void notEqualsNull() { + assertNotEquals(null, singleLabelSchema()); + } + + @Test + void hashCodeConsistent() { + ApgTypeside ts = emptyTypeside(); + Map> map = new HashMap<>(); + map.put("X", ApgTy.ApgTyB("Int")); + ApgSchema s1 = new ApgSchema<>(ts, map); + ApgSchema s2 = new ApgSchema<>(ts, new HashMap<>(map)); + assertEquals(s1.hashCode(), s2.hashCode()); + } + } + + @Nested + class ToStringTest { + + @Test + void toStringContainsLabels() { + ApgSchema schema = singleLabelSchema(); + String str = schema.toString(); + assertTrue(str.contains("labels")); + } + } +} diff --git a/src/test/java/catdata/apg/ApgTermTest.java b/src/test/java/catdata/apg/ApgTermTest.java new file mode 100644 index 0000000..89e7c21 --- /dev/null +++ b/src/test/java/catdata/apg/ApgTermTest.java @@ -0,0 +1,273 @@ +package catdata.apg; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashMap; +import java.util.Map; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ApgTermTest { + + @Nested + class ApgTermETest { + + @Test + void factorySetsElement() { + ApgTerm t = ApgTerm.ApgTermE("elem1"); + assertEquals("elem1", t.e); + assertNull(t.value); + assertNull(t.fields); + assertNull(t.inj); + assertNull(t.var); + } + + @Test + void cachedSameReference() { + ApgTerm t1 = ApgTerm.ApgTermE("cached_elem"); + ApgTerm t2 = ApgTerm.ApgTermE("cached_elem"); + assertSame(t1, t2); + } + + @Test + void toStringReturnsElement() { + ApgTerm t = ApgTerm.ApgTermE("hello"); + assertEquals("hello", t.toString()); + } + } + + @Nested + class ApgTermVTest { + + @Test + void factorySetsValueAndPrim() { + ApgTerm t = ApgTerm.ApgTermV(42, "Int"); + assertEquals(42, t.value); + assertEquals("Int", t.prim); + assertNull(t.e); + assertNull(t.fields); + } + + @Test + void toStringReturnsValue() { + ApgTerm t = ApgTerm.ApgTermV(42, "Int"); + assertEquals("42", t.toString()); + } + } + + @Nested + class ApgTermTupleTest { + + @Test + void factorySetsFields() { + Map> fields = new LinkedHashMap<>(); + fields.put("x", ApgTerm.ApgTermV(1, "Int")); + fields.put("y", ApgTerm.ApgTermV(2, "Int")); + ApgTerm t = ApgTerm.ApgTermTuple(fields); + assertNotNull(t.fields); + assertEquals(2, t.fields.size()); + assertNull(t.e); + } + + @Test + void toStringContainsColon() { + Map> fields = new LinkedHashMap<>(); + fields.put("a", ApgTerm.ApgTermV(1, "Int")); + ApgTerm t = ApgTerm.ApgTermTuple(fields); + assertTrue(t.toString().contains(":")); + } + } + + @Nested + class ApgTermInjTest { + + @Test + void factorySetsInjAndArg() { + ApgTy ty = ApgTy.ApgTyB("Bool"); + ApgTerm inner = ApgTerm.ApgTermV(true, "Bool"); + ApgTerm t = ApgTerm.ApgTermInj("left", inner, ty); + assertEquals("left", t.inj); + assertSame(inner, t.a); + assertSame(ty, t.cases_t); + } + + @Test + void toStringContainsAngleBrackets() { + ApgTy ty = ApgTy.ApgTyB("Bool"); + ApgTerm inner = ApgTerm.ApgTermV(true, "Bool"); + ApgTerm t = ApgTerm.ApgTermInj("left", inner, ty); + String s = t.toString(); + assertTrue(s.contains("<")); + assertTrue(s.contains(">")); + } + } + + @Nested + class ApgTermVarTest { + + @Test + void factorySetsVar() { + ApgTerm t = ApgTerm.ApgTermVar("x"); + assertEquals("x", t.var); + assertNull(t.e); + assertNull(t.value); + } + + @Test + void toStringReturnsVar() { + ApgTerm t = ApgTerm.ApgTermVar("myVar"); + assertEquals("myVar", t.toString()); + } + + @Test + void substReplacesMatchingVar() { + ApgTerm varTerm = ApgTerm.ApgTermVar("x"); + ApgTerm replacement = ApgTerm.ApgTermV(99, "Int"); + ApgTerm result = varTerm.subst("x", replacement); + assertSame(replacement, result); + } + + @Test + void substPreservesNonMatchingVar() { + ApgTerm varTerm = ApgTerm.ApgTermVar("y"); + ApgTerm replacement = ApgTerm.ApgTermV(99, "Int"); + ApgTerm result = varTerm.subst("x", replacement); + assertEquals("y", result.var); + } + + @Test + void renameChangesMatchingVar() { + ApgTerm varTerm = ApgTerm.ApgTermVar("x"); + ApgTerm result = varTerm.rename("x", "z"); + assertEquals("z", result.var); + } + + @Test + void renamePreservesNonMatchingVar() { + ApgTerm varTerm = ApgTerm.ApgTermVar("y"); + ApgTerm result = varTerm.rename("x", "z"); + assertSame(varTerm, result); + } + } + + @Nested + class ApgTermProjTest { + + @Test + void factorySetsProjAndArg() { + ApgTerm inner = ApgTerm.ApgTermVar("t"); + ApgTerm t = ApgTerm.ApgTermProj("field1", inner); + assertEquals("field1", t.proj); + assertSame(inner, t.a); + } + + @Test + void toStringContainsDot() { + ApgTerm inner = ApgTerm.ApgTermVar("t"); + ApgTerm t = ApgTerm.ApgTermProj("field1", inner); + assertTrue(t.toString().contains(".")); + } + } + + @Nested + class ApgTermDerefTest { + + @Test + void factorySetsDerefAndArg() { + ApgTerm inner = ApgTerm.ApgTermVar("t"); + ApgTerm t = ApgTerm.ApgTermDeref("label", inner); + assertEquals("label", t.deref); + assertSame(inner, t.a); + } + + @Test + void toStringContainsBang() { + ApgTerm inner = ApgTerm.ApgTermVar("t"); + ApgTerm t = ApgTerm.ApgTermDeref("label", inner); + assertTrue(t.toString().contains("!")); + } + } + + @Nested + class MapTest { + + @Test + void mapTransformsElement() { + ApgTerm t = ApgTerm.ApgTermE(5); + ApgTerm result = t.map(Object::toString); + assertEquals("5", result.e); + } + + @Test + void mapPreservesValue() { + ApgTerm t = ApgTerm.ApgTermV(42, "Int"); + ApgTerm result = t.map(Object::toString); + assertEquals(42, result.value); + assertEquals("Int", result.prim); + } + + @Test + void mapPreservesVar() { + ApgTerm t = ApgTerm.ApgTermVar("x"); + ApgTerm result = t.map(Object::toString); + assertEquals("x", result.var); + } + + @Test + void mapTransformsTupleElements() { + Map> fields = new LinkedHashMap<>(); + fields.put("a", ApgTerm.ApgTermE(1)); + fields.put("b", ApgTerm.ApgTermE(2)); + ApgTerm t = ApgTerm.ApgTermTuple(fields); + ApgTerm result = t.map(Object::toString); + assertNotNull(result.fields); + assertEquals("1", result.fields.get("a").e); + assertEquals("2", result.fields.get("b").e); + } + + @Test + void mapTransformsInjElement() { + ApgTy ty = ApgTy.ApgTyB("Bool"); + ApgTerm inner = ApgTerm.ApgTermE(10); + ApgTerm t = ApgTerm.ApgTermInj("left", inner, ty); + ApgTerm result = t.map(Object::toString); + assertEquals("left", result.inj); + assertEquals("10", result.a.e); + } + } + + @Nested + class Equals2Test { + + @Test + void sameStructureReturnsTrue() { + ApgTerm t1 = ApgTerm.ApgTermE("a"); + ApgTerm t2 = ApgTerm.ApgTermE("a"); + assertTrue(t1.equals2(t2)); + } + + @Test + void differentStructureReturnsFalse() { + ApgTerm t1 = ApgTerm.ApgTermE("a"); + ApgTerm t2 = ApgTerm.ApgTermE("b"); + assertFalse(t1.equals2(t2)); + } + + @Test + void reflexive() { + ApgTerm t = ApgTerm.ApgTermV(42, "Int"); + assertTrue(t.equals2(t)); + } + + @Test + void nullReturnsFalse() { + ApgTerm t = ApgTerm.ApgTermVar("x"); + assertFalse(t.equals2(null)); + } + } +} diff --git a/src/test/java/catdata/apg/ApgTransformTest.java b/src/test/java/catdata/apg/ApgTransformTest.java new file mode 100644 index 0000000..3e2d83a --- /dev/null +++ b/src/test/java/catdata/apg/ApgTransformTest.java @@ -0,0 +1,205 @@ +package catdata.apg; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import catdata.Pair; +import catdata.cql.Kind; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ApgTransformTest { + + private static final Map, Function>> INT_TYS = + Map.of("Int", new Pair<>(Integer.class, (Function) Integer::parseInt)); + + private static ApgTypeside intTypeside() { + return new ApgTypeside(new HashMap<>(INT_TYS), Collections.emptyMap()); + } + + private static ApgSchema personSchema() { + return new ApgSchema<>(intTypeside(), new HashMap<>(Map.of("person", ApgTy.ApgTyB("Int")))); + } + + private static ApgInstance emptyInstance(ApgSchema schema) { + return new ApgInstance<>(schema, Collections.emptyMap()); + } + + private static ApgInstance singletonInstance(ApgSchema schema) { + return new ApgInstance<>( + schema, Map.of("e1", new Pair<>("person", ApgTerm.ApgTermV(42, "Int")))); + } + + @Nested + class ConstructorTest { + + @Test + void emptyTransformCreatesSuccessfully() { + ApgSchema schema = personSchema(); + ApgInstance src = emptyInstance(schema); + ApgInstance dst = emptyInstance(schema); + assertDoesNotThrow( + () -> + new ApgTransform<>( + src, dst, Collections.emptyMap(), Collections.emptyMap())); + } + + @Test + void identityTransformCreatesSuccessfully() { + ApgSchema schema = personSchema(); + ApgInstance inst = singletonInstance(schema); + assertDoesNotThrow( + () -> + new ApgTransform<>( + inst, inst, Map.of("person", "person"), Map.of("e1", "e1"))); + } + + @Test + void throwsForMissingLabelMapping() { + ApgSchema schema = personSchema(); + ApgInstance inst = singletonInstance(schema); + assertThrows( + RuntimeException.class, + () -> + new ApgTransform<>( + inst, inst, Collections.emptyMap(), Map.of("e1", "e1"))); + } + + @Test + void throwsForMissingElementMapping() { + ApgSchema schema = personSchema(); + ApgInstance inst = singletonInstance(schema); + assertThrows( + RuntimeException.class, + () -> + new ApgTransform<>( + inst, inst, Map.of("person", "person"), Collections.emptyMap())); + } + } + + @Nested + class KindAndSizeTest { + + @Test + void kindReturnsApgMorphism() { + ApgSchema schema = personSchema(); + ApgInstance src = emptyInstance(schema); + ApgInstance dst = emptyInstance(schema); + ApgTransform t = + new ApgTransform<>(src, dst, Collections.emptyMap(), Collections.emptyMap()); + assertEquals(Kind.APG_morphism, t.kind()); + } + + @Test + void sizeReturnsSumOfSrcAndDst() { + ApgSchema schema = personSchema(); + ApgInstance inst = singletonInstance(schema); + ApgTransform t = + new ApgTransform<>(inst, inst, Map.of("person", "person"), Map.of("e1", "e1")); + assertEquals(inst.size() + inst.size(), t.size()); + } + } + + @Nested + class ValidateTest { + + @Test + void throwsWhenLabelNotMapped() { + ApgSchema schema = personSchema(); + ApgInstance inst = singletonInstance(schema); + assertThrows( + RuntimeException.class, + () -> + new ApgTransform<>( + inst, inst, Collections.emptyMap(), Map.of("e1", "e1"))); + } + + @Test + void throwsWhenElementNotMapped() { + ApgSchema schema = personSchema(); + ApgInstance inst = singletonInstance(schema); + assertThrows( + RuntimeException.class, + () -> + new ApgTransform<>( + inst, inst, Map.of("person", "person"), Collections.emptyMap())); + } + + @Test + void throwsWhenLabelsInconsistent() { + ApgTypeside ts = intTypeside(); + ApgSchema schema = + new ApgSchema<>( + ts, + new HashMap<>( + Map.of( + "person", ApgTy.ApgTyB("Int"), + "animal", ApgTy.ApgTyB("Int")))); + Map>> srcEs = + Map.of("e1", new Pair<>("person", ApgTerm.ApgTermV(42, "Int"))); + Map>> dstEs = + Map.of("e2", new Pair<>("animal", ApgTerm.ApgTermV(42, "Int"))); + ApgInstance src = new ApgInstance<>(schema, srcEs); + ApgInstance dst = new ApgInstance<>(schema, dstEs); + // Label maps person -> person, but target element e2 has label "animal" + assertThrows( + RuntimeException.class, + () -> + new ApgTransform<>( + src, dst, Map.of("person", "person"), Map.of("e1", "e2"))); + } + } + + @Nested + class EqualsTest { + + @Test + void equalsSame() { + ApgSchema schema = personSchema(); + ApgInstance inst = singletonInstance(schema); + ApgTransform t1 = + new ApgTransform<>(inst, inst, Map.of("person", "person"), Map.of("e1", "e1")); + ApgTransform t2 = + new ApgTransform<>(inst, inst, Map.of("person", "person"), Map.of("e1", "e1")); + assertEquals(t1, t2); + } + + @Test + void notEqualsDifferent() { + ApgSchema schema = personSchema(); + ApgInstance src = emptyInstance(schema); + ApgInstance dst = singletonInstance(schema); + ApgTransform t1 = + new ApgTransform<>(src, src, Collections.emptyMap(), Collections.emptyMap()); + ApgTransform t2 = + new ApgTransform<>(src, dst, Collections.emptyMap(), Collections.emptyMap()); + assertNotEquals(t1, t2); + } + + @Test + void equalsReflexive() { + ApgSchema schema = personSchema(); + ApgInstance inst = emptyInstance(schema); + ApgTransform t = + new ApgTransform<>(inst, inst, Collections.emptyMap(), Collections.emptyMap()); + assertEquals(t, t); + } + + @Test + void hashCodeConsistent() { + ApgSchema schema = personSchema(); + ApgInstance inst = singletonInstance(schema); + ApgTransform t1 = + new ApgTransform<>(inst, inst, Map.of("person", "person"), Map.of("e1", "e1")); + ApgTransform t2 = + new ApgTransform<>(inst, inst, Map.of("person", "person"), Map.of("e1", "e1")); + assertEquals(t1.hashCode(), t2.hashCode()); + } + } +} diff --git a/src/test/java/catdata/apg/ApgTyTest.java b/src/test/java/catdata/apg/ApgTyTest.java new file mode 100644 index 0000000..21b03d4 --- /dev/null +++ b/src/test/java/catdata/apg/ApgTyTest.java @@ -0,0 +1,237 @@ +package catdata.apg; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ApgTyTest { + + @Nested + class FactoryMethods { + + @Test + void apgTyLSetsLabel() { + ApgTy ty = ApgTy.ApgTyL("myLabel"); + assertEquals("myLabel", ty.l); + assertNull(ty.b); + assertNull(ty.m); + } + + @Test + void apgTyBSetsBaseType() { + ApgTy ty = ApgTy.ApgTyB("Int"); + assertEquals("Int", ty.b); + assertNull(ty.l); + assertNull(ty.m); + } + + @Test + void apgTyPSetsProductFields() { + Map> fields = new HashMap<>(); + fields.put("name", ApgTy.ApgTyB("String")); + fields.put("age", ApgTy.ApgTyB("Int")); + ApgTy ty = ApgTy.ApgTyP(true, fields); + assertTrue(ty.all); + assertNotNull(ty.m); + assertEquals(2, ty.m.size()); + assertNull(ty.l); + assertNull(ty.b); + } + + @Test + void apgTyPSetsSumFields() { + Map> fields = new HashMap<>(); + fields.put("left", ApgTy.ApgTyB("Int")); + fields.put("right", ApgTy.ApgTyB("String")); + ApgTy ty = ApgTy.ApgTyP(false, fields); + assertFalse(ty.all); + assertNotNull(ty.m); + assertEquals(2, ty.m.size()); + } + } + + @Nested + class Caching { + + @Test + void cachedInstancesSameReference() { + ApgTy ty1 = ApgTy.ApgTyB("CachedType"); + ApgTy ty2 = ApgTy.ApgTyB("CachedType"); + assertSame(ty1, ty2); + } + } + + @Nested + class EqualsHashCode { + + @Test + void identityEquals() { + ApgTy ty1 = ApgTy.ApgTyB("X"); + ApgTy ty2 = ApgTy.ApgTyB("Y"); + // identity-based equals: same ref returns true + assertEquals(ty1, ty1); + // different structural types are different references + assertNotEquals(ty1, ty2); + // even structurally different objects are not equal + assertFalse(ty1.equals(ty2)); + } + + @Test + void equals2MatchesStructurally() { + // Since the cache returns the same instance for same args, + // we test equals2 with two instances that are the same ref + ApgTy ty = ApgTy.ApgTyB("Str"); + assertTrue(ty.equals2(ty)); + + // Also test with a label type + ApgTy tyL1 = ApgTy.ApgTyL("lab"); + assertTrue(tyL1.equals2(tyL1)); + } + + @Test + void equals2DifferentStructure() { + ApgTy tyB = ApgTy.ApgTyB("Int"); + ApgTy tyL = ApgTy.ApgTyL("label"); + assertFalse(tyB.equals2(tyL)); + assertFalse(tyL.equals2(tyB)); + } + + @Test + void hashCode2ConsistentWithEquals2() { + ApgTy ty1 = ApgTy.ApgTyB("HC"); + ApgTy ty2 = ApgTy.ApgTyB("HC"); + // Same instance from cache, so equals2 is true + assertTrue(ty1.equals2(ty2)); + assertEquals(ty1.hashCode2(), ty2.hashCode2()); + } + } + + @Nested + class ToStringTest { + + @Test + void toStringLabel() { + ApgTy ty = ApgTy.ApgTyL("Person"); + assertEquals("Person", ty.toString()); + } + + @Test + void toStringBaseType() { + ApgTy ty = ApgTy.ApgTyB("Int"); + assertEquals("Int", ty.toString()); + } + + @Test + void toStringProduct() { + Map> fields = new HashMap<>(); + fields.put("x", ApgTy.ApgTyB("Int")); + fields.put("y", ApgTy.ApgTyB("Str")); + ApgTy ty = ApgTy.ApgTyP(true, fields); + String str = ty.toString(); + assertTrue(str.startsWith("("), "product toString should start with '(' but was: " + str); + assertTrue(str.endsWith(")"), "product toString should end with ')' but was: " + str); + assertTrue(str.contains("x"), "product toString should contain field 'x' but was: " + str); + assertTrue(str.contains("*"), "product toString should contain '*' separator but was: " + str); + } + + @Test + void toStringSum() { + Map> fields = new HashMap<>(); + fields.put("a", ApgTy.ApgTyB("Int")); + fields.put("b", ApgTy.ApgTyB("Str")); + ApgTy ty = ApgTy.ApgTyP(false, fields); + String str = ty.toString(); + assertTrue(str.startsWith("<"), "sum toString should start with '<' but was: " + str); + assertTrue(str.endsWith(">"), "sum toString should end with '>' but was: " + str); + assertTrue(str.contains("a"), "sum toString should contain field 'a' but was: " + str); + assertTrue(str.contains("+"), "sum toString should contain '+' separator but was: " + str); + } + } + + @Nested + class MapTest { + + @Test + void mapTransformsLabel() { + ApgTy ty = ApgTy.ApgTyL("old"); + ApgTy mapped = ty.map(label -> ApgTy.ApgTyL(label.length())); + assertEquals(3, mapped.l); + } + + @Test + void mapPreservesBase() { + ApgTy ty = ApgTy.ApgTyB("Int"); + ApgTy mapped = ty.map(label -> ApgTy.ApgTyL(label.length())); + assertEquals("Int", mapped.b); + assertNull(mapped.l); + } + + @Test + void mapTransformsProduct() { + Map> fields = new HashMap<>(); + fields.put("f", ApgTy.ApgTyL("lbl")); + ApgTy ty = ApgTy.ApgTyP(true, fields); + ApgTy mapped = ty.map(label -> ApgTy.ApgTyL(label.toUpperCase())); + assertNotNull(mapped.m); + assertTrue(mapped.all); + assertEquals("LBL", mapped.m.get("f").l); + } + } + + @Nested + class ValidateTest { + + private ApgSchema createSchema(Map> schemaMap) { + ApgTypeside typeside = new ApgTypeside(Collections.emptyMap(), Collections.emptyMap()); + return new ApgSchema<>(typeside, schemaMap); + } + + @Test + void validateSucceedsForValidLabel() { + Map> schemaMap = new HashMap<>(); + schemaMap.put("Person", ApgTy.ApgTyB("String")); + ApgSchema schema = createSchema(schemaMap); + + ApgTy ty = ApgTy.ApgTyL("Person"); + ty.validate(schema); // should not throw + } + + @Test + void validateThrowsForInvalidLabel() { + Map> schemaMap = new HashMap<>(); + ApgSchema schema = createSchema(schemaMap); + + ApgTy ty = ApgTy.ApgTyL("Missing"); + assertThrows(RuntimeException.class, () -> ty.validate(schema)); + } + + @Test + void validateRecursesIntoFields() { + Map> schemaMap = new HashMap<>(); + schemaMap.put("Name", ApgTy.ApgTyB("String")); + ApgSchema schema = createSchema(schemaMap); + + Map> fields = new HashMap<>(); + fields.put("name", ApgTy.ApgTyL("Name")); + ApgTy product = ApgTy.ApgTyP(true, fields); + product.validate(schema); // should not throw, "Name" exists in schema + + // Now with invalid nested label + Map> badFields = new HashMap<>(); + badFields.put("missing", ApgTy.ApgTyL("NoSuchLabel")); + ApgTy badProduct = ApgTy.ApgTyP(true, badFields); + assertThrows(RuntimeException.class, () -> badProduct.validate(schema)); + } + } +} diff --git a/src/test/java/catdata/apg/ApgTypesideTest.java b/src/test/java/catdata/apg/ApgTypesideTest.java new file mode 100644 index 0000000..b5a758f --- /dev/null +++ b/src/test/java/catdata/apg/ApgTypesideTest.java @@ -0,0 +1,139 @@ +package catdata.apg; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import catdata.Pair; +import catdata.Triple; +import catdata.cql.Kind; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ApgTypesideTest { + + private static final Map, Function>> EMPTY_BS = + Collections.emptyMap(); + + private static final Map, String, Function, Object>>> EMPTY_UDFS = + Collections.emptyMap(); + + private ApgTypeside createEmpty() { + return new ApgTypeside(EMPTY_BS, EMPTY_UDFS); + } + + private ApgTypeside createWithOneType() { + Map, Function>> bs = + Map.of("Int", new Pair<>(Integer.class, Integer::parseInt)); + return new ApgTypeside(bs, EMPTY_UDFS); + } + + @Nested + class Constructor { + + @Test + void constructorSetsFields() { + Map, Function>> bs = + Map.of("Int", new Pair<>(Integer.class, Integer::parseInt)); + Map, String, Function, Object>>> udfs = + Map.of("add", new Triple<>(List.of("Int", "Int"), "Int", args -> args.get(0))); + ApgTypeside ts = new ApgTypeside(bs, udfs); + assertNotNull(ts.Bs); + assertNotNull(ts.udfs); + assertEquals(1, ts.Bs.size()); + assertTrue(ts.Bs.containsKey("Int")); + assertEquals(1, ts.udfs.size()); + assertTrue(ts.udfs.containsKey("add")); + } + } + + @Nested + class KindTest { + + @Test + void kindReturnsApgTypeside() { + ApgTypeside ts = createEmpty(); + assertEquals(Kind.APG_typeside, ts.kind()); + } + } + + @Nested + class SizeTest { + + @Test + void sizeReturnsSumOfBsAndUdfs() { + Map, Function>> bs = + Map.of("Int", new Pair<>(Integer.class, Integer::parseInt)); + Map, String, Function, Object>>> udfs = + Map.of("add", new Triple<>(List.of("Int", "Int"), "Int", args -> args.get(0)), + "sub", new Triple<>(List.of("Int", "Int"), "Int", args -> args.get(0))); + ApgTypeside ts = new ApgTypeside(bs, udfs); + assertEquals(3, ts.size()); + } + + @Test + void sizeEmptyReturnsZero() { + ApgTypeside ts = createEmpty(); + assertEquals(0, ts.size()); + } + } + + @Nested + class EqualsHashCode { + + @Test + void equalsSame() { + ApgTypeside ts1 = createEmpty(); + ApgTypeside ts2 = createEmpty(); + assertEquals(ts1, ts2); + } + + @Test + void notEqualsDifferentBs() { + ApgTypeside ts1 = createEmpty(); + ApgTypeside ts2 = createWithOneType(); + assertNotEquals(ts1, ts2); + } + + @Test + void equalsReflexive() { + ApgTypeside ts = createEmpty(); + assertEquals(ts, ts); + } + + @Test + void notEqualsNull() { + ApgTypeside ts = createEmpty(); + assertNotEquals(null, ts); + } + + @Test + void notEqualsDifferentType() { + ApgTypeside ts = createEmpty(); + assertNotEquals("not a typeside", ts); + } + + @Test + void hashCodeConsistent() { + ApgTypeside ts1 = createEmpty(); + ApgTypeside ts2 = createEmpty(); + assertEquals(ts1.hashCode(), ts2.hashCode()); + } + } + + @Nested + class ToStringTest { + + @Test + void toStringContainsTypeInfo() { + ApgTypeside ts = createWithOneType(); + String str = ts.toString(); + assertTrue(str.contains("Int")); + } + } +} diff --git a/src/test/java/catdata/apg/exp/ApgInstExpTest.java b/src/test/java/catdata/apg/exp/ApgInstExpTest.java new file mode 100644 index 0000000..c22c5e9 --- /dev/null +++ b/src/test/java/catdata/apg/exp/ApgInstExpTest.java @@ -0,0 +1,404 @@ +package catdata.apg.exp; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import catdata.Pair; +import catdata.Unit; +import catdata.apg.exp.ApgInstExp.ApgInstExpCoEqualize; +import catdata.apg.exp.ApgInstExp.ApgInstExpDelta; +import catdata.apg.exp.ApgInstExp.ApgInstExpEqualize; +import catdata.apg.exp.ApgInstExp.ApgInstExpInitial; +import catdata.apg.exp.ApgInstExp.ApgInstExpPlus; +import catdata.apg.exp.ApgInstExp.ApgInstExpTerminal; +import catdata.apg.exp.ApgInstExp.ApgInstExpTimes; +import catdata.apg.exp.ApgInstExp.ApgInstExpVar; +import catdata.apg.exp.ApgMapExp.ApgMapExpVar; +import catdata.apg.exp.ApgSchExp.ApgSchExpVar; +import catdata.apg.exp.ApgTransExp.ApgTransExpVar; +import catdata.apg.exp.ApgTyExp.ApgTyExpVar; +import catdata.cql.Kind; +import catdata.cql.exp.AqlTyping; +import java.util.Collection; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ApgInstExpTest { + + @Nested + class ApgInstExpVarTest { + + @Test + void constructorSetsVar() { + assertEquals("i1", new ApgInstExpVar("i1").var); + } + + @Test + void kindReturnsApgInstance() { + assertEquals(Kind.APG_instance, new ApgInstExpVar("i").kind()); + } + + @Test + void isVarReturnsTrue() { + assertTrue(new ApgInstExpVar("i").isVar()); + } + + @Test + void toStringReturnsVar() { + assertEquals("myInst", new ApgInstExpVar("myInst").toString()); + } + + @Test + void depsReturnsSingleton() { + Collection> deps = new ApgInstExpVar("i1").deps(); + assertEquals(1, deps.size()); + Pair dep = deps.iterator().next(); + assertEquals("i1", dep.first); + assertEquals(Kind.APG_instance, dep.second); + } + + @Test + void equalsSameVar() { + ApgInstExpVar v1 = new ApgInstExpVar("i"); + ApgInstExpVar v2 = new ApgInstExpVar("i"); + assertEquals(v1, v2); + assertEquals(v1.hashCode(), v2.hashCode()); + } + + @Test + void notEqualsDifferentVar() { + assertNotEquals(new ApgInstExpVar("a"), new ApgInstExpVar("b")); + } + + @Test + void equalsReflexive() { + ApgInstExpVar v = new ApgInstExpVar("i"); + assertEquals(v, v); + } + + @Test + void notEqualsNull() { + assertNotEquals(null, new ApgInstExpVar("i")); + } + + @Test + void typeThrowsWhenUndefined() { + AqlTyping typing = new AqlTyping(); + assertThrows(RuntimeException.class, () -> new ApgInstExpVar("missing").type(typing)); + } + + @Test + void typeReturnsSchExpWhenDefined() { + AqlTyping typing = new AqlTyping(); + ApgSchExpVar schExp = new ApgSchExpVar("s1"); + typing.defs.apgis.put("i1", schExp); + assertEquals(schExp, new ApgInstExpVar("i1").type(typing)); + } + + @Test + void acceptDelegatesToVisitor() throws Exception { + ApgInstExpVar v = new ApgInstExpVar("i"); + @SuppressWarnings("unchecked") + ApgInstExp.ApgInstExpVisitor visitor = + mock(ApgInstExp.ApgInstExpVisitor.class); + v.accept(null, visitor); + verify(visitor).visit(null, v); + } + + @Test + void varMethodCreatesNew() { + var newVar = new ApgInstExpVar("i1").Var("i2"); + assertTrue(newVar instanceof ApgInstExpVar); + assertEquals("i2", ((ApgInstExpVar) newVar).var); + } + } + + @Nested + class ApgInstExpInitialTest { + + @Test + void constructorSetsTypeside() { + ApgSchExpVar sch = new ApgSchExpVar("s"); + ApgInstExpInitial init = new ApgInstExpInitial(sch); + assertEquals(sch, init.typeside); + } + + @Test + void equalsSame() { + ApgInstExpInitial i1 = new ApgInstExpInitial(new ApgSchExpVar("s")); + ApgInstExpInitial i2 = new ApgInstExpInitial(new ApgSchExpVar("s")); + assertEquals(i1, i2); + assertEquals(i1.hashCode(), i2.hashCode()); + } + + @Test + void notEqualsDifferent() { + assertNotEquals( + new ApgInstExpInitial(new ApgSchExpVar("a")), + new ApgInstExpInitial(new ApgSchExpVar("b"))); + } + + @Test + void toStringContainsEmpty() { + assertTrue(new ApgInstExpInitial(new ApgSchExpVar("s")).toString().contains("empty")); + } + + @Test + void typeReturnsTypeside() { + AqlTyping typing = new AqlTyping(); + ApgTyExpVar tyExp = new ApgTyExpVar("ts"); + typing.defs.apgschemas.put("s", tyExp); + ApgSchExpVar sch = new ApgSchExpVar("s"); + assertEquals(sch, new ApgInstExpInitial(sch).type(typing)); + } + + @Test + void acceptDelegatesToVisitor() throws Exception { + ApgInstExpInitial init = new ApgInstExpInitial(new ApgSchExpVar("s")); + @SuppressWarnings("unchecked") + ApgInstExp.ApgInstExpVisitor visitor = + mock(ApgInstExp.ApgInstExpVisitor.class); + init.accept(null, visitor); + verify(visitor).visit(null, init); + } + } + + @Nested + class ApgInstExpTerminalTest { + + @Test + void constructorSetsTypeside() { + ApgTyExpVar ts = new ApgTyExpVar("ts"); + ApgInstExpTerminal term = new ApgInstExpTerminal(ts); + assertEquals(ts, term.typeside); + } + + @Test + void equalsSame() { + ApgInstExpTerminal t1 = new ApgInstExpTerminal(new ApgTyExpVar("ts")); + ApgInstExpTerminal t2 = new ApgInstExpTerminal(new ApgTyExpVar("ts")); + assertEquals(t1, t2); + } + + @Test + void toStringContainsUnit() { + assertTrue(new ApgInstExpTerminal(new ApgTyExpVar("ts")).toString().contains("unit")); + } + + @Test + void typeReturnsSchExpTerminal() { + AqlTyping typing = new AqlTyping(); + typing.defs.apgts.put("ts", Unit.unit); + ApgTyExpVar ts = new ApgTyExpVar("ts"); + ApgSchExp result = new ApgInstExpTerminal(ts).type(typing); + assertTrue(result instanceof ApgSchExp.ApgSchExpTerminal); + } + } + + @Nested + class ApgInstExpTimesTest { + + @Test + void constructorSetsFields() { + ApgInstExpVar l = new ApgInstExpVar("i1"); + ApgInstExpVar r = new ApgInstExpVar("i2"); + ApgInstExpTimes times = new ApgInstExpTimes(l, r); + assertEquals(l, times.l); + assertEquals(r, times.r); + } + + @Test + void equalsSame() { + ApgInstExpTimes t1 = new ApgInstExpTimes(new ApgInstExpVar("a"), new ApgInstExpVar("b")); + ApgInstExpTimes t2 = new ApgInstExpTimes(new ApgInstExpVar("a"), new ApgInstExpVar("b")); + assertEquals(t1, t2); + assertEquals(t1.hashCode(), t2.hashCode()); + } + + @Test + void notEqualsSwapped() { + assertNotEquals( + new ApgInstExpTimes(new ApgInstExpVar("a"), new ApgInstExpVar("b")), + new ApgInstExpTimes(new ApgInstExpVar("b"), new ApgInstExpVar("a"))); + } + + @Test + void toStringContainsStar() { + assertTrue( + new ApgInstExpTimes(new ApgInstExpVar("a"), new ApgInstExpVar("b")) + .toString() + .contains("*")); + } + + @Test + void depsUnionsBoth() { + ApgInstExpTimes times = + new ApgInstExpTimes(new ApgInstExpVar("a"), new ApgInstExpVar("b")); + assertEquals(2, times.deps().size()); + } + + @Test + void typeThrowsOnDifferentTypesides() { + AqlTyping typing = new AqlTyping(); + typing.defs.apgts.put("ts1", Unit.unit); + typing.defs.apgts.put("ts2", Unit.unit); + typing.defs.apgschemas.put("s1", new ApgTyExpVar("ts1")); + typing.defs.apgschemas.put("s2", new ApgTyExpVar("ts2")); + typing.defs.apgis.put("a", new ApgSchExpVar("s1")); + typing.defs.apgis.put("b", new ApgSchExpVar("s2")); + ApgInstExpTimes times = new ApgInstExpTimes(new ApgInstExpVar("a"), new ApgInstExpVar("b")); + assertThrows(RuntimeException.class, () -> times.type(typing)); + } + } + + @Nested + class ApgInstExpPlusTest { + + @Test + void constructorSetsFields() { + ApgInstExpVar l = new ApgInstExpVar("i1"); + ApgInstExpVar r = new ApgInstExpVar("i2"); + ApgInstExpPlus plus = new ApgInstExpPlus(l, r); + assertEquals(l, plus.l); + assertEquals(r, plus.r); + } + + @Test + void equalsSame() { + ApgInstExpPlus p1 = new ApgInstExpPlus(new ApgInstExpVar("a"), new ApgInstExpVar("b")); + ApgInstExpPlus p2 = new ApgInstExpPlus(new ApgInstExpVar("a"), new ApgInstExpVar("b")); + assertEquals(p1, p2); + assertEquals(p1.hashCode(), p2.hashCode()); + } + + @Test + void toStringContainsPlus() { + assertTrue( + new ApgInstExpPlus(new ApgInstExpVar("a"), new ApgInstExpVar("b")) + .toString() + .contains("+")); + } + + @Test + void acceptDelegatesToVisitor() throws Exception { + ApgInstExpPlus plus = new ApgInstExpPlus(new ApgInstExpVar("a"), new ApgInstExpVar("b")); + @SuppressWarnings("unchecked") + ApgInstExp.ApgInstExpVisitor visitor = + mock(ApgInstExp.ApgInstExpVisitor.class); + plus.accept(null, visitor); + verify(visitor).visit(null, plus); + } + } + + @Nested + class ApgInstExpEqualizeTest { + + @Test + void constructorSetsFields() { + ApgTransExpVar l = new ApgTransExpVar("t1"); + ApgTransExpVar r = new ApgTransExpVar("t2"); + ApgInstExpEqualize eq = new ApgInstExpEqualize(l, r); + assertEquals(l, eq.l); + assertEquals(r, eq.r); + } + + @Test + void equalsSame() { + ApgInstExpEqualize e1 = + new ApgInstExpEqualize(new ApgTransExpVar("t1"), new ApgTransExpVar("t2")); + ApgInstExpEqualize e2 = + new ApgInstExpEqualize(new ApgTransExpVar("t1"), new ApgTransExpVar("t2")); + assertEquals(e1, e2); + assertEquals(e1.hashCode(), e2.hashCode()); + } + + @Test + void toStringContainsEqualize() { + assertTrue( + new ApgInstExpEqualize(new ApgTransExpVar("t1"), new ApgTransExpVar("t2")) + .toString() + .contains("equalize")); + } + + @Test + void depsUnionsBoth() { + ApgInstExpEqualize eq = + new ApgInstExpEqualize(new ApgTransExpVar("t1"), new ApgTransExpVar("t2")); + assertEquals(2, eq.deps().size()); + } + } + + @Nested + class ApgInstExpCoEqualizeTest { + + @Test + void constructorSetsFields() { + ApgTransExpVar l = new ApgTransExpVar("t1"); + ApgTransExpVar r = new ApgTransExpVar("t2"); + ApgInstExpCoEqualize ceq = new ApgInstExpCoEqualize(l, r); + assertEquals(l, ceq.l); + assertEquals(r, ceq.r); + } + + @Test + void equalsSame() { + ApgInstExpCoEqualize c1 = + new ApgInstExpCoEqualize(new ApgTransExpVar("t1"), new ApgTransExpVar("t2")); + ApgInstExpCoEqualize c2 = + new ApgInstExpCoEqualize(new ApgTransExpVar("t1"), new ApgTransExpVar("t2")); + assertEquals(c1, c2); + } + + @Test + void toStringContainsCoequalize() { + assertTrue( + new ApgInstExpCoEqualize(new ApgTransExpVar("t1"), new ApgTransExpVar("t2")) + .toString() + .contains("coequalize")); + } + } + + @Nested + class ApgInstExpDeltaTest { + + @Test + void constructorSetsFields() { + ApgMapExpVar f = new ApgMapExpVar("m"); + ApgInstExpVar j = new ApgInstExpVar("i"); + ApgInstExpDelta delta = new ApgInstExpDelta(f, j); + assertEquals(f, delta.F); + assertEquals(j, delta.J); + } + + @Test + void equalsSame() { + ApgInstExpDelta d1 = new ApgInstExpDelta(new ApgMapExpVar("m"), new ApgInstExpVar("i")); + ApgInstExpDelta d2 = new ApgInstExpDelta(new ApgMapExpVar("m"), new ApgInstExpVar("i")); + assertEquals(d1, d2); + assertEquals(d1.hashCode(), d2.hashCode()); + } + + @Test + void toStringContainsDelta() { + assertTrue( + new ApgInstExpDelta(new ApgMapExpVar("m"), new ApgInstExpVar("i")) + .toString() + .contains("delta")); + } + + @Test + void depsUnionsBoth() { + ApgInstExpDelta delta = new ApgInstExpDelta(new ApgMapExpVar("m"), new ApgInstExpVar("i")); + assertEquals(2, delta.deps().size()); + } + } + + @Test + void optionsReturnsEmptyMap() { + assertTrue(new ApgInstExpVar("x").options().isEmpty()); + } +} diff --git a/src/test/java/catdata/apg/exp/ApgMapExpTest.java b/src/test/java/catdata/apg/exp/ApgMapExpTest.java new file mode 100644 index 0000000..2bb0bfd --- /dev/null +++ b/src/test/java/catdata/apg/exp/ApgMapExpTest.java @@ -0,0 +1,215 @@ +package catdata.apg.exp; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import catdata.Pair; +import catdata.Unit; +import catdata.apg.exp.ApgMapExp.ApgMapExpCompose; +import catdata.apg.exp.ApgMapExp.ApgMapExpVar; +import catdata.apg.exp.ApgSchExp.ApgSchExpVar; +import catdata.apg.exp.ApgTyExp.ApgTyExpVar; +import catdata.cql.Kind; +import catdata.cql.exp.AqlTyping; +import java.util.Collection; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ApgMapExpTest { + + @Nested + class ApgMapExpVarTest { + + @Test + void constructorSetsVar() { + assertEquals("m1", new ApgMapExpVar("m1").var); + } + + @Test + void kindReturnsApgMapping() { + assertEquals(Kind.APG_mapping, new ApgMapExpVar("m").kind()); + } + + @Test + void isVarReturnsTrue() { + assertTrue(new ApgMapExpVar("m").isVar()); + } + + @Test + void toStringReturnsVar() { + assertEquals("myMap", new ApgMapExpVar("myMap").toString()); + } + + @Test + void depsReturnsSingleton() { + Collection> deps = new ApgMapExpVar("m1").deps(); + assertEquals(1, deps.size()); + Pair dep = deps.iterator().next(); + assertEquals("m1", dep.first); + assertEquals(Kind.APG_instance, dep.second); + } + + @Test + void equalsSameVar() { + ApgMapExpVar v1 = new ApgMapExpVar("m"); + ApgMapExpVar v2 = new ApgMapExpVar("m"); + assertEquals(v1, v2); + assertEquals(v1.hashCode(), v2.hashCode()); + } + + @Test + void notEqualsDifferentVar() { + assertNotEquals(new ApgMapExpVar("a"), new ApgMapExpVar("b")); + } + + @Test + void equalsReflexive() { + ApgMapExpVar v = new ApgMapExpVar("m"); + assertEquals(v, v); + } + + @Test + void notEqualsNull() { + assertNotEquals(null, new ApgMapExpVar("m")); + } + + @Test + void notEqualsDifferentType() { + assertNotEquals("m", new ApgMapExpVar("m")); + } + + @Test + void typeThrowsWhenUndefined() { + AqlTyping typing = new AqlTyping(); + assertThrows(RuntimeException.class, () -> new ApgMapExpVar("missing").type(typing)); + } + + @Test + void typeReturnsPairWhenDefined() { + AqlTyping typing = new AqlTyping(); + Pair pair = + new Pair<>(new ApgSchExpVar("s1"), new ApgSchExpVar("s2")); + typing.defs.apgmappings.put("m1", pair); + assertEquals(pair, new ApgMapExpVar("m1").type(typing)); + } + + @Test + void acceptDelegatesToVisitor() { + ApgMapExpVar v = new ApgMapExpVar("m"); + @SuppressWarnings("unchecked") + ApgMapExp.ApgMapExpVisitor visitor = mock(ApgMapExp.ApgMapExpVisitor.class); + v.accept(null, visitor); + verify(visitor).visit(null, v); + } + + @Test + void coAcceptDelegatesToCoVisitor() { + ApgMapExpVar v = new ApgMapExpVar("m"); + @SuppressWarnings("unchecked") + ApgMapExp.ApgMapExpCoVisitor coVisitor = + mock(ApgMapExp.ApgMapExpCoVisitor.class); + v.coaccept(null, coVisitor, "r"); + verify(coVisitor).visitApgMapExpVar(null, "r"); + } + + @Test + void varMethodCreatesNew() { + var newVar = new ApgMapExpVar("m1").Var("m2"); + assertTrue(newVar instanceof ApgMapExpVar); + assertEquals("m2", ((ApgMapExpVar) newVar).var); + } + } + + @Nested + class ApgMapExpComposeTest { + + @Test + void constructorSetsFields() { + ApgMapExpVar h1 = new ApgMapExpVar("m1"); + ApgMapExpVar h2 = new ApgMapExpVar("m2"); + ApgMapExpCompose compose = new ApgMapExpCompose(h1, h2); + assertEquals(h1, compose.h1); + assertEquals(h2, compose.h2); + } + + @Test + void equalsSame() { + ApgMapExpCompose c1 = new ApgMapExpCompose(new ApgMapExpVar("a"), new ApgMapExpVar("b")); + ApgMapExpCompose c2 = new ApgMapExpCompose(new ApgMapExpVar("a"), new ApgMapExpVar("b")); + assertEquals(c1, c2); + assertEquals(c1.hashCode(), c2.hashCode()); + } + + @Test + void notEqualsSwapped() { + assertNotEquals( + new ApgMapExpCompose(new ApgMapExpVar("a"), new ApgMapExpVar("b")), + new ApgMapExpCompose(new ApgMapExpVar("b"), new ApgMapExpVar("a"))); + } + + @Test + void toStringContainsSemicolon() { + String s = new ApgMapExpCompose(new ApgMapExpVar("a"), new ApgMapExpVar("b")).toString(); + assertTrue(s.contains(";")); + } + + @Test + void depsUnionsBoth() { + ApgMapExpCompose compose = + new ApgMapExpCompose(new ApgMapExpVar("a"), new ApgMapExpVar("b")); + assertEquals(2, compose.deps().size()); + } + + @Test + void acceptReturnsNull() { + // ApgMapExpCompose.accept returns null (commented-out delegation) + ApgMapExpCompose compose = + new ApgMapExpCompose(new ApgMapExpVar("a"), new ApgMapExpVar("b")); + @SuppressWarnings("unchecked") + ApgMapExp.ApgMapExpVisitor visitor = mock(ApgMapExp.ApgMapExpVisitor.class); + assertNull(compose.accept(null, visitor)); + } + + @Test + void typeThrowsOnIntermediateMismatch() { + AqlTyping typing = new AqlTyping(); + typing.defs.apgts.put("ts", Unit.unit); + typing.defs.apgschemas.put("s1", new ApgTyExpVar("ts")); + typing.defs.apgschemas.put("s2", new ApgTyExpVar("ts")); + typing.defs.apgschemas.put("s3", new ApgTyExpVar("ts")); + typing.defs.apgmappings.put( + "m1", new Pair<>(new ApgSchExpVar("s1"), new ApgSchExpVar("s2"))); + typing.defs.apgmappings.put( + "m2", new Pair<>(new ApgSchExpVar("s3"), new ApgSchExpVar("s1"))); + ApgMapExpCompose compose = new ApgMapExpCompose(new ApgMapExpVar("m1"), new ApgMapExpVar("m2")); + assertThrows(RuntimeException.class, () -> compose.type(typing)); + } + + @Test + void typeSucceedsOnMatchingIntermediate() { + AqlTyping typing = new AqlTyping(); + typing.defs.apgts.put("ts", Unit.unit); + typing.defs.apgschemas.put("s1", new ApgTyExpVar("ts")); + typing.defs.apgschemas.put("s2", new ApgTyExpVar("ts")); + typing.defs.apgschemas.put("s3", new ApgTyExpVar("ts")); + typing.defs.apgmappings.put( + "m1", new Pair<>(new ApgSchExpVar("s1"), new ApgSchExpVar("s2"))); + typing.defs.apgmappings.put( + "m2", new Pair<>(new ApgSchExpVar("s2"), new ApgSchExpVar("s3"))); + ApgMapExpCompose compose = new ApgMapExpCompose(new ApgMapExpVar("m1"), new ApgMapExpVar("m2")); + Pair result = compose.type(typing); + assertEquals(new ApgSchExpVar("s1"), result.first); + assertEquals(new ApgSchExpVar("s3"), result.second); + } + } + + @Test + void optionsReturnsEmptyMap() { + assertTrue(new ApgMapExpVar("x").options().isEmpty()); + } +} diff --git a/src/test/java/catdata/apg/exp/ApgSchExpTest.java b/src/test/java/catdata/apg/exp/ApgSchExpTest.java new file mode 100644 index 0000000..6af6b61 --- /dev/null +++ b/src/test/java/catdata/apg/exp/ApgSchExpTest.java @@ -0,0 +1,436 @@ +package catdata.apg.exp; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import catdata.LocStr; +import catdata.Pair; +import catdata.Unit; +import catdata.apg.ApgTy; +import catdata.apg.exp.ApgSchExp.ApgSchExpInitial; +import catdata.apg.exp.ApgSchExp.ApgSchExpPlus; +import catdata.apg.exp.ApgSchExp.ApgSchExpRaw; +import catdata.apg.exp.ApgSchExp.ApgSchExpTerminal; +import catdata.apg.exp.ApgSchExp.ApgSchExpTimes; +import catdata.apg.exp.ApgSchExp.ApgSchExpVar; +import catdata.apg.exp.ApgTyExp.ApgTyExpVar; +import catdata.cql.Kind; +import catdata.cql.exp.AqlTyping; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ApgSchExpTest { + + @Nested + class ApgSchExpVarTest { + + @Test + void constructorSetsVar() { + ApgSchExpVar v = new ApgSchExpVar("s1"); + assertEquals("s1", v.var); + } + + @Test + void kindReturnsApgSchema() { + ApgSchExpVar v = new ApgSchExpVar("s"); + assertEquals(Kind.APG_schema, v.kind()); + } + + @Test + void isVarReturnsTrue() { + assertTrue(new ApgSchExpVar("s").isVar()); + } + + @Test + void toStringReturnsVar() { + assertEquals("mySchema", new ApgSchExpVar("mySchema").toString()); + } + + @Test + void depsReturnsSingleton() { + Collection> deps = new ApgSchExpVar("s1").deps(); + assertEquals(1, deps.size()); + Pair dep = deps.iterator().next(); + assertEquals("s1", dep.first); + assertEquals(Kind.APG_instance, dep.second); + } + + @Test + void equalsSameVar() { + ApgSchExpVar v1 = new ApgSchExpVar("s"); + ApgSchExpVar v2 = new ApgSchExpVar("s"); + assertEquals(v1, v2); + assertEquals(v1.hashCode(), v2.hashCode()); + } + + @Test + void notEqualsDifferentVar() { + assertNotEquals(new ApgSchExpVar("a"), new ApgSchExpVar("b")); + } + + @Test + void equalsReflexive() { + ApgSchExpVar v = new ApgSchExpVar("s"); + assertEquals(v, v); + } + + @Test + void notEqualsNull() { + assertNotEquals(null, new ApgSchExpVar("s")); + } + + @Test + void notEqualsDifferentType() { + assertNotEquals("s", new ApgSchExpVar("s")); + } + + @Test + void typeThrowsWhenUndefined() { + AqlTyping typing = new AqlTyping(); + assertThrows(RuntimeException.class, () -> new ApgSchExpVar("missing").type(typing)); + } + + @Test + void typeReturnsExpWhenDefined() { + AqlTyping typing = new AqlTyping(); + ApgTyExpVar tyExp = new ApgTyExpVar("ts1"); + typing.defs.apgschemas.put("s1", tyExp); + ApgTyExp result = new ApgSchExpVar("s1").type(typing); + assertEquals(tyExp, result); + } + + @Test + void acceptDelegatesToVisitor() { + ApgSchExpVar v = new ApgSchExpVar("s"); + @SuppressWarnings("unchecked") + ApgSchExp.ApgSchExpVisitor visitor = mock(ApgSchExp.ApgSchExpVisitor.class); + v.accept(null, visitor); + verify(visitor).visit(null, v); + } + + @Test + void coAcceptDelegatesToCoVisitor() { + ApgSchExpVar v = new ApgSchExpVar("s"); + @SuppressWarnings("unchecked") + ApgSchExp.ApgSchExpCoVisitor coVisitor = + mock(ApgSchExp.ApgSchExpCoVisitor.class); + v.coaccept(null, coVisitor, "r"); + verify(coVisitor).visitApgSchExpVar(null, "r"); + } + + @Test + void varMethodCreatesNewSchExpVar() { + ApgSchExpVar v = new ApgSchExpVar("s1"); + var newVar = v.Var("s2"); + assertTrue(newVar instanceof ApgSchExpVar); + assertEquals("s2", ((ApgSchExpVar) newVar).var); + } + } + + @Nested + class ApgSchExpRawTest { + + private ApgSchExpRaw createEmptyRaw() { + return new ApgSchExpRaw( + new ApgTyExpVar("ts"), Collections.emptyList(), Collections.emptyList()); + } + + @Test + void constructorSetsFields() { + ApgSchExpRaw raw = createEmptyRaw(); + assertNotNull(raw.typeside); + assertNotNull(raw.imports); + assertNotNull(raw.Ls); + assertTrue(raw.imports.isEmpty()); + assertTrue(raw.Ls.isEmpty()); + } + + @Test + void constructorWithLabels() { + ApgTy ty = ApgTy.ApgTyB("Int"); + List>> labels = + List.of(new Pair<>(new LocStr(0, "age"), ty)); + ApgSchExpRaw raw = new ApgSchExpRaw(new ApgTyExpVar("ts"), Collections.emptyList(), labels); + assertEquals(1, raw.Ls.size()); + assertTrue(raw.Ls.containsKey("age")); + } + + @Test + void depsIncludesTypesideDeps() { + ApgSchExpRaw raw = createEmptyRaw(); + Collection> deps = raw.deps(); + assertEquals(1, deps.size()); + assertEquals("ts", deps.iterator().next().first); + } + + @Test + void equalsSameContent() { + ApgSchExpRaw r1 = createEmptyRaw(); + ApgSchExpRaw r2 = createEmptyRaw(); + assertEquals(r1, r2); + assertEquals(r1.hashCode(), r2.hashCode()); + } + + @Test + void notEqualsDifferentTypeside() { + ApgSchExpRaw r1 = createEmptyRaw(); + ApgSchExpRaw r2 = + new ApgSchExpRaw( + new ApgTyExpVar("other"), Collections.emptyList(), Collections.emptyList()); + assertNotEquals(r1, r2); + } + + @Test + void toStringContainsLiteral() { + assertTrue(createEmptyRaw().toString().contains("literal")); + } + + @Test + void rawReturnsNonNull() { + assertNotNull(createEmptyRaw().raw()); + } + + @Test + void acceptDelegatesToVisitor() { + ApgSchExpRaw raw = createEmptyRaw(); + @SuppressWarnings("unchecked") + ApgSchExp.ApgSchExpVisitor visitor = mock(ApgSchExp.ApgSchExpVisitor.class); + raw.accept(null, visitor); + verify(visitor).visit(null, raw); + } + + @Test + void typeReturnsTypeside() { + AqlTyping typing = new AqlTyping(); + typing.defs.apgts.put("ts", Unit.unit); + ApgSchExpRaw raw = createEmptyRaw(); + ApgTyExp result = raw.type(typing); + assertEquals(new ApgTyExpVar("ts"), result); + } + + @Test + void isVarReturnsFalse() { + assertFalse(createEmptyRaw().isVar()); + } + } + + @Nested + class ApgSchExpInitialTest { + + @Test + void constructorSetsTypeside() { + ApgTyExpVar ts = new ApgTyExpVar("ts"); + ApgSchExpInitial init = new ApgSchExpInitial(ts); + assertEquals(ts, init.typeside); + } + + @Test + void equalsSame() { + ApgSchExpInitial i1 = new ApgSchExpInitial(new ApgTyExpVar("ts")); + ApgSchExpInitial i2 = new ApgSchExpInitial(new ApgTyExpVar("ts")); + assertEquals(i1, i2); + assertEquals(i1.hashCode(), i2.hashCode()); + } + + @Test + void notEqualsDifferent() { + assertNotEquals( + new ApgSchExpInitial(new ApgTyExpVar("a")), + new ApgSchExpInitial(new ApgTyExpVar("b"))); + } + + @Test + void toStringContainsEmpty() { + assertTrue(new ApgSchExpInitial(new ApgTyExpVar("ts")).toString().contains("empty")); + } + + @Test + void depsMatchesTypesideDeps() { + ApgSchExpInitial init = new ApgSchExpInitial(new ApgTyExpVar("ts")); + assertEquals(new ApgTyExpVar("ts").deps(), init.deps()); + } + + @Test + void typeReturnsTypeside() { + AqlTyping typing = new AqlTyping(); + typing.defs.apgts.put("ts", Unit.unit); + ApgTyExpVar ts = new ApgTyExpVar("ts"); + ApgSchExpInitial init = new ApgSchExpInitial(ts); + assertEquals(ts, init.type(typing)); + } + + @Test + void acceptDelegatesToVisitor() { + ApgSchExpInitial init = new ApgSchExpInitial(new ApgTyExpVar("ts")); + @SuppressWarnings("unchecked") + ApgSchExp.ApgSchExpVisitor visitor = mock(ApgSchExp.ApgSchExpVisitor.class); + init.accept(null, visitor); + verify(visitor).visit(null, init); + } + } + + @Nested + class ApgSchExpTerminalTest { + + @Test + void constructorSetsTypeside() { + ApgTyExpVar ts = new ApgTyExpVar("ts"); + ApgSchExpTerminal term = new ApgSchExpTerminal(ts); + assertEquals(ts, term.typeside); + } + + @Test + void equalsSame() { + ApgSchExpTerminal t1 = new ApgSchExpTerminal(new ApgTyExpVar("ts")); + ApgSchExpTerminal t2 = new ApgSchExpTerminal(new ApgTyExpVar("ts")); + assertEquals(t1, t2); + } + + @Test + void toStringContainsUnit() { + assertTrue(new ApgSchExpTerminal(new ApgTyExpVar("ts")).toString().contains("unit")); + } + + @Test + void typeReturnsTypeside() { + AqlTyping typing = new AqlTyping(); + typing.defs.apgts.put("ts", Unit.unit); + ApgTyExpVar ts = new ApgTyExpVar("ts"); + assertEquals(ts, new ApgSchExpTerminal(ts).type(typing)); + } + + @Test + void acceptDelegatesToVisitor() { + ApgSchExpTerminal term = new ApgSchExpTerminal(new ApgTyExpVar("ts")); + @SuppressWarnings("unchecked") + ApgSchExp.ApgSchExpVisitor visitor = mock(ApgSchExp.ApgSchExpVisitor.class); + term.accept(null, visitor); + verify(visitor).visit(null, term); + } + } + + @Nested + class ApgSchExpTimesTest { + + @Test + void constructorSetsFields() { + ApgSchExpVar l = new ApgSchExpVar("s1"); + ApgSchExpVar r = new ApgSchExpVar("s2"); + ApgSchExpTimes times = new ApgSchExpTimes(l, r); + assertEquals(l, times.l); + assertEquals(r, times.r); + } + + @Test + void equalsSame() { + ApgSchExpTimes t1 = new ApgSchExpTimes(new ApgSchExpVar("a"), new ApgSchExpVar("b")); + ApgSchExpTimes t2 = new ApgSchExpTimes(new ApgSchExpVar("a"), new ApgSchExpVar("b")); + assertEquals(t1, t2); + assertEquals(t1.hashCode(), t2.hashCode()); + } + + @Test + void notEqualsSwapped() { + ApgSchExpTimes t1 = new ApgSchExpTimes(new ApgSchExpVar("a"), new ApgSchExpVar("b")); + ApgSchExpTimes t2 = new ApgSchExpTimes(new ApgSchExpVar("b"), new ApgSchExpVar("a")); + assertNotEquals(t1, t2); + } + + @Test + void toStringContainsStar() { + String s = new ApgSchExpTimes(new ApgSchExpVar("a"), new ApgSchExpVar("b")).toString(); + assertTrue(s.contains("*")); + } + + @Test + void depsUnionsBothSides() { + ApgSchExpTimes times = + new ApgSchExpTimes(new ApgSchExpVar("a"), new ApgSchExpVar("b")); + Collection> deps = times.deps(); + assertEquals(2, deps.size()); + } + + @Test + void typeThrowsOnDifferentTypesides() { + AqlTyping typing = new AqlTyping(); + typing.defs.apgts.put("ts1", Unit.unit); + typing.defs.apgts.put("ts2", Unit.unit); + typing.defs.apgschemas.put("a", new ApgTyExpVar("ts1")); + typing.defs.apgschemas.put("b", new ApgTyExpVar("ts2")); + ApgSchExpTimes times = new ApgSchExpTimes(new ApgSchExpVar("a"), new ApgSchExpVar("b")); + assertThrows(RuntimeException.class, () -> times.type(typing)); + } + + @Test + void typeSucceedsWithSameTypeside() { + AqlTyping typing = new AqlTyping(); + typing.defs.apgts.put("ts", Unit.unit); + typing.defs.apgschemas.put("a", new ApgTyExpVar("ts")); + typing.defs.apgschemas.put("b", new ApgTyExpVar("ts")); + ApgSchExpTimes times = new ApgSchExpTimes(new ApgSchExpVar("a"), new ApgSchExpVar("b")); + ApgTyExp result = times.type(typing); + assertEquals(new ApgTyExpVar("ts"), result); + } + } + + @Nested + class ApgSchExpPlusTest { + + @Test + void constructorSetsFields() { + ApgSchExpVar l = new ApgSchExpVar("s1"); + ApgSchExpVar r = new ApgSchExpVar("s2"); + ApgSchExpPlus plus = new ApgSchExpPlus(l, r); + assertEquals(l, plus.l); + assertEquals(r, plus.r); + } + + @Test + void equalsSame() { + ApgSchExpPlus p1 = new ApgSchExpPlus(new ApgSchExpVar("a"), new ApgSchExpVar("b")); + ApgSchExpPlus p2 = new ApgSchExpPlus(new ApgSchExpVar("a"), new ApgSchExpVar("b")); + assertEquals(p1, p2); + assertEquals(p1.hashCode(), p2.hashCode()); + } + + @Test + void toStringContainsPlus() { + String s = new ApgSchExpPlus(new ApgSchExpVar("a"), new ApgSchExpVar("b")).toString(); + assertTrue(s.contains("+")); + } + + @Test + void typeThrowsOnDifferentTypesides() { + AqlTyping typing = new AqlTyping(); + typing.defs.apgts.put("ts1", Unit.unit); + typing.defs.apgts.put("ts2", Unit.unit); + typing.defs.apgschemas.put("a", new ApgTyExpVar("ts1")); + typing.defs.apgschemas.put("b", new ApgTyExpVar("ts2")); + ApgSchExpPlus plus = new ApgSchExpPlus(new ApgSchExpVar("a"), new ApgSchExpVar("b")); + assertThrows(RuntimeException.class, () -> plus.type(typing)); + } + + @Test + void acceptDelegatesToVisitor() { + ApgSchExpPlus plus = new ApgSchExpPlus(new ApgSchExpVar("a"), new ApgSchExpVar("b")); + @SuppressWarnings("unchecked") + ApgSchExp.ApgSchExpVisitor visitor = mock(ApgSchExp.ApgSchExpVisitor.class); + plus.accept(null, visitor); + verify(visitor).visit(null, plus); + } + } + + @Test + void optionsReturnsEmptyMap() { + assertTrue(new ApgSchExpVar("x").options().isEmpty()); + } +} diff --git a/src/test/java/catdata/apg/exp/ApgTransExpTest.java b/src/test/java/catdata/apg/exp/ApgTransExpTest.java new file mode 100644 index 0000000..31199e7 --- /dev/null +++ b/src/test/java/catdata/apg/exp/ApgTransExpTest.java @@ -0,0 +1,640 @@ +package catdata.apg.exp; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import catdata.Pair; +import catdata.Unit; +import catdata.apg.exp.ApgInstExp.ApgInstExpCoEqualize; +import catdata.apg.exp.ApgInstExp.ApgInstExpEqualize; +import catdata.apg.exp.ApgInstExp.ApgInstExpPlus; +import catdata.apg.exp.ApgInstExp.ApgInstExpTimes; +import catdata.apg.exp.ApgInstExp.ApgInstExpVar; +import catdata.apg.exp.ApgMapExp.ApgMapExpVar; +import catdata.apg.exp.ApgSchExp.ApgSchExpVar; +import catdata.apg.exp.ApgTransExp.ApgTransExpCase; +import catdata.apg.exp.ApgTransExp.ApgTransExpCoEqualize; +import catdata.apg.exp.ApgTransExp.ApgTransExpCoEqualizeU; +import catdata.apg.exp.ApgTransExp.ApgTransExpCompose; +import catdata.apg.exp.ApgTransExp.ApgTransExpDelta; +import catdata.apg.exp.ApgTransExp.ApgTransExpEqualize; +import catdata.apg.exp.ApgTransExp.ApgTransExpEqualizeU; +import catdata.apg.exp.ApgTransExp.ApgTransExpFst; +import catdata.apg.exp.ApgTransExp.ApgTransExpId; +import catdata.apg.exp.ApgTransExp.ApgTransExpInitial; +import catdata.apg.exp.ApgTransExp.ApgTransExpInl; +import catdata.apg.exp.ApgTransExp.ApgTransExpInr; +import catdata.apg.exp.ApgTransExp.ApgTransExpPair; +import catdata.apg.exp.ApgTransExp.ApgTransExpSnd; +import catdata.apg.exp.ApgTransExp.ApgTransExpTerminal; +import catdata.apg.exp.ApgTransExp.ApgTransExpVar; +import catdata.apg.exp.ApgTyExp.ApgTyExpVar; +import catdata.cql.Kind; +import catdata.cql.exp.AqlTyping; +import java.util.Collection; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ApgTransExpTest { + + private AqlTyping setupTypingWithInstances(String... instNames) { + AqlTyping typing = new AqlTyping(); + typing.defs.apgts.put("ts", Unit.unit); + typing.defs.apgschemas.put("s", new ApgTyExpVar("ts")); + for (String name : instNames) { + typing.defs.apgis.put(name, new ApgSchExpVar("s")); + } + return typing; + } + + private AqlTyping setupTypingWithTransforms( + String t1, String srcI1, String dstI1, String t2, String srcI2, String dstI2) { + AqlTyping typing = new AqlTyping(); + typing.defs.apgts.put("ts", Unit.unit); + typing.defs.apgschemas.put("s", new ApgTyExpVar("ts")); + typing.defs.apgis.put(srcI1, new ApgSchExpVar("s")); + typing.defs.apgis.put(dstI1, new ApgSchExpVar("s")); + typing.defs.apgis.put(srcI2, new ApgSchExpVar("s")); + typing.defs.apgis.put(dstI2, new ApgSchExpVar("s")); + typing.defs.apgms.put(t1, new Pair<>(new ApgInstExpVar(srcI1), new ApgInstExpVar(dstI1))); + typing.defs.apgms.put(t2, new Pair<>(new ApgInstExpVar(srcI2), new ApgInstExpVar(dstI2))); + return typing; + } + + @Nested + class ApgTransExpVarTest { + + @Test + void constructorSetsVar() { + assertEquals("t1", new ApgTransExpVar("t1").var); + } + + @Test + void kindReturnsApgMorphism() { + assertEquals(Kind.APG_morphism, new ApgTransExpVar("t").kind()); + } + + @Test + void isVarReturnsTrue() { + assertTrue(new ApgTransExpVar("t").isVar()); + } + + @Test + void toStringReturnsVar() { + assertEquals("myTrans", new ApgTransExpVar("myTrans").toString()); + } + + @Test + void depsReturnsSingleton() { + Collection> deps = new ApgTransExpVar("t1").deps(); + assertEquals(1, deps.size()); + assertEquals(Kind.APG_morphism, deps.iterator().next().second); + } + + @Test + void equalsSameVar() { + assertEquals(new ApgTransExpVar("t"), new ApgTransExpVar("t")); + assertEquals(new ApgTransExpVar("t").hashCode(), new ApgTransExpVar("t").hashCode()); + } + + @Test + void notEqualsDifferentVar() { + assertNotEquals(new ApgTransExpVar("a"), new ApgTransExpVar("b")); + } + + @Test + void typeThrowsWhenUndefined() { + AqlTyping typing = new AqlTyping(); + assertThrows(RuntimeException.class, () -> new ApgTransExpVar("missing").type(typing)); + } + + @Test + void typeReturnsPairWhenDefined() { + AqlTyping typing = new AqlTyping(); + Pair pair = + new Pair<>(new ApgInstExpVar("i1"), new ApgInstExpVar("i2")); + typing.defs.apgms.put("t1", pair); + assertEquals(pair, new ApgTransExpVar("t1").type(typing)); + } + + @Test + void acceptDelegatesToVisitor() { + ApgTransExpVar v = new ApgTransExpVar("t"); + @SuppressWarnings("unchecked") + ApgTransExp.ApgTransExpVisitor visitor = + mock(ApgTransExp.ApgTransExpVisitor.class); + v.accept(null, visitor); + verify(visitor).visit(null, v); + } + + @Test + void varMethodCreatesNew() { + var newVar = new ApgTransExpVar("t1").Var("t2"); + assertTrue(newVar instanceof ApgTransExpVar); + } + } + + @Nested + class ApgTransExpIdTest { + + @Test + void constructorSetsField() { + ApgInstExpVar g = new ApgInstExpVar("i"); + assertEquals(g, new ApgTransExpId(g).G); + } + + @Test + void equalsSame() { + assertEquals( + new ApgTransExpId(new ApgInstExpVar("i")), new ApgTransExpId(new ApgInstExpVar("i"))); + } + + @Test + void toStringContainsIdentity() { + assertTrue(new ApgTransExpId(new ApgInstExpVar("i")).toString().contains("identity")); + } + + @Test + void typeReturnsSameDomainAndCodomain() { + AqlTyping typing = setupTypingWithInstances("i"); + Pair result = new ApgTransExpId(new ApgInstExpVar("i")).type(typing); + assertEquals(result.first, result.second); + } + } + + @Nested + class ApgTransExpTerminalTest { + + @Test + void constructorSetsField() { + ApgInstExpVar g = new ApgInstExpVar("i"); + assertEquals(g, new ApgTransExpTerminal(g).G); + } + + @Test + void equalsSame() { + assertEquals( + new ApgTransExpTerminal(new ApgInstExpVar("i")), + new ApgTransExpTerminal(new ApgInstExpVar("i"))); + } + + @Test + void toStringContainsUnit() { + assertTrue(new ApgTransExpTerminal(new ApgInstExpVar("i")).toString().contains("unit")); + } + + @Test + void acceptDelegatesToVisitor() { + ApgTransExpTerminal term = new ApgTransExpTerminal(new ApgInstExpVar("i")); + @SuppressWarnings("unchecked") + ApgTransExp.ApgTransExpVisitor visitor = + mock(ApgTransExp.ApgTransExpVisitor.class); + term.accept(null, visitor); + verify(visitor).visit(null, term); + } + } + + @Nested + class ApgTransExpInitialTest { + + @Test + void constructorSetsField() { + ApgInstExpVar g = new ApgInstExpVar("i"); + assertEquals(g, new ApgTransExpInitial(g).G); + } + + @Test + void equalsSame() { + assertEquals( + new ApgTransExpInitial(new ApgInstExpVar("i")), + new ApgTransExpInitial(new ApgInstExpVar("i"))); + } + + @Test + void toStringContainsEmpty() { + assertTrue(new ApgTransExpInitial(new ApgInstExpVar("i")).toString().contains("empty")); + } + } + + @Nested + class ApgTransExpFstTest { + + @Test + void constructorSetsFields() { + ApgInstExpVar g1 = new ApgInstExpVar("i1"); + ApgInstExpVar g2 = new ApgInstExpVar("i2"); + ApgTransExpFst fst = new ApgTransExpFst(g1, g2); + assertEquals(g1, fst.G1); + assertEquals(g2, fst.G2); + } + + @Test + void equalsSame() { + assertEquals( + new ApgTransExpFst(new ApgInstExpVar("a"), new ApgInstExpVar("b")), + new ApgTransExpFst(new ApgInstExpVar("a"), new ApgInstExpVar("b"))); + } + + @Test + void toStringContainsFst() { + assertTrue( + new ApgTransExpFst(new ApgInstExpVar("a"), new ApgInstExpVar("b")) + .toString() + .contains("fst")); + } + + @Test + void typeReturnsTimes() { + AqlTyping typing = setupTypingWithInstances("a", "b"); + Pair result = + new ApgTransExpFst(new ApgInstExpVar("a"), new ApgInstExpVar("b")).type(typing); + assertTrue(result.first instanceof ApgInstExpTimes); + assertEquals(new ApgInstExpVar("a"), result.second); + } + + @Test + void typeThrowsOnDifferentTypesides() { + AqlTyping typing = new AqlTyping(); + typing.defs.apgts.put("ts1", Unit.unit); + typing.defs.apgts.put("ts2", Unit.unit); + typing.defs.apgschemas.put("s1", new ApgTyExpVar("ts1")); + typing.defs.apgschemas.put("s2", new ApgTyExpVar("ts2")); + typing.defs.apgis.put("a", new ApgSchExpVar("s1")); + typing.defs.apgis.put("b", new ApgSchExpVar("s2")); + assertThrows( + RuntimeException.class, + () -> + new ApgTransExpFst(new ApgInstExpVar("a"), new ApgInstExpVar("b")).type(typing)); + } + } + + @Nested + class ApgTransExpSndTest { + + @Test + void constructorSetsFields() { + ApgInstExpVar g1 = new ApgInstExpVar("i1"); + ApgInstExpVar g2 = new ApgInstExpVar("i2"); + ApgTransExpSnd snd = new ApgTransExpSnd(g1, g2); + assertEquals(g1, snd.G1); + assertEquals(g2, snd.G2); + } + + @Test + void typeReturnsSndProjection() { + AqlTyping typing = setupTypingWithInstances("a", "b"); + Pair result = + new ApgTransExpSnd(new ApgInstExpVar("a"), new ApgInstExpVar("b")).type(typing); + assertTrue(result.first instanceof ApgInstExpTimes); + assertEquals(new ApgInstExpVar("b"), result.second); + } + + @Test + void toStringContainsSnd() { + assertTrue( + new ApgTransExpSnd(new ApgInstExpVar("a"), new ApgInstExpVar("b")) + .toString() + .contains("snd")); + } + } + + @Nested + class ApgTransExpPairTest { + + @Test + void constructorSetsFields() { + ApgTransExpVar h1 = new ApgTransExpVar("t1"); + ApgTransExpVar h2 = new ApgTransExpVar("t2"); + ApgTransExpPair pair = new ApgTransExpPair(h1, h2); + assertEquals(h1, pair.h1); + assertEquals(h2, pair.h2); + } + + @Test + void equalsSame() { + assertEquals( + new ApgTransExpPair(new ApgTransExpVar("a"), new ApgTransExpVar("b")), + new ApgTransExpPair(new ApgTransExpVar("a"), new ApgTransExpVar("b"))); + } + + @Test + void typeThrowsOnDomainMismatch() { + AqlTyping typing = setupTypingWithTransforms("t1", "i1", "i2", "t2", "i3", "i4"); + assertThrows( + RuntimeException.class, + () -> + new ApgTransExpPair(new ApgTransExpVar("t1"), new ApgTransExpVar("t2")) + .type(typing)); + } + + @Test + void typeReturnsPairWithTimes() { + AqlTyping typing = setupTypingWithTransforms("t1", "i1", "i2", "t2", "i1", "i3"); + Pair result = + new ApgTransExpPair(new ApgTransExpVar("t1"), new ApgTransExpVar("t2")).type(typing); + assertEquals(new ApgInstExpVar("i1"), result.first); + assertTrue(result.second instanceof ApgInstExpTimes); + } + } + + @Nested + class ApgTransExpInlTest { + + @Test + void typeReturnsInlInjection() { + AqlTyping typing = setupTypingWithInstances("a", "b"); + Pair result = + new ApgTransExpInl(new ApgInstExpVar("a"), new ApgInstExpVar("b")).type(typing); + assertEquals(new ApgInstExpVar("a"), result.first); + assertTrue(result.second instanceof ApgInstExpPlus); + } + + @Test + void toStringContainsInl() { + assertTrue( + new ApgTransExpInl(new ApgInstExpVar("a"), new ApgInstExpVar("b")) + .toString() + .contains("inl")); + } + } + + @Nested + class ApgTransExpInrTest { + + @Test + void typeReturnsInrInjection() { + AqlTyping typing = setupTypingWithInstances("a", "b"); + Pair result = + new ApgTransExpInr(new ApgInstExpVar("a"), new ApgInstExpVar("b")).type(typing); + assertEquals(new ApgInstExpVar("b"), result.first); + assertTrue(result.second instanceof ApgInstExpPlus); + } + + @Test + void toStringContainsInr() { + assertTrue( + new ApgTransExpInr(new ApgInstExpVar("a"), new ApgInstExpVar("b")) + .toString() + .contains("inr")); + } + } + + @Nested + class ApgTransExpCaseTest { + + @Test + void constructorSetsFields() { + ApgTransExpVar h1 = new ApgTransExpVar("t1"); + ApgTransExpVar h2 = new ApgTransExpVar("t2"); + ApgTransExpCase caseExp = new ApgTransExpCase(h1, h2); + assertEquals(h1, caseExp.h1); + assertEquals(h2, caseExp.h2); + } + + @Test + void typeThrowsOnCodomainMismatch() { + AqlTyping typing = setupTypingWithTransforms("t1", "i1", "i2", "t2", "i3", "i4"); + assertThrows( + RuntimeException.class, + () -> + new ApgTransExpCase(new ApgTransExpVar("t1"), new ApgTransExpVar("t2")) + .type(typing)); + } + + @Test + void typeReturnsCaseWithPlus() { + AqlTyping typing = setupTypingWithTransforms("t1", "i1", "i3", "t2", "i2", "i3"); + Pair result = + new ApgTransExpCase(new ApgTransExpVar("t1"), new ApgTransExpVar("t2")).type(typing); + assertTrue(result.first instanceof ApgInstExpPlus); + assertEquals(new ApgInstExpVar("i3"), result.second); + } + + @Test + void toStringContainsPipe() { + assertTrue( + new ApgTransExpCase(new ApgTransExpVar("a"), new ApgTransExpVar("b")) + .toString() + .contains("|")); + } + } + + @Nested + class ApgTransExpComposeTest { + + @Test + void constructorSetsFields() { + ApgTransExpVar h1 = new ApgTransExpVar("t1"); + ApgTransExpVar h2 = new ApgTransExpVar("t2"); + ApgTransExpCompose compose = new ApgTransExpCompose(h1, h2); + assertEquals(h1, compose.h1); + assertEquals(h2, compose.h2); + } + + @Test + void typeThrowsOnIntermediateMismatch() { + AqlTyping typing = setupTypingWithTransforms("t1", "i1", "i2", "t2", "i3", "i4"); + assertThrows( + RuntimeException.class, + () -> + new ApgTransExpCompose(new ApgTransExpVar("t1"), new ApgTransExpVar("t2")) + .type(typing)); + } + + @Test + void typeSucceedsOnMatchingIntermediate() { + AqlTyping typing = setupTypingWithTransforms("t1", "i1", "i2", "t2", "i2", "i3"); + Pair result = + new ApgTransExpCompose(new ApgTransExpVar("t1"), new ApgTransExpVar("t2")).type(typing); + assertEquals(new ApgInstExpVar("i1"), result.first); + assertEquals(new ApgInstExpVar("i3"), result.second); + } + + @Test + void toStringContainsSemicolon() { + assertTrue( + new ApgTransExpCompose(new ApgTransExpVar("a"), new ApgTransExpVar("b")) + .toString() + .contains(";")); + } + } + + @Nested + class ApgTransExpEqualizeTest { + + @Test + void constructorSetsFields() { + ApgTransExpVar h1 = new ApgTransExpVar("t1"); + ApgTransExpVar h2 = new ApgTransExpVar("t2"); + ApgTransExpEqualize eq = new ApgTransExpEqualize(h1, h2); + assertEquals(h1, eq.h1); + assertEquals(h2, eq.h2); + } + + @Test + void equalsSame() { + assertEquals( + new ApgTransExpEqualize(new ApgTransExpVar("a"), new ApgTransExpVar("b")), + new ApgTransExpEqualize(new ApgTransExpVar("a"), new ApgTransExpVar("b"))); + } + + @Test + void typeThrowsOnDomainMismatch() { + AqlTyping typing = setupTypingWithTransforms("t1", "i1", "i3", "t2", "i2", "i3"); + assertThrows( + RuntimeException.class, + () -> + new ApgTransExpEqualize(new ApgTransExpVar("t1"), new ApgTransExpVar("t2")) + .type(typing)); + } + + @Test + void typeReturnsEqualizeResult() { + AqlTyping typing = setupTypingWithTransforms("t1", "i1", "i2", "t2", "i1", "i2"); + Pair result = + new ApgTransExpEqualize(new ApgTransExpVar("t1"), new ApgTransExpVar("t2")).type(typing); + assertTrue(result.first instanceof ApgInstExpEqualize); + assertEquals(new ApgInstExpVar("i1"), result.second); + } + + @Test + void toStringContainsEqualize() { + assertTrue( + new ApgTransExpEqualize(new ApgTransExpVar("a"), new ApgTransExpVar("b")) + .toString() + .contains("equalize")); + } + } + + @Nested + class ApgTransExpEqualizeUTest { + + @Test + void constructorSetsFields() { + ApgTransExpVar h1 = new ApgTransExpVar("t1"); + ApgTransExpVar h2 = new ApgTransExpVar("t2"); + ApgTransExpVar h = new ApgTransExpVar("t3"); + ApgTransExpEqualizeU equ = new ApgTransExpEqualizeU(h1, h2, h); + assertEquals(h1, equ.h1); + assertEquals(h2, equ.h2); + assertEquals(h, equ.h); + } + + @Test + void equalsSame() { + assertEquals( + new ApgTransExpEqualizeU( + new ApgTransExpVar("a"), new ApgTransExpVar("b"), new ApgTransExpVar("c")), + new ApgTransExpEqualizeU( + new ApgTransExpVar("a"), new ApgTransExpVar("b"), new ApgTransExpVar("c"))); + } + + @Test + void toStringContainsEqualizeU() { + assertTrue( + new ApgTransExpEqualizeU( + new ApgTransExpVar("a"), new ApgTransExpVar("b"), new ApgTransExpVar("c")) + .toString() + .contains("equalize_u")); + } + } + + @Nested + class ApgTransExpCoEqualizeTest { + + @Test + void constructorSetsFields() { + ApgTransExpVar h1 = new ApgTransExpVar("t1"); + ApgTransExpVar h2 = new ApgTransExpVar("t2"); + ApgTransExpCoEqualize ceq = new ApgTransExpCoEqualize(h1, h2); + assertEquals(h1, ceq.h1); + assertEquals(h2, ceq.h2); + } + + @Test + void typeReturnsCoEqualizeResult() { + AqlTyping typing = setupTypingWithTransforms("t1", "i1", "i2", "t2", "i1", "i2"); + Pair result = + new ApgTransExpCoEqualize(new ApgTransExpVar("t1"), new ApgTransExpVar("t2")) + .type(typing); + assertEquals(new ApgInstExpVar("i2"), result.first); + assertTrue(result.second instanceof ApgInstExpCoEqualize); + } + + @Test + void toStringContainsCoequalize() { + assertTrue( + new ApgTransExpCoEqualize(new ApgTransExpVar("a"), new ApgTransExpVar("b")) + .toString() + .contains("coequalize")); + } + } + + @Nested + class ApgTransExpCoEqualizeUTest { + + @Test + void constructorSetsFields() { + ApgTransExpVar h1 = new ApgTransExpVar("t1"); + ApgTransExpVar h2 = new ApgTransExpVar("t2"); + ApgTransExpVar h = new ApgTransExpVar("t3"); + ApgTransExpCoEqualizeU cequ = new ApgTransExpCoEqualizeU(h1, h2, h); + assertEquals(h1, cequ.h1); + assertEquals(h2, cequ.h2); + assertEquals(h, cequ.h); + } + + @Test + void toStringContainsCoequalizeU() { + assertTrue( + new ApgTransExpCoEqualizeU( + new ApgTransExpVar("a"), new ApgTransExpVar("b"), new ApgTransExpVar("c")) + .toString() + .contains("coequalize_u")); + } + } + + @Nested + class ApgTransExpDeltaTest { + + @Test + void constructorSetsFields() { + ApgMapExpVar f = new ApgMapExpVar("m"); + ApgTransExpVar h = new ApgTransExpVar("t"); + ApgTransExpDelta delta = new ApgTransExpDelta(f, h); + assertEquals(f, delta.F); + assertEquals(h, delta.h); + } + + @Test + void equalsSame() { + assertEquals( + new ApgTransExpDelta(new ApgMapExpVar("m"), new ApgTransExpVar("t")), + new ApgTransExpDelta(new ApgMapExpVar("m"), new ApgTransExpVar("t"))); + } + + @Test + void toStringContainsDelta() { + assertTrue( + new ApgTransExpDelta(new ApgMapExpVar("m"), new ApgTransExpVar("t")) + .toString() + .contains("delta")); + } + + @Test + void depsUnionsBoth() { + ApgTransExpDelta delta = + new ApgTransExpDelta(new ApgMapExpVar("m"), new ApgTransExpVar("t")); + assertEquals(2, delta.deps().size()); + } + } + + @Test + void optionsReturnsEmptyMap() { + assertTrue(new ApgTransExpVar("x").options().isEmpty()); + } +} diff --git a/src/test/java/catdata/apg/exp/ApgTyExpTest.java b/src/test/java/catdata/apg/exp/ApgTyExpTest.java new file mode 100644 index 0000000..f31400c --- /dev/null +++ b/src/test/java/catdata/apg/exp/ApgTyExpTest.java @@ -0,0 +1,275 @@ +package catdata.apg.exp; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import catdata.LocStr; +import catdata.Pair; +import catdata.Unit; +import catdata.apg.exp.ApgTyExp.ApgTyExpRaw; +import catdata.apg.exp.ApgTyExp.ApgTyExpVar; +import catdata.cql.Kind; +import catdata.cql.exp.AqlTyping; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ApgTyExpTest { + + @Nested + class ApgTyExpVarTest { + + @Test + void constructorSetsVar() { + ApgTyExpVar v = new ApgTyExpVar("myVar"); + assertEquals("myVar", v.var); + } + + @Test + void kindReturnsApgTypeside() { + ApgTyExpVar v = new ApgTyExpVar("x"); + assertEquals(Kind.APG_typeside, v.kind()); + } + + @Test + void isVarReturnsTrue() { + ApgTyExpVar v = new ApgTyExpVar("x"); + assertTrue(v.isVar()); + } + + @Test + void toStringReturnsVar() { + ApgTyExpVar v = new ApgTyExpVar("myTypeside"); + assertEquals("myTypeside", v.toString()); + } + + @Test + void depsReturnsSingletonWithCorrectKind() { + ApgTyExpVar v = new ApgTyExpVar("ts1"); + Collection> deps = v.deps(); + assertEquals(1, deps.size()); + Pair dep = deps.iterator().next(); + assertEquals("ts1", dep.first); + assertEquals(Kind.APG_typeside, dep.second); + } + + @Test + void equalsSameVar() { + ApgTyExpVar v1 = new ApgTyExpVar("x"); + ApgTyExpVar v2 = new ApgTyExpVar("x"); + assertEquals(v1, v2); + assertEquals(v1.hashCode(), v2.hashCode()); + } + + @Test + void notEqualsDifferentVar() { + ApgTyExpVar v1 = new ApgTyExpVar("x"); + ApgTyExpVar v2 = new ApgTyExpVar("y"); + assertNotEquals(v1, v2); + } + + @Test + void equalsReflexive() { + ApgTyExpVar v = new ApgTyExpVar("x"); + assertEquals(v, v); + } + + @Test + void notEqualsNull() { + ApgTyExpVar v = new ApgTyExpVar("x"); + assertNotEquals(null, v); + } + + @Test + void notEqualsDifferentType() { + ApgTyExpVar v = new ApgTyExpVar("x"); + assertNotEquals("x", v); + } + + @Test + void typeReturnsUnitWhenDefined() { + AqlTyping typing = new AqlTyping(); + typing.defs.apgts.put("x", Unit.unit); + ApgTyExpVar v = new ApgTyExpVar("x"); + assertEquals(Unit.unit, v.type(typing)); + } + + @Test + void typeThrowsWhenUndefined() { + AqlTyping typing = new AqlTyping(); + ApgTyExpVar v = new ApgTyExpVar("missing"); + assertThrows(RuntimeException.class, () -> v.type(typing)); + } + + @Test + void acceptDelegatesToVisitor() throws Exception { + ApgTyExpVar v = new ApgTyExpVar("x"); + @SuppressWarnings("unchecked") + ApgTyExp.ApgTyExpVisitor visitor = + mock(ApgTyExp.ApgTyExpVisitor.class); + v.accept(null, visitor); + verify(visitor).visit(null, v); + } + + @Test + void coAcceptDelegatesToCoVisitor() throws Exception { + ApgTyExpVar v = new ApgTyExpVar("x"); + @SuppressWarnings("unchecked") + ApgTyExp.ApgTyExpCoVisitor coVisitor = + mock(ApgTyExp.ApgTyExpCoVisitor.class); + v.coaccept(null, coVisitor, "result"); + verify(coVisitor).visitApgTyExpVar(null, "result"); + } + + @Test + void varMethodCreatesNewVar() { + ApgTyExpVar v = new ApgTyExpVar("x"); + var newVar = v.Var("y"); + assertNotNull(newVar); + assertTrue(newVar instanceof ApgTyExpVar); + assertEquals("y", ((ApgTyExpVar) newVar).var); + } + + @Test + void optionsReturnsEmptyMap() { + ApgTyExpVar v = new ApgTyExpVar("x"); + assertTrue(v.options().isEmpty()); + } + } + + @Nested + class ApgTyExpRawTest { + + private ApgTyExpRaw createEmptyRaw() { + return new ApgTyExpRaw(Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + } + + private ApgTyExpRaw createRawWithType(String name, String className, String parseFn) { + List>> types = + List.of(new Pair<>(new LocStr(0, name), new Pair<>(className, parseFn))); + return new ApgTyExpRaw(Collections.emptyList(), types, Collections.emptyList()); + } + + @Test + void constructorSetsFields() { + ApgTyExpRaw raw = createEmptyRaw(); + assertNotNull(raw.imports); + assertNotNull(raw.types); + assertNotNull(raw.udfs); + assertTrue(raw.imports.isEmpty()); + assertTrue(raw.types.isEmpty()); + assertTrue(raw.udfs.isEmpty()); + } + + @Test + void constructorWithTypes() { + ApgTyExpRaw raw = createRawWithType("Int", "java.lang.Integer", "parseInt"); + assertEquals(1, raw.types.size()); + assertTrue(raw.types.containsKey("Int")); + assertEquals("java.lang.Integer", raw.types.get("Int").first); + } + + @Test + void kindReturnsApgTypeside() { + ApgTyExpRaw raw = createEmptyRaw(); + assertEquals(Kind.APG_typeside, raw.kind()); + } + + @Test + void depsEmptyWhenNoImports() { + ApgTyExpRaw raw = createEmptyRaw(); + assertTrue(raw.deps().isEmpty()); + } + + @Test + void depsIncludesImportDeps() { + ApgTyExpVar importVar = new ApgTyExpVar("imported"); + ApgTyExpRaw raw = + new ApgTyExpRaw(List.of(importVar), Collections.emptyList(), Collections.emptyList()); + Collection> deps = raw.deps(); + assertEquals(1, deps.size()); + assertEquals("imported", deps.iterator().next().first); + } + + @Test + void equalsSameContent() { + ApgTyExpRaw r1 = createEmptyRaw(); + ApgTyExpRaw r2 = createEmptyRaw(); + assertEquals(r1, r2); + assertEquals(r1.hashCode(), r2.hashCode()); + } + + @Test + void notEqualsDifferentTypes() { + ApgTyExpRaw r1 = createEmptyRaw(); + ApgTyExpRaw r2 = createRawWithType("Int", "java.lang.Integer", "parseInt"); + assertNotEquals(r1, r2); + } + + @Test + void equalsReflexive() { + ApgTyExpRaw r = createEmptyRaw(); + assertEquals(r, r); + } + + @Test + void notEqualsNull() { + ApgTyExpRaw r = createEmptyRaw(); + assertNotEquals(null, r); + } + + @Test + void toStringContainsLiteral() { + ApgTyExpRaw raw = createEmptyRaw(); + assertTrue(raw.toString().contains("literal")); + } + + @Test + void rawReturnsNonNullMap() { + ApgTyExpRaw raw = createEmptyRaw(); + assertNotNull(raw.raw()); + } + + @Test + void acceptDelegatesToVisitor() throws Exception { + ApgTyExpRaw raw = createEmptyRaw(); + @SuppressWarnings("unchecked") + ApgTyExp.ApgTyExpVisitor visitor = + mock(ApgTyExp.ApgTyExpVisitor.class); + raw.accept(null, visitor); + verify(visitor).visit(null, raw); + } + + @Test + void coAcceptDelegatesToCoVisitor() throws Exception { + ApgTyExpRaw raw = createEmptyRaw(); + @SuppressWarnings("unchecked") + ApgTyExp.ApgTyExpCoVisitor coVisitor = + mock(ApgTyExp.ApgTyExpCoVisitor.class); + raw.coaccept(null, coVisitor, "result"); + verify(coVisitor).visitApgTyExpRaw(null, "result"); + } + + @Test + void isVarReturnsFalse() { + ApgTyExpRaw raw = createEmptyRaw(); + assertFalse(raw.isVar()); + } + } + + @Test + void baseTypeReturnsUnit() { + ApgTyExpVar v = new ApgTyExpVar("x"); + AqlTyping typing = new AqlTyping(); + typing.defs.apgts.put("x", Unit.unit); + assertEquals(Unit.unit, v.type(typing)); + } +}