diff --git a/ChangeLog b/ChangeLog index 3e90c4fb..384f9c6b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +Unreleased +---------- +- Task 06: Encapsulated MoveGen diagnostics via `SolverContext`; removed stray direct calls; all tests green and merged. +- Task 07: Migrated heuristic sorting helpers to use `HeuristicContext` snapshots; static analysis addressed; merged with tests green. +- Task 08 (in progress): + - Utilities: Introduced header-only `Utilities` with per-instance RNG (`std::mt19937`), an append-only log buffer, and minimal `Stats` counters. + - Deterministic seeding exposed via `SolverConfig.rngSeed` and `SolverContext::utilities()` accessors. + - Optional logging under `DDS_UTILITIES_LOG`: compact entries for TransTable lifecycle (create/dispose/clear/resize) and context ops (reset-for-solve, reset-best-moves-lite). + - Optional stats under `DDS_UTILITIES_STATS`: counters `tt_creates` and `tt_disposes` incremented at lifecycle points. + - Feature detection helpers `Utilities::log_enabled()` and `Utilities::stats_enabled()` added, with tests for enabled/disabled variants. + - Bazel wiring: dedicated testable targets enable flags only for targeted tests; default builds remain unaffected. + - Tests added: RNG determinism; logging enabled/disabled; stats enabled/disabled; context operation logs; feature-flag detection. + Release Notes DDS 2.9.1 ----------------------- test/print.cpp: Fixed bug in equals_to_string() noticed by tucsu. diff --git a/README.md b/README.md index 1e840f02..84af7c3f 100644 --- a/README.md +++ b/README.md @@ -92,3 +92,28 @@ Version 2.9.0 has no known bugs. Please report bugs to bo.haglund@bahnhof.se and soren.hein@gmail.com. + +Utilities (RNG, logging, stats) +================================ + +Task 08 introduced an instance-scoped Utilities bundle and opt-in diagnostics. These changes are behavior-neutral by default. + +- Per-instance RNG + - `dds::Utilities` (header-only) lives under `library/src/system/util/Utilities.h`. + - `SolverContext` exposes `utilities()` with access to a `std::mt19937` RNG. + - Deterministic seeding is available via `SolverConfig.rngSeed` on `SolverContext` construction. + +- Optional logging (compile-time) + - When compiled with `DDS_UTILITIES_LOG`, `SolverContext` appends compact entries for internal lifecycle (e.g., TransTable create/dispose/clear/resize, context resets/best-move reset). + - Log lines are available via `ctx.utilities().logBuffer()` and can be cleared with `logClear()`. + +- Optional stats (compile-time) + - When compiled with `DDS_UTILITIES_STATS`, `Utilities::Stats` exposes small counters (e.g., `tt_creates`, `tt_disposes`) incremented from `SolverContext` lifecycle. + - Stats can be reset with `stats_reset()`. + +Testing and build variants +- Default builds do not enable these flags and thus incur no overhead. +- Tests enable flags via dedicated Bazel targets so the main library remains unaffected: + - Logging: `//library/src/system:system_util_log`, `//library/src:testable_dds_util_log`. + - Stats: `//library/src/system:system_util_stats`, `//library/src:testable_dds_util_stats`. + diff --git a/library/src/BUILD.bazel b/library/src/BUILD.bazel index ee1e06b2..28b8db0c 100644 --- a/library/src/BUILD.bazel +++ b/library/src/BUILD.bazel @@ -34,6 +34,7 @@ cc_library( "//library/tests:__pkg__", "//library/tests/heuristic_sorting:__pkg__", "//library/tests/system:__pkg__", + "//library/tests/utility:__pkg__", "//library/tests/regression/heuristic_sorting:__pkg__", ], deps = [ @@ -47,3 +48,50 @@ cc_library( ], ) +# Variant of testable_dds with Utilities logging compiled in. +cc_library( + name = "testable_dds_util_log", + srcs = glob(["*.cpp", "*.h"]), + hdrs = glob(["*.h"]), + copts = DDS_CPPOPTS, + linkopts = DDS_LINKOPTS, + local_defines = DDS_LOCAL_DEFINES, + include_prefix = "dds", + visibility = [ + "//:__pkg__", + "//library/tests/system:__pkg__", + ], + deps = [ + "//library/src/data_types:dds_types", + "//library/src/utility:constants", + "//library/src/utility:lookup_tables", + "//library/src/heuristic_sorting", + "//library/src/trans_table", + "//library/src/moves:moves", + "//library/src/system:system_util_log", + ], +) + +cc_library( + name = "testable_dds_util_stats", + srcs = glob(["*.cpp", "*.h"]), + hdrs = glob(["*.h"]), + copts = DDS_CPPOPTS, + linkopts = DDS_LINKOPTS, + local_defines = DDS_LOCAL_DEFINES, + include_prefix = "dds", + visibility = [ + "//:__pkg__", + "//library/tests/system:__pkg__", + ], + deps = [ + "//library/src/data_types:dds_types", + "//library/src/utility:constants", + "//library/src/utility:lookup_tables", + "//library/src/heuristic_sorting", + "//library/src/trans_table", + "//library/src/moves:moves", + "//library/src/system:system_util_stats", + ], +) + diff --git a/library/src/system/BUILD.bazel b/library/src/system/BUILD.bazel index ced110f3..db4f4265 100644 --- a/library/src/system/BUILD.bazel +++ b/library/src/system/BUILD.bazel @@ -4,7 +4,7 @@ load("@rules_cc//cc:defs.bzl", "cc_library") cc_library( name = "system", srcs = glob(["*.cpp"]), - hdrs = glob(["*.h"]), + hdrs = glob(["*.h", "util/*.h"]), visibility = ["//visibility:public"], includes = [".."], deps = [ @@ -13,9 +13,49 @@ cc_library( "//library/src/data_types:dds_types", "//library/src/trans_table", "//library/src/moves:moves", + # Utilities lives under system/util (header-only for now) ], include_prefix = "system", copts = DDS_CPPOPTS, linkopts = DDS_LINKOPTS, local_defines = DDS_LOCAL_DEFINES + DDS_SCHEDULER_DEFINE, ) + +# Variant with Utilities logging enabled for tests that assert log behavior +cc_library( + name = "system_util_log", + srcs = glob(["*.cpp"]), + hdrs = glob(["*.h", "util/*.h"]), + visibility = ["//visibility:public"], + includes = [".."], + deps = [ + "//library/src/utility:constants", + "//library/src/utility:lookup_tables", + "//library/src/data_types:dds_types", + "//library/src/trans_table", + "//library/src/moves:moves", + ], + include_prefix = "system", + copts = DDS_CPPOPTS, + linkopts = DDS_LINKOPTS, + local_defines = DDS_LOCAL_DEFINES + DDS_SCHEDULER_DEFINE + ["DDS_UTILITIES_LOG"], +) + +cc_library( + name = "system_util_stats", + srcs = glob(["*.cpp"]), + hdrs = glob(["*.h", "util/*.h"]), + visibility = ["//visibility:public"], + includes = [".."], + deps = [ + "//library/src/utility:constants", + "//library/src/utility:lookup_tables", + "//library/src/data_types:dds_types", + "//library/src/trans_table", + "//library/src/moves:moves", + ], + include_prefix = "system", + copts = DDS_CPPOPTS, + linkopts = DDS_LINKOPTS, + local_defines = DDS_LOCAL_DEFINES + DDS_SCHEDULER_DEFINE + ["DDS_UTILITIES_STATS"], +) diff --git a/library/src/system/SolverContext.cpp b/library/src/system/SolverContext.cpp index 5fff6a8f..e3e51821 100644 --- a/library/src/system/SolverContext.cpp +++ b/library/src/system/SolverContext.cpp @@ -8,6 +8,7 @@ #include "data_types/dds.h" // THREADMEM_* defaults #include #include +#include #include namespace { @@ -85,6 +86,20 @@ TransTable* SolverContext::transTable() const created->SetMemoryMaximum(maxMB); created->MakeTT(); +#ifdef DDS_UTILITIES_LOG + // Append a tiny debug entry indicating TT creation and chosen kind/sizes. + { + char buf[96]; + const char kch = (kind == TTKind::Small ? 'S' : 'L'); + std::snprintf(buf, sizeof(buf), "tt:create|%c|%d|%d", kch, defMB, maxMB); + utilities().logAppend(std::string(buf)); + } +#endif + +#ifdef DDS_UTILITIES_STATS + utilities().util().stats().tt_creates++; +#endif + // Attach to registry registry()[thr_] = created; } @@ -148,6 +163,13 @@ void SolverContext::DisposeTransTable() const auto it = registry().find(thr_); if (it != registry().end()) { +#ifdef DDS_UTILITIES_LOG + // Append a tiny debug entry indicating TT disposal. + utilities().logAppend("tt:dispose"); +#endif +#ifdef DDS_UTILITIES_STATS + utilities().util().stats().tt_disposes++; +#endif delete it->second; it->second = nullptr; registry().erase(it); @@ -158,6 +180,9 @@ SolverContext::~SolverContext() = default; void SolverContext::ResetForSolve() const { +#ifdef DDS_UTILITIES_LOG + utilities().logAppend("ctx:reset_for_solve"); +#endif if (auto* tt = maybeTransTable()) tt->ResetMemory(TT_RESET_FREE_MEMORY); if (!thr_) return; @@ -185,12 +210,22 @@ void SolverContext::ResetForSolve() const void SolverContext::ClearTT() const { +#ifdef DDS_UTILITIES_LOG + utilities().logAppend("tt:clear"); +#endif if (auto* tt = maybeTransTable()) tt->ReturnAllMemory(); } void SolverContext::ResizeTT(int defMB, int maxMB) const { +#ifdef DDS_UTILITIES_LOG + { + char buf[64]; + std::snprintf(buf, sizeof(buf), "tt:resize|%d|%d", defMB, maxMB); + utilities().logAppend(std::string(buf)); + } +#endif if (auto* tt = maybeTransTable()) { if (maxMB < defMB) maxMB = defMB; @@ -202,6 +237,9 @@ void SolverContext::ResizeTT(int defMB, int maxMB) const // Lightweight reset matching legacy ResetBestMoves semantics. void SolverContext::ResetBestMovesLite() const { +#ifdef DDS_UTILITIES_LOG + utilities().logAppend("ctx:reset_best_moves_lite"); +#endif if (!thr_) return; for (int d = 0; d <= 49; ++d) { diff --git a/library/src/system/SolverContext.h b/library/src/system/SolverContext.h index 96390033..e8b9395a 100644 --- a/library/src/system/SolverContext.h +++ b/library/src/system/SolverContext.h @@ -18,6 +18,10 @@ struct pos; // from dds/dds.h struct relRanksType; // from dds/dds.h struct trickDataType; // from data_types/dds.h #include "trans_table/TransTable.h" // ensure complete type and enums +#include "system/util/Utilities.h" // instance-scoped RNG and logging +#include +#include +#include // Minimal configuration scaffold for future expansion. // TT configuration without depending on Memory headers. @@ -28,23 +32,50 @@ struct SolverConfig TTKind ttKind = TTKind::Small; int ttMemDefaultMB = 0; int ttMemMaximumMB = 0; + // Optional deterministic RNG seed (0 means "no explicit seed"). + unsigned long long rngSeed = 0ULL; }; class SolverContext { public: explicit SolverContext(ThreadData* thread, SolverConfig cfg = {}) - : thr_(thread), cfg_(cfg) {} + : thr_(thread), cfg_(cfg) + { + if (cfg_.rngSeed != 0ULL) utils_.seed(cfg_.rngSeed); + } // Allow construction from const ThreadData* for read-only contexts explicit SolverContext(const ThreadData* thread, SolverConfig cfg = {}) - : thr_(const_cast(thread)), cfg_(cfg) {} + : thr_(const_cast(thread)), cfg_(cfg) + { + if (cfg_.rngSeed != 0ULL) utils_.seed(cfg_.rngSeed); + } ~SolverContext(); ThreadData* thread() const { return thr_; } const SolverConfig& config() const { return cfg_; } + // --- Utilities facade --- + class UtilitiesContext { + public: + explicit UtilitiesContext(::dds::Utilities* util) : util_(util) {} + ::dds::Utilities& util() { return *util_; } + const ::dds::Utilities& util() const { return *util_; } + std::mt19937& rng() { return util_->rng(); } + const std::mt19937& rng() const { return util_->rng(); } + void seedRng(unsigned long long seed) { util_->seed(seed); } + void logAppend(const std::string& s) { util_->log_append(s); } + const std::vector& logBuffer() const { return util_->log_buffer(); } + void logClear() { util_->log_clear(); } + private: + ::dds::Utilities* util_ = nullptr; + }; + + inline UtilitiesContext utilities() { return UtilitiesContext(&utils_); } + inline UtilitiesContext utilities() const { return UtilitiesContext(&utils_); } + TransTable* transTable() const; TransTable* maybeTransTable() const; @@ -179,6 +210,7 @@ class SolverContext private: ThreadData* thr_ = nullptr; SolverConfig cfg_{}; + mutable ::dds::Utilities utils_{}; }; double ThreadMemoryUsed(); diff --git a/library/src/system/util/Utilities.h b/library/src/system/util/Utilities.h new file mode 100644 index 00000000..38a7b575 --- /dev/null +++ b/library/src/system/util/Utilities.h @@ -0,0 +1,72 @@ +#ifndef DDS_SYSTEM_UTIL_UTILITIES_H +#define DDS_SYSTEM_UTIL_UTILITIES_H + +#include +#include +#include +#include + +namespace dds { + +// A tiny, instance-scoped utility bundle holding RNG and a simple log buffer. +class Utilities { +public: + Utilities() = default; + + // Compile-time feature detection helpers for tests and guarded code paths. + static constexpr bool log_enabled() { +#ifdef DDS_UTILITIES_LOG + return true; +#else + return false; +#endif + } + + static constexpr bool stats_enabled() { +#ifdef DDS_UTILITIES_STATS + return true; +#else + return false; +#endif + } + + // RNG: mt19937 seeded with a 64-bit seed for determinism across platforms. + void seed(uint64_t seed_value) { + rng_.seed(static_cast(seed_value)); + } + + std::mt19937& rng() { return rng_; } + const std::mt19937& rng() const { return rng_; } + + // Logging: a very simple append-only buffer; callers can flush and clear. + void log_append(const std::string& s) { log_.push_back(s); } + const std::vector& log_buffer() const { return log_; } + size_t log_size() const { return log_.size(); } + bool log_contains(const std::string& prefix) const { + for (const auto& line : log_) { + if (line.rfind(prefix, 0) == 0) return true; // prefix match + } + return false; + } + void log_clear() { log_.clear(); } + + // Minimal stats: opt-in counters for smoke validation in tests. + struct Stats { + unsigned tt_creates = 0; + unsigned tt_disposes = 0; + }; + + const Stats& stats() const { return stats_; } + Stats& stats() { return stats_; } + Stats stats_snapshot() const { return stats_; } + void stats_reset() { stats_ = Stats{}; } + +private: + std::mt19937 rng_{}; // default-constructed; seed via seed() + std::vector log_{}; // minimal structured log lines + Stats stats_{}; // optional counters +}; + +} // namespace dds + +#endif // DDS_SYSTEM_UTIL_UTILITIES_H diff --git a/library/tests/system/BUILD.bazel b/library/tests/system/BUILD.bazel index ba8e76b8..96f05c37 100644 --- a/library/tests/system/BUILD.bazel +++ b/library/tests/system/BUILD.bazel @@ -35,3 +35,106 @@ cc_test( "@googletest//:gtest_main", ], ) + +# Utilities logging tests: one without define (expect empty), one with define (expect entries) +cc_test( + name = "utilities_log_test", + srcs = ["utilities_log_test.cpp"], + copts = [], + deps = [ + "//library/src:testable_dds", + "//library/src/data_types:dds_types", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "utilities_log_test_with_define", + srcs = ["utilities_log_test_with_define.cpp"], + deps = [ + "//library/src:testable_dds_util_log", + "//library/src/data_types:dds_types", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "utilities_log_ctx_ops_test_with_define", + srcs = ["utilities_log_ctx_ops_test_with_define.cpp"], + deps = [ + "//library/src:testable_dds_util_log", + "//library/src/data_types:dds_types", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "utilities_log_ctx_ops_test", + srcs = ["utilities_log_ctx_ops_test.cpp"], + deps = [ + "//library/src:testable_dds", + "//library/src/data_types:dds_types", + "@googletest//:gtest_main", + ], +) + +# Utilities stats tests: without/with define +cc_test( + name = "utilities_stats_test", + srcs = ["utilities_stats_test.cpp"], + deps = [ + "//library/src:testable_dds", + "//library/src/data_types:dds_types", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "utilities_stats_test_with_define", + srcs = ["utilities_stats_test_with_define.cpp"], + deps = [ + "//library/src:testable_dds_util_stats", + "//library/src/data_types:dds_types", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "utilities_log_contains_test", + srcs = ["utilities_log_contains_test.cpp"], + deps = [ + "//library/src:testable_dds", + "//library/src/data_types:dds_types", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "utilities_feature_flags_test", + srcs = ["utilities_feature_flags_test.cpp"], + deps = [ + "//library/src:testable_dds", + "//library/src/data_types:dds_types", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "utilities_feature_flags_test_with_log", + srcs = ["utilities_feature_flags_test_with_log.cpp"], + deps = [ + "//library/src:testable_dds_util_log", + "//library/src/data_types:dds_types", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "utilities_feature_flags_test_with_stats", + srcs = ["utilities_feature_flags_test_with_stats.cpp"], + deps = [ + "//library/src:testable_dds_util_stats", + "//library/src/data_types:dds_types", + "@googletest//:gtest_main", + ], +) diff --git a/library/tests/system/utilities_feature_flags_test.cpp b/library/tests/system/utilities_feature_flags_test.cpp new file mode 100644 index 00000000..de9c02da --- /dev/null +++ b/library/tests/system/utilities_feature_flags_test.cpp @@ -0,0 +1,16 @@ +#include "library/src/system/util/Utilities.h" +#include + +namespace dds { + +TEST(UtilitiesFeatureFlags, LogDisabledByDefault) { + // Default build should not define DDS_UTILITIES_LOG. + EXPECT_FALSE(Utilities::log_enabled()); +} + +TEST(UtilitiesFeatureFlags, StatsDisabledByDefault) { + // Default build should not define DDS_UTILITIES_STATS. + EXPECT_FALSE(Utilities::stats_enabled()); +} + +} // namespace dds diff --git a/library/tests/system/utilities_feature_flags_test_with_log.cpp b/library/tests/system/utilities_feature_flags_test_with_log.cpp new file mode 100644 index 00000000..7842b1bf --- /dev/null +++ b/library/tests/system/utilities_feature_flags_test_with_log.cpp @@ -0,0 +1,15 @@ +// Ensure the logging flag is enabled for this translation unit before including the header. +#ifndef DDS_UTILITIES_LOG +#define DDS_UTILITIES_LOG +#endif + +#include "library/src/system/util/Utilities.h" +#include + +namespace dds { + +TEST(UtilitiesFeatureFlagsWithLog, LogEnabledWithDefine) { + EXPECT_TRUE(Utilities::log_enabled()); +} + +} // namespace dds diff --git a/library/tests/system/utilities_feature_flags_test_with_stats.cpp b/library/tests/system/utilities_feature_flags_test_with_stats.cpp new file mode 100644 index 00000000..78798cea --- /dev/null +++ b/library/tests/system/utilities_feature_flags_test_with_stats.cpp @@ -0,0 +1,15 @@ +// Ensure the stats flag is enabled for this translation unit before including the header. +#ifndef DDS_UTILITIES_STATS +#define DDS_UTILITIES_STATS +#endif + +#include "library/src/system/util/Utilities.h" +#include + +namespace dds { + +TEST(UtilitiesFeatureFlagsWithStats, StatsEnabledWithDefine) { + EXPECT_TRUE(Utilities::stats_enabled()); +} + +} // namespace dds diff --git a/library/tests/system/utilities_log_contains_test.cpp b/library/tests/system/utilities_log_contains_test.cpp new file mode 100644 index 00000000..d6bc93eb --- /dev/null +++ b/library/tests/system/utilities_log_contains_test.cpp @@ -0,0 +1,17 @@ +#include "library/src/system/util/Utilities.h" +#include + +namespace dds { + +TEST(UtilitiesLogContains, PrefixMatchesWork) { + Utilities u; + u.log_clear(); + u.log_append("tt:create|K|1024|4096"); + u.log_append("ctx:reset_for_solve"); + EXPECT_EQ(u.log_size(), 2u); + EXPECT_TRUE(u.log_contains("tt:create")); + EXPECT_TRUE(u.log_contains("ctx:reset")); + EXPECT_FALSE(u.log_contains("not:there")); +} + +} // namespace dds diff --git a/library/tests/system/utilities_log_ctx_ops_test.cpp b/library/tests/system/utilities_log_ctx_ops_test.cpp new file mode 100644 index 00000000..c5eaeb50 --- /dev/null +++ b/library/tests/system/utilities_log_ctx_ops_test.cpp @@ -0,0 +1,30 @@ +#include +#include "system/SolverContext.h" +#include "system/Memory.h" +#include "data_types/dds.h" + +extern Memory memory; + +static void ensureThread() +{ + if (memory.NumThreads() == 0) + memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); +} + +TEST(UtilitiesLogCtxOpsNoDefine, NoEntriesWhenDisabled) +{ + ensureThread(); + ThreadData* thr = memory.GetPtr(0); + SolverContext ctx{thr}; + + // Start from a clean log buffer + ctx.utilities().logClear(); + + // Exercise the context operations (should not produce log entries by default) + ctx.ResetForSolve(); + ctx.ResetBestMovesLite(); + ctx.ResizeTT(8, 16); + ctx.ClearTT(); + + EXPECT_TRUE(ctx.utilities().logBuffer().empty()); +} diff --git a/library/tests/system/utilities_log_ctx_ops_test_with_define.cpp b/library/tests/system/utilities_log_ctx_ops_test_with_define.cpp new file mode 100644 index 00000000..1d79c2cf --- /dev/null +++ b/library/tests/system/utilities_log_ctx_ops_test_with_define.cpp @@ -0,0 +1,48 @@ +#include +#include "system/SolverContext.h" +#include "system/Memory.h" +#include "data_types/dds.h" + +extern Memory memory; + +static void ensureThread() +{ + if (memory.NumThreads() == 0) + memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); +} + +TEST(UtilitiesLogCtxOpsWithDefine, EmitsCtxAndTTOps) +{ + ensureThread(); + ThreadData* thr = memory.GetPtr(0); + SolverContext ctx{thr}; + + // Start from a clean log buffer + ctx.utilities().logClear(); + + // Exercise the newly logged operations + ctx.ResetForSolve(); + ctx.ResetBestMovesLite(); + ctx.ResizeTT(8, 16); + ctx.ClearTT(); + + const auto& logs = ctx.utilities().logBuffer(); + // We expect at least the four entries we just invoked + ASSERT_GE(logs.size(), 4u); + + bool sawResetForSolve = false; + bool sawResetBestMovesLite = false; + bool sawResize = false; + bool sawClear = false; + for (const auto& s : logs) { + if (s == "ctx:reset_for_solve") sawResetForSolve = true; + if (s == "ctx:reset_best_moves_lite") sawResetBestMovesLite = true; + if (s.rfind("tt:resize|", 0) == 0) sawResize = true; + if (s == "tt:clear") sawClear = true; + } + + EXPECT_TRUE(sawResetForSolve); + EXPECT_TRUE(sawResetBestMovesLite); + EXPECT_TRUE(sawResize); + EXPECT_TRUE(sawClear); +} diff --git a/library/tests/system/utilities_log_test.cpp b/library/tests/system/utilities_log_test.cpp new file mode 100644 index 00000000..814bd444 --- /dev/null +++ b/library/tests/system/utilities_log_test.cpp @@ -0,0 +1,28 @@ +#include +#include "system/SolverContext.h" +#include "system/Memory.h" +#include "data_types/dds.h" // THREADMEM_* defaults + +extern Memory memory; + +static void ensureThread() +{ + if (memory.NumThreads() == 0) + memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); +} + +TEST(UtilitiesLogTest, NoLogWithoutDefine) +{ + ensureThread(); + ThreadData* thr = memory.GetPtr(0); + SolverContext ctx{thr}; + + // Ensure clean start + ctx.utilities().logClear(); + + // Create TT and dispose it; without define there should be no logs + (void)ctx.transTable(); + ctx.DisposeTransTable(); + + EXPECT_TRUE(ctx.utilities().logBuffer().empty()); +} diff --git a/library/tests/system/utilities_log_test_with_define.cpp b/library/tests/system/utilities_log_test_with_define.cpp new file mode 100644 index 00000000..1ddc04e2 --- /dev/null +++ b/library/tests/system/utilities_log_test_with_define.cpp @@ -0,0 +1,34 @@ +#include +#include "system/SolverContext.h" +#include "system/Memory.h" +#include "data_types/dds.h" + +extern Memory memory; + +static void ensureThread() +{ + if (memory.NumThreads() == 0) + memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); +} + +TEST(UtilitiesLogTestWithDefine, LogsPresentWhenEnabled) +{ + ensureThread(); + ThreadData* thr = memory.GetPtr(0); + SolverContext ctx{thr}; + ctx.utilities().logClear(); + + (void)ctx.transTable(); + ctx.DisposeTransTable(); + + const auto& logs = ctx.utilities().logBuffer(); + ASSERT_GE(logs.size(), 1u); + // First log must be tt:create|K|def|max where K in {S,L} + EXPECT_TRUE(logs[0].rfind("tt:create|", 0) == 0); + // Last (or one of) should be dispose + bool sawDispose = false; + for (const auto& s : logs) { + if (s == "tt:dispose") { sawDispose = true; break; } + } + EXPECT_TRUE(sawDispose); +} diff --git a/library/tests/system/utilities_stats_test.cpp b/library/tests/system/utilities_stats_test.cpp new file mode 100644 index 00000000..5de48237 --- /dev/null +++ b/library/tests/system/utilities_stats_test.cpp @@ -0,0 +1,27 @@ +#include +#include "system/SolverContext.h" +#include "system/Memory.h" +#include "data_types/dds.h" + +extern Memory memory; + +static void ensureThread() +{ + if (memory.NumThreads() == 0) + memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); +} + +TEST(UtilitiesStatsTest, CountersRemainZeroWithoutDefine) +{ + ensureThread(); + ThreadData* thr = memory.GetPtr(0); + SolverContext ctx{thr}; + ctx.utilities().util().stats_reset(); + + (void)ctx.transTable(); + ctx.DisposeTransTable(); + + const auto& st = ctx.utilities().util().stats(); + EXPECT_EQ(0u, st.tt_creates); + EXPECT_EQ(0u, st.tt_disposes); +} diff --git a/library/tests/system/utilities_stats_test_with_define.cpp b/library/tests/system/utilities_stats_test_with_define.cpp new file mode 100644 index 00000000..4313a418 --- /dev/null +++ b/library/tests/system/utilities_stats_test_with_define.cpp @@ -0,0 +1,27 @@ +#include +#include "system/SolverContext.h" +#include "system/Memory.h" +#include "data_types/dds.h" + +extern Memory memory; + +static void ensureThread() +{ + if (memory.NumThreads() == 0) + memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); +} + +TEST(UtilitiesStatsTestWithDefine, CountersIncreaseWhenEnabled) +{ + ensureThread(); + ThreadData* thr = memory.GetPtr(0); + SolverContext ctx{thr}; + ctx.utilities().util().stats_reset(); + + (void)ctx.transTable(); + ctx.DisposeTransTable(); + + const auto& st = ctx.utilities().util().stats(); + EXPECT_EQ(1u, st.tt_creates); + EXPECT_EQ(1u, st.tt_disposes); +} diff --git a/library/tests/utility/BUILD.bazel b/library/tests/utility/BUILD.bazel index 5f58c685..2892b03a 100644 --- a/library/tests/utility/BUILD.bazel +++ b/library/tests/utility/BUILD.bazel @@ -1,5 +1,14 @@ load("@rules_cc//cc:defs.bzl", "cc_test") +cc_test( + name = "rng_determinism_test", + srcs = ["rng_determinism_test.cpp"], + deps = [ + "//library/src:testable_dds", + "@googletest//:gtest_main", + ], +) + cc_test( name = "constants_test", srcs = ["constants_test.cpp"], diff --git a/library/tests/utility/rng_determinism_test.cpp b/library/tests/utility/rng_determinism_test.cpp new file mode 100644 index 00000000..03124f35 --- /dev/null +++ b/library/tests/utility/rng_determinism_test.cpp @@ -0,0 +1,46 @@ +#include "gtest/gtest.h" +#include +#include +#include "system/SolverContext.h" +#include "system/Memory.h" + +TEST(UtilitiesRngTest, DeterministicSequenceWithSeed) { + // Set up a minimal ThreadData directly; TT is unused in this test. + ThreadData thrInst{}; + ThreadData* thr = &thrInst; + + // Seeded contexts should produce the same sequence for same seed. + SolverConfig cfg{}; + cfg.rngSeed = 123456789ULL; + + SolverContext ctx1(thr, cfg); + SolverContext ctx2(thr, cfg); + + // Generate a few values using uniform_int_distribution. + std::uniform_int_distribution dist(0, 1000000); + std::vector seq1, seq2; + for (int i = 0; i < 10; ++i) { + seq1.push_back(dist(ctx1.utilities().rng())); + seq2.push_back(dist(ctx2.utilities().rng())); + } + + EXPECT_EQ(seq1, seq2); +} + +TEST(UtilitiesRngTest, DifferentSeedsYieldDifferentSequences) { + ThreadData thrInst{}; + ThreadData* thr = &thrInst; + + SolverConfig cfgA{}; cfgA.rngSeed = 42ULL; + SolverConfig cfgB{}; cfgB.rngSeed = 43ULL; + + SolverContext ctxA(thr, cfgA); + SolverContext ctxB(thr, cfgB); + + std::uniform_int_distribution dist(0, 1000000); + // Compare first few numbers; it's extremely likely to be different. + int a0 = dist(ctxA.utilities().rng()); + int b0 = dist(ctxB.utilities().rng()); + // It's possible but astronomically unlikely they match; still assert inequality. + EXPECT_NE(a0, b0); +}