From 16a9b534adec2c53b50f92a43c1623918b1c59c0 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Thu, 7 Mar 2024 22:10:42 +0100 Subject: refactor: move terms and DNF into logic subproject --- .../dnf/DnfBuilderLiteralEliminationTest.java | 257 ++++++++++++++++ .../dnf/DnfBuilderVariableUnificationTest.java | 322 +++++++++++++++++++++ .../logic/dnf/DnfToDefinitionStringTest.java | 154 ++++++++++ .../tools/refinery/logic/dnf/HashCodeTest.java | 64 ++++ .../refinery/logic/dnf/TopologicalSortTest.java | 111 +++++++ .../refinery/logic/dnf/VariableDirectionTest.java | 247 ++++++++++++++++ .../logic/literal/AggregationLiteralTest.java | 89 ++++++ .../refinery/logic/literal/CallLiteralTest.java | 94 ++++++ .../logic/rewriter/DuplicateDnfRemoverTest.java | 162 +++++++++++ .../logic/rewriter/InputParameterResolverTest.java | 225 ++++++++++++++ .../refinery/logic/term/TermSubstitutionTest.java | 97 +++++++ .../logic/term/bool/BoolTermsEvaluateTest.java | 76 +++++ .../CardinalityIntervalTest.java | 127 ++++++++ .../CardinalityIntervalsTest.java | 27 ++ .../EmptyCardinalityIntervalTest.java | 19 ++ .../FiniteCardinalityIntervalTest.java | 27 ++ .../logic/term/int_/IntTermsEvaluateTest.java | 260 +++++++++++++++++ .../logic/term/real/RealTermEvaluateTest.java | 239 +++++++++++++++ .../FiniteUpperCardinalityTest.java | 17 ++ .../uppercardinality/UpperCardinalitiesTest.java | 30 ++ .../UpperCardinalitySumAggregatorStreamTest.java | 54 ++++ .../UpperCardinalitySumAggregatorTest.java | 79 +++++ .../UpperCardinalityTermsEvaluateTest.java | 103 +++++++ .../uppercardinality/UpperCardinalityTest.java | 115 ++++++++ .../refinery/logic/tests/FakeFunctionView.java | 57 ++++ .../refinery/logic/tests/FakeKeyOnlyView.java | 21 ++ .../logic/tests/StructurallyEqualToRawTest.java | 155 ++++++++++ .../logic/tests/StructurallyEqualToTest.java | 123 ++++++++ 28 files changed, 3351 insertions(+) create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfBuilderLiteralEliminationTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfBuilderVariableUnificationTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfToDefinitionStringTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/dnf/HashCodeTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/dnf/TopologicalSortTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/dnf/VariableDirectionTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/literal/AggregationLiteralTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/literal/CallLiteralTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/rewriter/DuplicateDnfRemoverTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/rewriter/InputParameterResolverTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/term/TermSubstitutionTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/term/bool/BoolTermsEvaluateTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervalTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervalsTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/EmptyCardinalityIntervalTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/FiniteCardinalityIntervalTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/term/int_/IntTermsEvaluateTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/term/real/RealTermEvaluateTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/FiniteUpperCardinalityTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitiesTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregatorStreamTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregatorTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTermsEvaluateTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/tests/FakeFunctionView.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/tests/FakeKeyOnlyView.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/tests/StructurallyEqualToRawTest.java create mode 100644 subprojects/logic/src/test/java/tools/refinery/logic/tests/StructurallyEqualToTest.java (limited to 'subprojects/logic/src/test') diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfBuilderLiteralEliminationTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfBuilderLiteralEliminationTest.java new file mode 100644 index 00000000..d5a9ccad --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfBuilderLiteralEliminationTest.java @@ -0,0 +1,257 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import tools.refinery.logic.Constraint; +import tools.refinery.logic.literal.BooleanLiteral; +import tools.refinery.logic.term.NodeVariable; +import tools.refinery.logic.term.ParameterDirection; +import tools.refinery.logic.term.Variable; +import tools.refinery.logic.term.bool.BoolTerms; +import tools.refinery.logic.tests.FakeKeyOnlyView; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static tools.refinery.logic.literal.Literals.check; +import static tools.refinery.logic.literal.Literals.not; +import static tools.refinery.logic.tests.QueryMatchers.structurallyEqualTo; + +class DnfBuilderLiteralEliminationTest { + private final Constraint friendView = new FakeKeyOnlyView("friend", 2); + private final NodeVariable p = Variable.of("p"); + private final NodeVariable q = Variable.of("q"); + private final Dnf trueDnf = Dnf.builder().parameter(p, ParameterDirection.IN).clause().build(); + private final Dnf falseDnf = Dnf.builder().parameter(p).build(); + + @Test + void eliminateTrueTest() { + var actual = Dnf.builder() + .parameters(p, q) + .clause(BooleanLiteral.TRUE, friendView.call(p, q)) + .build(); + var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @Test + void eliminateTrueAssumptionTest() { + var actual = Dnf.builder() + .parameters(p, q) + .clause(check(BoolTerms.constant(true)), friendView.call(p, q)) + .build(); + var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @Test + void eliminateFalseTest() { + var actual = Dnf.builder() + .parameters(p, q) + .clause(friendView.call(p, q)) + .clause(friendView.call(q, p), BooleanLiteral.FALSE) + .build(); + var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @ParameterizedTest + @CsvSource(value = { + "false", + "null" + }, nullValues = "null") + void eliminateFalseAssumptionTest(Boolean value) { + var actual = Dnf.builder() + .parameters(p, q) + .clause(friendView.call(p, q)) + .clause(friendView.call(q, p), check(BoolTerms.constant(value))) + .build(); + var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @Test + void alwaysTrueTest() { + var actual = Dnf.builder() + .parameters(List.of(p, q), ParameterDirection.IN) + .clause(friendView.call(p, q)) + .clause(BooleanLiteral.TRUE) + .build(); + var expected = Dnf.builder().parameters(List.of(p, q), ParameterDirection.IN).clause().build(); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @Test + void alwaysFalseTest() { + var actual = Dnf.builder() + .parameters(p, q) + .clause(friendView.call(p, q), BooleanLiteral.FALSE) + .build(); + var expected = Dnf.builder().parameters(p, q).build(); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @Test + void eliminateTrueDnfTest() { + var actual = Dnf.builder() + .parameters(p, q) + .clause(trueDnf.call(q), friendView.call(p, q)) + .build(); + var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @Test + void eliminateFalseDnfTest() { + var actual = Dnf.builder() + .parameters(p, q) + .clause(friendView.call(p, q)) + .clause(friendView.call(q, p), falseDnf.call(q)) + .build(); + var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @Test + void alwaysTrueDnfTest() { + var actual = Dnf.builder() + .parameters(List.of(p, q), ParameterDirection.IN) + .clause(friendView.call(p, q)) + .clause(trueDnf.call(q)) + .build(); + var expected = Dnf.builder().parameters(List.of(p, q), ParameterDirection.IN).clause().build(); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @Test + void alwaysFalseDnfTest() { + var actual = Dnf.builder() + .parameters(p, q) + .clause(friendView.call(p, q), falseDnf.call(q)) + .build(); + var expected = Dnf.builder().parameters(p, q).build(); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @Test + void eliminateNotFalseDnfTest() { + var actual = Dnf.builder() + .parameters(p, q) + .clause(not(falseDnf.call(q)), friendView.call(p, q)) + .build(); + var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @Test + void eliminateNotTrueDnfTest() { + var actual = Dnf.builder() + .parameters(p, q) + .clause(friendView.call(p, q)) + .clause(friendView.call(q, p), not(trueDnf.call(q))) + .build(); + var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @Test + void alwaysNotFalseDnfTest() { + var actual = Dnf.builder() + .parameters(List.of(p, q), ParameterDirection.IN) + .clause(friendView.call(p, q)) + .clause(not(falseDnf.call(q))) + .build(); + var expected = Dnf.builder().parameters(List.of(p, q), ParameterDirection.IN).clause().build(); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @Test + void alwaysNotTrueDnfTest() { + var actual = Dnf.builder() + .parameters(p, q) + .clause(friendView.call(p, q), not(trueDnf.call(q))) + .build(); + var expected = Dnf.builder().parameters(p, q).build(); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @Test + void removeDuplicateTest() { + var actual = Dnf.of(builder -> builder.clause((p, q) -> List.of( + friendView.call(p, q), + friendView.call(p, q) + ))); + var expected = Dnf.of(builder -> builder.clause((p, q) -> List.of(friendView.call(p, q)))); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @Test + void removeContradictoryTest() { + var actual = Dnf.of(builder -> builder.clause((p, q) -> List.of( + friendView.call(p, q), + not(friendView.call(p, q)) + ))); + var expected = Dnf.builder().build(); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @Test + void removeContradictoryUniversalTest() { + var actual = Dnf.of(builder -> builder.clause((p, q) -> List.of( + friendView.call(q, q), + friendView.call(p, q), + not(friendView.call(p, Variable.of())) + ))); + var expected = Dnf.builder().build(); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @Test + void removeContradictoryExistentialUniversalTest() { + var actual = Dnf.of(builder -> builder.clause((p) -> List.of( + friendView.call(p, Variable.of()), + not(friendView.call(p, Variable.of())) + ))); + var expected = Dnf.builder().build(); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @Test + void removeContradictoryUniversalParameterTest() { + var actual = Dnf.of(builder -> { + var p = builder.parameter("p"); + builder.clause((q) -> List.of( + friendView.call(q, q), + friendView.call(p, q), + not(friendView.call(p, Variable.of())) + )); + }); + var expected = Dnf.builder().parameter(p).build(); + + assertThat(actual, structurallyEqualTo(expected)); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfBuilderVariableUnificationTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfBuilderVariableUnificationTest.java new file mode 100644 index 00000000..0e1f77e2 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfBuilderVariableUnificationTest.java @@ -0,0 +1,322 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import org.junit.jupiter.api.Test; +import tools.refinery.logic.Constraint; +import tools.refinery.logic.term.ParameterDirection; +import tools.refinery.logic.term.Variable; +import tools.refinery.logic.tests.FakeKeyOnlyView; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static tools.refinery.logic.tests.QueryMatchers.structurallyEqualTo; + +class DnfBuilderVariableUnificationTest { + private final Constraint friendView = new FakeKeyOnlyView("friend", 2); + private final Constraint childrenView = new FakeKeyOnlyView("children", 2); + + @Test + void equalToParameterTest() { + var actual = Dnf.of(builder -> { + var p = builder.parameter("p"); + builder.clause(q -> List.of( + friendView.call(p, q), + p.isEquivalent(q) + )); + }); + + var expectedP = Variable.of("p"); + assertThat(actual, structurallyEqualTo( + List.of(new SymbolicParameter(expectedP, ParameterDirection.OUT)), + List.of( + List.of(friendView.call(expectedP, expectedP)) + ) + )); + } + + @Test + void equalToParameterReverseTest() { + var actual = Dnf.of(builder -> { + var p = builder.parameter("p"); + builder.clause(q -> List.of( + friendView.call(p, q), + q.isEquivalent(p) + )); + }); + + var expectedP = Variable.of("p"); + assertThat(actual, structurallyEqualTo( + List.of(new SymbolicParameter(expectedP, ParameterDirection.OUT)), + List.of( + List.of(friendView.call(expectedP, expectedP)) + ) + )); + } + + @Test + void equalQuantifiedTest() { + var actual = Dnf.of(builder -> builder.clause((p, q) -> List.of( + friendView.call(p, q), + p.isEquivalent(q) + ))); + + var expectedP = Variable.of("p"); + assertThat(actual, structurallyEqualTo( + List.of(), + List.of( + List.of(friendView.call(expectedP, expectedP)) + ) + )); + } + + @Test + void equalQuantifiedTransitiveTest() { + var actual = Dnf.of(builder -> builder.clause((p, q, r) -> List.of( + friendView.call(p, q), + p.isEquivalent(q), + childrenView.call(p, r), + q.isEquivalent(r) + ))); + + var expectedP = Variable.of("p"); + assertThat(actual, structurallyEqualTo( + List.of(), + List.of( + List.of(friendView.call(expectedP, expectedP), childrenView.call(expectedP, expectedP)) + ) + )); + } + + @Test + void equalQuantifiedTransitiveRemoveDuplicateTest() { + var actual = Dnf.of(builder -> builder.clause((p, q, r) -> List.of( + friendView.call(p, q), + p.isEquivalent(q), + friendView.call(p, r), + q.isEquivalent(r) + ))); + + var expectedP = Variable.of("p"); + assertThat(actual, structurallyEqualTo( + List.of(), + List.of( + List.of(friendView.call(expectedP, expectedP)) + ) + )); + } + + @Test + void parametersEqualTest() { + var actual = Dnf.of(builder -> { + var p = builder.parameter("p"); + var q = builder.parameter("q"); + builder.clause( + friendView.call(p, q), + p.isEquivalent(q) + ); + }); + + var expectedP = Variable.of("p"); + var expectedQ = Variable.of("q"); + assertThat(actual, structurallyEqualTo( + List.of( + new SymbolicParameter(expectedP, ParameterDirection.OUT), + new SymbolicParameter(expectedQ, ParameterDirection.OUT) + ), + List.of( + List.of(friendView.call(expectedP, expectedP), expectedQ.isEquivalent(expectedP)) + ) + )); + } + + @Test + void parametersEqualTransitiveTest() { + var actual = Dnf.of(builder -> { + var p = builder.parameter("p"); + var q = builder.parameter("q"); + var r = builder.parameter("r"); + builder.clause( + friendView.call(p, q), + childrenView.call(p, r), + p.isEquivalent(q), + r.isEquivalent(q) + ); + }); + + var expectedP = Variable.of("p"); + var expectedQ = Variable.of("q"); + var expectedR = Variable.of("r"); + assertThat(actual, structurallyEqualTo( + List.of( + new SymbolicParameter(expectedP, ParameterDirection.OUT), + new SymbolicParameter(expectedQ, ParameterDirection.OUT), + new SymbolicParameter(expectedR, ParameterDirection.OUT) + ), + List.of( + List.of( + friendView.call(expectedP, expectedP), + expectedQ.isEquivalent(expectedP), + expectedR.isEquivalent(expectedP), + childrenView.call(expectedP, expectedP) + ) + ) + )); + } + + @Test + void parameterAndQuantifiedEqualsTest() { + var actual = Dnf.of(builder -> { + var p = builder.parameter("p"); + var q = builder.parameter("q"); + builder.clause((r) -> List.of( + friendView.call(p, r), + p.isEquivalent(r), + childrenView.call(q, r), + q.isEquivalent(r) + )); + }); + + + var expectedP = Variable.of("p"); + var expectedQ = Variable.of("q"); + assertThat(actual, structurallyEqualTo( + List.of( + new SymbolicParameter(expectedP, ParameterDirection.OUT), + new SymbolicParameter(expectedQ, ParameterDirection.OUT) + ), + List.of( + List.of( + friendView.call(expectedP, expectedP), + expectedQ.isEquivalent(expectedP), + childrenView.call(expectedP, expectedP) + ) + ) + )); + } + + @Test + void parameterAndQuantifiedEqualsReverseFirstTest() { + var actual = Dnf.of(builder -> { + var p = builder.parameter("p"); + var q = builder.parameter("q"); + builder.clause((r) -> List.of( + friendView.call(p, r), + r.isEquivalent(p), + childrenView.call(q, r), + q.isEquivalent(r) + )); + }); + + var expectedP = Variable.of("p"); + var expectedQ = Variable.of("q"); + assertThat(actual, structurallyEqualTo( + List.of( + new SymbolicParameter(expectedP, ParameterDirection.OUT), + new SymbolicParameter(expectedQ, ParameterDirection.OUT) + ), + List.of( + List.of( + friendView.call(expectedP, expectedP), + expectedQ.isEquivalent(expectedP), + childrenView.call(expectedP, expectedP) + ) + ) + )); + } + + @Test + void parameterAndQuantifiedEqualsReverseSecondTest() { + var actual = Dnf.of(builder -> { + var p = builder.parameter("p"); + var q = builder.parameter("q"); + builder.clause((r) -> List.of( + friendView.call(p, r), + p.isEquivalent(r), + childrenView.call(q, r), + r.isEquivalent(q) + )); + }); + + var expectedP = Variable.of("p"); + var expectedQ = Variable.of("q"); + assertThat(actual, structurallyEqualTo( + List.of( + new SymbolicParameter(expectedP, ParameterDirection.OUT), + new SymbolicParameter(expectedQ, ParameterDirection.OUT) + ), + List.of( + List.of( + friendView.call(expectedP, expectedP), + expectedQ.isEquivalent(expectedP), + childrenView.call(expectedP, expectedP) + ) + ) + )); + } + + @Test + void parameterAndQuantifiedEqualsReverseBoth() { + var actual = Dnf.of(builder -> { + var p = builder.parameter("p"); + var q = builder.parameter("q"); + builder.clause((r) -> List.of( + friendView.call(p, r), + p.isEquivalent(r), + childrenView.call(q, r), + r.isEquivalent(q) + )); + }); + + var expectedP = Variable.of("p"); + var expectedQ = Variable.of("q"); + assertThat(actual, structurallyEqualTo( + List.of( + new SymbolicParameter(expectedP, ParameterDirection.OUT), + new SymbolicParameter(expectedQ, ParameterDirection.OUT) + ), + List.of( + List.of( + friendView.call(expectedP, expectedP), + expectedQ.isEquivalent(expectedP), + childrenView.call(expectedP, expectedP) + ) + ) + )); + } + + @Test + void parameterAndTwoQuantifiedEqualsTest() { + var actual = Dnf.of(builder -> { + var p = builder.parameter("p"); + var q = builder.parameter("q"); + builder.clause((r, s) -> List.of( + r.isEquivalent(s), + friendView.call(p, r), + p.isEquivalent(r), + childrenView.call(q, s), + q.isEquivalent(s) + )); + }); + + var expectedP = Variable.of("p"); + var expectedQ = Variable.of("q"); + assertThat(actual, structurallyEqualTo( + List.of( + new SymbolicParameter(expectedP, ParameterDirection.OUT), + new SymbolicParameter(expectedQ, ParameterDirection.OUT) + ), + List.of( + List.of( + friendView.call(expectedP, expectedP), + expectedQ.isEquivalent(expectedP), + childrenView.call(expectedP, expectedP) + ) + ) + )); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfToDefinitionStringTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfToDefinitionStringTest.java new file mode 100644 index 00000000..dd624548 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfToDefinitionStringTest.java @@ -0,0 +1,154 @@ +/* + * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import org.junit.jupiter.api.Test; +import tools.refinery.logic.Constraint; +import tools.refinery.logic.term.NodeVariable; +import tools.refinery.logic.term.ParameterDirection; +import tools.refinery.logic.term.Variable; +import tools.refinery.logic.tests.FakeKeyOnlyView; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static tools.refinery.logic.literal.Literals.not; + +class DnfToDefinitionStringTest { + private static final Constraint personView = new FakeKeyOnlyView("person", 1); + private static final Constraint friendView = new FakeKeyOnlyView("friend", 2); + private static final NodeVariable p = Variable.of("p"); + private static final NodeVariable q = Variable.of("q"); + + @Test + void noClausesTest() { + var dnf = Dnf.builder("Example").parameter(p).build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example(p) <-> + . + """)); + } + + @Test + void noParametersTest() { + var dnf = Dnf.builder("Example").build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example() <-> + . + """)); + } + + @Test + void emptyClauseTest() { + var dnf = Dnf.builder("Example").parameter(p, ParameterDirection.IN).clause().build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example(in p) <-> + . + """)); + } + + @Test + void relationViewPositiveTest() { + var dnf = Dnf.builder("Example").parameter(p).clause(friendView.call(p, q)).build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example(p) <-> + friend(p, q). + """)); + } + + @Test + void relationViewNegativeTest() { + var dnf = Dnf.builder("Example") + .parameter(p, ParameterDirection.IN) + .clause(not(friendView.call(p, q))) + .build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example(in p) <-> + !(friend(p, q)). + """)); + } + + @Test + void relationViewTransitiveTest() { + var dnf = Dnf.builder("Example").parameter(p).clause(friendView.callTransitive(p, q)).build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example(p) <-> + friend+(p, q). + """)); + } + + @Test + void multipleParametersTest() { + var dnf = Dnf.builder("Example").parameters(p, q).clause(friendView.call(p, q)).build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example(p, q) <-> + friend(p, q). + """)); + } + + @Test + void multipleLiteralsTest() { + var dnf = Dnf.builder("Example") + .parameter(p) + .clause( + personView.call(p), + personView.call(q), + friendView.call(p, q) + ) + .build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example(p) <-> + person(p), + person(q), + friend(p, q). + """)); + } + + @Test + void multipleClausesTest() { + var dnf = Dnf.builder("Example") + .parameter(p) + .clause(friendView.call(p, q)) + .clause(friendView.call(q, p)) + .build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example(p) <-> + friend(p, q) + ; + friend(q, p). + """)); + } + + @Test + void dnfTest() { + var r = Variable.of("r"); + var s = Variable.of("s"); + var called = Dnf.builder("Called").parameters(r, s).clause(friendView.call(r, s)).build(); + var dnf = Dnf.builder("Example") + .parameter(p) + .clause( + personView.call(p), + personView.call(q), + not(called.call(p, q)) + ) + .build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example(p) <-> + person(p), + person(q), + !(@Dnf Called(p, q)). + """)); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/dnf/HashCodeTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/HashCodeTest.java new file mode 100644 index 00000000..e140be1e --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/HashCodeTest.java @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import org.junit.jupiter.api.Test; +import tools.refinery.logic.Constraint; +import tools.refinery.logic.term.NodeVariable; +import tools.refinery.logic.term.Variable; +import tools.refinery.logic.tests.FakeKeyOnlyView; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + +class HashCodeTest { + private static final Constraint personView = new FakeKeyOnlyView("Person", 1); + private static final Constraint friendView = new FakeKeyOnlyView("friend", 2); + private static final NodeVariable p = Variable.of("p"); + private static final NodeVariable q = Variable.of("q"); + + @Test + void flatEqualsTest() { + var expected = Dnf.builder("Expected").parameters(q).clause(personView.call(q)).build(); + var actual = Dnf.builder("Actual").parameters(p).clause(personView.call(p)).build(); + + assertThat(actual.hashCodeWithSubstitution(), is(expected.hashCodeWithSubstitution())); + } + + @Test + void flatNotEqualsTest() { + var expected = Dnf.builder("Expected").parameters(q).clause(friendView.call(q, q)).build(); + var actual = Dnf.builder("Actual").parameters(p).clause(friendView.call(p, q)).build(); + + assertThat(actual.hashCodeWithSubstitution(), not(expected.hashCodeWithSubstitution())); + } + + @Test + void deepEqualsTest() { + var expected2 = Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build(); + var expected = Dnf.builder("Expected").parameters(q).clause( + expected2.call(q) + ).build(); + var actual = Dnf.builder("Actual").parameters(q).clause( + expected2.call(q) + ).build(); + + assertThat(actual.hashCodeWithSubstitution(), is(expected.hashCodeWithSubstitution())); + } + + @Test + void deepNotEqualsTest() { + var expected = Dnf.builder("Expected").parameters(q).clause( + Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build().call(q) + ).build(); + var actual = Dnf.builder("Actual").parameters(q).clause( + Dnf.builder("Actual2").parameters(p).clause(personView.call(p)).build().call(q) + ).build(); + + assertThat(actual.hashCodeWithSubstitution(), not(expected.hashCodeWithSubstitution())); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/dnf/TopologicalSortTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/TopologicalSortTest.java new file mode 100644 index 00000000..8ea27cc9 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/TopologicalSortTest.java @@ -0,0 +1,111 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import org.junit.jupiter.api.Test; +import tools.refinery.logic.Constraint; +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.term.NodeVariable; +import tools.refinery.logic.term.ParameterDirection; +import tools.refinery.logic.term.Variable; +import tools.refinery.logic.tests.FakeKeyOnlyView; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static tools.refinery.logic.literal.Literals.not; +import static tools.refinery.logic.tests.QueryMatchers.structurallyEqualTo; + +class TopologicalSortTest { + private static final Constraint friendView = new FakeKeyOnlyView("friend", 2); + private static final Dnf example = Dnf.of("example", builder -> { + var a = builder.parameter("a", ParameterDirection.IN); + var b = builder.parameter("b", ParameterDirection.IN); + var c = builder.parameter("c", ParameterDirection.OUT); + var d = builder.parameter("d", ParameterDirection.OUT); + builder.clause( + friendView.call(a, b), + friendView.call(b, c), + friendView.call(c, d) + ); + }); + private static final NodeVariable p = Variable.of("p"); + private static final NodeVariable q = Variable.of("q"); + private static final NodeVariable r = Variable.of("r"); + private static final NodeVariable s = Variable.of("s"); + private static final NodeVariable t = Variable.of("t"); + + @Test + void topologicalSortTest() { + var actual = Dnf.builder("Actual") + .parameter(p, ParameterDirection.IN) + .parameter(q, ParameterDirection.OUT) + .clause( + not(friendView.call(p, q)), + example.call(p, q, r, s), + example.call(r, t, q, s), + friendView.call(r, t) + ) + .build(); + + assertThat(actual, structurallyEqualTo( + List.of( + new SymbolicParameter(p, ParameterDirection.IN), + new SymbolicParameter(q, ParameterDirection.OUT) + ), + List.of( + List.of( + friendView.call(r, t), + example.call(r, t, q, s), + not(friendView.call(p, q)), + example.call(p, q, r, s) + ) + ) + )); + } + + @Test + void missingInputTest() { + var builder = Dnf.builder("Actual") + .parameter(p, ParameterDirection.OUT) + .parameter(q, ParameterDirection.OUT) + .clause( + not(friendView.call(p, q)), + example.call(p, q, r, s), + example.call(r, t, q, s), + friendView.call(r, t) + ); + assertThrows(InvalidQueryException.class, builder::build); + } + + @Test + void missingVariableTest() { + var builder = Dnf.builder("Actual") + .parameter(p, ParameterDirection.IN) + .parameter(q, ParameterDirection.OUT) + .clause( + not(friendView.call(p, q)), + example.call(p, q, r, s), + example.call(r, t, q, s) + ); + assertThrows(InvalidQueryException.class, builder::build); + } + + @Test + void circularDependencyTest() { + var builder = Dnf.builder("Actual") + .parameter(p, ParameterDirection.IN) + .parameter(q, ParameterDirection.OUT) + .clause( + not(friendView.call(p, q)), + example.call(p, q, r, s), + example.call(r, t, q, s), + example.call(p, q, r, t) + ); + assertThrows(InvalidQueryException.class, builder::build); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/dnf/VariableDirectionTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/VariableDirectionTest.java new file mode 100644 index 00000000..f9f39b8a --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/VariableDirectionTest.java @@ -0,0 +1,247 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import tools.refinery.logic.Constraint; +import tools.refinery.logic.literal.BooleanLiteral; +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.DataVariable; +import tools.refinery.logic.term.NodeVariable; +import tools.refinery.logic.term.ParameterDirection; +import tools.refinery.logic.term.Variable; +import tools.refinery.logic.tests.FakeFunctionView; +import tools.refinery.logic.tests.FakeKeyOnlyView; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static tools.refinery.logic.literal.Literals.not; +import static tools.refinery.logic.term.int_.IntTerms.INT_SUM; + +class VariableDirectionTest { + private static final Constraint personView = new FakeKeyOnlyView("Person", 1); + private static final Constraint friendView = new FakeKeyOnlyView("friend", 2); + private static final FakeFunctionView ageView = new FakeFunctionView<>("age", 1, Integer.class); + private static final NodeVariable p = Variable.of("p"); + private static final NodeVariable q = Variable.of("q"); + private static final DataVariable x = Variable.of("x", Integer.class); + private static final DataVariable y = Variable.of("y", Integer.class); + private static final DataVariable z = Variable.of("z", Integer.class); + + @ParameterizedTest + @MethodSource("clausesWithVariableInput") + void unboundOutVariableTest(List clause) { + var builder = Dnf.builder().parameter(p, ParameterDirection.OUT).clause(clause); + assertThrows(InvalidClauseException.class, builder::build); + } + + @ParameterizedTest + @MethodSource("clausesWithVariableInput") + void unboundInVariableTest(List clause) { + var builder = Dnf.builder().parameter(p, ParameterDirection.IN).clause(clause); + var dnf = assertDoesNotThrow(builder::build); + var clauses = dnf.getClauses(); + if (!clauses.isEmpty()) { + assertThat(clauses.getFirst().positiveVariables(), hasItem(p)); + } + } + + @ParameterizedTest + @MethodSource("clausesWithVariableInput") + void boundPrivateVariableTest(List clause) { + var clauseWithBinding = new ArrayList(clause); + clauseWithBinding.add(personView.call(p)); + var builder = Dnf.builder().clause(clauseWithBinding); + var dnf = assertDoesNotThrow(builder::build); + var clauses = dnf.getClauses(); + if (!clauses.isEmpty()) { + assertThat(clauses.getFirst().positiveVariables(), hasItem(p)); + } + } + + static Stream clausesWithVariableInput() { + return Stream.concat( + clausesNotBindingVariable(), + literalToClauseArgumentStream(literalsWithRequiredVariableInput()) + ); + } + + @ParameterizedTest + @MethodSource("clausesNotBindingVariable") + void unboundPrivateVariableTest(List clause) { + var builder = Dnf.builder().clause(clause); + var dnf = assertDoesNotThrow(builder::build); + var clauses = dnf.getClauses(); + if (!clauses.isEmpty()) { + assertThat(clauses.getFirst().positiveVariables(), not(hasItem(p))); + } + } + + @ParameterizedTest + @MethodSource("clausesNotBindingVariable") + void unboundByEquivalencePrivateVariableTest(List clause) { + var r = Variable.of("r"); + var clauseWithEquivalence = new ArrayList(clause); + clauseWithEquivalence.add(r.isEquivalent(p)); + var builder = Dnf.builder().clause(clauseWithEquivalence); + assertThrows(InvalidClauseException.class, builder::build); + } + + static Stream clausesNotBindingVariable() { + return Stream.concat( + Stream.of( + Arguments.of(List.of()), + Arguments.of(List.of(BooleanLiteral.TRUE)), + Arguments.of(List.of(BooleanLiteral.FALSE)) + ), + literalToClauseArgumentStream(literalsWithPrivateVariable()) + ); + } + + @ParameterizedTest + @MethodSource("literalsWithPrivateVariable") + void unboundTwicePrivateVariableTest(Literal literal) { + var builder = Dnf.builder().clause(not(personView.call(p)), literal); + assertThrows(InvalidClauseException.class, builder::build); + } + + @ParameterizedTest + @MethodSource("literalsWithPrivateVariable") + void unboundTwiceByEquivalencePrivateVariableTest(Literal literal) { + var r = Variable.of("r"); + var builder = Dnf.builder().clause(not(personView.call(r)), r.isEquivalent(p), literal); + assertThrows(InvalidClauseException.class, builder::build); + } + + static Stream literalsWithPrivateVariable() { + var dnfWithOutput = Dnf.builder("WithOutput") + .parameter(p, ParameterDirection.OUT) + .parameter(q, ParameterDirection.OUT) + .clause(friendView.call(p, q)) + .build(); + var dnfWithOutputToAggregate = Dnf.builder("WithOutputToAggregate") + .parameter(p, ParameterDirection.OUT) + .parameter(q, ParameterDirection.OUT) + .parameter(x, ParameterDirection.OUT) + .clause( + friendView.call(p, q), + ageView.call(q, x) + ) + .build(); + + return Stream.of( + Arguments.of(not(friendView.call(p, q))), + Arguments.of(y.assign(friendView.count(p, q))), + Arguments.of(y.assign(ageView.aggregate(INT_SUM, p))), + Arguments.of(not(dnfWithOutput.call(p, q))), + Arguments.of(y.assign(dnfWithOutput.count(p, q))), + Arguments.of(y.assign(dnfWithOutputToAggregate.aggregateBy(z, INT_SUM, p, q, z))) + ); + } + + @ParameterizedTest + @MethodSource("literalsWithRequiredVariableInput") + void unboundPrivateVariableTest(Literal literal) { + var builder = Dnf.builder().clause(literal); + assertThrows(InvalidClauseException.class, builder::build); + } + + @ParameterizedTest + @MethodSource("literalsWithRequiredVariableInput") + void boundPrivateVariableInputTest(Literal literal) { + var builder = Dnf.builder().clause(personView.call(p), literal); + var dnf = assertDoesNotThrow(builder::build); + assertThat(dnf.getClauses().getFirst().positiveVariables(), hasItem(p)); + } + + static Stream literalsWithRequiredVariableInput() { + var dnfWithInput = Dnf.builder("WithInput") + .parameter(p, ParameterDirection.IN) + .parameter(q, ParameterDirection.OUT) + .clause(friendView.call(p, q)).build(); + var dnfWithInputToAggregate = Dnf.builder("WithInputToAggregate") + .parameter(p, ParameterDirection.IN) + .parameter(q, ParameterDirection.OUT) + .parameter(x, ParameterDirection.OUT) + .clause( + friendView.call(p, q), + ageView.call(q, x) + ).build(); + + return Stream.of( + Arguments.of(dnfWithInput.call(p, q)), + Arguments.of(dnfWithInput.call(p, p)), + Arguments.of(not(dnfWithInput.call(p, q))), + Arguments.of(not(dnfWithInput.call(p, p))), + Arguments.of(y.assign(dnfWithInput.count(p, q))), + Arguments.of(y.assign(dnfWithInput.count(p, p))), + Arguments.of(y.assign(dnfWithInputToAggregate.aggregateBy(z, INT_SUM, p, q, z))), + Arguments.of(y.assign(dnfWithInputToAggregate.aggregateBy(z, INT_SUM, p, p, z))) + ); + } + + @ParameterizedTest + @MethodSource("literalsWithVariableOutput") + void boundParameterTest(Literal literal) { + var builder = Dnf.builder().parameter(p, ParameterDirection.OUT).clause(literal); + var dnf = assertDoesNotThrow(builder::build); + assertThat(dnf.getClauses().getFirst().positiveVariables(), hasItem(p)); + } + + @ParameterizedTest + @MethodSource("literalsWithVariableOutput") + void boundTwiceParameterTest(Literal literal) { + var builder = Dnf.builder().parameter(p, ParameterDirection.IN).clause(literal); + var dnf = assertDoesNotThrow(builder::build); + assertThat(dnf.getClauses().getFirst().positiveVariables(), hasItem(p)); + } + + @ParameterizedTest + @MethodSource("literalsWithVariableOutput") + void boundPrivateVariableOutputTest(Literal literal) { + var dnfWithInput = Dnf.builder("WithInput") + .parameter(p, ParameterDirection.IN) + .clause(personView.call(p)) + .build(); + var builder = Dnf.builder().clause(dnfWithInput.call(p), literal); + var dnf = assertDoesNotThrow(builder::build); + assertThat(dnf.getClauses().getFirst().positiveVariables(), hasItem(p)); + } + + @ParameterizedTest + @MethodSource("literalsWithVariableOutput") + void boundTwicePrivateVariableOutputTest(Literal literal) { + var builder = Dnf.builder().clause(personView.call(p), literal); + var dnf = assertDoesNotThrow(builder::build); + assertThat(dnf.getClauses().getFirst().positiveVariables(), hasItem(p)); + } + + static Stream literalsWithVariableOutput() { + var dnfWithOutput = Dnf.builder("WithOutput") + .parameter(p, ParameterDirection.OUT) + .parameter(q, ParameterDirection.OUT) + .clause(friendView.call(p, q)) + .build(); + + return Stream.of( + Arguments.of(friendView.call(p, q)), + Arguments.of(dnfWithOutput.call(p, q)) + ); + } + + private static Stream literalToClauseArgumentStream(Stream literalArgumentsStream) { + return literalArgumentsStream.map(arguments -> Arguments.of(List.of(arguments.get()[0]))); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/literal/AggregationLiteralTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/literal/AggregationLiteralTest.java new file mode 100644 index 00000000..76639e18 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/literal/AggregationLiteralTest.java @@ -0,0 +1,89 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +import org.junit.jupiter.api.Test; +import tools.refinery.logic.Constraint; +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.dnf.Dnf; +import tools.refinery.logic.dnf.InvalidClauseException; +import tools.refinery.logic.term.*; + +import java.util.List; +import java.util.Set; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static tools.refinery.logic.literal.Literals.not; +import static tools.refinery.logic.term.int_.IntTerms.INT_SUM; +import static tools.refinery.logic.term.int_.IntTerms.constant; + +class AggregationLiteralTest { + private static final NodeVariable p = Variable.of("p"); + private static final DataVariable x = Variable.of("x", Integer.class); + private static final DataVariable y = Variable.of("y", Integer.class); + private static final DataVariable z = Variable.of("z", Integer.class); + private static final Constraint fakeConstraint = new Constraint() { + @Override + public String name() { + return getClass().getName(); + } + + @Override + public List getParameters() { + return List.of( + new Parameter(null, ParameterDirection.OUT), + new Parameter(Integer.class, ParameterDirection.OUT) + ); + } + }; + + @Test + void parameterDirectionTest() { + var literal = x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y)); + assertAll( + () -> assertThat(literal.getOutputVariables(), containsInAnyOrder(x)), + () -> assertThat(literal.getInputVariables(Set.of()), empty()), + () -> assertThat(literal.getInputVariables(Set.of(p)), containsInAnyOrder(p)), + () -> assertThat(literal.getPrivateVariables(Set.of()), containsInAnyOrder(p, y)), + () -> assertThat(literal.getPrivateVariables(Set.of(p)), containsInAnyOrder(y)) + ); + } + + @Test + void missingAggregationVariableTest() { + var aggregation = fakeConstraint.aggregateBy(y, INT_SUM, p, z); + assertThrows(InvalidQueryException.class, () -> x.assign(aggregation)); + } + + @Test + void circularAggregationVariableTest() { + var aggregation = fakeConstraint.aggregateBy(x, INT_SUM, p, x); + assertThrows(InvalidQueryException.class, () -> x.assign(aggregation)); + } + + @Test + void unboundTwiceVariableTest() { + var builder = Dnf.builder() + .clause( + not(fakeConstraint.call(p, y)), + x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y)) + ); + assertThrows(InvalidClauseException.class, builder::build); + } + + @Test + void unboundBoundVariableTest() { + var builder = Dnf.builder() + .clause( + y.assign(constant(27)), + x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y)) + ); + assertThrows(InvalidClauseException.class, builder::build); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/literal/CallLiteralTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/literal/CallLiteralTest.java new file mode 100644 index 00000000..0fb2e7c9 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/literal/CallLiteralTest.java @@ -0,0 +1,94 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +import org.junit.jupiter.api.Test; +import tools.refinery.logic.Constraint; +import tools.refinery.logic.term.NodeVariable; +import tools.refinery.logic.term.Parameter; +import tools.refinery.logic.term.ParameterDirection; +import tools.refinery.logic.term.Variable; + +import java.util.List; +import java.util.Set; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.junit.jupiter.api.Assertions.assertAll; +import static tools.refinery.logic.literal.Literals.not; + +class CallLiteralTest { + private static final NodeVariable p = Variable.of("p"); + private static final NodeVariable q = Variable.of("q"); + private static final NodeVariable r = Variable.of("r"); + private static final NodeVariable s = Variable.of("s"); + + private static final Constraint fakeConstraint = new Constraint() { + @Override + public String name() { + return getClass().getName(); + } + + @Override + public List getParameters() { + return List.of( + new Parameter(null, ParameterDirection.IN), + new Parameter(null, ParameterDirection.IN), + new Parameter(null, ParameterDirection.OUT), + new Parameter(null, ParameterDirection.OUT) + ); + } + }; + + @Test + void notRepeatedPositiveDirectionTest() { + var literal = fakeConstraint.call(p, q, r, s); + assertAll( + () -> assertThat(literal.getOutputVariables(), containsInAnyOrder(r, s)), + () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p, q)), + () -> assertThat(literal.getInputVariables(Set.of(p, q, r)), containsInAnyOrder(p, q)), + () -> assertThat(literal.getPrivateVariables(Set.of()), empty()), + () -> assertThat(literal.getPrivateVariables(Set.of(p, q, r)), empty()) + ); + } + + @Test + void notRepeatedNegativeDirectionTest() { + var literal = not(fakeConstraint.call(p, q, r, s)); + assertAll( + () -> assertThat(literal.getOutputVariables(), empty()), + () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p, q)), + () -> assertThat(literal.getInputVariables(Set.of(p, q, r)), containsInAnyOrder(p, q, r)), + () -> assertThat(literal.getPrivateVariables(Set.of()), containsInAnyOrder(r, s)), + () -> assertThat(literal.getPrivateVariables(Set.of(p, q, r)), containsInAnyOrder(s)) + ); + } + + @Test + void repeatedPositiveDirectionTest() { + var literal = fakeConstraint.call(p, p, q, q); + assertAll( + () -> assertThat(literal.getOutputVariables(), containsInAnyOrder(q)), + () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p)), + () -> assertThat(literal.getInputVariables(Set.of(p, q)), containsInAnyOrder(p)), + () -> assertThat(literal.getPrivateVariables(Set.of()), empty()), + () -> assertThat(literal.getPrivateVariables(Set.of(p, q)), empty()) + ); + } + + @Test + void repeatedNegativeDirectionTest() { + var literal = not(fakeConstraint.call(p, p, q, q)); + assertAll( + () -> assertThat(literal.getOutputVariables(), empty()), + () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p)), + () -> assertThat(literal.getInputVariables(Set.of(p, q)), containsInAnyOrder(p, q)), + () -> assertThat(literal.getPrivateVariables(Set.of()), containsInAnyOrder(q)), + () -> assertThat(literal.getPrivateVariables(Set.of(p, q)), empty()) + ); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/rewriter/DuplicateDnfRemoverTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/rewriter/DuplicateDnfRemoverTest.java new file mode 100644 index 00000000..7b2ce8b2 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/rewriter/DuplicateDnfRemoverTest.java @@ -0,0 +1,162 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.rewriter; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import tools.refinery.logic.Constraint; +import tools.refinery.logic.dnf.Query; +import tools.refinery.logic.literal.AbstractCallLiteral; +import tools.refinery.logic.literal.Reduction; +import tools.refinery.logic.term.Variable; +import tools.refinery.logic.tests.FakeKeyOnlyView; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static tools.refinery.logic.literal.Literals.not; + +class DuplicateDnfRemoverTest { + private final static Constraint friendView = new FakeKeyOnlyView("friend", 2); + + private DuplicateDnfRemover sut; + + @BeforeEach + void beforeEach() { + sut = new DuplicateDnfRemover(); + } + + @Test + void removeDuplicateSimpleTest() { + var one = Query.of("One", (builder, x, y) -> builder.clause( + friendView.call(x, y), + friendView.call(y, x) + )); + var two = Query.of("Two", (builder, x, y) -> builder.clause( + friendView.call(x, y), + friendView.call(y, x) + )); + + var oneResult = sut.rewrite(one); + var twoResult = sut.rewrite(two); + + assertThat(oneResult, is(twoResult)); + assertThat(one, is(oneResult)); + } + + @Test + void notDuplicateSimpleTest() { + var one = Query.of("One", (builder, x, y) -> builder.clause( + friendView.call(x, y), + friendView.call(y, x) + )); + var two = Query.of("Two", (builder, x, y) -> builder.clause((z) -> List.of( + friendView.call(x, y), + friendView.call(y, z) + ))); + + var oneResult = sut.rewrite(one); + var twoResult = sut.rewrite(two); + + assertThat(one, is(oneResult)); + assertThat(two, is(twoResult)); + } + + @Test + void removeDuplicateRecursiveTest() { + var oneSubQuery = Query.of("OneSubQuery", (builder, x, y) -> builder.clause( + friendView.call(x, y), + friendView.call(y, x) + )); + var one = Query.of("One", (builder, x) -> builder.clause( + oneSubQuery.call(x, Variable.of()) + )); + var twoSubQuery = Query.of("TwoSubQuery", (builder, x, y) -> builder.clause( + friendView.call(x, y), + friendView.call(y, x) + )); + var two = Query.of("Two", (builder, x) -> builder.clause( + twoSubQuery.call(x, Variable.of()) + )); + + var oneResult = sut.rewrite(one); + var twoResult = sut.rewrite(two); + + assertThat(oneResult, is(twoResult)); + assertThat(one, is(oneResult)); + } + + @Test + void notDuplicateRecursiveTest() { + var oneSubQuery = Query.of("OneSubQuery", (builder, x, y) -> builder.clause( + friendView.call(x, y), + friendView.call(y, x) + )); + var one = Query.of("One", (builder, x) -> builder.clause( + oneSubQuery.call(x, Variable.of()) + )); + var twoSubQuery = Query.of("TwoSubQuery", (builder, x, y) -> builder.clause( + friendView.call(x, y), + friendView.call(y, x) + )); + var two = Query.of("Two", (builder, x) -> builder.clause( + twoSubQuery.call(Variable.of(), x) + )); + + var oneResult = sut.rewrite(one); + var twoResult = sut.rewrite(two); + + assertThat(one, is(oneResult)); + assertThat(oneResult, is(not(twoResult))); + + var oneCall = (AbstractCallLiteral) oneResult.getDnf().getClauses().getFirst().literals().getFirst(); + var twoCall = (AbstractCallLiteral) twoResult.getDnf().getClauses().getFirst().literals().getFirst(); + + assertThat(oneCall.getTarget(), is(twoCall.getTarget())); + } + + @Test + void removeContradictionTest() { + var oneSubQuery = Query.of("OneSubQuery", (builder, x, y) -> builder.clause( + friendView.call(x, y), + friendView.call(y, x) + )); + var twoSubQuery = Query.of("TwoSubQuery", (builder, x, y) -> builder.clause( + friendView.call(x, y), + friendView.call(y, x) + )); + var query = Query.of("Contradiction", (builder, x, y) -> builder.clause( + oneSubQuery.call(x, y), + not(twoSubQuery.call(x, y)) + )); + + var result = sut.rewrite(query); + + assertThat(result.getDnf().getReduction(), is(Reduction.ALWAYS_FALSE)); + } + + @Test + void removeQuantifiedContradictionTest() { + var oneSubQuery = Query.of("OneSubQuery", (builder, x, y) -> builder.clause( + friendView.call(x, y), + friendView.call(y, x) + )); + var twoSubQuery = Query.of("TwoSubQuery", (builder, x, y) -> builder.clause( + friendView.call(x, y), + friendView.call(y, x) + )); + var query = Query.of("Contradiction", (builder, x) -> builder.clause( + oneSubQuery.call(x, Variable.of()), + not(twoSubQuery.call(x, Variable.of())) + )); + + var result = sut.rewrite(query); + + assertThat(result.getDnf().getReduction(), is(Reduction.ALWAYS_FALSE)); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/rewriter/InputParameterResolverTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/rewriter/InputParameterResolverTest.java new file mode 100644 index 00000000..5e5fdb64 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/rewriter/InputParameterResolverTest.java @@ -0,0 +1,225 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.rewriter; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import tools.refinery.logic.Constraint; +import tools.refinery.logic.dnf.Dnf; +import tools.refinery.logic.dnf.Query; +import tools.refinery.logic.term.ParameterDirection; +import tools.refinery.logic.term.Variable; +import tools.refinery.logic.tests.FakeKeyOnlyView; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static tools.refinery.logic.literal.Literals.not; +import static tools.refinery.logic.tests.QueryMatchers.structurallyEqualTo; + +class InputParameterResolverTest { + private final static Constraint personView = new FakeKeyOnlyView("Person", 1); + private final static Constraint friendView = new FakeKeyOnlyView("friend", 2); + + private InputParameterResolver sut; + + @BeforeEach + void beforeEach() { + sut = new InputParameterResolver(); + } + + @Test + void inlineSingleClauseTest() { + var dnf = Dnf.of("SubQuery", builder -> { + var x = builder.parameter("x", ParameterDirection.OUT); + builder.clause(friendView.call(x, Variable.of())); + }); + var query = Query.of("Actual", (builder, x) -> builder.clause( + dnf.call(x), + personView.call(x) + )); + + var actual = sut.rewrite(query); + + var expected = Query.of("Expected", (builder, x) -> builder.clause( + friendView.call(x, Variable.of()), + personView.call(x) + )); + + assertThat(actual.getDnf(), structurallyEqualTo(expected.getDnf())); + } + + @Test + void inlineSingleClauseWIthInputTest() { + var dnf = Dnf.of("SubQuery", builder -> { + var x = builder.parameter("x", ParameterDirection.IN); + builder.clause(not(friendView.call(x, Variable.of()))); + }); + var query = Query.of("Actual", (builder, x) -> builder.clause( + dnf.call(x), + personView.call(x) + )); + + var actual = sut.rewrite(query); + + var expected = Query.of("Expected", (builder, x) -> builder.clause( + personView.call(x), + not(friendView.call(x, Variable.of())) + )); + + assertThat(actual.getDnf(), structurallyEqualTo(expected.getDnf())); + } + + @Test + void singleLiteralDemandSetTest() { + var dnf = Dnf.of("SubQuery", builder -> { + var x = builder.parameter("x", ParameterDirection.IN); + builder.clause(not(friendView.call(x, Variable.of()))); + builder.clause(not(friendView.call(Variable.of(), x))); + }); + var query = Query.of("Actual", (builder, x) -> builder.clause( + dnf.call(x), + personView.call(x) + )); + + var actual = sut.rewrite(query); + + var expectedSubQuery = Dnf.of("ExpectedSubQuery", builder -> { + var x = builder.parameter("x", ParameterDirection.OUT); + builder.clause( + personView.call(x), + not(friendView.call(x, Variable.of())) + ); + builder.clause( + personView.call(x), + not(friendView.call(Variable.of(), x)) + ); + }); + var expected = Query.of("Expected", (builder, x) -> builder.clause( + personView.call(x), + expectedSubQuery.call(x) + )); + + assertThat(actual.getDnf(), structurallyEqualTo(expected.getDnf())); + } + + @Test + void multipleLiteralDemandSetTest() { + var dnf = Dnf.of("SubQuery", builder -> { + var x = builder.parameter("x", ParameterDirection.IN); + var y = builder.parameter("y", ParameterDirection.IN); + builder.clause(not(friendView.call(x, y))); + builder.clause(not(friendView.call(y, x))); + }); + var query = Query.of("Actual", (builder, p1) -> builder.clause(p2 -> List.of( + not(dnf.call(p1, p2)), + personView.call(p1), + personView.call(p2) + ))); + + var actual = sut.rewrite(query); + + var context = Dnf.of("Context", builder -> { + var x = builder.parameter("x", ParameterDirection.OUT); + var y = builder.parameter("y", ParameterDirection.OUT); + builder.clause( + personView.call(x), + personView.call(y) + ); + }); + var expectedSubQuery = Dnf.of("ExpectedSubQuery", builder -> { + var x = builder.parameter("x", ParameterDirection.OUT); + var y = builder.parameter("x", ParameterDirection.OUT); + builder.clause( + context.call(x, y), + not(friendView.call(x, y)) + ); + builder.clause( + context.call(x, y), + not(friendView.call(y, x)) + ); + }); + var expected = Query.of("Expected", (builder, p1) -> builder.clause(p2 -> List.of( + context.call(p1, p2), + not(expectedSubQuery.call(p1, p2)) + ))); + + assertThat(actual.getDnf(), structurallyEqualTo(expected.getDnf())); + } + + @Test + void multipleParameterDemandSetTest() { + var dnf = Dnf.of("SubQuery", builder -> { + var x = builder.parameter("x", ParameterDirection.IN); + var y = builder.parameter("y", ParameterDirection.IN); + builder.clause(not(friendView.call(x, y))); + builder.clause(not(friendView.call(y, x))); + }); + var query = Query.of("Actual", (builder, p1) -> builder.clause( + not(dnf.call(p1, p1)), + personView.call(p1) + )); + + var actual = sut.rewrite(query); + + var expectedSubQuery = Dnf.of("ExpectedSubQuery", builder -> { + var x = builder.parameter("x", ParameterDirection.OUT); + var y = builder.parameter("y", ParameterDirection.OUT); + builder.clause( + y.isEquivalent(x), + personView.call(x), + not(friendView.call(x, x)) + ); + }); + var expected = Query.of("Expected", (builder, p1) -> builder.clause( + personView.call(p1), + not(expectedSubQuery.call(p1, p1)) + )); + + assertThat(actual.getDnf(), structurallyEqualTo(expected.getDnf())); + } + + @Test + void eliminateDoubleNegationTest() { + var dnf = Dnf.of("SubQuery", builder -> { + var x = builder.parameter("x", ParameterDirection.IN); + builder.clause(not(friendView.call(x, Variable.of()))); + }); + var query = Query.of("Actual", (builder, p1) -> builder.clause( + personView.call(p1), + not(dnf.call(p1)) + )); + + var actual = sut.rewrite(query); + + var expected = Query.of("Actual", (builder, p1) -> builder.clause( + personView.call(p1), + friendView.call(p1, Variable.of()) + )); + + assertThat(actual.getDnf(), structurallyEqualTo(expected.getDnf())); + } + + @Test + void identityWhenNoWorkToDoTest() { + var dnf = Dnf.of("SubQuery", builder -> { + var x = builder.parameter("x", ParameterDirection.OUT); + builder.clause( + personView.call(x), + not(friendView.call(x, Variable.of())) + ); + }); + var query = Query.of("Actual", (builder, p1) -> builder.clause( + personView.call(p1), + not(dnf.call(p1)) + )); + + var actual = sut.rewrite(query); + + assertThat(actual, is(query)); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/TermSubstitutionTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/TermSubstitutionTest.java new file mode 100644 index 00000000..52b21692 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/TermSubstitutionTest.java @@ -0,0 +1,97 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import tools.refinery.logic.equality.DnfEqualityChecker; +import tools.refinery.logic.equality.SubstitutingLiteralEqualityHelper; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.bool.BoolTerms; +import tools.refinery.logic.term.int_.IntTerms; +import tools.refinery.logic.term.real.RealTerms; +import tools.refinery.logic.term.uppercardinality.UpperCardinality; +import tools.refinery.logic.term.uppercardinality.UpperCardinalityTerms; + +import java.util.List; +import java.util.stream.Stream; + +class TermSubstitutionTest { + private final static DataVariable intA = Variable.of("intA", Integer.class); + private final static DataVariable intB = Variable.of("intB", Integer.class); + private final static DataVariable realA = Variable.of("realA", Double.class); + private final static DataVariable realB = Variable.of("realB", Double.class); + private final static DataVariable boolA = Variable.of("boolA", Boolean.class); + private final static DataVariable boolB = Variable.of("boolB", Boolean.class); + private final static DataVariable upperCardinalityA = Variable.of("upperCardinalityA", + UpperCardinality.class); + private final static DataVariable upperCardinalityB = Variable.of("upperCardinalityB", + UpperCardinality.class); + private final static Substitution substitution = Substitution.builder() + .put(intA, intB) + .put(intB, intA) + .put(realA, realB) + .put(realB, realA) + .put(boolA, boolB) + .put(boolB, boolA) + .put(upperCardinalityA, upperCardinalityB) + .put(upperCardinalityB, upperCardinalityA) + .build(); + + @ParameterizedTest + @MethodSource + void substitutionTest(AnyTerm term) { + var substitutedTerm1 = term.substitute(substitution); + Assertions.assertNotEquals(term, substitutedTerm1, "Original term is not equal to substituted term"); + var helper = new SubstitutingLiteralEqualityHelper(DnfEqualityChecker.DEFAULT, List.of(), List.of()); + Assertions.assertTrue(term.equalsWithSubstitution(helper, substitutedTerm1), "Terms are equal by helper"); + // The {@link #substitution} is its own inverse. + var substitutedTerm2 = substitutedTerm1.substitute(substitution); + Assertions.assertEquals(term, substitutedTerm2, "Original term is not equal to back-substituted term"); + } + + static Stream substitutionTest() { + return Stream.of( + Arguments.of(IntTerms.plus(intA)), + Arguments.of(IntTerms.minus(intA)), + Arguments.of(IntTerms.add(intA, intB)), + Arguments.of(IntTerms.sub(intA, intB)), + Arguments.of(IntTerms.mul(intA, intB)), + Arguments.of(IntTerms.div(intA, intB)), + Arguments.of(IntTerms.pow(intA, intB)), + Arguments.of(IntTerms.min(intA, intB)), + Arguments.of(IntTerms.max(intA, intB)), + Arguments.of(IntTerms.eq(intA, intB)), + Arguments.of(IntTerms.notEq(intA, intB)), + Arguments.of(IntTerms.less(intA, intB)), + Arguments.of(IntTerms.lessEq(intA, intB)), + Arguments.of(IntTerms.greater(intA, intB)), + Arguments.of(IntTerms.greaterEq(intA, intB)), + Arguments.of(IntTerms.asInt(realA)), + Arguments.of(RealTerms.plus(realA)), + Arguments.of(RealTerms.minus(realA)), + Arguments.of(RealTerms.add(realA, realB)), + Arguments.of(RealTerms.sub(realA, realB)), + Arguments.of(RealTerms.mul(realA, realB)), + Arguments.of(RealTerms.div(realA, realB)), + Arguments.of(RealTerms.pow(realA, realB)), + Arguments.of(RealTerms.min(realA, realB)), + Arguments.of(RealTerms.max(realA, realB)), + Arguments.of(RealTerms.asReal(intA)), + Arguments.of(BoolTerms.not(boolA)), + Arguments.of(BoolTerms.and(boolA, boolB)), + Arguments.of(BoolTerms.or(boolA, boolB)), + Arguments.of(BoolTerms.xor(boolA, boolB)), + Arguments.of(RealTerms.eq(realA, realB)), + Arguments.of(UpperCardinalityTerms.add(upperCardinalityA, upperCardinalityB)), + Arguments.of(UpperCardinalityTerms.mul(upperCardinalityA, upperCardinalityB)), + Arguments.of(UpperCardinalityTerms.min(upperCardinalityA, upperCardinalityB)), + Arguments.of(UpperCardinalityTerms.max(upperCardinalityA, upperCardinalityB)) + ); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/bool/BoolTermsEvaluateTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/bool/BoolTermsEvaluateTest.java new file mode 100644 index 00000000..7f65591f --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/bool/BoolTermsEvaluateTest.java @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.bool; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import tools.refinery.logic.term.bool.BoolTerms; +import tools.refinery.logic.valuation.Valuation; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +class BoolTermsEvaluateTest { + @ParameterizedTest(name = "!{0} == {1}") + @CsvSource(value = { + "false, true", + "true, false", + "null, null" + }, nullValues = "null") + void notTest(Boolean a, Boolean result) { + var term = BoolTerms.not(BoolTerms.constant(a)); + assertThat(term.getType(), is(Boolean.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "{0} && {1} == {2}") + @CsvSource(value = { + "false, false, false", + "false, true, false", + "true, false, false", + "true, true, true", + "false, null, null", + "null, false, null", + "null, null, null" + }, nullValues = "null") + void andTest(Boolean a, Boolean b, Boolean result) { + var term = BoolTerms.and(BoolTerms.constant(a), BoolTerms.constant(b)); + assertThat(term.getType(), is(Boolean.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "{0} || {1} == {2}") + @CsvSource(value = { + "false, false, false", + "false, true, true", + "true, false, true", + "true, true, true", + "true, null, null", + "null, true, null", + "null, null, null" + }, nullValues = "null") + void orTest(Boolean a, Boolean b, Boolean result) { + var term = BoolTerms.or(BoolTerms.constant(a), BoolTerms.constant(b)); + assertThat(term.getType(), is(Boolean.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "{0} ^^ {1} == {2}") + @CsvSource(value = { + "false, false, false", + "false, true, true", + "true, false, true", + "true, true, false", + "false, null, null", + "null, false, null", + "null, null, null" + }, nullValues = "null") + void xorTest(Boolean a, Boolean b, Boolean result) { + var term = BoolTerms.xor(BoolTerms.constant(a), BoolTerms.constant(b)); + assertThat(term.getType(), is(Boolean.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervalTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervalTest.java new file mode 100644 index 00000000..ee2dd61c --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervalTest.java @@ -0,0 +1,127 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.cardinalityinterval; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static tools.refinery.logic.term.cardinalityinterval.CardinalityIntervals.*; + +class CardinalityIntervalTest { + @ParameterizedTest(name = "min({0}, {1}) == {2}") + @MethodSource + void minTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) { + assertThat(a.min(b), equalTo(expected)); + } + + static Stream minTest() { + return Stream.of( + Arguments.of(atMost(1), atMost(1), atMost(1)), + Arguments.of(atMost(1), between(2, 3), atMost(1)), + Arguments.of(atMost(1), atLeast(2), atMost(1)), + Arguments.of(atMost(1), ERROR, ERROR), + Arguments.of(atLeast(1), atLeast(2), atLeast(1)), + Arguments.of(atLeast(1), ERROR, ERROR), + Arguments.of(ERROR, atLeast(2), ERROR), + Arguments.of(ERROR, ERROR, ERROR) + ); + } + + @ParameterizedTest(name = "max({0}, {1}) == {2}") + @MethodSource + void maxTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) { + assertThat(a.max(b), equalTo(expected)); + } + + static Stream maxTest() { + return Stream.of( + Arguments.of(atMost(1), atMost(1), atMost(1)), + Arguments.of(atMost(1), between(2, 3), between(2, 3)), + Arguments.of(atMost(1), atLeast(2), atLeast(2)), + Arguments.of(atMost(1), ERROR, ERROR), + Arguments.of(atLeast(1), atLeast(2), atLeast(2)), + Arguments.of(atLeast(1), ERROR, ERROR), + Arguments.of(ERROR, atLeast(2), ERROR), + Arguments.of(ERROR, ERROR, ERROR) + ); + } + + @ParameterizedTest(name = "{0} + {1} == {2}") + @MethodSource + void addTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) { + assertThat(a.add(b), equalTo(expected)); + } + + static Stream addTest() { + return Stream.of( + Arguments.of(atMost(1), atMost(1), atMost(2)), + Arguments.of(atMost(1), between(2, 3), between(2, 4)), + Arguments.of(atMost(1), atLeast(2), atLeast(2)), + Arguments.of(atMost(1), ERROR, ERROR), + Arguments.of(atLeast(1), atLeast(2), atLeast(3)), + Arguments.of(atLeast(1), ERROR, ERROR), + Arguments.of(ERROR, atLeast(2), ERROR), + Arguments.of(ERROR, ERROR, ERROR) + ); + } + + @ParameterizedTest(name = "{0} * {1} == {2}") + @MethodSource + void multiplyTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) { + assertThat(a.multiply(b), equalTo(expected)); + } + + static Stream multiplyTest() { + return Stream.of( + Arguments.of(between(2, 3), between(4, 5), between(8, 15)), + Arguments.of(atLeast(2), between(4, 5), atLeast(8)), + Arguments.of(between(2, 3), atLeast(4), atLeast(8)), + Arguments.of(between(2, 3), ERROR, ERROR), + Arguments.of(ERROR, between(4, 5), ERROR), + Arguments.of(ERROR, ERROR, ERROR) + ); + } + + @ParameterizedTest(name = "{0} /\\ {1} == {2}") + @MethodSource + void meetTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) { + assertThat(a.meet(b), equalTo(expected)); + } + + static Stream meetTest() { + return Stream.of( + Arguments.of(atMost(1), atMost(2), atMost(1)), + Arguments.of(atMost(2), between(1, 3), between(1, 2)), + Arguments.of(atMost(1), between(1, 3), exactly(1)), + Arguments.of(atMost(1), between(2, 3), ERROR), + Arguments.of(atMost(1), ERROR, ERROR), + Arguments.of(ERROR, atMost(1), ERROR), + Arguments.of(ERROR, ERROR, ERROR) + ); + } + + @ParameterizedTest(name = "{0} \\/ {1} == {2}") + @MethodSource + void joinTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) { + assertThat(a.join(b), equalTo(expected)); + } + + static Stream joinTest() { + return Stream.of( + Arguments.of(atMost(1), atMost(2), atMost(2)), + Arguments.of(atMost(2), between(1, 3), atMost(3)), + Arguments.of(atMost(1), between(2, 3), atMost(3)), + Arguments.of(atMost(1), ERROR, atMost(1)), + Arguments.of(ERROR, atMost(1), atMost(1)), + Arguments.of(ERROR, ERROR, ERROR) + ); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervalsTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervalsTest.java new file mode 100644 index 00000000..5441c837 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervalsTest.java @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.cardinalityinterval; + +import org.junit.jupiter.api.Test; +import tools.refinery.logic.term.uppercardinality.UpperCardinalities; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +class CardinalityIntervalsTest { + @Test + void betweenEmptyTest() { + var interval = CardinalityIntervals.between(2, 1); + assertThat(interval.isEmpty(), equalTo(true)); + } + + @Test + void betweenNegativeUpperBoundTest() { + var interval = CardinalityIntervals.between(0, -1); + assertThat(interval.upperBound(), equalTo(UpperCardinalities.UNBOUNDED)); + assertThat(interval.isEmpty(), equalTo(false)); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/EmptyCardinalityIntervalTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/EmptyCardinalityIntervalTest.java new file mode 100644 index 00000000..0dbc7f61 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/EmptyCardinalityIntervalTest.java @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.cardinalityinterval; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.lessThan; + +class EmptyCardinalityIntervalTest { + @Test + void inconsistentBoundsTest() { + assertThat(CardinalityIntervals.ERROR.upperBound().compareToInt(CardinalityIntervals.ERROR.lowerBound()), + lessThan(0)); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/FiniteCardinalityIntervalTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/FiniteCardinalityIntervalTest.java new file mode 100644 index 00000000..588b25ab --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/FiniteCardinalityIntervalTest.java @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.cardinalityinterval; + +import org.junit.jupiter.api.Test; +import tools.refinery.logic.term.uppercardinality.UpperCardinality; +import tools.refinery.logic.term.uppercardinality.UpperCardinalities; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +class FiniteCardinalityIntervalTest { + @Test + void invalidLowerBoundConstructorTest() { + assertThrows(IllegalArgumentException.class, () -> new NonEmptyCardinalityInterval(-1, + UpperCardinalities.UNBOUNDED)); + } + + @Test + void invalidUpperBoundConstructorTest() { + var upperCardinality = UpperCardinality.of(1); + assertThrows(IllegalArgumentException.class, () -> new NonEmptyCardinalityInterval(2, + upperCardinality)); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/int_/IntTermsEvaluateTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/int_/IntTermsEvaluateTest.java new file mode 100644 index 00000000..55d9b740 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/int_/IntTermsEvaluateTest.java @@ -0,0 +1,260 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.int_; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; +import tools.refinery.logic.term.int_.IntTerms; +import tools.refinery.logic.term.real.RealTerms; +import tools.refinery.logic.valuation.Valuation; + +import java.util.stream.Stream; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +class IntTermsEvaluateTest { + @ParameterizedTest(name = "+{0} == {1}") + @CsvSource(value = { + "2, 2", + "null, null" + }, nullValues = "null") + void plusTest(Integer a, Integer result) { + var term = IntTerms.plus(IntTerms.constant(a)); + assertThat(term.getType(), is(Integer.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "-{0} == {1}") + @CsvSource(value = { + "2, -2", + "null, null" + }, nullValues = "null") + void minusTest(Integer a, Integer result) { + var term = IntTerms.minus(IntTerms.constant(a)); + assertThat(term.getType(), is(Integer.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "{0} + {1} == {2}") + @CsvSource(value = { + "1, 2, 3", + "null, 2, null", + "1, null, null", + "null, null, null" + }, nullValues = "null") + void addTest(Integer a, Integer b, Integer result) { + var term = IntTerms.add(IntTerms.constant(a), IntTerms.constant(b)); + assertThat(term.getType(), is(Integer.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "{0} - {1} == {2}") + @CsvSource(value = { + "1, 3, -2", + "null, 3, null", + "1, null, null", + "null, null, null" + }, nullValues = "null") + void subTest(Integer a, Integer b, Integer result) { + var term = IntTerms.sub(IntTerms.constant(a), IntTerms.constant(b)); + assertThat(term.getType(), is(Integer.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "{0} * {1} == {2}") + @CsvSource(value = { + "2, 3, 6", + "null, 3, null", + "2, null, null", + "null, null, null" + }, nullValues = "null") + void mulTest(Integer a, Integer b, Integer result) { + var term = IntTerms.mul(IntTerms.constant(a), IntTerms.constant(b)); + assertThat(term.getType(), is(Integer.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "{0} * {1} == {2}") + @CsvSource(value = { + "6, 3, 2", + "7, 3, 2", + "6, 0, null", + "null, 3, null", + "6, null, null", + "null, null, null" + }, nullValues = "null") + void divTest(Integer a, Integer b, Integer result) { + var term = IntTerms.div(IntTerms.constant(a), IntTerms.constant(b)); + assertThat(term.getType(), is(Integer.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "{0} ** {1} == {2}") + @CsvSource(value = { + "1, 0, 1", + "1, 3, 1", + "1, -3, null", + "2, 0, 1", + "2, 2, 4", + "2, 3, 8", + "2, 4, 16", + "2, 5, 32", + "2, 6, 64", + "2, -3, null", + "null, 3, null", + "2, null, null", + "null, null, null" + }, nullValues = "null") + void powTest(Integer a, Integer b, Integer result) { + var term = IntTerms.pow(IntTerms.constant(a), IntTerms.constant(b)); + assertThat(term.getType(), is(Integer.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "min({0}, {1}) == {2}") + @CsvSource(value = { + "1, 2, 1", + "2, 1, 1", + "null, 2, null", + "1, null, null", + "null, null, null" + }, nullValues = "null") + void minTest(Integer a, Integer b, Integer result) { + var term = IntTerms.min(IntTerms.constant(a), IntTerms.constant(b)); + assertThat(term.getType(), is(Integer.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "max({0}, {1}) == {2}") + @CsvSource(value = { + "1, 2, 2", + "2, 1, 2", + "null, 2, null", + "1, null, null", + "null, null, null" + }, nullValues = "null") + void maxTest(Integer a, Integer b, Integer result) { + var term = IntTerms.max(IntTerms.constant(a), IntTerms.constant(b)); + assertThat(term.getType(), is(Integer.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "({0} == {1}) == {2}") + @CsvSource(value = { + "1, 1, true", + "1, 2, false", + "null, 1, null", + "1, null, null", + "null, null, null" + }, nullValues = "null") + void eqTest(Integer a, Integer b, Boolean result) { + var term = IntTerms.eq(IntTerms.constant(a), IntTerms.constant(b)); + assertThat(term.getType(), is(Boolean.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "({0} != {1}) == {2}") + @CsvSource(value = { + "1, 1, false", + "1, 2, true", + "null, 1, null", + "1, null, null", + "null, null, null" + }, nullValues = "null") + void notEqTest(Integer a, Integer b, Boolean result) { + var term = IntTerms.notEq(IntTerms.constant(a), IntTerms.constant(b)); + assertThat(term.getType(), is(Boolean.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "({0} < {1}) == {2}") + @CsvSource(value = { + "1, -2, false", + "1, 1, false", + "1, 2, true", + "null, 1, null", + "1, null, null", + "null, null, null" + }, nullValues = "null") + void lessTest(Integer a, Integer b, Boolean result) { + var term = IntTerms.less(IntTerms.constant(a), IntTerms.constant(b)); + assertThat(term.getType(), is(Boolean.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "({0} <= {1}) == {2}") + @CsvSource(value = { + "1, -2, false", + "1, 1, true", + "1, 2, true", + "null, 1, null", + "1, null, null", + "null, null, null" + }, nullValues = "null") + void lessEqTest(Integer a, Integer b, Boolean result) { + var term = IntTerms.lessEq(IntTerms.constant(a), IntTerms.constant(b)); + assertThat(term.getType(), is(Boolean.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "({0} > {1}) == {2}") + @CsvSource(value = { + "1, -2, true", + "1, 1, false", + "1, 2, false", + "null, 1, null", + "1, null, null", + "null, null, null" + }, nullValues = "null") + void greaterTest(Integer a, Integer b, Boolean result) { + var term = IntTerms.greater(IntTerms.constant(a), IntTerms.constant(b)); + assertThat(term.getType(), is(Boolean.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "({0} >= {1}) == {2}") + @CsvSource(value = { + "1, -2, true", + "1, 1, true", + "1, 2, false", + "null, 1, null", + "1, null, null", + "null, null, null" + }, nullValues = "null") + void greaterEqTest(Integer a, Integer b, Boolean result) { + var term = IntTerms.greaterEq(IntTerms.constant(a), IntTerms.constant(b)); + assertThat(term.getType(), is(Boolean.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "{0} as int == {1}") + @MethodSource + void asIntTest(Double a, Integer result) { + var term = IntTerms.asInt(RealTerms.constant(a)); + assertThat(term.getType(), is(Integer.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + static Stream asIntTest() { + return Stream.of( + Arguments.of(2.0, 2), + Arguments.of(2.1, 2), + Arguments.of(2.9, 2), + Arguments.of(-2.0, -2), + Arguments.of(-2.1, -2), + Arguments.of(-2.9, -2), + Arguments.of(0.0, 0), + Arguments.of(-0.0, 0), + Arguments.of(Double.POSITIVE_INFINITY, Integer.MAX_VALUE), + Arguments.of(Double.NEGATIVE_INFINITY, Integer.MIN_VALUE), + Arguments.of(Double.NaN, null), + Arguments.of(null, null) + ); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/real/RealTermEvaluateTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/real/RealTermEvaluateTest.java new file mode 100644 index 00000000..042d1807 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/real/RealTermEvaluateTest.java @@ -0,0 +1,239 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.real; + +import org.hamcrest.Matcher; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import tools.refinery.logic.term.int_.IntTerms; +import tools.refinery.logic.term.real.RealTerms; +import tools.refinery.logic.valuation.Valuation; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +class RealTermEvaluateTest { + public static final double TOLERANCE = 1e-6; + + private static Matcher closeToOrNull(Double expected) { + return expected == null ? nullValue(Double.class) : closeTo(expected, TOLERANCE); + } + + @ParameterizedTest(name = "+{0} == {1}") + @CsvSource(value = { + "2.5, 2.5", + "null, null" + }, nullValues = "null") + void plusTest(Double a, Double result) { + var term = RealTerms.plus(RealTerms.constant(a)); + assertThat(term.getType(), is(Double.class)); + assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); + } + + @ParameterizedTest(name = "-{0} == {1}") + @CsvSource(value = { + "2.5, -2.5", + "null, null" + }, nullValues = "null") + void minusTest(Double a, Double result) { + var term = RealTerms.minus(RealTerms.constant(a)); + assertThat(term.getType(), is(Double.class)); + assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); + } + + @ParameterizedTest(name = "{0} + {1} == {2}") + @CsvSource(value = { + "1.2, 2.3, 3.5", + "null, 2.3, null", + "1.2, null, null", + "null, null, null" + }, nullValues = "null") + void addTest(Double a, Double b, Double result) { + var term = RealTerms.add(RealTerms.constant(a), RealTerms.constant(b)); + assertThat(term.getType(), is(Double.class)); + assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); + } + + @ParameterizedTest(name = "{0} - {1} == {2}") + @CsvSource(value = { + "1.2, 3.4, -2.2", + "null, 3.4, null", + "1.2, null, null", + "null, null, null" + }, nullValues = "null") + void subTest(Double a, Double b, Double result) { + var term = RealTerms.sub(RealTerms.constant(a), RealTerms.constant(b)); + assertThat(term.getType(), is(Double.class)); + assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); + } + + @ParameterizedTest(name = "{0} * {1} == {2}") + @CsvSource(value = { + "2.3, 3.4, 7.82", + "null, 3.4, null", + "2.3, null, null", + "null, null, null" + }, nullValues = "null") + void mulTest(Double a, Double b, Double result) { + var term = RealTerms.mul(RealTerms.constant(a), RealTerms.constant(b)); + assertThat(term.getType(), is(Double.class)); + assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); + } + + @ParameterizedTest(name = "{0} * {1} == {2}") + @CsvSource(value = { + "7.82, 3.4, 2.3", + "null, 3.4, null", + "7.82, null, null", + "null, null, null" + }, nullValues = "null") + void divTest(Double a, Double b, Double result) { + var term = RealTerms.div(RealTerms.constant(a), RealTerms.constant(b)); + assertThat(term.getType(), is(Double.class)); + assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); + } + + @ParameterizedTest(name = "{0} ** {1} == {2}") + @CsvSource(value = { + "2.0, 6.0, 64.0", + "null, 6.0, null", + "2.0, null, null", + "null, null, null" + }, nullValues = "null") + void powTest(Double a, Double b, Double result) { + var term = RealTerms.pow(RealTerms.constant(a), RealTerms.constant(b)); + assertThat(term.getType(), is(Double.class)); + assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); + } + + @ParameterizedTest(name = "min({0}, {1}) == {2}") + @CsvSource(value = { + "1.5, 2.7, 1.5", + "2.7, 1.5, 1.5", + "null, 2.7, null", + "1.5, null, null", + "null, null, null" + }, nullValues = "null") + void minTest(Double a, Double b, Double result) { + var term = RealTerms.min(RealTerms.constant(a), RealTerms.constant(b)); + assertThat(term.getType(), is(Double.class)); + assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); + } + + @ParameterizedTest(name = "max({0}, {1}) == {2}") + @CsvSource(value = { + "1.5, 2.7, 2.7", + "2.7, 1.7, 2.7", + "null, 2.7, null", + "1.5, null, null", + "null, null, null" + }, nullValues = "null") + void maxTest(Double a, Double b, Double result) { + var term = RealTerms.max(RealTerms.constant(a), RealTerms.constant(b)); + assertThat(term.getType(), is(Double.class)); + assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); + } + + @ParameterizedTest(name = "({0} == {1}) == {2}") + @CsvSource(value = { + "1.5, 1.5, true", + "1.5, 2.7, false", + "null, 1.5, null", + "1.5, null, null", + "null, null, null" + }, nullValues = "null") + void eqTest(Double a, Double b, Boolean result) { + var term = RealTerms.eq(RealTerms.constant(a), RealTerms.constant(b)); + assertThat(term.getType(), is(Boolean.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "({0} != {1}) == {2}") + @CsvSource(value = { + "1.5, 1.5, false", + "1.5, 2.7, true", + "null, 1.5, null", + "1.5, null, null", + "null, null, null" + }, nullValues = "null") + void notEqTest(Double a, Double b, Boolean result) { + var term = RealTerms.notEq(RealTerms.constant(a), RealTerms.constant(b)); + assertThat(term.getType(), is(Boolean.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "({0} < {1}) == {2}") + @CsvSource(value = { + "1.5, -2.7, false", + "1.5, 1.5, false", + "1.5, 2.7, true", + "null, 1.5, null", + "1.5, null, null", + "null, null, null" + }, nullValues = "null") + void lessTest(Double a, Double b, Boolean result) { + var term = RealTerms.less(RealTerms.constant(a), RealTerms.constant(b)); + assertThat(term.getType(), is(Boolean.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "({0} <= {1}) == {2}") + @CsvSource(value = { + "1.5, -2.7, false", + "1.5, 1.5, true", + "1.5, 2.7, true", + "null, 1.5, null", + "1.5, null, null", + "null, null, null" + }, nullValues = "null") + void lessEqTest(Double a, Double b, Boolean result) { + var term = RealTerms.lessEq(RealTerms.constant(a), RealTerms.constant(b)); + assertThat(term.getType(), is(Boolean.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "({0} > {1}) == {2}") + @CsvSource(value = { + "1.5, -2.7, true", + "1.5, 1.5, false", + "1.5, 2.7, false", + "null, 1.5, null", + "1.5, null, null", + "null, null, null" + }, nullValues = "null") + void greaterTest(Double a, Double b, Boolean result) { + var term = RealTerms.greater(RealTerms.constant(a), RealTerms.constant(b)); + assertThat(term.getType(), is(Boolean.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "({0} >= {1}) == {2}") + @CsvSource(value = { + "1.5, -2.7, true", + "1.5, 1.5, true", + "1.5, 2.7, false", + "null, 1.5, null", + "1.5, null, null", + "null, null, null" + }, nullValues = "null") + void greaterEqTest(Double a, Double b, Boolean result) { + var term = RealTerms.greaterEq(RealTerms.constant(a), RealTerms.constant(b)); + assertThat(term.getType(), is(Boolean.class)); + assertThat(term.evaluate(Valuation.empty()), is(result)); + } + + @ParameterizedTest(name = "{0} as real == {1}") + @CsvSource(value = { + "0, 0.0", + "5, 5.0", + "null, null" + }, nullValues = "null") + void asRealTest(Integer a, Double result) { + var term = RealTerms.asReal(IntTerms.constant(a)); + assertThat(term.getType(), is(Double.class)); + assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/FiniteUpperCardinalityTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/FiniteUpperCardinalityTest.java new file mode 100644 index 00000000..8a57f029 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/FiniteUpperCardinalityTest.java @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.uppercardinality; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +class FiniteUpperCardinalityTest { + @Test + void invalidConstructorTest() { + assertThrows(IllegalArgumentException.class, () -> new FiniteUpperCardinality(-1)); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitiesTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitiesTest.java new file mode 100644 index 00000000..bdb6a833 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitiesTest.java @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.uppercardinality; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; + +class UpperCardinalitiesTest { + @ParameterizedTest + @ValueSource(ints = {0, 1, 255, 256, 1000, Integer.MAX_VALUE}) + void valueOfBoundedTest(int value) { + var upperCardinality = UpperCardinalities.atMost(value); + assertThat(upperCardinality, instanceOf(FiniteUpperCardinality.class)); + assertThat(((FiniteUpperCardinality) upperCardinality).finiteUpperBound(), equalTo(value)); + } + + @Test + void valueOfUnboundedTest() { + var upperCardinality = UpperCardinalities.atMost(-1); + assertThat(upperCardinality, instanceOf(UnboundedUpperCardinality.class)); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregatorStreamTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregatorStreamTest.java new file mode 100644 index 00000000..fc8522d4 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregatorStreamTest.java @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.uppercardinality; + +import org.hamcrest.Matchers; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.List; +import java.util.stream.Stream; + +import static org.hamcrest.MatcherAssert.assertThat; + +class UpperCardinalitySumAggregatorStreamTest { + @ParameterizedTest + @MethodSource + void testStream(List list, UpperCardinality expected) { + var result = UpperCardinalitySumAggregator.INSTANCE.aggregateStream(list.stream()); + assertThat(result, Matchers.is(expected)); + } + + static Stream testStream() { + return Stream.of( + Arguments.of(List.of(), UpperCardinalities.ZERO), + Arguments.of(List.of(UpperCardinality.of(3)), UpperCardinality.of(3)), + Arguments.of( + List.of( + UpperCardinality.of(2), + UpperCardinality.of(3) + ), + UpperCardinality.of(5) + ), + Arguments.of(List.of(UpperCardinalities.UNBOUNDED), UpperCardinalities.UNBOUNDED), + Arguments.of( + List.of( + UpperCardinalities.UNBOUNDED, + UpperCardinalities.UNBOUNDED + ), + UpperCardinalities.UNBOUNDED + ), + Arguments.of( + List.of( + UpperCardinalities.UNBOUNDED, + UpperCardinality.of(3) + ), + UpperCardinalities.UNBOUNDED + ) + ); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregatorTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregatorTest.java new file mode 100644 index 00000000..e252b097 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregatorTest.java @@ -0,0 +1,79 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.uppercardinality; + +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import tools.refinery.logic.term.StatefulAggregate; + +import static org.hamcrest.MatcherAssert.assertThat; + +class UpperCardinalitySumAggregatorTest { + private StatefulAggregate accumulator; + + @BeforeEach + void beforeEach() { + accumulator = UpperCardinalitySumAggregator.INSTANCE.createEmptyAggregate(); + } + + @Test + void emptyAggregationTest() { + MatcherAssert.assertThat(accumulator.getResult(), Matchers.is(UpperCardinality.of(0))); + } + + @Test + void singleBoundedTest() { + accumulator.add(UpperCardinality.of(3)); + MatcherAssert.assertThat(accumulator.getResult(), Matchers.is(UpperCardinality.of(3))); + } + + @Test + void multipleBoundedTest() { + accumulator.add(UpperCardinality.of(2)); + accumulator.add(UpperCardinality.of(3)); + MatcherAssert.assertThat(accumulator.getResult(), Matchers.is(UpperCardinality.of(5))); + } + + @Test + void singleUnboundedTest() { + accumulator.add(UpperCardinalities.UNBOUNDED); + assertThat(accumulator.getResult(), Matchers.is(UpperCardinalities.UNBOUNDED)); + } + + @Test + void multipleUnboundedTest() { + accumulator.add(UpperCardinalities.UNBOUNDED); + accumulator.add(UpperCardinalities.UNBOUNDED); + assertThat(accumulator.getResult(), Matchers.is(UpperCardinalities.UNBOUNDED)); + } + + @Test + void removeBoundedTest() { + accumulator.add(UpperCardinality.of(2)); + accumulator.add(UpperCardinality.of(3)); + accumulator.remove(UpperCardinality.of(2)); + MatcherAssert.assertThat(accumulator.getResult(), Matchers.is(UpperCardinality.of(3))); + } + + @Test + void removeAllUnboundedTest() { + accumulator.add(UpperCardinalities.UNBOUNDED); + accumulator.add(UpperCardinality.of(3)); + accumulator.remove(UpperCardinalities.UNBOUNDED); + MatcherAssert.assertThat(accumulator.getResult(), Matchers.is(UpperCardinality.of(3))); + } + + @Test + void removeSomeUnboundedTest() { + accumulator.add(UpperCardinalities.UNBOUNDED); + accumulator.add(UpperCardinalities.UNBOUNDED); + accumulator.add(UpperCardinality.of(3)); + accumulator.remove(UpperCardinalities.UNBOUNDED); + assertThat(accumulator.getResult(), Matchers.is(UpperCardinalities.UNBOUNDED)); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTermsEvaluateTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTermsEvaluateTest.java new file mode 100644 index 00000000..ab71b716 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTermsEvaluateTest.java @@ -0,0 +1,103 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.uppercardinality; + +import org.hamcrest.Matchers; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import tools.refinery.logic.valuation.Valuation; + +import java.util.stream.Stream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +class UpperCardinalityTermsEvaluateTest { + @ParameterizedTest(name = "min({0}, {1}) == {2}") + @MethodSource + void minTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { + var term = UpperCardinalityTerms.min(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b)); + assertThat(term.getType(), is(UpperCardinality.class)); + assertThat(term.evaluate(Valuation.empty()), Matchers.is(expected)); + } + + static Stream minTest() { + return Stream.of( + Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)), + Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(0)), + Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(0)), + Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinality.of(0)), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinality.of(0)), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinality.of(1), null, null), + Arguments.of(null, UpperCardinality.of(1), null), + Arguments.of(null, null, null) + ); + } + + @ParameterizedTest(name = "max({0}, {1}) == {2}") + @MethodSource + void maxTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { + var term = UpperCardinalityTerms.max(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b)); + assertThat(term.getType(), is(UpperCardinality.class)); + assertThat(term.evaluate(Valuation.empty()), Matchers.is(expected)); + } + + static Stream maxTest() { + return Stream.of( + Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)), + Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(1)), + Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(1)), + Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinality.of(1), null, null), + Arguments.of(null, UpperCardinality.of(1), null), + Arguments.of(null, null, null) + ); + } + + @ParameterizedTest(name = "{0} + {1} == {2}") + @MethodSource + void addTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { + var term = UpperCardinalityTerms.add(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b)); + assertThat(term.getType(), is(UpperCardinality.class)); + assertThat(term.evaluate(Valuation.empty()), Matchers.is(expected)); + } + + static Stream addTest() { + return Stream.of( + Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(5)), + Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinality.of(1), null, null), + Arguments.of(null, UpperCardinality.of(1), null), + Arguments.of(null, null, null) + ); + } + + @ParameterizedTest(name = "{0} * {1} == {2}") + @MethodSource + void mulTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { + var term = UpperCardinalityTerms.mul(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b)); + assertThat(term.getType(), is(UpperCardinality.class)); + assertThat(term.evaluate(Valuation.empty()), Matchers.is(expected)); + } + + static Stream mulTest() { + return Stream.of( + Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(6)), + Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinality.of(1), null, null), + Arguments.of(null, UpperCardinality.of(1), null), + Arguments.of(null, null, null) + ); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTest.java new file mode 100644 index 00000000..70cb6695 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTest.java @@ -0,0 +1,115 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.uppercardinality; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +class UpperCardinalityTest { + @ParameterizedTest(name = "min({0}, {1}) == {2}") + @MethodSource + void minTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { + assertThat(a.min(b), equalTo(expected)); + } + + static Stream minTest() { + return Stream.of( + Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)), + Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(0)), + Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(0)), + Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinality.of(0)), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinality.of(0)), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED) + ); + } + + @ParameterizedTest(name = "max({0}, {1}) == {2}") + @MethodSource + void maxTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { + assertThat(a.max(b), equalTo(expected)); + } + + static Stream maxTest() { + return Stream.of( + Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)), + Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(1)), + Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(1)), + Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED) + ); + } + + @ParameterizedTest(name = "{0} + {1} == {2}") + @MethodSource + void addTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { + assertThat(a.add(b), equalTo(expected)); + } + + static Stream addTest() { + return Stream.of( + Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(5)), + Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED) + ); + } + + @ParameterizedTest(name = "{0} * {1} == {2}") + @MethodSource + void multiplyTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { + assertThat(a.multiply(b), equalTo(expected)); + } + + static Stream multiplyTest() { + return Stream.of( + Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(6)), + Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED) + ); + } + + @ParameterizedTest(name = "{0}.compareTo({1}) == {2}") + @MethodSource + void compareToTest(UpperCardinality a, UpperCardinality b, int expected) { + assertThat(a.compareTo(b), equalTo(expected)); + } + + static Stream compareToTest() { + return Stream.of( + Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), 0), + Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), -1), + Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), 1), + Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, -1), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), 1), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, 0) + ); + } + + @ParameterizedTest(name = "{0}.compareToInt({1}) == {2}") + @MethodSource + void compareToIntTest(UpperCardinality a, int b, int expected) { + assertThat(a.compareToInt(b), equalTo(expected)); + } + + static Stream compareToIntTest() { + return Stream.of( + Arguments.of(UpperCardinality.of(3), -1, 1), + Arguments.of(UpperCardinality.of(3), 2, 1), + Arguments.of(UpperCardinality.of(3), 3, 0), + Arguments.of(UpperCardinality.of(3), 4, -1), + Arguments.of(UpperCardinalities.UNBOUNDED, -1, 1), + Arguments.of(UpperCardinalities.UNBOUNDED, 3, 1) + ); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/tests/FakeFunctionView.java b/subprojects/logic/src/test/java/tools/refinery/logic/tests/FakeFunctionView.java new file mode 100644 index 00000000..4a55f561 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/tests/FakeFunctionView.java @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: 2024 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.tests; + +import tools.refinery.logic.Constraint; +import tools.refinery.logic.term.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public record FakeFunctionView(String name, int keyArity, Class valueType) implements Constraint { + @Override + public int arity() { + return keyArity + 1; + } + + @Override + public List getParameters() { + var parameters = new Parameter[keyArity + 1]; + Arrays.fill(parameters, Parameter.NODE_OUT); + parameters[keyArity] = new Parameter(valueType, ParameterDirection.OUT); + return List.of(parameters); + } + + public AssignedValue aggregate(Aggregator aggregator, List arguments) { + return targetVariable -> { + var placeholderVariable = Variable.of(valueType); + var argumentsWithPlaceholder = new ArrayList(arguments.size() + 1); + argumentsWithPlaceholder.addAll(arguments); + argumentsWithPlaceholder.add(placeholderVariable); + return aggregateBy(placeholderVariable, aggregator, argumentsWithPlaceholder).toLiteral(targetVariable); + }; + } + + public AssignedValue aggregate(Aggregator aggregator, NodeVariable... arguments) { + return aggregate(aggregator, List.of(arguments)); + } + + public AssignedValue leftJoin(T defaultValue, List arguments) { + return targetVariable -> { + var placeholderVariable = Variable.of(valueType); + var argumentsWithPlaceholder = new ArrayList(arguments.size() + 1); + argumentsWithPlaceholder.addAll(arguments); + argumentsWithPlaceholder.add(placeholderVariable); + return leftJoinBy(placeholderVariable, defaultValue, argumentsWithPlaceholder).toLiteral(targetVariable); + }; + } + + public AssignedValue leftJoin(T defaultValue, NodeVariable... arguments) { + return leftJoin(defaultValue, List.of(arguments)); + + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/tests/FakeKeyOnlyView.java b/subprojects/logic/src/test/java/tools/refinery/logic/tests/FakeKeyOnlyView.java new file mode 100644 index 00000000..7e09ddab --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/tests/FakeKeyOnlyView.java @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2024 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.tests; + +import tools.refinery.logic.Constraint; +import tools.refinery.logic.term.Parameter; + +import java.util.Arrays; +import java.util.List; + +public record FakeKeyOnlyView(String name, int arity) implements Constraint { + @Override + public List getParameters() { + var parameters = new Parameter[arity]; + Arrays.fill(parameters, Parameter.NODE_OUT); + return List.of(parameters); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/tests/StructurallyEqualToRawTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/tests/StructurallyEqualToRawTest.java new file mode 100644 index 00000000..52a22ce1 --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/tests/StructurallyEqualToRawTest.java @@ -0,0 +1,155 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.tests; + +import org.junit.jupiter.api.Test; +import tools.refinery.logic.Constraint; +import tools.refinery.logic.dnf.Dnf; +import tools.refinery.logic.dnf.SymbolicParameter; +import tools.refinery.logic.term.NodeVariable; +import tools.refinery.logic.term.ParameterDirection; +import tools.refinery.logic.term.Variable; + +import java.util.List; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static tools.refinery.logic.tests.QueryMatchers.structurallyEqualTo; + +class StructurallyEqualToRawTest { + private static final Constraint personView = new FakeKeyOnlyView("Person", 1); + private static final Constraint friendView = new FakeKeyOnlyView("friend", 2); + private static final NodeVariable p = Variable.of("p"); + private static final NodeVariable q = Variable.of("q"); + + @Test + void flatEqualsTest() { + var actual = Dnf.builder("Actual").parameters(p).clause(personView.call(p)).build(); + + assertThat(actual, structurallyEqualTo( + List.of(new SymbolicParameter(q, ParameterDirection.OUT)), + List.of(List.of(personView.call(q))) + )); + } + + @Test + void flatNotEqualsTest() { + var actual = Dnf.builder("Actual").parameters(p).clause(friendView.call(p, q)).build(); + + var assertion = structurallyEqualTo( + List.of(new SymbolicParameter(q, ParameterDirection.OUT)), + List.of(List.of(friendView.call(q, q))) + ); + assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); + } + + @Test + void deepEqualsTest() { + var actual = Dnf.builder("Actual").parameters(q).clause( + Dnf.builder("Actual2").parameters(p).clause(personView.call(p)).build().call(q) + ).build(); + + assertThat(actual, structurallyEqualTo( + List.of(new SymbolicParameter(q, ParameterDirection.OUT)), + List.of( + List.of( + Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build().call(q) + ) + ) + )); + } + + @Test + void deepNotEqualsTest() { + var actual = Dnf.builder("Actual").parameter(q).clause( + Dnf.builder("Actual2").parameters(p).clause(friendView.call(p, q)).build().call(q) + ).build(); + + var assertion = structurallyEqualTo( + List.of(new SymbolicParameter(q, ParameterDirection.OUT)), + List.of( + List.of( + Dnf.builder("Expected2") + .parameters(p) + .clause(friendView.call(p, p)) + .build() + .call(q) + ) + ) + ); + var error = assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); + assertThat(error.getMessage(), allOf(containsString("Expected2"), containsString("Actual2"))); + } + + @Test + void parameterListLengthMismatchTest() { + var actual = Dnf.builder("Actual").parameters(p, q).clause( + friendView.call(p, q) + ).build(); + + var assertion = structurallyEqualTo( + List.of(new SymbolicParameter(p, ParameterDirection.OUT)), + List.of(List.of(friendView.call(p, p))) + ); + + assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); + } + + @Test + void parameterDirectionMismatchTest() { + var actual = Dnf.builder("Actual").parameter(p, ParameterDirection.IN).clause( + personView.call(p) + ).build(); + + var assertion = structurallyEqualTo( + List.of(new SymbolicParameter(p, ParameterDirection.OUT)), + List.of(List.of(personView.call(p))) + ); + + assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); + } + + @Test + void clauseCountMismatchTest() { + var actual = Dnf.builder("Actual").parameters(p, q).clause( + friendView.call(p, q) + ).build(); + + var assertion = structurallyEqualTo( + List.of( + new SymbolicParameter(p, ParameterDirection.OUT), + new SymbolicParameter(q, ParameterDirection.OUT) + ), + List.of( + List.of(friendView.call(p, q)), + List.of(friendView.call(q, p)) + ) + ); + + assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); + } + + @Test + void literalCountMismatchTest() { + var actual = Dnf.builder("Actual").parameters(p, q).clause( + friendView.call(p, q) + ).build(); + + var assertion = structurallyEqualTo( + List.of( + new SymbolicParameter(p, ParameterDirection.OUT), + new SymbolicParameter(q, ParameterDirection.OUT) + ), + List.of( + List.of(friendView.call(p, q), friendView.call(q, p)) + ) + ); + + assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); + } +} diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/tests/StructurallyEqualToTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/tests/StructurallyEqualToTest.java new file mode 100644 index 00000000..663b115a --- /dev/null +++ b/subprojects/logic/src/test/java/tools/refinery/logic/tests/StructurallyEqualToTest.java @@ -0,0 +1,123 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.tests; + +import org.junit.jupiter.api.Test; +import tools.refinery.logic.Constraint; +import tools.refinery.logic.dnf.Dnf; +import tools.refinery.logic.term.NodeVariable; +import tools.refinery.logic.term.ParameterDirection; +import tools.refinery.logic.term.Variable; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static tools.refinery.logic.tests.QueryMatchers.structurallyEqualTo; + +class StructurallyEqualToTest { + private static final Constraint personView = new FakeKeyOnlyView("Person", 1); + private static final Constraint friendView = new FakeKeyOnlyView("friend", 2); + private static final NodeVariable p = Variable.of("p"); + private static final NodeVariable q = Variable.of("q"); + + @Test + void flatEqualsTest() { + var expected = Dnf.builder("Expected").parameters(q).clause(personView.call(q)).build(); + var actual = Dnf.builder("Actual").parameters(p).clause(personView.call(p)).build(); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @Test + void flatNotEqualsTest() { + var expected = Dnf.builder("Expected").parameters(q).clause(friendView.call(q, q)).build(); + var actual = Dnf.builder("Actual").parameters(p).clause(friendView.call(p, q)).build(); + + var assertion = structurallyEqualTo(expected); + assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); + } + + @Test + void deepEqualsTest() { + var expected = Dnf.builder("Expected").parameters(q).clause( + Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build().call(q) + ).build(); + var actual = Dnf.builder("Actual").parameters(q).clause( + Dnf.builder("Actual2").parameters(p).clause(personView.call(p)).build().call(q) + ).build(); + + assertThat(actual, structurallyEqualTo(expected)); + } + + @Test + void deepNotEqualsTest() { + var expected = Dnf.builder("Expected").parameters(q).clause( + Dnf.builder("Expected2").parameters(p).clause(friendView.call(p, p)).build().call(q) + ).build(); + var actual = Dnf.builder("Actual").parameter(q).clause( + Dnf.builder("Actual2").parameters(p).clause(friendView.call(p, q)).build().call(q) + ).build(); + + var assertion = structurallyEqualTo(expected); + var error = assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); + assertThat(error.getMessage(), containsString(" called from Expected/1 ")); + } + + @Test + void parameterListLengthMismatchTest() { + var expected = Dnf.builder("Expected").parameter(p).clause( + friendView.call(p, p) + ).build(); + var actual = Dnf.builder("Actual").parameters(p, q).clause( + friendView.call(p, q) + ).build(); + + var assertion = structurallyEqualTo(expected); + assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); + } + + @Test + void parameterDirectionMismatchTest() { + var expected = Dnf.builder("Expected").parameter(p, ParameterDirection.OUT).clause( + personView.call(p) + ).build(); + var actual = Dnf.builder("Actual").parameter(p, ParameterDirection.IN).clause( + personView.call(p) + ).build(); + + var assertion = structurallyEqualTo(expected); + assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); + } + + @Test + void clauseCountMismatchTest() { + var expected = Dnf.builder("Expected") + .parameters(p, q) + .clause(friendView.call(p, q)) + .clause(friendView.call(q, p)) + .build(); + var actual = Dnf.builder("Actual").parameters(p, q).clause( + friendView.call(p, q) + ).build(); + + var assertion = structurallyEqualTo(expected); + assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); + } + + @Test + void literalCountMismatchTest() { + var expected = Dnf.builder("Expected").parameters(p, q).clause( + friendView.call(p, q), + friendView.call(q, p) + ).build(); + var actual = Dnf.builder("Actual").parameters(p, q).clause( + friendView.call(p, q) + ).build(); + + var assertion = structurallyEqualTo(expected); + assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); + } +} -- cgit v1.2.3-54-g00ecf