// v2.11.0 T2 — relation reverse index tests. // // Storage-only: this exercises LmdbDocumentStore::relation_index_{add,remove, // child_count,children} directly. No document-write hooks (Task 3), no // enforcement (Task 4) are involved. // // test_index_created_at_runtime_is_visible_after_reopen is THE regression // test for this task: since v2.8.1 the read path serves only from the primed // dbi cache, so a sub-db created at runtime is invisible to every later read // in the process unless cacheCommittedDbi() runs after commit. Removing that // call from relation_index_add must fail this test. #include #include #include #include #include #include #include #include #include "storage/document_store_lmdb.hpp" #include "storage/lmdb_env.hpp" namespace fs = std::filesystem; using smartbotic::db::storage::LmdbDocumentStore; using smartbotic::db::storage::LmdbEnv; using smartbotic::db::storage::LmdbEnvOpts; namespace { int g_pass = 0; int g_fail = 0; void check(bool cond, const char* msg) { if (cond) { ++g_pass; } else { ++g_fail; std::cerr << "FAIL: " << msg << "\n"; } } std::string make_tmpdir(const char* tag) { static std::atomic counter{0}; std::string path = "/tmp/relidx-test-" + std::to_string(::getpid()) + "-" + std::to_string(counter.fetch_add(1)) + "-" + tag; std::error_code ec; fs::remove_all(path, ec); return path; } struct TmpEnv { std::string path; LmdbEnv env; explicit TmpEnv(const char* tag) : path(make_tmpdir(tag)), env(LmdbEnvOpts{path, 64ULL << 20, 256, 126, false}) {} ~TmpEnv() { std::error_code ec; fs::remove_all(path, ec); } TmpEnv(const TmpEnv&) = delete; TmpEnv& operator=(const TmpEnv&) = delete; }; // ------------------------------------------------------------------------- // DUPSORT shape, per the re-validated design. void test_children_of_a_parent_are_a_dup_set() { TmpEnv t("relidx"); LmdbDocumentStore store(t.env); check(store.relation_index_add("r1", "wf-1", "exec-a"), "added a child"); check(store.relation_index_add("r1", "wf-1", "exec-b"), "and another"); check(store.relation_index_add("r1", "wf-2", "exec-c"), "under a second parent"); // THE operation restrict and DescribeDelete need: a count without reading // the children. mdb_cursor_count makes it O(1)-ish. check(store.relation_index_child_count("r1", "wf-1") == 2, "two children of wf-1"); check(store.relation_index_child_count("r1", "wf-2") == 1, "one child of wf-2"); check(store.relation_index_child_count("r1", "wf-none") == 0, "an unreferenced parent has none, and that is not an error"); auto kids = store.relation_index_children("r1", "wf-1", 10); std::sort(kids.begin(), kids.end()); check(kids == std::vector{"exec-a", "exec-b"}, "children listed"); // Removing one pair must not remove the sibling. check(store.relation_index_remove("r1", "wf-1", "exec-a"), "removed one pair"); check(store.relation_index_child_count("r1", "wf-1") == 1, "the sibling survives"); // Idempotent: re-adding the same pair is a no-op, not a duplicate. store.relation_index_add("r1", "wf-1", "exec-b"); check(store.relation_index_child_count("r1", "wf-1") == 1, "no duplicate posting"); // A distinct relation is a distinct sub-db: same parent id, no crosstalk. check(store.relation_index_child_count("r2", "wf-1") == 0, "a different relation's index is independent"); // Removing a pair that was never present is reported, not thrown. check(!store.relation_index_remove("r1", "wf-1", "exec-does-not-exist"), "removing an absent pair returns false rather than throwing"); check(!store.relation_index_remove("r1", "wf-none", "exec-a"), "removing under an absent parent returns false rather than throwing"); } // The failure this repo is most likely to reproduce. Since v2.8.1 reads serve // only from the primed dbi cache, so a sub-db created at runtime is invisible // unless registered after commit - and relations would silently not enforce. void test_index_created_at_runtime_is_visible_after_reopen() { const std::string path = make_tmpdir("relidx-visible"); const LmdbEnvOpts opts{path, 64ULL << 20, 256, 126, false}; { LmdbEnv env(opts); LmdbDocumentStore store(env); store.relation_index_add("r1", "wf-1", "exec-a"); // Same process, no reopen: must be visible immediately. check(store.relation_index_child_count("r1", "wf-1") == 1, "visible in the process that created it - this is what " "cacheCommittedDbi() buys"); } LmdbEnv env2(opts); LmdbDocumentStore store2(env2); // priming runs in the constructor check(store2.relation_index_child_count("r1", "wf-1") == 1, "and after a restart, via prime_dbi_cache()"); std::error_code ec; fs::remove_all(path, ec); } } // namespace int main() { std::cout << "=== test_relation_index ===\n"; test_children_of_a_parent_are_a_dup_set(); test_index_created_at_runtime_is_visible_after_reopen(); std::cout << "passed: " << g_pass << ", failed: " << g_fail << "\n"; return g_fail == 0 ? 0 : 1; }