diff options
| author | Marcos Magueta <maguetamarcos@gmail.com> | 2026-06-05 21:40:44 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-06-05 21:40:44 -0300 |
| commit | 5174fc25698cff33b03b5746bf1742e417988f7d (patch) | |
| tree | 232a3acdd9b458de481dbd5cbde889505168c8e8 /test | |
| parent | 85ed897de3676af866cc619dc700025820c02201 (diff) | |
| download | RNT-5174fc25698cff33b03b5746bf1742e417988f7d.tar.gz RNT-5174fc25698cff33b03b5746bf1742e417988f7d.zip | |
Project C interface, reroute FOL plan enums & format
Diffstat (limited to '')
| -rw-r--r-- | test/C_API.cpp | 141 | ||||
| -rw-r--r-- | test/CursorManager.cpp | 86 | ||||
| -rw-r--r-- | test/LifecycleManager.cpp | 96 | ||||
| -rw-r--r-- | test/MultigroupCodec.cpp | 67 | ||||
| -rw-r--r-- | test/NamespaceReferenceManager.cpp | 162 | ||||
| -rw-r--r-- | test/ObjectManager.cpp | 33 | ||||
| -rw-r--r-- | test/VM.cpp | 159 |
7 files changed, 318 insertions, 426 deletions
diff --git a/test/C_API.cpp b/test/C_API.cpp index f186374..892ed24 100644 --- a/test/C_API.cpp +++ b/test/C_API.cpp @@ -15,27 +15,26 @@ // test does not affect another. // --------------------------------------------------------------------------- -namespace -{ +namespace { struct InitGuard { - InitGuard() { REQUIRE(rnt_init("memory", nullptr) == 0); } + InitGuard() { + REQUIRE(rnt_init("memory", nullptr) == 0); + } }; - std::string take_string(char* p) - { + std::string take_string(char* p) { std::string s = p ? p : ""; rnt_free_string(p); return s; } -} +} // namespace // --------------------------------------------------------------------------- // Step 2 — Branch hash-pointer model // --------------------------------------------------------------------------- TEST_CASE("rnt_register_branch with empty target creates an unborn branch", - "[step2][capi][branch]") -{ + "[step2][capi][branch]") { InitGuard _; const char* path = "/system/branches/test_step2_unborn"; REQUIRE(rnt_register_branch(path, "") == 0); @@ -50,25 +49,20 @@ TEST_CASE("rnt_register_branch with empty target creates an unborn branch", REQUIRE(rnt_close_handle(h) == 0); } -TEST_CASE("rnt_register_branch rejects an unregistered target hash", - "[step2][capi][branch]") -{ +TEST_CASE("rnt_register_branch rejects an unregistered target hash", "[step2][capi][branch]") { InitGuard _; - REQUIRE(rnt_register_branch("/system/branches/test_step2_bad_init", - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") - != 0); + REQUIRE(rnt_register_branch( + "/system/branches/test_step2_bad_init", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") != 0); } -TEST_CASE("rnt_branch_advance rejects an unregistered target hash", - "[step2][capi][branch]") -{ +TEST_CASE("rnt_branch_advance rejects an unregistered target hash", "[step2][capi][branch]") { InitGuard _; const char* bpath = "/system/branches/test_step2_bad_advance"; REQUIRE(rnt_register_branch(bpath, "") == 0); REQUIRE(rnt_branch_advance( - bpath, - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") != 0); + bpath, "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") != 0); // Target unchanged. rnt_handle_t h = rnt_open_handle(bpath, nullptr); @@ -79,9 +73,7 @@ TEST_CASE("rnt_branch_advance rejects an unregistered target hash", rnt_close_handle(h); } -TEST_CASE("Writing a tuple advances the branch to a new snapshot", - "[step2][step3][capi][branch]") -{ +TEST_CASE("Writing a tuple advances the branch to a new snapshot", "[step2][step3][capi][branch]") { InitGuard _; const char* bpath = "/system/branches/test_step2_advance"; const char* rpath = "/system/branches/test_step2_advance/multigroups/public/relations/items"; @@ -117,9 +109,7 @@ TEST_CASE("Writing a tuple advances the branch to a new snapshot", // Step 3+4 — Copy-on-write writes and resolver wiring // --------------------------------------------------------------------------- -TEST_CASE("link_tuple commits a new snapshot and relation_root reflects it", - "[step3][capi][cow]") -{ +TEST_CASE("link_tuple commits a new snapshot and relation_root reflects it", "[step3][capi][cow]") { InitGuard _; const char* bpath = "/system/branches/test_step3_link"; const char* rpath = "/system/branches/test_step3_link/multigroups/public/relations/items"; @@ -138,12 +128,10 @@ TEST_CASE("link_tuple commits a new snapshot and relation_root reflects it", REQUIRE(rnt_relation_root(rpath, &root) == 0); const std::string new_root = take_string(root); - REQUIRE(new_root.size() == 64); // a real Merkle root now + REQUIRE(new_root.size() == 64); // a real Merkle root now } -TEST_CASE("unlink_tuple and clear_relation walk back through new snapshots", - "[step3][capi][cow]") -{ +TEST_CASE("unlink_tuple and clear_relation walk back through new snapshots", "[step3][capi][cow]") { InitGuard _; const char* bpath = "/system/branches/test_step3_unlink"; const char* rpath = "/system/branches/test_step3_unlink/multigroups/public/relations/items"; @@ -171,8 +159,7 @@ TEST_CASE("unlink_tuple and clear_relation walk back through new snapshots", } TEST_CASE("rnt_open_handle resolves branch-relative paths to snapshot relations", - "[step4][capi][resolver]") -{ + "[step4][capi][resolver]") { InitGuard _; const char* bpath = "/system/branches/test_step4_resolve"; const char* rpath = "/system/branches/test_step4_resolve/multigroups/public/relations/items"; @@ -203,26 +190,23 @@ TEST_CASE("rnt_open_handle resolves branch-relative paths to snapshot relations" rnt_close_handle(h); } -TEST_CASE("Reading an unborn branch's relation fails cleanly", - "[step4][capi][resolver]") -{ +TEST_CASE("Reading an unborn branch's relation fails cleanly", "[step4][capi][resolver]") { InitGuard _; const char* bpath = "/system/branches/test_step4_unborn"; REQUIRE(rnt_register_branch(bpath, "") == 0); // No relation registered: relation_root miss returns nonzero. char* root = nullptr; - REQUIRE(rnt_relation_root( - "/system/branches/test_step4_unborn/multigroups/public/relations/missing", &root) != 0); + REQUIRE( + rnt_relation_root("/system/branches/test_step4_unborn/multigroups/public/relations/missing", + &root) != 0); } // --------------------------------------------------------------------------- // Step 5 — Session lifecycle and overrides // --------------------------------------------------------------------------- -TEST_CASE("Each rnt_session_open returns a distinct 64-char hex hash", - "[step5][capi][session]") -{ +TEST_CASE("Each rnt_session_open returns a distinct 64-char hex hash", "[step5][capi][session]") { InitGuard _; char* a = nullptr; char* b = nullptr; @@ -238,17 +222,14 @@ TEST_CASE("Each rnt_session_open returns a distinct 64-char hex hash", REQUIRE(rnt_session_close(hb.c_str()) == 0); } -TEST_CASE("rnt_session_close on an unknown hash fails", - "[step5][capi][session]") -{ +TEST_CASE("rnt_session_close on an unknown hash fails", "[step5][capi][session]") { InitGuard _; - REQUIRE(rnt_session_close( - "0000000000000000000000000000000000000000000000000000000000000000") != 0); + REQUIRE(rnt_session_close("0000000000000000000000000000000000000000000000000000000000000000") != + 0); } TEST_CASE("Session override redirects branch-relative reads to a different snapshot", - "[step5][capi][session][resolver]") -{ + "[step5][capi][session][resolver]") { InitGuard _; const char* bpath = "/system/branches/test_step5_override"; const char* rpath = "/system/branches/test_step5_override/multigroups/public/relations/items"; @@ -273,9 +254,7 @@ TEST_CASE("Session override redirects branch-relative reads to a different snaps char* sess = nullptr; REQUIRE(rnt_session_open(nullptr, &sess) == 0); const std::string sid = take_string(sess); - REQUIRE(rnt_session_set_branch(sid.c_str(), - "test_step5_override", - s1.c_str()) == 0); + REQUIRE(rnt_session_set_branch(sid.c_str(), "test_step5_override", s1.c_str()) == 0); // Advance the branch to S2 — global HEAD moves on; the session override // keeps S1 resident. @@ -283,9 +262,8 @@ TEST_CASE("Session override redirects branch-relative reads to a different snaps rnt_free_string(th); // Session view: still S1 (one tuple). - const std::string spath = - "/system/sessions/" + sid + - "/branches/test_step5_override/multigroups/public/relations/items"; + const std::string spath = "/system/sessions/" + sid + + "/branches/test_step5_override/multigroups/public/relations/items"; rnt_handle_t h_session = rnt_open_handle(spath.c_str(), nullptr); REQUIRE(h_session != nullptr); @@ -294,8 +272,7 @@ TEST_CASE("Session override redirects branch-relative reads to a different snaps int rows_under_session = 0; char* row = nullptr; - while (rnt_cursor_next(c_session, &row) == 1) - { + while (rnt_cursor_next(c_session, &row) == 1) { ++rows_under_session; rnt_free_string(row); } @@ -310,7 +287,10 @@ TEST_CASE("Session override redirects branch-relative reads to a different snaps rnt_cursor_t c_global = rnt_cursor_open(h_global); REQUIRE(c_global != nullptr); int rows_global = 0; - while (rnt_cursor_next(c_global, &row) == 1) { ++rows_global; rnt_free_string(row); } + while (rnt_cursor_next(c_global, &row) == 1) { + ++rows_global; + rnt_free_string(row); + } REQUIRE(rows_global == 2); rnt_cursor_close(c_global); rnt_close_handle(h_global); @@ -319,8 +299,7 @@ TEST_CASE("Session override redirects branch-relative reads to a different snaps } TEST_CASE("Clearing a session override falls back to the global branch HEAD", - "[step5][capi][session][resolver]") -{ + "[step5][capi][session][resolver]") { InitGuard _; const char* bpath = "/system/branches/test_step5_clear"; const char* rpath = "/system/branches/test_step5_clear/multigroups/public/relations/items"; @@ -351,15 +330,17 @@ TEST_CASE("Clearing a session override falls back to the global branch HEAD", // Now the session sees the global view (two tuples). const std::string spath = - "/system/sessions/" + sid + - "/branches/test_step5_clear/multigroups/public/relations/items"; + "/system/sessions/" + sid + "/branches/test_step5_clear/multigroups/public/relations/items"; rnt_handle_t h = rnt_open_handle(spath.c_str(), nullptr); REQUIRE(h != nullptr); rnt_cursor_t c = rnt_cursor_open(h); REQUIRE(c != nullptr); int rows = 0; char* row = nullptr; - while (rnt_cursor_next(c, &row) == 1) { ++rows; rnt_free_string(row); } + while (rnt_cursor_next(c, &row) == 1) { + ++rows; + rnt_free_string(row); + } REQUIRE(rows == 2); rnt_cursor_close(c); rnt_close_handle(h); @@ -367,18 +348,15 @@ TEST_CASE("Clearing a session override falls back to the global branch HEAD", REQUIRE(rnt_session_close(sid.c_str()) == 0); } -TEST_CASE("rnt_session_set_branch rejects an unregistered snapshot", - "[step5][capi][session]") -{ +TEST_CASE("rnt_session_set_branch rejects an unregistered snapshot", "[step5][capi][session]") { InitGuard _; char* sess = nullptr; REQUIRE(rnt_session_open(nullptr, &sess) == 0); const std::string sid = take_string(sess); REQUIRE(rnt_session_set_branch( - sid.c_str(), - "anything", - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") != 0); + sid.c_str(), "anything", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") != 0); REQUIRE(rnt_session_close(sid.c_str()) == 0); } @@ -389,39 +367,34 @@ TEST_CASE("rnt_session_set_branch rejects an unregistered snapshot", // or its enumerable relation set. // --------------------------------------------------------------------------- -TEST_CASE("Two multigroups under one branch advance independently", - "[capi][multi-mg]") -{ +TEST_CASE("Two multigroups under one branch advance independently", "[capi][multi-mg]") { InitGuard _; const std::string bpath = "/system/branches/test_multi_mg"; REQUIRE(rnt_register_branch(bpath.c_str(), "") == 0); // Register one relation in each of two distinct multigroups. - REQUIRE(rnt_register_relation( - (bpath + "/multigroups/warehouse/relations/orders").c_str()) == 0); - REQUIRE(rnt_register_relation( - (bpath + "/multigroups/audit/relations/events").c_str()) == 0); + REQUIRE(rnt_register_relation((bpath + "/multigroups/warehouse/relations/orders").c_str()) == + 0); + REQUIRE(rnt_register_relation((bpath + "/multigroups/audit/relations/events").c_str()) == 0); char* mgs_raw = nullptr; REQUIRE(rnt_list_branch_multigroups(bpath.c_str(), &mgs_raw) == 0); const std::string mgs = take_string(mgs_raw); REQUIRE(mgs.find("warehouse\t") != std::string::npos); - REQUIRE(mgs.find("audit\t") != std::string::npos); + REQUIRE(mgs.find("audit\t") != std::string::npos); // Insert into warehouse; record audit's root before/after. char* audit_before = nullptr; - REQUIRE(rnt_list_relations( - (bpath + "/multigroups/audit").c_str(), &audit_before) == 0); + REQUIRE(rnt_list_relations((bpath + "/multigroups/audit").c_str(), &audit_before) == 0); const std::string audit_before_s = take_string(audit_before); char* th = nullptr; - REQUIRE(rnt_link_tuple((bpath + "/multigroups/warehouse/relations/orders").c_str(), - "id=1\n", &th) == 0); + REQUIRE(rnt_link_tuple((bpath + "/multigroups/warehouse/relations/orders").c_str(), "id=1\n", + &th) == 0); rnt_free_string(th); char* audit_after = nullptr; - REQUIRE(rnt_list_relations( - (bpath + "/multigroups/audit").c_str(), &audit_after) == 0); + REQUIRE(rnt_list_relations((bpath + "/multigroups/audit").c_str(), &audit_after) == 0); const std::string audit_after_s = take_string(audit_after); // audit's relation set is byte-identical: the warehouse-side mutation @@ -430,13 +403,13 @@ TEST_CASE("Two multigroups under one branch advance independently", // warehouse/orders is now non-empty. char* root = nullptr; - REQUIRE(rnt_relation_root( - (bpath + "/multigroups/warehouse/relations/orders").c_str(), &root) == 0); + REQUIRE(rnt_relation_root((bpath + "/multigroups/warehouse/relations/orders").c_str(), &root) == + 0); REQUIRE(!take_string(root).empty()); // audit/events still empty. char* root2 = nullptr; - REQUIRE(rnt_relation_root( - (bpath + "/multigroups/audit/relations/events").c_str(), &root2) == 0); + REQUIRE(rnt_relation_root((bpath + "/multigroups/audit/relations/events").c_str(), &root2) == + 0); REQUIRE(take_string(root2).empty()); } diff --git a/test/CursorManager.cpp b/test/CursorManager.cpp index 8c7de1a..c823ec5 100644 --- a/test/CursorManager.cpp +++ b/test/CursorManager.cpp @@ -27,55 +27,48 @@ // the handle's death is degenerate but safe. // --------------------------------------------------------------------------- -namespace -{ - std::unique_ptr<nt::ObjectManager::object_type> make_type(OBJECT_TYPE label) - { - auto t = std::make_unique<nt::ObjectManager::object_type>(); - t->label = label; - t->methods = { OPEN, CLOSE }; +namespace { + std::unique_ptr<nt::ObjectManager::object_type> make_type(OBJECT_TYPE label) { + auto t = std::make_unique<nt::ObjectManager::object_type>(); + t->label = label; + t->methods = {OPEN, CLOSE}; return t; } std::unique_ptr<nt::ObjectManager::ephemeral_object_type> - make_constant_eph_type(std::vector<nt::Attribute> attrs) - { - auto t = std::make_unique<nt::ObjectManager::ephemeral_object_type>(); - t->label = EPHEMERAL_RELATION; - t->methods = { OPEN, CLOSE }; + make_constant_eph_type(std::vector<nt::Attribute> attrs) { + auto t = std::make_unique<nt::ObjectManager::ephemeral_object_type>(); + t->label = EPHEMERAL_RELATION; + t->methods = {OPEN, CLOSE}; t->cardinality = nt::ObjectManager::ephemeral_object_type::Cardinality::Finite; - t->generator = [attrs](const std::vector<std::string>&, - std::size_t offset, std::size_t limit) - -> std::vector<nt::Tuple> - { - if (offset > 0 || limit == 0) return {}; - return { nt::Tuple(attrs) }; + t->generator = [attrs](const std::vector<std::string>&, std::size_t offset, + std::size_t limit) -> std::vector<nt::Tuple> { + if (offset > 0 || limit == 0) + return {}; + return {nt::Tuple(attrs)}; }; return t; } struct Stack { - nt::InMemoryBackend backend; - nt::ObjectManager objects; - nt::PermissionsManager permissions; - nt::IdentityManager identities; - nt::LifecycleManager lifecycles { objects, backend }; - nt::NamespaceReferenceManager references { objects, backend }; - nt::HandlerManager handler { objects, permissions, identities, - lifecycles, references }; - nt::CursorManager cursors { backend, &lifecycles, &objects }; + nt::InMemoryBackend backend; + nt::ObjectManager objects; + nt::PermissionsManager permissions; + nt::IdentityManager identities; + nt::LifecycleManager lifecycles{objects, backend}; + nt::NamespaceReferenceManager references{objects, backend}; + nt::HandlerManager handler{objects, permissions, identities, lifecycles, references}; + nt::CursorManager cursors{backend, &lifecycles, &objects}; }; -} +} // namespace -TEST_CASE("Cursor pins the parent snapshot and unpins on close", - "[step6][cursor][pin]") -{ +TEST_CASE("Cursor pins the parent snapshot and unpins on close", "[step6][cursor][pin]") { Stack s; // Stand up a minimal snapshot with one relation. auto& om = s.objects; - om.Register({"system", "snapshots", "Hcur"}, - std::make_unique<nt::ObjectManager::Multigroup>(), make_type(MULTIGROUP)); + om.Register({"system", "snapshots", "Hcur"}, std::make_unique<nt::ObjectManager::Multigroup>(), + make_type(MULTIGROUP)); om.Register({"system", "snapshots", "Hcur", "relations", "items"}, std::make_unique<nt::ObjectManager::Relation>(), make_type(RELATION)); @@ -93,27 +86,25 @@ TEST_CASE("Cursor pins the parent snapshot and unpins on close", int conn = 1; auto* h = s.handler.Open({"system", "snapshots", "Hcur", "relations", "items"}, &conn); REQUIRE(h != nullptr); - REQUIRE(mg_entry->head->reference_count == 1); // external pin only + REQUIRE(mg_entry->head->reference_count == 1); // external pin only auto* c = s.cursors.Open(h); REQUIRE(c != nullptr); REQUIRE(c->pinned_snapshot == mg_entry); - REQUIRE(mg_entry->head->reference_count == 2); // external + cursor pin + REQUIRE(mg_entry->head->reference_count == 2); // external + cursor pin s.cursors.Close(c); - REQUIRE(mg_entry->head->reference_count == 1); // back to external only + REQUIRE(mg_entry->head->reference_count == 1); // back to external only s.handler.Close(h); - s.lifecycles.Unpin(mg_entry); // releases the external pin; cascade-collects. + s.lifecycles.Unpin(mg_entry); // releases the external pin; cascade-collects. } TEST_CASE("Closing the handle mid-iteration does not crash an ephemeral cursor", - "[step6][cursor][handle-independence]") -{ + "[step6][cursor][handle-independence]") { Stack s; // Register an ephemeral relation; its generator yields one tuple. - s.objects.Register({"eph"}, - std::make_unique<nt::ObjectManager::Relation>(), + s.objects.Register({"eph"}, std::make_unique<nt::ObjectManager::Relation>(), make_constant_eph_type({{"k", "v"}})); int conn = 1; @@ -124,7 +115,7 @@ TEST_CASE("Closing the handle mid-iteration does not crash an ephemeral cursor", // Ephemeral cursors start exhausted; reset to simulate a JOIN probe. c->exhausted = false; - c->args = {}; + c->args = {}; // Now violate the documented contract: close the handle BEFORE Next(). // The cursor must still produce its tuple from the captured generator @@ -135,18 +126,19 @@ TEST_CASE("Closing the handle mid-iteration does not crash an ephemeral cursor", REQUIRE(tuple != nullptr); bool found = false; for (const auto& a : tuple->attrs()) - if (a.name == "k" && a.value == "v") { found = true; break; } + if (a.name == "k" && a.value == "v") { + found = true; + break; + } REQUIRE(found); s.cursors.Close(c); } TEST_CASE("Cursor opened against a non-snapshot relation does not pin anything", - "[step6][cursor][pin]") -{ + "[step6][cursor][pin]") { Stack s; - s.objects.Register({"plain", "rel"}, - std::make_unique<nt::ObjectManager::Relation>(), + s.objects.Register({"plain", "rel"}, std::make_unique<nt::ObjectManager::Relation>(), make_type(RELATION)); int conn = 1; auto* h = s.handler.Open({"plain", "rel"}, &conn); diff --git a/test/LifecycleManager.cpp b/test/LifecycleManager.cpp index aa75e14..3712522 100644 --- a/test/LifecycleManager.cpp +++ b/test/LifecycleManager.cpp @@ -16,22 +16,18 @@ // unpin every Relation registered under their snapshot subtree. // --------------------------------------------------------------------------- -namespace -{ - std::unique_ptr<nt::ObjectManager::object_type> make_type(OBJECT_TYPE label) - { - auto t = std::make_unique<nt::ObjectManager::object_type>(); - t->label = label; - t->methods = { OPEN, CLOSE }; +namespace { + std::unique_ptr<nt::ObjectManager::object_type> make_type(OBJECT_TYPE label) { + auto t = std::make_unique<nt::ObjectManager::object_type>(); + t->label = label; + t->methods = {OPEN, CLOSE}; return t; } -} +} // namespace -TEST_CASE("Pin and Unpin adjust reference_count symmetrically", - "[step6][lifecycle][pin]") -{ +TEST_CASE("Pin and Unpin adjust reference_count symmetrically", "[step6][lifecycle][pin]") { nt::InMemoryBackend store; - nt::ObjectManager om; + nt::ObjectManager om; nt::LifecycleManager lm(om, store); om.Register({"r1"}, std::make_unique<nt::ObjectManager::Relation>(), make_type(RELATION)); auto* entry = om.Find({"r1"}); @@ -48,19 +44,17 @@ TEST_CASE("Pin and Unpin adjust reference_count symmetrically", REQUIRE(om.Find({"r1"}) != nullptr); } -TEST_CASE("Pin on nullptr is a no-op", "[step6][lifecycle][pin]") -{ +TEST_CASE("Pin on nullptr is a no-op", "[step6][lifecycle][pin]") { nt::InMemoryBackend store; - nt::ObjectManager om; + nt::ObjectManager om; nt::LifecycleManager lm(om, store); REQUIRE_NOTHROW(lm.Pin(nullptr)); REQUIRE_NOTHROW(lm.Unpin(nullptr)); } -TEST_CASE("Unpin below zero does not underflow", "[step6][lifecycle][pin]") -{ +TEST_CASE("Unpin below zero does not underflow", "[step6][lifecycle][pin]") { nt::InMemoryBackend store; - nt::ObjectManager om; + nt::ObjectManager om; nt::LifecycleManager lm(om, store); om.Register({"r1"}, std::make_unique<nt::ObjectManager::Relation>(), make_type(RELATION)); auto* entry = om.Find({"r1"}); @@ -69,10 +63,9 @@ TEST_CASE("Unpin below zero does not underflow", "[step6][lifecycle][pin]") REQUIRE(entry->head->reference_count == 0); } -TEST_CASE("GC is gated on both counters reaching zero", "[step6][lifecycle][gc]") -{ +TEST_CASE("GC is gated on both counters reaching zero", "[step6][lifecycle][gc]") { nt::InMemoryBackend store; - nt::ObjectManager om; + nt::ObjectManager om; nt::LifecycleManager lm(om, store); om.Register({"r1"}, std::make_unique<nt::ObjectManager::Relation>(), make_type(RELATION)); auto* entry = om.Find({"r1"}); @@ -81,22 +74,21 @@ TEST_CASE("GC is gated on both counters reaching zero", "[step6][lifecycle][gc]" lm.Pin(entry); // handle_count=1, ref_count=1. - lm.Unpin(entry); // ref_count → 0, but handle_count still 1. + lm.Unpin(entry); // ref_count → 0, but handle_count still 1. REQUIRE(om.Find({"r1"}) != nullptr); lm.Pin(entry); - lm.Unmonitor(entry); // handle_count → 0, but ref_count still 1. + lm.Unmonitor(entry); // handle_count → 0, but ref_count still 1. REQUIRE(om.Find({"r1"}) != nullptr); - lm.Unpin(entry); // both zero → GC. + lm.Unpin(entry); // both zero → GC. REQUIRE(om.Find({"r1"}) == nullptr); } TEST_CASE("Unmonitor that brings both counters to zero collects the entry", - "[step6][lifecycle][gc]") -{ + "[step6][lifecycle][gc]") { nt::InMemoryBackend store; - nt::ObjectManager om; + nt::ObjectManager om; nt::LifecycleManager lm(om, store); om.Register({"r1"}, std::make_unique<nt::ObjectManager::Relation>(), make_type(RELATION)); auto* entry = om.Find({"r1"}); @@ -107,11 +99,9 @@ TEST_CASE("Unmonitor that brings both counters to zero collects the entry", REQUIRE(om.Find({"r1"}) == nullptr); } -TEST_CASE("IsEligibleForGC reflects the joint-zero rule", - "[step6][lifecycle][gc]") -{ +TEST_CASE("IsEligibleForGC reflects the joint-zero rule", "[step6][lifecycle][gc]") { nt::InMemoryBackend store; - nt::ObjectManager om; + nt::ObjectManager om; nt::LifecycleManager lm(om, store); om.Register({"r1"}, std::make_unique<nt::ObjectManager::Relation>(), make_type(RELATION)); auto* entry = om.Find({"r1"}); @@ -122,21 +112,18 @@ TEST_CASE("IsEligibleForGC reflects the joint-zero rule", } TEST_CASE("CascadeMultigroup unpins child Relations on snapshot collection", - "[step6][lifecycle][cascade]") -{ + "[step6][lifecycle][cascade]") { nt::InMemoryBackend store; - nt::ObjectManager om; + nt::ObjectManager om; nt::LifecycleManager lm(om, store); - om.Register({"system", "snapshots", "H1"}, - std::make_unique<nt::ObjectManager::Multigroup>(), make_type(MULTIGROUP)); + om.Register({"system", "snapshots", "H1"}, std::make_unique<nt::ObjectManager::Multigroup>(), + make_type(MULTIGROUP)); auto* mg_entry = om.Find({"system", "snapshots", "H1"}); - for (const auto& n : { std::string{"foo"}, std::string{"bar"} }) - { + for (const auto& n : {std::string{"foo"}, std::string{"bar"}}) { om.Register({"system", "snapshots", "H1", "relations", n}, - std::make_unique<nt::ObjectManager::Relation>(), - make_type(RELATION)); + std::make_unique<nt::ObjectManager::Relation>(), make_type(RELATION)); // The parent Multigroup's pin on each child, mirroring what // register_snapshot does in the C API layer. lm.Pin(om.Find({"system", "snapshots", "H1", "relations", n})); @@ -148,38 +135,35 @@ TEST_CASE("CascadeMultigroup unpins child Relations on snapshot collection", lm.Pin(mg_entry); lm.Unpin(mg_entry); - REQUIRE(om.Find({"system", "snapshots", "H1"}) == nullptr); - REQUIRE(om.Find({"system", "snapshots", "H1", "relations", "foo"}) == nullptr); - REQUIRE(om.Find({"system", "snapshots", "H1", "relations", "bar"}) == nullptr); + REQUIRE(om.Find({"system", "snapshots", "H1"}) == nullptr); + REQUIRE(om.Find({"system", "snapshots", "H1", "relations", "foo"}) == nullptr); + REQUIRE(om.Find({"system", "snapshots", "H1", "relations", "bar"}) == nullptr); } -TEST_CASE("Cascade survives a child with an open handle", - "[step6][lifecycle][cascade]") -{ +TEST_CASE("Cascade survives a child with an open handle", "[step6][lifecycle][cascade]") { nt::InMemoryBackend store; - nt::ObjectManager om; + nt::ObjectManager om; nt::LifecycleManager lm(om, store); - om.Register({"system", "snapshots", "H2"}, - std::make_unique<nt::ObjectManager::Multigroup>(), make_type(MULTIGROUP)); + om.Register({"system", "snapshots", "H2"}, std::make_unique<nt::ObjectManager::Multigroup>(), + make_type(MULTIGROUP)); auto* mg_entry = om.Find({"system", "snapshots", "H2"}); om.Register({"system", "snapshots", "H2", "relations", "live"}, - std::make_unique<nt::ObjectManager::Relation>(), - make_type(RELATION)); + std::make_unique<nt::ObjectManager::Relation>(), make_type(RELATION)); auto* child = om.Find({"system", "snapshots", "H2", "relations", "live"}); - lm.Pin(child); // parent's pin - lm.Monitor(child); // simulate an open handle on the child + lm.Pin(child); // parent's pin + lm.Monitor(child); // simulate an open handle on the child // Drop the Multigroup's external ref: cascade unpins child (ref → 0), // but handle_count is still 1 — child must survive. lm.Pin(mg_entry); lm.Unpin(mg_entry); - REQUIRE(om.Find({"system", "snapshots", "H2"}) == nullptr); - REQUIRE(om.Find({"system", "snapshots", "H2", "relations", "live"}) != nullptr); + REQUIRE(om.Find({"system", "snapshots", "H2"}) == nullptr); + REQUIRE(om.Find({"system", "snapshots", "H2", "relations", "live"}) != nullptr); // Closing the handle now releases the child. lm.Unmonitor(child); - REQUIRE(om.Find({"system", "snapshots", "H2", "relations", "live"}) == nullptr); + REQUIRE(om.Find({"system", "snapshots", "H2", "relations", "live"}) == nullptr); } diff --git a/test/MultigroupCodec.cpp b/test/MultigroupCodec.cpp index c1e2509..b04c405 100644 --- a/test/MultigroupCodec.cpp +++ b/test/MultigroupCodec.cpp @@ -17,48 +17,42 @@ namespace { -// Pad a short label into a deterministic 64-char hex string so the codec can -// decode it as a 32-byte payload. -std::string mk_root(char c, char d = '0') -{ - std::string h(64, '0'); - h[0] = c; - h[1] = d; - return h; -} - -} // namespace - -TEST_CASE("Build is order-independent - tree sorts intrinsically", - "[multigroup-codec]") -{ + // Pad a short label into a deterministic 64-char hex string so the codec can + // decode it as a 32-byte payload. + std::string mk_root(char c, char d = '0') { + std::string h(64, '0'); + h[0] = c; + h[1] = d; + return h; + } + +} // namespace + +TEST_CASE("Build is order-independent - tree sorts intrinsically", "[multigroup-codec]") { nt::InMemoryBackend a, b; std::vector<nt::MultigroupCodec::RelationEntry> in_order = { - {"alpha", mk_root('a')}, - {"bravo", mk_root('b')}, + {"alpha", mk_root('a')}, + {"bravo", mk_root('b')}, {"charlie", mk_root('c')}, }; std::vector<nt::MultigroupCodec::RelationEntry> shuffled = { {"charlie", mk_root('c')}, - {"alpha", mk_root('a')}, - {"bravo", mk_root('b')}, + {"alpha", mk_root('a')}, + {"bravo", mk_root('b')}, }; - REQUIRE(nt::MultigroupCodec::Build(a, in_order) - == nt::MultigroupCodec::Build(b, shuffled)); + REQUIRE(nt::MultigroupCodec::Build(a, in_order) == nt::MultigroupCodec::Build(b, shuffled)); } -TEST_CASE("Build distinguishes content changes", "[multigroup-codec]") -{ +TEST_CASE("Build distinguishes content changes", "[multigroup-codec]") { nt::InMemoryBackend backend; std::vector<nt::MultigroupCodec::RelationEntry> base = {{"foo", mk_root('a')}}; std::vector<nt::MultigroupCodec::RelationEntry> different_root = {{"foo", mk_root('b')}}; std::vector<nt::MultigroupCodec::RelationEntry> different_name = {{"bar", mk_root('a')}}; - std::vector<nt::MultigroupCodec::RelationEntry> extra_member = { - {"foo", mk_root('a')}, {"bar", mk_root('b')} - }; + std::vector<nt::MultigroupCodec::RelationEntry> extra_member = {{"foo", mk_root('a')}, + {"bar", mk_root('b')}}; const auto h_base = nt::MultigroupCodec::Build(backend, base); REQUIRE(h_base != nt::MultigroupCodec::Build(backend, different_root)); @@ -66,9 +60,7 @@ TEST_CASE("Build distinguishes content changes", "[multigroup-codec]") REQUIRE(h_base != nt::MultigroupCodec::Build(backend, extra_member)); } -TEST_CASE("Build then List round-trips entries in key-sorted order", - "[multigroup-codec]") -{ +TEST_CASE("Build then List round-trips entries in key-sorted order", "[multigroup-codec]") { nt::InMemoryBackend backend; std::vector<nt::MultigroupCodec::RelationEntry> entries = { @@ -86,8 +78,7 @@ TEST_CASE("Build then List round-trips entries in key-sorted order", REQUIRE(listed[0].second == mk_root('b', '2')); } -TEST_CASE("Lookup returns payload by name", "[multigroup-codec]") -{ +TEST_CASE("Lookup returns payload by name", "[multigroup-codec]") { nt::InMemoryBackend backend; std::vector<nt::MultigroupCodec::RelationEntry> entries = { @@ -101,9 +92,7 @@ TEST_CASE("Lookup returns payload by name", "[multigroup-codec]") REQUIRE(nt::MultigroupCodec::Lookup(backend, root, "missing").empty()); } -TEST_CASE("InsertOne mutates one entry; sibling subtree is untouched", - "[multigroup-codec]") -{ +TEST_CASE("InsertOne mutates one entry; sibling subtree is untouched", "[multigroup-codec]") { nt::InMemoryBackend backend; std::vector<nt::MultigroupCodec::RelationEntry> entries = { @@ -113,16 +102,13 @@ TEST_CASE("InsertOne mutates one entry; sibling subtree is untouched", const auto root0 = nt::MultigroupCodec::Build(backend, entries); // Mutate foo only; bar's payload must round-trip unchanged. - const auto root1 = nt::MultigroupCodec::InsertOne( - backend, root0, "foo", mk_root('d')); + const auto root1 = nt::MultigroupCodec::InsertOne(backend, root0, "foo", mk_root('d')); REQUIRE(root1 != root0); REQUIRE(nt::MultigroupCodec::Lookup(backend, root1, "foo") == mk_root('d')); REQUIRE(nt::MultigroupCodec::Lookup(backend, root1, "bar") == mk_root('b')); } -TEST_CASE("RemoveOne deletes one entry; sibling preserved", - "[multigroup-codec]") -{ +TEST_CASE("RemoveOne deletes one entry; sibling preserved", "[multigroup-codec]") { nt::InMemoryBackend backend; std::vector<nt::MultigroupCodec::RelationEntry> entries = { @@ -136,8 +122,7 @@ TEST_CASE("RemoveOne deletes one entry; sibling preserved", REQUIRE(nt::MultigroupCodec::Lookup(backend, root1, "bar") == mk_root('b')); } -TEST_CASE("Empty multigroup builds to an empty root", "[multigroup-codec]") -{ +TEST_CASE("Empty multigroup builds to an empty root", "[multigroup-codec]") { nt::InMemoryBackend backend; std::vector<nt::MultigroupCodec::RelationEntry> empty; REQUIRE(nt::MultigroupCodec::Build(backend, empty).empty()); diff --git a/test/NamespaceReferenceManager.cpp b/test/NamespaceReferenceManager.cpp index 27f0dcd..90e65e5 100644 --- a/test/NamespaceReferenceManager.cpp +++ b/test/NamespaceReferenceManager.cpp @@ -21,74 +21,62 @@ namespace { -using Path = std::vector<std::string>; - -// Pad a label into a deterministic 64-char hex string so the merkle codec can -// decode it as a 32-byte payload. -std::string mk_hash(char c, char d = '0') -{ - std::string h(64, '0'); - h[0] = c; - h[1] = d; - return h; -} - -std::unique_ptr<nt::ObjectManager::object_type> make_type(OBJECT_TYPE label) -{ - auto t = std::make_unique<nt::ObjectManager::object_type>(); - t->label = label; - t->methods = { OPEN, CLOSE }; - return t; -} - -void register_branch(nt::ObjectManager& om, - const std::string& name, - const std::string& target_hash) -{ - auto br = std::make_unique<nt::ObjectManager::Branch>(); - br->name = name; - br->target_hash = target_hash; - om.Register({"system", "branches", name}, std::move(br), make_type(BRANCH)); -} - -void register_session(nt::ObjectManager& om, - const std::string& id, - std::map<std::string, std::string> overrides = {}) -{ - auto s = std::make_unique<nt::ObjectManager::Session>(); - s->branch_overrides = std::move(overrides); - om.Register({"system", "sessions", id}, std::move(s), make_type(SESSION)); -} - -// Builds a branch tree with a single (mg_name → mg_hash) entry. Returns the -// branch-tree root hex. -std::string build_branch_tree(nt::InMemoryBackend& store, - const std::string& mg_name, - const std::string& mg_hash_hex) -{ - return nt::Merkle<std::string>::Insert(store, "", mg_name, - nt::hex_to_bin(mg_hash_hex)); -} - -} // namespace - -TEST_CASE("Branch sub-path rewrites through the branch tree to the mg snapshot", - "[resolve]") -{ + using Path = std::vector<std::string>; + + // Pad a label into a deterministic 64-char hex string so the merkle codec can + // decode it as a 32-byte payload. + std::string mk_hash(char c, char d = '0') { + std::string h(64, '0'); + h[0] = c; + h[1] = d; + return h; + } + + std::unique_ptr<nt::ObjectManager::object_type> make_type(OBJECT_TYPE label) { + auto t = std::make_unique<nt::ObjectManager::object_type>(); + t->label = label; + t->methods = {OPEN, CLOSE}; + return t; + } + + void register_branch(nt::ObjectManager& om, const std::string& name, + const std::string& target_hash) { + auto br = std::make_unique<nt::ObjectManager::Branch>(); + br->name = name; + br->target_hash = target_hash; + om.Register({"system", "branches", name}, std::move(br), make_type(BRANCH)); + } + + void register_session(nt::ObjectManager& om, const std::string& id, + std::map<std::string, std::string> overrides = {}) { + auto s = std::make_unique<nt::ObjectManager::Session>(); + s->branch_overrides = std::move(overrides); + om.Register({"system", "sessions", id}, std::move(s), make_type(SESSION)); + } + + // Builds a branch tree with a single (mg_name → mg_hash) entry. Returns the + // branch-tree root hex. + std::string build_branch_tree(nt::InMemoryBackend& store, const std::string& mg_name, + const std::string& mg_hash_hex) { + return nt::Merkle<std::string>::Insert(store, "", mg_name, nt::hex_to_bin(mg_hash_hex)); + } + +} // namespace + +TEST_CASE("Branch sub-path rewrites through the branch tree to the mg snapshot", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; const std::string mg_hash = mk_hash('a'); - const std::string tree = build_branch_tree(store, "warehouse", mg_hash); + const std::string tree = build_branch_tree(store, "warehouse", mg_hash); register_branch(om, "main", tree); nt::NamespaceReferenceManager nrm(om, store); - REQUIRE(nrm.Resolve({"system", "branches", "main", "multigroups", "warehouse", "relations", "items"}) - == Path{"system", "snapshots", mg_hash, "relations", "items"}); + REQUIRE(nrm.Resolve({"system", "branches", "main", "multigroups", "warehouse", "relations", + "items"}) == Path{"system", "snapshots", mg_hash, "relations", "items"}); } -TEST_CASE("Resolve on the branch object itself is left unchanged", "[resolve]") -{ +TEST_CASE("Resolve on the branch object itself is left unchanged", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; const std::string tree = build_branch_tree(store, "warehouse", mk_hash('a')); @@ -99,19 +87,17 @@ TEST_CASE("Resolve on the branch object itself is left unchanged", "[resolve]") REQUIRE(nrm.Resolve(p) == p); } -TEST_CASE("Resolve on an unborn branch leaves the path unchanged", "[resolve]") -{ +TEST_CASE("Resolve on an unborn branch leaves the path unchanged", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; - register_branch(om, "main", ""); // unborn + register_branch(om, "main", ""); // unborn nt::NamespaceReferenceManager nrm(om, store); const Path p{"system", "branches", "main", "multigroups", "warehouse", "relations", "items"}; REQUIRE(nrm.Resolve(p) == p); } -TEST_CASE("Resolve on an absent multigroup leaves the path unchanged", "[resolve]") -{ +TEST_CASE("Resolve on an absent multigroup leaves the path unchanged", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; const std::string tree = build_branch_tree(store, "warehouse", mk_hash('a')); @@ -122,8 +108,7 @@ TEST_CASE("Resolve on an absent multigroup leaves the path unchanged", "[resolve REQUIRE(nrm.Resolve(p) == p); } -TEST_CASE("Resolve on an unknown branch leaves the path unchanged", "[resolve]") -{ +TEST_CASE("Resolve on an unknown branch leaves the path unchanged", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; nt::NamespaceReferenceManager nrm(om, store); @@ -131,8 +116,7 @@ TEST_CASE("Resolve on an unknown branch leaves the path unchanged", "[resolve]") REQUIRE(nrm.Resolve(p) == p); } -TEST_CASE("Non-branch paths pass through unchanged", "[resolve]") -{ +TEST_CASE("Non-branch paths pass through unchanged", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; nt::NamespaceReferenceManager nrm(om, store); @@ -140,62 +124,52 @@ TEST_CASE("Non-branch paths pass through unchanged", "[resolve]") REQUIRE(nrm.Resolve(p) == p); } -TEST_CASE("Session override takes precedence over the global branch HEAD", - "[resolve]") -{ +TEST_CASE("Session override takes precedence over the global branch HEAD", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; - const std::string mg_global = mk_hash('a'); + const std::string mg_global = mk_hash('a'); const std::string mg_session = mk_hash('b'); - const std::string tree_global = - build_branch_tree(store, "warehouse", mg_global); - const std::string tree_session = - build_branch_tree(store, "warehouse", mg_session); + const std::string tree_global = build_branch_tree(store, "warehouse", mg_global); + const std::string tree_session = build_branch_tree(store, "warehouse", mg_session); register_branch(om, "main", tree_global); register_session(om, "sess123", {{"main", tree_session}}); nt::NamespaceReferenceManager nrm(om, store); - REQUIRE(nrm.Resolve({"system", "sessions", "sess123", "branches", "main", - "multigroups", "warehouse", "relations", "items"}) - == Path{"system", "snapshots", mg_session, "relations", "items"}); + REQUIRE(nrm.Resolve({"system", "sessions", "sess123", "branches", "main", "multigroups", + "warehouse", "relations", "items"}) == + Path{"system", "snapshots", mg_session, "relations", "items"}); } -TEST_CASE("Session resolution falls back to global branch when override is absent", - "[resolve]") -{ +TEST_CASE("Session resolution falls back to global branch when override is absent", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; const std::string mg_global = mk_hash('a'); - const std::string tree_global = - build_branch_tree(store, "warehouse", mg_global); + const std::string tree_global = build_branch_tree(store, "warehouse", mg_global); register_branch(om, "main", tree_global); - register_session(om, "sess123"); // no overrides + register_session(om, "sess123"); // no overrides nt::NamespaceReferenceManager nrm(om, store); - REQUIRE(nrm.Resolve({"system", "sessions", "sess123", "branches", "main", - "multigroups", "warehouse", "relations", "items"}) - == Path{"system", "snapshots", mg_global, "relations", "items"}); + REQUIRE(nrm.Resolve({"system", "sessions", "sess123", "branches", "main", "multigroups", + "warehouse", "relations", "items"}) == + Path{"system", "snapshots", mg_global, "relations", "items"}); } -TEST_CASE("Session resolution with neither override nor global branch is unchanged", - "[resolve]") -{ +TEST_CASE("Session resolution with neither override nor global branch is unchanged", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; register_session(om, "sess123"); nt::NamespaceReferenceManager nrm(om, store); - const Path p{"system", "sessions", "sess123", "branches", "main", + const Path p{"system", "sessions", "sess123", "branches", "main", "multigroups", "warehouse", "relations", "items"}; REQUIRE(nrm.Resolve(p) == p); } -TEST_CASE("Empty input path is returned unchanged", "[resolve]") -{ +TEST_CASE("Empty input path is returned unchanged", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; nt::NamespaceReferenceManager nrm(om, store); diff --git a/test/ObjectManager.cpp b/test/ObjectManager.cpp index e97bcb2..f90de3a 100644 --- a/test/ObjectManager.cpp +++ b/test/ObjectManager.cpp @@ -13,23 +13,19 @@ // share. // --------------------------------------------------------------------------- -namespace -{ - std::unique_ptr<nt::ObjectManager::object_type> session_type() - { - auto t = std::make_unique<nt::ObjectManager::object_type>(); - t->label = SESSION; +namespace { + std::unique_ptr<nt::ObjectManager::object_type> session_type() { + auto t = std::make_unique<nt::ObjectManager::object_type>(); + t->label = SESSION; t->disposable = true; - t->methods = { OPEN, CLOSE }; + t->methods = {OPEN, CLOSE}; return t; } -} +} // namespace -TEST_CASE("Unregister removes the entry and Find returns nullptr", "[step5][object-manager]") -{ +TEST_CASE("Unregister removes the entry and Find returns nullptr", "[step5][object-manager]") { nt::ObjectManager om; - om.Register({"system", "sessions", "alpha"}, - std::make_unique<nt::ObjectManager::Session>(), + om.Register({"system", "sessions", "alpha"}, std::make_unique<nt::ObjectManager::Session>(), session_type()); REQUIRE(om.Find({"system", "sessions", "alpha"}) != nullptr); @@ -37,15 +33,13 @@ TEST_CASE("Unregister removes the entry and Find returns nullptr", "[step5][obje REQUIRE(om.Find({"system", "sessions", "alpha"}) == nullptr); } -TEST_CASE("Unregister returns false for missing paths", "[step5][object-manager]") -{ +TEST_CASE("Unregister returns false for missing paths", "[step5][object-manager]") { nt::ObjectManager om; REQUIRE(om.Unregister({"never", "registered"}) == false); } TEST_CASE("Unregister splices a middle entry without disturbing neighbours", - "[step5][object-manager]") -{ + "[step5][object-manager]") { nt::ObjectManager om; om.Register({"a"}, std::make_unique<nt::ObjectManager::Session>(), session_type()); om.Register({"b"}, std::make_unique<nt::ObjectManager::Session>(), session_type()); @@ -58,16 +52,15 @@ TEST_CASE("Unregister splices a middle entry without disturbing neighbours", REQUIRE(om.Find({"c"}) != nullptr); } -TEST_CASE("Unregister at the head of the list works", "[step5][object-manager]") -{ +TEST_CASE("Unregister at the head of the list works", "[step5][object-manager]") { nt::ObjectManager om; - om.Register({"first"}, std::make_unique<nt::ObjectManager::Session>(), session_type()); + om.Register({"first"}, std::make_unique<nt::ObjectManager::Session>(), session_type()); om.Register({"second"}, std::make_unique<nt::ObjectManager::Session>(), session_type()); // The most recently registered entry is the list head (LIFO). REQUIRE(om.Unregister({"second"}) == true); REQUIRE(om.Find({"second"}) == nullptr); - REQUIRE(om.Find({"first"}) != nullptr); + REQUIRE(om.Find({"first"}) != nullptr); // And the new head is removable too. REQUIRE(om.Unregister({"first"}) == true); diff --git a/test/VM.cpp b/test/VM.cpp index f4ce453..7e61da6 100644 --- a/test/VM.cpp +++ b/test/VM.cpp @@ -21,48 +21,45 @@ // --------------------------------------------------------------------------- // Stores a tuple in the backend and updates the Relation object's Merkle root. -static void store_tuple(nt::InMemoryBackend& backend, - nt::ObjectManager& objects, - const std::vector<std::string>& path, - std::vector<nt::Attribute> attrs) -{ +static void store_tuple(nt::InMemoryBackend& backend, nt::ObjectManager& objects, + const std::vector<std::string>& path, std::vector<nt::Attribute> attrs) { auto bytes = nt::TupleCodec::Serialize(attrs); - auto hash = backend.Put(std::move(bytes)); + auto hash = backend.Put(std::move(bytes)); auto* entry = objects.Find(path); - if (!entry) return; + if (!entry) + return; auto* rel = dynamic_cast<nt::ObjectManager::Relation*>(entry->object.get()); - if (!rel) return; + if (!rel) + return; auto hash_bin = nt::hex_to_bin(hash); - rel->merkle_root = nt::Merkle<nt::Hash32>::Insert(backend, rel->merkle_root, - hash_bin, hash_bin); + rel->merkle_root = + nt::Merkle<nt::Hash32>::Insert(backend, rel->merkle_root, hash_bin, hash_bin); } -static std::unique_ptr<nt::ObjectManager::object_type> make_relation_type() -{ +static std::unique_ptr<nt::ObjectManager::object_type> make_relation_type() { auto t = std::make_unique<nt::ObjectManager::object_type>(); - t->label = RELATION; + t->label = RELATION; t->disposable = false; - t->methods = { OPEN, CLOSE }; + t->methods = {OPEN, CLOSE}; return t; } // Generator for eq[A, B]: returns one tuple when A == B, nothing otherwise. // Treats both args as integers when parseable, strings otherwise. -static std::unique_ptr<nt::ObjectManager::ephemeral_object_type> make_eq_type() -{ +static std::unique_ptr<nt::ObjectManager::ephemeral_object_type> make_eq_type() { auto t = std::make_unique<nt::ObjectManager::ephemeral_object_type>(); - t->label = EPHEMERAL_RELATION; - t->disposable = false; - t->methods = { OPEN, CLOSE }; + t->label = EPHEMERAL_RELATION; + t->disposable = false; + t->methods = {OPEN, CLOSE}; t->cardinality = nt::ObjectManager::ephemeral_object_type::Cardinality::AlephZero; - t->generator = [](const std::vector<std::string>& args, - std::size_t offset, - std::size_t limit) -> std::vector<nt::Tuple> - { - if (args.size() < 2 || offset > 0 || limit == 0) return {}; - if (args[0] != args[1]) return {}; - return { nt::Tuple({ { "left", args[0] }, { "right", args[1] } }) }; + t->generator = [](const std::vector<std::string>& args, std::size_t offset, + std::size_t limit) -> std::vector<nt::Tuple> { + if (args.size() < 2 || offset > 0 || limit == 0) + return {}; + if (args[0] != args[1]) + return {}; + return {nt::Tuple({{"left", args[0]}, {"right", args[1]}})}; }; return t; } @@ -71,16 +68,15 @@ static std::unique_ptr<nt::ObjectManager::ephemeral_object_type> make_eq_type() // Fixture: shared manager stack // --------------------------------------------------------------------------- -struct Fixture -{ - nt::InMemoryBackend backend; - nt::ObjectManager objects; +struct Fixture { + nt::InMemoryBackend backend; + nt::ObjectManager objects; nt::PermissionsManager permissions; - nt::IdentityManager identities; - nt::LifecycleManager lifecycles { objects, backend }; - nt::NamespaceReferenceManager references { objects, backend }; - nt::HandlerManager handler { objects, permissions, identities, lifecycles, references }; - nt::CursorManager cursors { backend, &lifecycles, &objects }; + nt::IdentityManager identities; + nt::LifecycleManager lifecycles{objects, backend}; + nt::NamespaceReferenceManager references{objects, backend}; + nt::HandlerManager handler{objects, permissions, identities, lifecycles, references}; + nt::CursorManager cursors{backend, &lifecycles, &objects}; int conn = 1; }; @@ -88,14 +84,13 @@ struct Fixture // Tests // --------------------------------------------------------------------------- -TEST_CASE("SCAN returns all tuples from a stored relation", "[scan]") -{ +TEST_CASE("SCAN returns all tuples from a stored relation", "[scan]") { Fixture f; - const std::vector<std::string> path = { "relations", "villager" }; + const std::vector<std::string> path = {"relations", "villager"}; f.objects.Register(path, std::make_unique<nt::ObjectManager::Relation>(), make_relation_type()); - store_tuple(f.backend, f.objects, path, { { "name", "John" }, { "age", "30" } }); - store_tuple(f.backend, f.objects, path, { { "name", "Peter" }, { "age", "25" } }); + store_tuple(f.backend, f.objects, path, {{"name", "John"}, {"age", "30"}}); + store_tuple(f.backend, f.objects, path, {{"name", "Peter"}, {"age", "25"}}); auto* handle = f.handler.Open(path, &f.conn); REQUIRE(handle != nullptr); @@ -104,12 +99,13 @@ TEST_CASE("SCAN returns all tuples from a stored relation", "[scan]") REQUIRE(cursor != nullptr); nt::PlanNode plan; - plan.op = nt::PlanNode::Op::SCAN; + plan.op = nt::FOL_OPERATION_SCAN; plan.scan_cursor = cursor; nt::VM vm(f.cursors); int count = 0; - while (vm.Next(&plan)) ++count; + while (vm.Next(&plan)) + ++count; REQUIRE(count == 2); @@ -117,25 +113,21 @@ TEST_CASE("SCAN returns all tuples from a stored relation", "[scan]") f.handler.Close(handle); } -TEST_CASE("JOIN filters a stored relation with eq ephemeral", "[join]") -{ +TEST_CASE("JOIN filters a stored relation with eq ephemeral", "[join]") { Fixture f; - const std::vector<std::string> people_path = { "relations", "people" }; - const std::vector<std::string> eq_path = { "builtins", "eq" }; + const std::vector<std::string> people_path = {"relations", "people"}; + const std::vector<std::string> eq_path = {"builtins", "eq"}; // Register stored relation: people(name, age) - f.objects.Register(people_path, - std::make_unique<nt::ObjectManager::Relation>(), + f.objects.Register(people_path, std::make_unique<nt::ObjectManager::Relation>(), make_relation_type()); - store_tuple(f.backend, f.objects, people_path, { { "name", "John" }, { "age", "30" } }); - store_tuple(f.backend, f.objects, people_path, { { "name", "Peter" }, { "age", "25" } }); - store_tuple(f.backend, f.objects, people_path, { { "name", "Jude" }, { "age", "25" } }); + store_tuple(f.backend, f.objects, people_path, {{"name", "John"}, {"age", "30"}}); + store_tuple(f.backend, f.objects, people_path, {{"name", "Peter"}, {"age", "25"}}); + store_tuple(f.backend, f.objects, people_path, {{"name", "Jude"}, {"age", "25"}}); // Register ephemeral eq relation - f.objects.Register(eq_path, - std::make_unique<nt::ObjectManager::Relation>(), - make_eq_type()); + f.objects.Register(eq_path, std::make_unique<nt::ObjectManager::Relation>(), make_eq_type()); // Open handles and cursors auto* people_handle = f.handler.Open(people_path, &f.conn); @@ -143,8 +135,8 @@ TEST_CASE("JOIN filters a stored relation with eq ephemeral", "[join]") auto* eq_handle = f.handler.Open(eq_path, &f.conn); REQUIRE(eq_handle != nullptr); - auto* people_rel = dynamic_cast<nt::ObjectManager::Relation*>( - f.objects.Find(people_path)->object.get()); + auto* people_rel = + dynamic_cast<nt::ObjectManager::Relation*>(f.objects.Find(people_path)->object.get()); auto* people_cursor = f.cursors.Open(people_handle, people_rel->merkle_root); REQUIRE(people_cursor != nullptr); auto* eq_cursor = f.cursors.Open(eq_handle); // EPHEMERAL: starts exhausted, JOIN resets @@ -152,24 +144,23 @@ TEST_CASE("JOIN filters a stored relation with eq ephemeral", "[join]") // Plan: JOIN( SCAN(people), SCAN(eq[ Var("age"), Const("25") ]) ) nt::PlanNode right; - right.op = nt::PlanNode::Op::SCAN; + right.op = nt::FOL_OPERATION_SCAN; right.scan_cursor = eq_cursor; - right.scan_args = { nt::PathArg::Var("age"), nt::PathArg::Const("25") }; + right.scan_args = {nt::PathArg::Var("age"), nt::PathArg::Const("25")}; nt::PlanNode left; - left.op = nt::PlanNode::Op::SCAN; + left.op = nt::FOL_OPERATION_SCAN; left.scan_cursor = people_cursor; nt::PlanNode root; - root.op = nt::PlanNode::Op::JOIN; - root.left = &left; + root.op = nt::FOL_OPERATION_JOIN; + root.left = &left; root.right = &right; // Execute and collect matched names nt::VM vm(f.cursors); std::vector<std::string> matched; - while (nt::Tuple* t = vm.Next(&root)) - { + while (nt::Tuple* t = vm.Next(&root)) { for (const auto& attr : t->attrs()) if (attr.name == "name") matched.push_back(attr.value); @@ -177,9 +168,9 @@ TEST_CASE("JOIN filters a stored relation with eq ephemeral", "[join]") // Jude (age=25) and Peter (age=25) should match; Jogn (age=30) should not REQUIRE(matched.size() == 2); - REQUIRE(std::find(matched.begin(), matched.end(), "Jude") != matched.end()); + REQUIRE(std::find(matched.begin(), matched.end(), "Jude") != matched.end()); REQUIRE(std::find(matched.begin(), matched.end(), "Peter") != matched.end()); - REQUIRE(std::find(matched.begin(), matched.end(), "John") == matched.end()); + REQUIRE(std::find(matched.begin(), matched.end(), "John") == matched.end()); f.cursors.Close(people_cursor); f.cursors.Close(eq_cursor); @@ -187,15 +178,14 @@ TEST_CASE("JOIN filters a stored relation with eq ephemeral", "[join]") f.handler.Close(eq_handle); } -TEST_CASE("TAKE limits tuples emitted from a SCAN", "[take]") -{ +TEST_CASE("TAKE limits tuples emitted from a SCAN", "[take]") { Fixture f; - const std::vector<std::string> path = { "relations", "items" }; + const std::vector<std::string> path = {"relations", "items"}; f.objects.Register(path, std::make_unique<nt::ObjectManager::Relation>(), make_relation_type()); - store_tuple(f.backend, f.objects, path, { { "id", "1" } }); - store_tuple(f.backend, f.objects, path, { { "id", "2" } }); - store_tuple(f.backend, f.objects, path, { { "id", "3" } }); + store_tuple(f.backend, f.objects, path, {{"id", "1"}}); + store_tuple(f.backend, f.objects, path, {{"id", "2"}}); + store_tuple(f.backend, f.objects, path, {{"id", "3"}}); auto* handle = f.handler.Open(path, &f.conn); REQUIRE(handle != nullptr); @@ -204,17 +194,18 @@ TEST_CASE("TAKE limits tuples emitted from a SCAN", "[take]") REQUIRE(cursor != nullptr); nt::PlanNode scan; - scan.op = nt::PlanNode::Op::SCAN; + scan.op = nt::FOL_OPERATION_SCAN; scan.scan_cursor = cursor; nt::PlanNode take; - take.op = nt::PlanNode::Op::TAKE; - take.left = &scan; + take.op = nt::FOL_OPERATION_TAKE; + take.left = &scan; take.take_limit = 2; nt::VM vm(f.cursors); int count = 0; - while (vm.Next(&take)) ++count; + while (vm.Next(&take)) + ++count; REQUIRE(count == 2); @@ -222,13 +213,12 @@ TEST_CASE("TAKE limits tuples emitted from a SCAN", "[take]") f.handler.Close(handle); } -TEST_CASE("PROJECT filters tuple attributes by name", "[project]") -{ +TEST_CASE("PROJECT filters tuple attributes by name", "[project]") { Fixture f; - const std::vector<std::string> path = { "relations", "people" }; + const std::vector<std::string> path = {"relations", "people"}; f.objects.Register(path, std::make_unique<nt::ObjectManager::Relation>(), make_relation_type()); - store_tuple(f.backend, f.objects, path, { { "name", "John" }, { "age", "30" }, { "city", "Lisbon" } }); + store_tuple(f.backend, f.objects, path, {{"name", "John"}, {"age", "30"}, {"city", "Lisbon"}}); auto* handle = f.handler.Open(path, &f.conn); REQUIRE(handle != nullptr); @@ -237,20 +227,21 @@ TEST_CASE("PROJECT filters tuple attributes by name", "[project]") REQUIRE(cursor != nullptr); nt::PlanNode scan; - scan.op = nt::PlanNode::Op::SCAN; + scan.op = nt::FOL_OPERATION_SCAN; scan.scan_cursor = cursor; nt::PlanNode project; - project.op = nt::PlanNode::Op::PROJECT; - project.left = &scan; - project.project_attrs = { "name", "city" }; + project.op = nt::FOL_OPERATION_PROJECT; + project.left = &scan; + project.project_attrs = {"name", "city"}; nt::VM vm(f.cursors); nt::Tuple* t = vm.Next(&project); REQUIRE(t != nullptr); std::vector<std::string> names; - for (const auto& attr : t->attrs()) names.push_back(attr.name); + for (const auto& attr : t->attrs()) + names.push_back(attr.name); REQUIRE(names.size() == 2); REQUIRE(std::find(names.begin(), names.end(), "name") != names.end()); |
