test_relation_index.cpp 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. // v2.11.0 T2 — relation reverse index tests.
  2. //
  3. // Storage-only: this exercises LmdbDocumentStore::relation_index_{add,remove,
  4. // child_count,children} directly. No document-write hooks (Task 3), no
  5. // enforcement (Task 4) are involved.
  6. //
  7. // test_index_created_at_runtime_is_visible_after_reopen is THE regression
  8. // test for this task: since v2.8.1 the read path serves only from the primed
  9. // dbi cache, so a sub-db created at runtime is invisible to every later read
  10. // in the process unless cacheCommittedDbi() runs after commit. Removing that
  11. // call from relation_index_add must fail this test.
  12. #include <algorithm>
  13. #include <atomic>
  14. #include <cstdio>
  15. #include <filesystem>
  16. #include <iostream>
  17. #include <string>
  18. #include <unistd.h>
  19. #include <vector>
  20. #include "storage/document_store_lmdb.hpp"
  21. #include "storage/lmdb_env.hpp"
  22. namespace fs = std::filesystem;
  23. using smartbotic::db::storage::LmdbDocumentStore;
  24. using smartbotic::db::storage::LmdbEnv;
  25. using smartbotic::db::storage::LmdbEnvOpts;
  26. namespace {
  27. int g_pass = 0;
  28. int g_fail = 0;
  29. void check(bool cond, const char* msg) {
  30. if (cond) {
  31. ++g_pass;
  32. } else {
  33. ++g_fail;
  34. std::cerr << "FAIL: " << msg << "\n";
  35. }
  36. }
  37. std::string make_tmpdir(const char* tag) {
  38. static std::atomic<int> counter{0};
  39. std::string path = "/tmp/relidx-test-" + std::to_string(::getpid()) + "-" +
  40. std::to_string(counter.fetch_add(1)) + "-" + tag;
  41. std::error_code ec;
  42. fs::remove_all(path, ec);
  43. return path;
  44. }
  45. struct TmpEnv {
  46. std::string path;
  47. LmdbEnv env;
  48. explicit TmpEnv(const char* tag)
  49. : path(make_tmpdir(tag)),
  50. env(LmdbEnvOpts{path, 64ULL << 20, 256, 126, false}) {}
  51. ~TmpEnv() {
  52. std::error_code ec;
  53. fs::remove_all(path, ec);
  54. }
  55. TmpEnv(const TmpEnv&) = delete;
  56. TmpEnv& operator=(const TmpEnv&) = delete;
  57. };
  58. // -------------------------------------------------------------------------
  59. // DUPSORT shape, per the re-validated design.
  60. void test_children_of_a_parent_are_a_dup_set() {
  61. TmpEnv t("relidx");
  62. LmdbDocumentStore store(t.env);
  63. check(store.relation_index_add("r1", "wf-1", "exec-a"), "added a child");
  64. check(store.relation_index_add("r1", "wf-1", "exec-b"), "and another");
  65. check(store.relation_index_add("r1", "wf-2", "exec-c"), "under a second parent");
  66. // THE operation restrict and DescribeDelete need: a count without reading
  67. // the children. mdb_cursor_count makes it O(1)-ish.
  68. check(store.relation_index_child_count("r1", "wf-1") == 2, "two children of wf-1");
  69. check(store.relation_index_child_count("r1", "wf-2") == 1, "one child of wf-2");
  70. check(store.relation_index_child_count("r1", "wf-none") == 0,
  71. "an unreferenced parent has none, and that is not an error");
  72. auto kids = store.relation_index_children("r1", "wf-1", 10);
  73. std::sort(kids.begin(), kids.end());
  74. check(kids == std::vector<std::string>{"exec-a", "exec-b"}, "children listed");
  75. // Removing one pair must not remove the sibling.
  76. check(store.relation_index_remove("r1", "wf-1", "exec-a"), "removed one pair");
  77. check(store.relation_index_child_count("r1", "wf-1") == 1, "the sibling survives");
  78. // Idempotent: re-adding the same pair is a no-op, not a duplicate.
  79. store.relation_index_add("r1", "wf-1", "exec-b");
  80. check(store.relation_index_child_count("r1", "wf-1") == 1, "no duplicate posting");
  81. // A distinct relation is a distinct sub-db: same parent id, no crosstalk.
  82. check(store.relation_index_child_count("r2", "wf-1") == 0,
  83. "a different relation's index is independent");
  84. // Removing a pair that was never present is reported, not thrown.
  85. check(!store.relation_index_remove("r1", "wf-1", "exec-does-not-exist"),
  86. "removing an absent pair returns false rather than throwing");
  87. check(!store.relation_index_remove("r1", "wf-none", "exec-a"),
  88. "removing under an absent parent returns false rather than throwing");
  89. }
  90. // The failure this repo is most likely to reproduce. Since v2.8.1 reads serve
  91. // only from the primed dbi cache, so a sub-db created at runtime is invisible
  92. // unless registered after commit - and relations would silently not enforce.
  93. void test_index_created_at_runtime_is_visible_after_reopen() {
  94. const std::string path = make_tmpdir("relidx-visible");
  95. const LmdbEnvOpts opts{path, 64ULL << 20, 256, 126, false};
  96. {
  97. LmdbEnv env(opts);
  98. LmdbDocumentStore store(env);
  99. store.relation_index_add("r1", "wf-1", "exec-a");
  100. // Same process, no reopen: must be visible immediately.
  101. check(store.relation_index_child_count("r1", "wf-1") == 1,
  102. "visible in the process that created it - this is what "
  103. "cacheCommittedDbi() buys");
  104. }
  105. LmdbEnv env2(opts);
  106. LmdbDocumentStore store2(env2); // priming runs in the constructor
  107. check(store2.relation_index_child_count("r1", "wf-1") == 1,
  108. "and after a restart, via prime_dbi_cache()");
  109. std::error_code ec;
  110. fs::remove_all(path, ec);
  111. }
  112. } // namespace
  113. int main() {
  114. std::cout << "=== test_relation_index ===\n";
  115. test_children_of_a_parent_are_a_dup_set();
  116. test_index_created_at_runtime_is_visible_after_reopen();
  117. std::cout << "passed: " << g_pass << ", failed: " << g_fail << "\n";
  118. return g_fail == 0 ? 0 : 1;
  119. }