aboutsummaryrefslogtreecommitdiff
path: root/tests/test_term_bank.cc
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_term_bank.cc')
-rw-r--r--tests/test_term_bank.cc316
1 files changed, 316 insertions, 0 deletions
diff --git a/tests/test_term_bank.cc b/tests/test_term_bank.cc
index b62ffd0..63acc34 100644
--- a/tests/test_term_bank.cc
+++ b/tests/test_term_bank.cc
@@ -16,4 +16,320 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+#include <gtest/gtest.h>
+#include "atp/core/clause.h"
+#include "atp/core/clause_store.h"
+#include "atp/core/literal.h"
+#include "atp/core/symbol_table.h"
+#include "atp/core/term.h"
+#include "atp/core/term_bank.h"
+#include "atp/core/types.h"
+
+namespace atp {
+namespace {
+
+// ═══════════════════════════════════════════════════════════════════════════
+// SymbolTable Tests
+// ═══════════════════════════════════════════════════════════════════════════
+
+TEST(SymbolTableTest, InternReturnsConsistentId) {
+ SymbolTable st;
+ SymbolId id1 = st.intern("foo", SymbolKind::kFunction, 2);
+ SymbolId id2 = st.intern("foo", SymbolKind::kFunction, 2);
+ EXPECT_EQ(id1, id2);
+}
+
+TEST(SymbolTableTest, DifferentNamesGetDifferentIds) {
+ SymbolTable st;
+ SymbolId a = st.intern("a", SymbolKind::kConstant);
+ SymbolId b = st.intern("b", SymbolKind::kConstant);
+ EXPECT_NE(a, b);
+}
+
+TEST(SymbolTableTest, GetNameRoundTrips) {
+ SymbolTable st;
+ SymbolId id = st.intern("hello", SymbolKind::kFunction, 1);
+ EXPECT_EQ(st.getName(id), "hello");
+}
+
+TEST(SymbolTableTest, GetInfoPreservesMetadata) {
+ SymbolTable st;
+ SymbolId id = st.intern("P", SymbolKind::kPredicate, 3, 42);
+ const SymbolInfo& info = st.getInfo(id);
+ EXPECT_EQ(info.name, "P");
+ EXPECT_EQ(info.kind, SymbolKind::kPredicate);
+ EXPECT_EQ(info.arity, 3);
+ EXPECT_EQ(info.sort, 42);
+}
+
+TEST(SymbolTableTest, IsVariableDetectsKind) {
+ SymbolTable st;
+ SymbolId var = st.intern("X", SymbolKind::kVariable);
+ SymbolId con = st.intern("a", SymbolKind::kConstant);
+ SymbolId fun = st.intern("f", SymbolKind::kFunction, 1);
+ EXPECT_TRUE(st.isVariable(var));
+ EXPECT_FALSE(st.isVariable(con));
+ EXPECT_FALSE(st.isVariable(fun));
+}
+
+TEST(SymbolTableTest, MismatchKindThrows) {
+ SymbolTable st;
+ st.intern("x", SymbolKind::kConstant);
+ EXPECT_THROW(st.intern("x", SymbolKind::kVariable), std::runtime_error);
+}
+
+TEST(SymbolTableTest, MismatchArityThrows) {
+ SymbolTable st;
+ st.intern("f", SymbolKind::kFunction, 1);
+ EXPECT_THROW(st.intern("f", SymbolKind::kFunction, 2), std::runtime_error);
+}
+
+TEST(SymbolTableTest, SizeTracksInternedSymbols) {
+ SymbolTable st;
+ EXPECT_EQ(st.size(), 0);
+ st.intern("a", SymbolKind::kConstant);
+ EXPECT_EQ(st.size(), 1);
+ st.intern("b", SymbolKind::kConstant);
+ EXPECT_EQ(st.size(), 2);
+ st.intern("a", SymbolKind::kConstant); // duplicate
+ EXPECT_EQ(st.size(), 2);
+}
+
+// ═══════════════════════════════════════════════════════════════════════════
+// TermBank Tests
+// ═══════════════════════════════════════════════════════════════════════════
+
+class TermBankTest : public ::testing::Test {
+ protected:
+ SymbolTable symbols;
+ TermBank bank{symbols};
+
+ SymbolId sym_a, sym_b, sym_f, sym_g, sym_X, sym_Y;
+
+ void SetUp() override {
+ sym_a = symbols.intern("a", SymbolKind::kConstant);
+ sym_b = symbols.intern("b", SymbolKind::kConstant);
+ sym_f = symbols.intern("f", SymbolKind::kFunction, 1);
+ sym_g = symbols.intern("g", SymbolKind::kFunction, 2);
+ sym_X = symbols.intern("X", SymbolKind::kVariable);
+ sym_Y = symbols.intern("Y", SymbolKind::kVariable);
+ }
+};
+
+TEST_F(TermBankTest, HashConsingReturnsSameId) {
+ TermId a1 = bank.makeTerm(sym_a, {});
+ TermId a2 = bank.makeTerm(sym_a, {});
+ EXPECT_EQ(a1, a2) << "Hash consing failed: same term got different IDs";
+}
+
+TEST_F(TermBankTest, DifferentTermsGetDifferentIds) {
+ TermId a = bank.makeTerm(sym_a, {});
+ TermId b = bank.makeTerm(sym_b, {});
+ EXPECT_NE(a, b);
+}
+
+TEST_F(TermBankTest, CompoundTermHashConsing) {
+ TermId a = bank.makeTerm(sym_a, {});
+ TermId b = bank.makeTerm(sym_b, {});
+ // f(a)
+ TermId fa1 = bank.makeTerm(sym_f, {{a}});
+ TermId fa2 = bank.makeTerm(sym_f, {{a}});
+ EXPECT_EQ(fa1, fa2);
+ // f(b) != f(a)
+ TermId fb = bank.makeTerm(sym_f, {{b}});
+ EXPECT_NE(fa1, fb);
+}
+
+TEST_F(TermBankTest, NestedTermHashConsing) {
+ TermId a = bank.makeTerm(sym_a, {});
+ TermId fa = bank.makeTerm(sym_f, {{a}});
+ // g(f(a), a)
+ TermId gfa_a_1 = bank.makeTerm(sym_g, {{fa, a}});
+ TermId gfa_a_2 = bank.makeTerm(sym_g, {{fa, a}});
+ EXPECT_EQ(gfa_a_1, gfa_a_2);
+}
+
+TEST_F(TermBankTest, GetTermReturnsCorrectSymbol) {
+ TermId a = bank.makeTerm(sym_a, {});
+ const Term& t = bank.getTerm(a);
+ EXPECT_EQ(t.symbol_id, sym_a);
+ EXPECT_EQ(t.arity(), 0);
+}
+
+TEST_F(TermBankTest, GetTermReturnsCorrectArgs) {
+ TermId a = bank.makeTerm(sym_a, {});
+ TermId b = bank.makeTerm(sym_b, {});
+ TermId gab = bank.makeTerm(sym_g, {{a, b}});
+
+ const Term& t = bank.getTerm(gab);
+ EXPECT_EQ(t.symbol_id, sym_g);
+ EXPECT_EQ(t.arity(), 2);
+ ASSERT_EQ(t.args.size(), 2);
+ EXPECT_EQ(t.args[0], a);
+ EXPECT_EQ(t.args[1], b);
+}
+
+TEST_F(TermBankTest, MakeVarCreatesZeroArityTerm) {
+ TermId x = bank.makeVar(sym_X);
+ const Term& t = bank.getTerm(x);
+ EXPECT_EQ(t.symbol_id, sym_X);
+ EXPECT_EQ(t.arity(), 0);
+ EXPECT_TRUE(t.args.empty());
+}
+
+TEST_F(TermBankTest, IsVariableDistinguishesFromConstants) {
+ TermId x = bank.makeVar(sym_X);
+ TermId a = bank.makeTerm(sym_a, {});
+
+ EXPECT_TRUE(bank.isVariable(x)) << "Variable should be detected as variable";
+ EXPECT_FALSE(bank.isVariable(a)) << "Constant should NOT be detected as variable";
+}
+
+TEST_F(TermBankTest, VariableHashConsing) {
+ TermId x1 = bank.makeVar(sym_X);
+ TermId x2 = bank.makeVar(sym_X);
+ EXPECT_EQ(x1, x2);
+
+ TermId y = bank.makeVar(sym_Y);
+ EXPECT_NE(x1, y);
+}
+
+TEST_F(TermBankTest, SizeTracksUniqueTerms) {
+ EXPECT_EQ(bank.size(), 0);
+ TermId a = bank.makeTerm(sym_a, {});
+ EXPECT_EQ(bank.size(), 1);
+ bank.makeTerm(sym_a, {}); // duplicate
+ EXPECT_EQ(bank.size(), 1);
+ bank.makeTerm(sym_b, {});
+ EXPECT_EQ(bank.size(), 2);
+ bank.makeTerm(sym_f, {{a}});
+ EXPECT_EQ(bank.size(), 3);
+}
+
+TEST_F(TermBankTest, CompoundTermWithVariableArgs) {
+ // f(X) — a function applied to a variable
+ TermId x = bank.makeVar(sym_X);
+ TermId fx = bank.makeTerm(sym_f, {{x}});
+
+ const Term& t = bank.getTerm(fx);
+ EXPECT_EQ(t.symbol_id, sym_f);
+ EXPECT_EQ(t.arity(), 1);
+ EXPECT_EQ(t.args[0], x);
+
+ EXPECT_FALSE(bank.isVariable(fx));
+ EXPECT_TRUE(bank.isVariable(x));
+}
+
+// ═══════════════════════════════════════════════════════════════════════════
+// ClauseStore Tests
+// ═══════════════════════════════════════════════════════════════════════════
+
+TEST(ClauseStoreTest, AddAndRetrieve) {
+ ClauseStore store;
+ Clause c;
+ c.literals.push_back(Literal{.atom = 0, .is_positive = true});
+ c.literals.push_back(Literal{.atom = 1, .is_positive = false});
+
+ ClauseId id = store.addClause(std::move(c));
+ EXPECT_EQ(id, 0);
+
+ const Clause& retrieved = store.getClause(id);
+ EXPECT_EQ(retrieved.id, id);
+ EXPECT_EQ(retrieved.literals.size(), 2);
+ EXPECT_EQ(retrieved.literals[0].atom, 0);
+ EXPECT_TRUE(retrieved.literals[0].is_positive);
+ EXPECT_EQ(retrieved.literals[1].atom, 1);
+ EXPECT_FALSE(retrieved.literals[1].is_positive);
+}
+
+TEST(ClauseStoreTest, AssignsSequentialIds) {
+ ClauseStore store;
+ ClauseId id0 = store.addClause(Clause{});
+ ClauseId id1 = store.addClause(Clause{});
+ ClauseId id2 = store.addClause(Clause{});
+ EXPECT_EQ(id0, 0);
+ EXPECT_EQ(id1, 1);
+ EXPECT_EQ(id2, 2);
+ EXPECT_EQ(store.size(), 3);
+}
+
+TEST(ClauseStoreTest, StampsClauseWithId) {
+ ClauseStore store;
+ Clause c;
+ c.id = kInvalidId; // unset
+ ClauseId id = store.addClause(std::move(c));
+ EXPECT_EQ(store.getClause(id).id, id);
+}
+
+TEST(ClauseStoreTest, MutableAccess) {
+ ClauseStore store;
+ Clause c;
+ c.depth = 0;
+ ClauseId id = store.addClause(std::move(c));
+
+ store.getMutableClause(id).depth = 42;
+ EXPECT_EQ(store.getClause(id).depth, 42);
+}
+
+TEST(ClauseStoreTest, ForEachVisitsAll) {
+ ClauseStore store;
+ store.addClause(Clause{});
+ store.addClause(Clause{});
+ store.addClause(Clause{});
+
+ size_t count = 0;
+ store.forEach([&count](const Clause&) { count++; });
+ EXPECT_EQ(count, 3);
+}
+
+TEST(ClauseStoreTest, EmptyClauseDetection) {
+ Clause c;
+ EXPECT_TRUE(c.isEmpty());
+ c.literals.push_back(Literal{.atom = 0, .is_positive = true});
+ EXPECT_FALSE(c.isEmpty());
+ EXPECT_EQ(c.size(), 1);
+}
+
+TEST(ClauseStoreTest, ProvenancePreserved) {
+ ClauseStore store;
+ Clause c;
+ c.rule = InferenceRule::kResolution;
+ c.parent1 = 3;
+ c.parent2 = 7;
+ c.depth = 5;
+
+ ClauseId id = store.addClause(std::move(c));
+ const Clause& retrieved = store.getClause(id);
+ EXPECT_EQ(retrieved.rule, InferenceRule::kResolution);
+ EXPECT_EQ(retrieved.parent1, 3);
+ EXPECT_EQ(retrieved.parent2, 7);
+ EXPECT_EQ(retrieved.depth, 5);
+}
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Literal Tests
+// ═══════════════════════════════════════════════════════════════════════════
+
+TEST(LiteralTest, Complement) {
+ Literal pos{.atom = 42, .is_positive = true};
+ Literal neg = pos.complement();
+ EXPECT_EQ(neg.atom, 42);
+ EXPECT_FALSE(neg.is_positive);
+
+ Literal back = neg.complement();
+ EXPECT_EQ(back, pos);
+}
+
+TEST(LiteralTest, Equality) {
+ Literal a{.atom = 1, .is_positive = true};
+ Literal b{.atom = 1, .is_positive = true};
+ Literal c{.atom = 1, .is_positive = false};
+ Literal d{.atom = 2, .is_positive = true};
+ EXPECT_EQ(a, b);
+ EXPECT_NE(a, c);
+ EXPECT_NE(a, d);
+}
+
+} // namespace
+} // namespace atp