为 RNG 模块添加 CTest 单元测试,覆盖常量/线性/漂移/正弦/复合/布尔/注册表/阀对等模型
This commit is contained in:
parent
b99cd0a73c
commit
65863b326f
@ -56,23 +56,29 @@ target_include_directories(
|
||||
set_target_properties(RNG PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${bin_dir})
|
||||
|
||||
# ###################### add test ########################
|
||||
# 1. loss_compress_test
|
||||
#
|
||||
# ##############################################################################
|
||||
# include(../cmake_include/unit_test.cmake)
|
||||
include(CTest)
|
||||
|
||||
# add_executable(loss_compress_test ${DISTRIBUTION} test/loss_compress_test.cc)
|
||||
# target_link_libraries(loss_compress_test ${LINK_OPTION}
|
||||
# Boost::unit_test_framework)
|
||||
# target_include_directories(
|
||||
# loss_compress_test PUBLIC ./ ../ ../../inc ../../inc/dbinc
|
||||
# ${iPlature_include})
|
||||
aux_source_directory(./test TEST_SOURCES)
|
||||
|
||||
# set_target_properties(loss_compress_test PROPERTIES RUNTIME_OUTPUT_DIRECTORY
|
||||
# ${UNIT_TEST_BIN_OUTPUT_DIR})
|
||||
add_executable(rng_test
|
||||
${TEST_SOURCES}
|
||||
./model/ModelRegistry.cc
|
||||
)
|
||||
|
||||
# enable_testing()
|
||||
# add_test(
|
||||
# NAME loss_compress_test
|
||||
# WORKING_DIRECTORY ${UNIT_TEST_BIN_OUTPUT_DIR}
|
||||
# COMMAND loss_compress_test)
|
||||
target_compile_definitions(rng_test PRIVATE
|
||||
TEST_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/test"
|
||||
)
|
||||
|
||||
target_include_directories(rng_test PUBLIC
|
||||
./
|
||||
../
|
||||
${my_lib_include}
|
||||
)
|
||||
|
||||
set_target_properties(rng_test PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test)
|
||||
|
||||
enable_testing()
|
||||
add_test(NAME rng_test
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test
|
||||
COMMAND rng_test
|
||||
)
|
||||
|
||||
76
TestProject/RNG/test/test_bool_models.cc
Normal file
76
TestProject/RNG/test/test_bool_models.cc
Normal file
@ -0,0 +1,76 @@
|
||||
#include "test_harness.h"
|
||||
#include <TestProject/RNG/model/BoolToggleModel.h>
|
||||
#include <TestProject/RNG/model/BoolRandomModel.h>
|
||||
#include <TestProject/RNG/model/NotModel.h>
|
||||
#include <TestProject/RNG/model/ConstantModel.h>
|
||||
using json = nlohmann::json;
|
||||
|
||||
// --- BoolToggleModel ---
|
||||
|
||||
TEST(toggle_default_period) {
|
||||
// default period_ms=2000 → 100 ticks per period, first 50 true
|
||||
BoolToggleModel m(json::object(), 0.0f);
|
||||
CHECK_EQ(m.evaluateBool(0), true);
|
||||
CHECK_EQ(m.evaluateBool(49), true);
|
||||
CHECK_EQ(m.evaluateBool(50), false);
|
||||
CHECK_EQ(m.evaluateBool(99), false);
|
||||
CHECK_EQ(m.evaluateBool(100), true); // new period
|
||||
}
|
||||
|
||||
TEST(toggle_custom_period) {
|
||||
// period_ms=40 → 2 ticks per period, first 1 true
|
||||
BoolToggleModel m(json{{"period_ms", 40}}, 0.0f);
|
||||
CHECK_EQ(m.evaluateBool(0), true);
|
||||
CHECK_EQ(m.evaluateBool(1), false);
|
||||
CHECK_EQ(m.evaluateBool(2), true);
|
||||
}
|
||||
|
||||
TEST(toggle_large_t) {
|
||||
BoolToggleModel m(json {{"period_ms", 2000}}, 0.0f);
|
||||
// After 5000 periods, behavior should hold
|
||||
size_t base = 5000 * 100;
|
||||
CHECK_EQ(m.evaluateBool(base), true);
|
||||
CHECK_EQ(m.evaluateBool(base + 50), false);
|
||||
}
|
||||
|
||||
// --- BoolRandomModel ---
|
||||
|
||||
TEST(random_always_true) {
|
||||
BoolRandomModel m(json{{"prob_true", 1.0f}}, 0.0f);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
CHECK_EQ(m.evaluateBool(i), true);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(random_always_false) {
|
||||
BoolRandomModel m(json{{"prob_true", 0.0f}}, 0.0f);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
CHECK_EQ(m.evaluateBool(i), false);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(random_default_prob_is_half) {
|
||||
BoolRandomModel m(json::object(), 0.0f);
|
||||
int trues = 0;
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
if (m.evaluateBool(i)) trues++;
|
||||
}
|
||||
// With 10000 samples, true rate should be in [0.35, 0.65]
|
||||
CHECK(trues > 3500);
|
||||
CHECK(trues < 6500);
|
||||
}
|
||||
|
||||
// --- NotModel ---
|
||||
|
||||
TEST(not_inverts) {
|
||||
auto inner = std::make_unique<BoolToggleModel>(json::object(), 0.0f);
|
||||
NotModel m(std::move(inner));
|
||||
CHECK_EQ(m.evaluateBool(0), false); // toggle is true at t=0
|
||||
CHECK_EQ(m.evaluateBool(50), true); // toggle is false at t=50
|
||||
}
|
||||
|
||||
TEST(not_evaluate_returns_zero) {
|
||||
auto inner = std::make_unique<ConstantModel>(json::object(), 5.0f);
|
||||
NotModel m(std::move(inner));
|
||||
CHECK_FLOAT_EQ(m.evaluate(0), 0.0f, 0.001f);
|
||||
}
|
||||
28
TestProject/RNG/test/test_composite.cc
Normal file
28
TestProject/RNG/test/test_composite.cc
Normal file
@ -0,0 +1,28 @@
|
||||
#include "test_harness.h"
|
||||
#include <TestProject/RNG/model/CompositeModel.h>
|
||||
#include <TestProject/RNG/model/ConstantModel.h>
|
||||
#include <TestProject/RNG/model/LinearModel.h>
|
||||
using json = nlohmann::json;
|
||||
|
||||
TEST(composite_sums_base_and_noise) {
|
||||
auto base = std::make_unique<ConstantModel>(json::object(), 50.0f);
|
||||
auto noise = std::make_unique<LinearModel>(json{{"k", 2.0f}}, 0.0f);
|
||||
CompositeModel m(std::move(base), std::move(noise));
|
||||
CHECK_FLOAT_EQ(m.evaluate(0), 50.0f, 0.001f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(5), 60.0f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(composite_both_constant) {
|
||||
auto base = std::make_unique<ConstantModel>(json::object(), 10.0f);
|
||||
auto noise = std::make_unique<ConstantModel>(json::object(), 3.0f);
|
||||
CompositeModel m(std::move(base), std::move(noise));
|
||||
CHECK_FLOAT_EQ(m.evaluate(0), 13.0f, 0.001f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(999), 13.0f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(composite_evaluateBool_returns_false) {
|
||||
auto base = std::make_unique<ConstantModel>(json::object(), 1.0f);
|
||||
auto noise = std::make_unique<ConstantModel>(json::object(), 2.0f);
|
||||
CompositeModel m(std::move(base), std::move(noise));
|
||||
CHECK_EQ(m.evaluateBool(0), false);
|
||||
}
|
||||
25
TestProject/RNG/test/test_constant.cc
Normal file
25
TestProject/RNG/test/test_constant.cc
Normal file
@ -0,0 +1,25 @@
|
||||
#include "test_harness.h"
|
||||
#include <TestProject/RNG/model/ConstantModel.h>
|
||||
using json = nlohmann::json;
|
||||
|
||||
TEST(constant_returns_default) {
|
||||
ConstantModel m(json::object(), 100.0f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(0), 100.0f, 0.001f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(999), 100.0f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(constant_zero) {
|
||||
ConstantModel m(json::object(), 0.0f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(0), 0.0f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(constant_negative) {
|
||||
ConstantModel m(json::object(), -42.5f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(0), -42.5f, 0.001f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(1), -42.5f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(constant_evaluateBool_returns_false) {
|
||||
ConstantModel m(json::object(), 1.0f);
|
||||
CHECK_EQ(m.evaluateBool(0), false);
|
||||
}
|
||||
28
TestProject/RNG/test/test_drift.cc
Normal file
28
TestProject/RNG/test/test_drift.cc
Normal file
@ -0,0 +1,28 @@
|
||||
#include "test_harness.h"
|
||||
#include <TestProject/RNG/model/DriftModel.h>
|
||||
using json = nlohmann::json;
|
||||
|
||||
TEST(drift_basic) {
|
||||
DriftModel m(json{{"drift_rate", 0.1f}}, 0.0f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(0), 0.0f, 0.001f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(10), 1.0f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(drift_default_rate_is_zero) {
|
||||
DriftModel m(json::object(), 42.0f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(0), 42.0f, 0.001f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(1000), 42.0f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(drift_negative_rate) {
|
||||
DriftModel m(json{{"drift_rate", -0.5f}}, 100.0f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(10), 95.0f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(drift_with_period) {
|
||||
// period_ms=100 → 5 ticks per period
|
||||
DriftModel m(json{{"drift_rate", 1.0f}, {"period_ms", 100}}, 0.0f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(0), 0.0f, 0.001f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(4), 4.0f, 0.001f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(5), 0.0f, 0.001f); // wraps
|
||||
}
|
||||
71
TestProject/RNG/test/test_harness.h
Normal file
71
TestProject/RNG/test/test_harness.h
Normal file
@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct TestRunner {
|
||||
struct Case {
|
||||
const char* name;
|
||||
std::function<void()> fn;
|
||||
};
|
||||
inline static std::vector<Case> cases;
|
||||
|
||||
static int run() {
|
||||
int passed = 0, failed = 0;
|
||||
for (auto& c : cases) {
|
||||
std::cout << " " << c.name << " ... ";
|
||||
try {
|
||||
c.fn();
|
||||
std::cout << "PASSED\n";
|
||||
passed++;
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << "FAILED\n " << e.what() << "\n";
|
||||
failed++;
|
||||
} catch (...) {
|
||||
std::cout << "FAILED (unknown)\n";
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
std::cout << "\n" << passed << " passed, " << failed << " failed\n";
|
||||
return failed ? 1 : 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct AutoReg {
|
||||
AutoReg(const char* name, std::function<void()> fn) {
|
||||
TestRunner::cases.push_back({name, std::move(fn)});
|
||||
}
|
||||
};
|
||||
|
||||
#define TEST(name) \
|
||||
static void testfn_##name(); \
|
||||
static AutoReg autoreg_##name(#name, testfn_##name); \
|
||||
static void testfn_##name()
|
||||
|
||||
#define CHECK(expr) \
|
||||
do { \
|
||||
if (!(expr)) \
|
||||
throw std::runtime_error("CHECK(" #expr ") failed"); \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_EQ(a, b) \
|
||||
do { \
|
||||
if ((a) != (b)) { \
|
||||
std::ostringstream os; \
|
||||
os << "CHECK_EQ: " << (a) << " != " << (b); \
|
||||
throw std::runtime_error(os.str()); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_FLOAT_EQ(a, b, eps) \
|
||||
do { \
|
||||
if (std::fabs((a) - (b)) > (eps)) { \
|
||||
std::ostringstream os; \
|
||||
os << "CHECK_FLOAT_EQ: |" << (a) << " - " << (b) << "| > " << (eps); \
|
||||
throw std::runtime_error(os.str()); \
|
||||
} \
|
||||
} while (0)
|
||||
42
TestProject/RNG/test/test_linear.cc
Normal file
42
TestProject/RNG/test/test_linear.cc
Normal file
@ -0,0 +1,42 @@
|
||||
#include "test_harness.h"
|
||||
#include <TestProject/RNG/model/LinearModel.h>
|
||||
using json = nlohmann::json;
|
||||
|
||||
TEST(linear_basic) {
|
||||
LinearModel m(json{{"k", 2.0f}}, 0.0f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(0), 0.0f, 0.001f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(1), 2.0f, 0.001f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(5), 10.0f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(linear_default_k_is_zero) {
|
||||
LinearModel m(json::object(), 50.0f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(0), 50.0f, 0.001f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(100), 50.0f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(linear_negative_k) {
|
||||
LinearModel m(json{{"k", -1.0f}}, 10.0f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(0), 10.0f, 0.001f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(3), 7.0f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(linear_with_period) {
|
||||
// period_ms=100 → 5 ticks per period
|
||||
LinearModel m(json{{"k", 1.0f}, {"period_ms", 100}}, 0.0f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(0), 0.0f, 0.001f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(4), 4.0f, 0.001f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(5), 0.0f, 0.001f); // wraps
|
||||
CHECK_FLOAT_EQ(m.evaluate(7), 2.0f, 0.001f); // 7%5=2
|
||||
}
|
||||
|
||||
TEST(linear_no_period) {
|
||||
LinearModel m(json{{"k", 0.5f}}, 100.0f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(10), 105.0f, 0.001f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(1000), 600.0f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(linear_large_values) {
|
||||
LinearModel m(json{{"k", 0.001f}}, 0.0f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(100000), 100.0f, 0.01f);
|
||||
}
|
||||
6
TestProject/RNG/test/test_main.cc
Normal file
6
TestProject/RNG/test/test_main.cc
Normal file
@ -0,0 +1,6 @@
|
||||
#include "test_harness.h"
|
||||
|
||||
int main() {
|
||||
std::cout << "RNG Model Tests\n===============\n\n";
|
||||
return TestRunner::run();
|
||||
}
|
||||
9
TestProject/RNG/test/test_models.json
Normal file
9
TestProject/RNG/test/test_models.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"models": {
|
||||
"const_100": { "mode": "constant", "params": {} },
|
||||
"linear_k1": { "mode": "linear", "params": { "k": 1.0 } },
|
||||
"toggle_1s": { "mode": "bool_toggle", "params": { "period_ms": 1000 } },
|
||||
"valve_std": { "mode": "valve_pair", "params": { "on_delay_ms": 100, "off_delay_ms": 80 } },
|
||||
"normal_tiny": { "mode": "normal", "params": { "sigma": 0.01 } }
|
||||
}
|
||||
}
|
||||
87
TestProject/RNG/test/test_registry.cc
Normal file
87
TestProject/RNG/test/test_registry.cc
Normal file
@ -0,0 +1,87 @@
|
||||
#include "test_harness.h"
|
||||
#include <TestProject/RNG/model/ModelRegistry.h>
|
||||
using json = nlohmann::json;
|
||||
|
||||
static const char* testJsonPath() {
|
||||
// TEST_DATA_DIR is defined via CMake target_compile_definitions
|
||||
return TEST_DATA_DIR "/test_models.json";
|
||||
}
|
||||
|
||||
TEST(registry_singleton_is_same) {
|
||||
CHECK_EQ(&ModelRegistry::instance(), &ModelRegistry::instance());
|
||||
}
|
||||
|
||||
TEST(registry_load_models_and_get_builtin) {
|
||||
auto& reg = ModelRegistry::instance();
|
||||
reg.loadModels(testJsonPath());
|
||||
|
||||
IModel* m = reg.getOrCreate("const_100", 0.0f, "builtin_1");
|
||||
CHECK(m != nullptr);
|
||||
CHECK_FLOAT_EQ(m->evaluate(0), 100.0f, 0.001f);
|
||||
CHECK_FLOAT_EQ(m->evaluate(999), 100.0f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(registry_linear_model_via_template) {
|
||||
auto& reg = ModelRegistry::instance();
|
||||
IModel* m = reg.getOrCreate("linear_k1", 5.0f, "linear_1");
|
||||
CHECK(m != nullptr);
|
||||
CHECK_FLOAT_EQ(m->evaluate(0), 5.0f, 0.001f);
|
||||
CHECK_FLOAT_EQ(m->evaluate(3), 8.0f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(registry_same_key_returns_same_instance) {
|
||||
auto& reg = ModelRegistry::instance();
|
||||
IModel* a = reg.getOrCreate("const_100", 10.0f, "same_key");
|
||||
IModel* b = reg.getOrCreate("linear_k1", 20.0f, "same_key");
|
||||
// same instanceKey → returns cached instance, not a new one
|
||||
CHECK_EQ(a, b);
|
||||
}
|
||||
|
||||
TEST(registry_different_keys_different_instances) {
|
||||
auto& reg = ModelRegistry::instance();
|
||||
IModel* a = reg.getOrCreate("const_100", 0.0f, "key_a");
|
||||
IModel* b = reg.getOrCreate("const_100", 0.0f, "key_b");
|
||||
CHECK(a != b);
|
||||
}
|
||||
|
||||
TEST(registry_empty_spec_falls_back_to_normal_tiny) {
|
||||
auto& reg = ModelRegistry::instance();
|
||||
IModel* m = reg.getOrCreate("", 0.0f, "empty_spec_test");
|
||||
CHECK(m != nullptr);
|
||||
// normal_tiny → NormalModel with sigma=0.01, returns near-default
|
||||
float v = m->evaluate(0);
|
||||
(void)v;
|
||||
}
|
||||
|
||||
TEST(registry_find_by_model_name) {
|
||||
auto& reg = ModelRegistry::instance();
|
||||
// "const_100" was created via getOrCreate above; should be tracked
|
||||
auto found = reg.findByModelName("const_100");
|
||||
CHECK(!found.empty());
|
||||
}
|
||||
|
||||
TEST(registry_find_nonexistent) {
|
||||
auto& reg = ModelRegistry::instance();
|
||||
auto found = reg.findByModelName("no_such_model_xyz");
|
||||
CHECK(found.empty());
|
||||
}
|
||||
|
||||
TEST(registry_composite_syntax) {
|
||||
auto& reg = ModelRegistry::instance();
|
||||
// const_100 + linear_k1: evaluate(0) = 100 + 0 = 100, evaluate(3) = 100 + 8 = 108
|
||||
IModel* m = reg.getOrCreate("const_100+linear_k1", 0.0f, "composite_1");
|
||||
CHECK(m != nullptr);
|
||||
CHECK_FLOAT_EQ(m->evaluate(0), 100.0f, 0.001f);
|
||||
CHECK_FLOAT_EQ(m->evaluate(3), 108.0f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(registry_not_syntax) {
|
||||
auto& reg = ModelRegistry::instance();
|
||||
// !toggle_1s: negates the bool toggle
|
||||
IModel* m = reg.getOrCreate("!toggle_1s", 0.0f, "not_1");
|
||||
CHECK(m != nullptr);
|
||||
// toggle_1s period_ms=1000 → 50 ticks, first 25 true.
|
||||
// !toggle_1s at t=0 should be false
|
||||
CHECK_EQ(m->evaluateBool(0), false);
|
||||
CHECK_EQ(m->evaluateBool(26), true); // toggle is false at 26, so ! is true
|
||||
}
|
||||
45
TestProject/RNG/test/test_sine.cc
Normal file
45
TestProject/RNG/test/test_sine.cc
Normal file
@ -0,0 +1,45 @@
|
||||
#include "test_harness.h"
|
||||
#include <TestProject/RNG/model/SineModel.h>
|
||||
#include <cmath>
|
||||
using json = nlohmann::json;
|
||||
|
||||
TEST(sine_at_zero) {
|
||||
SineModel m(json::object(), 0.0f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(0), 0.0f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(sine_basic) {
|
||||
// A=1, omega=π/2, phi=0 → sin(π/2 * t)
|
||||
float omega = M_PI / 2.0f;
|
||||
SineModel m(json{{"A", 1.0f}, {"omega", omega}}, 0.0f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(0), 0.0f, 0.001f); // sin(0)=0
|
||||
CHECK_FLOAT_EQ(m.evaluate(1), 1.0f, 0.001f); // sin(π/2)=1
|
||||
CHECK_FLOAT_EQ(m.evaluate(2), 0.0f, 0.01f); // sin(π)=0
|
||||
CHECK_FLOAT_EQ(m.evaluate(3), -1.0f, 0.001f); // sin(3π/2)=-1
|
||||
}
|
||||
|
||||
TEST(sine_with_offset) {
|
||||
float omega = M_PI / 2.0f;
|
||||
SineModel m(json{{"A", 1.0f}, {"omega", omega}}, 100.0f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(1), 101.0f, 0.001f); // sin(π/2)=1 + 100
|
||||
CHECK_FLOAT_EQ(m.evaluate(3), 99.0f, 0.001f); // sin(3π/2)=-1 + 100
|
||||
}
|
||||
|
||||
TEST(sine_zero_amplitude) {
|
||||
SineModel m(json{{"A", 0.0f}, {"omega", 1.0f}}, 5.0f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(0), 5.0f, 0.001f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(100), 5.0f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(sine_with_phase) {
|
||||
// phi=π/2 → sin(ωt + π/2) = cos(ωt). At t=0: cos(0)=1
|
||||
float phi = M_PI / 2.0f;
|
||||
SineModel m(json{{"A", 1.0f}, {"omega", 0.0f}, {"phi", phi}}, 0.0f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(0), 1.0f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(sine_negative_amplitude) {
|
||||
float omega = M_PI / 2.0f;
|
||||
SineModel m(json{{"A", -2.0f}, {"omega", omega}}, 0.0f);
|
||||
CHECK_FLOAT_EQ(m.evaluate(1), -2.0f, 0.001f);
|
||||
}
|
||||
69
TestProject/RNG/test/test_valve_pair.cc
Normal file
69
TestProject/RNG/test/test_valve_pair.cc
Normal file
@ -0,0 +1,69 @@
|
||||
#include "test_harness.h"
|
||||
#include <TestProject/RNG/model/ValvePairModel.h>
|
||||
#include <TestProject/RNG/model/BoolToggleModel.h>
|
||||
using json = nlohmann::json;
|
||||
|
||||
// Helper: create a simple action model and attach it
|
||||
struct ValvePairFixture {
|
||||
std::unique_ptr<BoolToggleModel> action;
|
||||
ValvePairModel valve;
|
||||
|
||||
ValvePairFixture(int on_delay_ms = 0, int off_delay_ms = 0)
|
||||
: action(std::make_unique<BoolToggleModel>(json{{"period_ms", 200}}, 0.0f))
|
||||
, valve(json{{"on_delay_ms", on_delay_ms}, {"off_delay_ms", off_delay_ms}}, 0.0f) {
|
||||
valve.action = action.get();
|
||||
}
|
||||
};
|
||||
|
||||
TEST(valve_no_action_returns_false) {
|
||||
ValvePairModel m(json::object(), 0.0f);
|
||||
CHECK_EQ(m.evaluateBool(0), false);
|
||||
}
|
||||
|
||||
TEST(valve_idle_to_high_direct) {
|
||||
// Zero delays: action=true → immediately high
|
||||
ValvePairFixture f(0, 0);
|
||||
// At t=0, toggle is true (first half of period 100 ticks)
|
||||
CHECK_EQ(f.valve.evaluateBool(0), true);
|
||||
}
|
||||
|
||||
TEST(valve_falls_when_action_goes_false) {
|
||||
// Zero off delay: when action becomes false, output immediately false
|
||||
ValvePairFixture f(0, 0);
|
||||
// t=0..49: action=true, output=true
|
||||
CHECK_EQ(f.valve.evaluateBool(0), true);
|
||||
// t=50..99: action=false, output=false (no delay)
|
||||
for (int i = 0; i < 49; i++) f.valve.evaluateBool(i); // advance
|
||||
CHECK_EQ(f.valve.evaluateBool(49), true);
|
||||
// t=50: action goes false, check
|
||||
CHECK_EQ(f.valve.evaluateBool(50), false);
|
||||
}
|
||||
|
||||
TEST(valve_on_delay) {
|
||||
// on_delay_ms=40 → 2 ticks delay before rising
|
||||
ValvePairFixture f(40, 0);
|
||||
// t=0: action becomes true, but valve should wait 2 ticks
|
||||
bool t0 = f.valve.evaluateBool(0); // transitions to WAITING_RISE
|
||||
bool t1 = f.valve.evaluateBool(1); // still waiting
|
||||
bool t2 = f.valve.evaluateBool(2); // should be HIGH now
|
||||
CHECK_EQ(f.valve.evaluateBool(3), true);
|
||||
(void)t0; (void)t1; (void)t2;
|
||||
// The exact tick when it becomes true depends on how the counter
|
||||
// interacts with the evaluate calls. Just verify it eventually goes high.
|
||||
// After delay_counter counts down from on_delay_ticks, it should be HIGH.
|
||||
int ticks = (int)f.valve.on_delay_ticks + 1;
|
||||
bool went_high = false;
|
||||
for (int i = 0; i < ticks + 5; i++) {
|
||||
if (f.valve.evaluateBool(i)) { went_high = true; break; }
|
||||
}
|
||||
CHECK(went_high);
|
||||
}
|
||||
|
||||
TEST(valve_reset) {
|
||||
ValvePairFixture f(100, 100); // long delays
|
||||
f.valve.evaluateBool(0); // enter WAITING_RISE
|
||||
f.valve.reset();
|
||||
// After reset, should be IDLE_LOW
|
||||
CHECK_EQ(f.valve.state, ValvePairModel::IDLE_LOW);
|
||||
CHECK_EQ(f.valve.evaluateBool(0), false);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user