| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- // 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 <algorithm>
- #include <atomic>
- #include <cstdio>
- #include <filesystem>
- #include <iostream>
- #include <string>
- #include <unistd.h>
- #include <vector>
- #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<int> 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<std::string>{"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;
- }
|