test_timestamp_precision.cpp 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. /**
  2. * Unit tests for per-collection timestamp precision (v1.6.0 Task 7).
  3. *
  4. * Exercises MemoryStore + CollectionConfigManager directly — no gRPC server.
  5. *
  6. * Test cases:
  7. * 1. ms is the default precision
  8. * 2. ns yields unique timestamps in a tight 1000-doc insert loop
  9. * 3. ms stamps fit in the ms range (< 10^15) over 1000 docs
  10. * 4. setConfig() is idempotent
  11. * 5. configFor() reflects setConfig() immediately (no reload required)
  12. * 6. Unknown collections return the default config
  13. */
  14. #include "../service/src/memory_store.hpp"
  15. #include "../service/src/config/collection_config_manager.hpp"
  16. #include "../service/src/document.hpp"
  17. #include <cassert>
  18. #include <cstdint>
  19. #include <iostream>
  20. #include <nlohmann/json.hpp>
  21. #include <set>
  22. #include <string>
  23. using smartbotic::database::CollectionCfg;
  24. using smartbotic::database::CollectionConfigManager;
  25. using smartbotic::database::CollectionOptions;
  26. using smartbotic::database::Document;
  27. using smartbotic::database::MemoryStore;
  28. namespace {
  29. // Anything below 10^15 fits comfortably in the ms-since-epoch range (≈ 2001..),
  30. // anything above is unambiguously ns-since-epoch in the current era.
  31. constexpr uint64_t NS_THRESHOLD = 1'000'000'000'000'000ULL; // 10^15
  32. MemoryStore::Config defaultConfig() {
  33. MemoryStore::Config cfg;
  34. cfg.nodeId = "test";
  35. cfg.maxMemoryBytes = 256ULL * 1024 * 1024;
  36. return cfg;
  37. }
  38. Document makeDoc(const std::string& id = "") {
  39. Document d;
  40. if (!id.empty()) d.id = id;
  41. d.set_data(nlohmann::json{{"value", "x"}});
  42. return d;
  43. }
  44. } // anonymous namespace
  45. void test_default_is_ns() {
  46. // v2.2 — default flipped to "ns" so rapid-write collections get
  47. // strict-ordering timestamps without an explicit configureCollection().
  48. MemoryStore store(defaultConfig());
  49. store.start();
  50. CollectionConfigManager mgr(store);
  51. store.setConfigManager(&mgr);
  52. store.createCollection("docs_default", CollectionOptions{});
  53. auto id = store.insert("docs_default", makeDoc());
  54. auto doc = store.get("docs_default", id);
  55. assert(doc.has_value());
  56. assert(doc->createdAt > 0);
  57. assert(doc->createdAt >= NS_THRESHOLD); // ns range
  58. assert(doc->updatedAt > 0);
  59. assert(doc->updatedAt >= NS_THRESHOLD);
  60. store.stop();
  61. std::cout << "PASS: default precision is ns (createdAt=" << doc->createdAt << ")\n";
  62. }
  63. void test_ns_yields_unique_timestamps_in_tight_loop() {
  64. MemoryStore store(defaultConfig());
  65. store.start();
  66. CollectionConfigManager mgr(store);
  67. store.setConfigManager(&mgr);
  68. store.createCollection("docs_ns", CollectionOptions{});
  69. CollectionCfg ns_cfg;
  70. ns_cfg.timestampPrecision = "ns";
  71. std::string err;
  72. bool ok = mgr.setConfig("docs_ns", ns_cfg, err);
  73. assert(ok);
  74. std::set<uint64_t> seen;
  75. constexpr int N = 1000;
  76. for (int i = 0; i < N; ++i) {
  77. auto id = store.insert("docs_ns", makeDoc());
  78. auto doc = store.get("docs_ns", id);
  79. assert(doc.has_value());
  80. assert(doc->createdAt >= NS_THRESHOLD); // ns range
  81. auto [it, inserted] = seen.insert(doc->createdAt);
  82. assert(inserted); // every timestamp must be unique
  83. }
  84. assert(seen.size() == static_cast<size_t>(N));
  85. store.stop();
  86. std::cout << "PASS: ns precision produces " << N
  87. << " unique timestamps in tight loop\n";
  88. }
  89. void test_ms_baseline_fits_in_ms_range() {
  90. MemoryStore store(defaultConfig());
  91. store.start();
  92. CollectionConfigManager mgr(store);
  93. store.setConfigManager(&mgr);
  94. store.createCollection("docs_ms", CollectionOptions{});
  95. CollectionCfg ms_cfg;
  96. ms_cfg.timestampPrecision = "ms";
  97. std::string err;
  98. bool ok = mgr.setConfig("docs_ms", ms_cfg, err);
  99. assert(ok);
  100. constexpr int N = 1000;
  101. for (int i = 0; i < N; ++i) {
  102. auto id = store.insert("docs_ms", makeDoc());
  103. auto doc = store.get("docs_ms", id);
  104. assert(doc.has_value());
  105. assert(doc->createdAt < NS_THRESHOLD); // ms range
  106. }
  107. // We deliberately do NOT assert on tie-production here — it's machine-
  108. // dependent. The invariant that ms-stamped docs stay in the ms range is
  109. // what the default-preservation story needs.
  110. store.stop();
  111. std::cout << "PASS: ms precision stamps fit in ms range (" << N << " docs)\n";
  112. }
  113. void test_idempotent_configure() {
  114. MemoryStore store(defaultConfig());
  115. store.start();
  116. CollectionConfigManager mgr(store);
  117. store.setConfigManager(&mgr);
  118. CollectionCfg cfg;
  119. cfg.timestampPrecision = "ns";
  120. std::string err;
  121. assert(mgr.setConfig("docs_idem", cfg, err));
  122. assert(mgr.configFor("docs_idem").timestampPrecision == "ns");
  123. assert(mgr.hasExplicitConfig("docs_idem"));
  124. // Second call with the same value — should succeed and leave state intact.
  125. assert(mgr.setConfig("docs_idem", cfg, err));
  126. assert(mgr.configFor("docs_idem").timestampPrecision == "ns");
  127. assert(mgr.hasExplicitConfig("docs_idem"));
  128. store.stop();
  129. std::cout << "PASS: setConfig is idempotent\n";
  130. }
  131. void test_cache_reflects_configure_immediately() {
  132. MemoryStore store(defaultConfig());
  133. store.start();
  134. CollectionConfigManager mgr(store);
  135. store.setConfigManager(&mgr);
  136. // Before configure: default ns (v2.2), not explicit.
  137. assert(mgr.configFor("docs_cache").timestampPrecision == "ns");
  138. assert(!mgr.hasExplicitConfig("docs_cache"));
  139. CollectionCfg cfg;
  140. cfg.timestampPrecision = "ms";
  141. std::string err;
  142. assert(mgr.setConfig("docs_cache", cfg, err));
  143. // After configure: ms, immediately — no reloadFromStore() required.
  144. assert(mgr.configFor("docs_cache").timestampPrecision == "ms");
  145. assert(mgr.hasExplicitConfig("docs_cache"));
  146. store.stop();
  147. std::cout << "PASS: cache reflects setConfig immediately\n";
  148. }
  149. void test_default_for_unknown_collection() {
  150. MemoryStore store(defaultConfig());
  151. store.start();
  152. CollectionConfigManager mgr(store);
  153. store.setConfigManager(&mgr);
  154. auto cfg = mgr.configFor("never_configured_collection");
  155. // v2.2 — default flipped from "ms" to "ns" so rapid-write collections
  156. // get nanosecond stamps without an explicit configureCollection() call.
  157. assert(cfg.timestampPrecision == "ns");
  158. assert(!mgr.hasExplicitConfig("never_configured_collection"));
  159. store.stop();
  160. std::cout << "PASS: default config returned for unknown collection\n";
  161. }
  162. int main() {
  163. test_default_is_ns();
  164. test_ns_yields_unique_timestamps_in_tight_loop();
  165. test_ms_baseline_fits_in_ms_range();
  166. test_idempotent_configure();
  167. test_cache_reflects_configure_immediately();
  168. test_default_for_unknown_collection();
  169. std::cout << "\nAll timestamp precision tests PASSED!\n";
  170. return 0;
  171. }