Use a deterministic clock for input expression tests

This commit is contained in:
patchzyy
2026-09-07 23:17:14 +02:00
parent e05e15bce5
commit d4c53dad4b
3 changed files with 36 additions and 15 deletions
+19 -6
View File
@@ -9,11 +9,24 @@
#include <unordered_map>
namespace InputExpr {
#ifdef MKW_INPUT_EXPR_TEST_CLOCK
// Only the standalone test target supplies this clock; runtime builds use the
// steady clock directly, with no mutable override or extra runtime state.
std::chrono::steady_clock::time_point TestClockNow();
#endif
namespace {
using Clock = std::chrono::steady_clock;
using FSec = std::chrono::duration<double>;
static Clock::time_point Now() {
#ifdef MKW_INPUT_EXPR_TEST_CLOCK
return TestClockNow();
#else
return Clock::now();
#endif
}
enum class Kind {
Literal, Input, Not, Add, Sub, Mul, Div, And, Or, Xor,
Greater, Less, Equal,
@@ -56,7 +69,7 @@ struct Node {
mutable bool state = false;
mutable unsigned taps = 0;
mutable double value = 0.0;
mutable Clock::time_point mark = Clock::now();
mutable Clock::time_point mark = Now();
mutable bool marked = false;
};
@@ -400,7 +413,7 @@ double Eval(const Node& node, const InputSource& source) {
return std::copysign(std::max(0.0, std::abs(v) - dz) / (1.0 - dz), v);
}
case Kind::FnTimer: {
const auto now = Clock::now();
const auto now = Now();
if (!node.marked) {
node.mark = now;
node.marked = true;
@@ -431,7 +444,7 @@ double Eval(const Node& node, const InputSource& source) {
return node.state ? 1.0 : 0.0;
}
case Kind::FnHold: {
const auto now = Clock::now();
const auto now = Now();
if (!node.marked) {
node.mark = now;
node.marked = true;
@@ -448,7 +461,7 @@ double Eval(const Node& node, const InputSource& source) {
return node.state ? 1.0 : 0.0;
}
case Kind::FnTap: {
const auto now = Clock::now();
const auto now = Now();
if (!node.marked) {
node.mark = now;
node.marked = true;
@@ -480,7 +493,7 @@ double Eval(const Node& node, const InputSource& source) {
return desired == node.taps ? 1.0 : 0.0;
}
case Kind::FnPulse: {
const auto now = Clock::now();
const auto now = Now();
const double input = Arg(node, 0, source);
if (input < kConditionThreshold) {
node.released = true;
@@ -502,7 +515,7 @@ double Eval(const Node& node, const InputSource& source) {
return node.state ? 1.0 : 0.0;
}
case Kind::FnSmooth: {
const auto now = Clock::now();
const auto now = Now();
if (!node.marked) {
node.mark = now;
node.marked = true;
+1
View File
@@ -36,6 +36,7 @@ target_include_directories(mkw_nand_move_tests PRIVATE "${runtime_root}/src/hle/
mkw_add_test(mkw_nand_settings_tests nand_settings_tests.cpp)
mkw_add_test(mkw_sc_serial_tests sc_serial_tests.cpp)
mkw_add_test(mkw_input_expr_tests test_expr.cpp "${runtime_root}/src/input_expr.cpp")
target_compile_definitions(mkw_input_expr_tests PRIVATE MKW_INPUT_EXPR_TEST_CLOCK)
if(WIN32)
mkw_add_test(mkw_windows_host_context_tests host_context_tests.cpp
+16 -9
View File
@@ -8,7 +8,14 @@
#include <cstdio>
#include <map>
#include <string>
#include <thread>
#ifndef MKW_INPUT_EXPR_TEST_CLOCK
#error "Build this test through CMake so the deterministic clock is enabled"
#endif
static std::chrono::steady_clock::time_point g_now{};
namespace InputExpr {
std::chrono::steady_clock::time_point TestClockNow() { return g_now; }
}
static int g_failures = 0;
static std::map<std::string, double> g_inputs;
@@ -41,7 +48,7 @@ static bool Pressed(const InputExpr::Expression& e) {
return e.Evaluate(Source()) > InputExpr::kConditionThreshold;
}
static void Sleep(int ms) { std::this_thread::sleep_for(std::chrono::milliseconds(ms)); }
static void AdvanceTime(int ms) { g_now += std::chrono::milliseconds(ms); }
int main() {
std::printf("Dolphin expression engine\n");
@@ -82,7 +89,7 @@ int main() {
auto hold = Compile("hold(`H`, 0.05)");
g_inputs["H"] = 1.0;
Check(!Pressed(hold), "hold not satisfied immediately");
Sleep(70);
AdvanceTime(70);
Check(Pressed(hold), "hold satisfied after the interval");
g_inputs["H"] = 0.0;
Check(!Pressed(hold), "hold clears on release");
@@ -93,7 +100,7 @@ int main() {
pulse.Evaluate(Source());
g_inputs["P"] = 1.0;
Check(Pressed(pulse), "pulse fires on rising edge");
Sleep(80);
AdvanceTime(80);
Check(!Pressed(pulse), "pulse expires");
// The timing-window idiom seen in shared Dolphin configs.
@@ -102,9 +109,9 @@ int main() {
window.Evaluate(Source());
g_inputs["W"] = 1.0;
Check(!Pressed(window), "window closed before its start");
Sleep(90);
AdvanceTime(90);
Check(Pressed(window), "window open between the two pulses");
Sleep(90);
AdvanceTime(90);
Check(!Pressed(window), "window closed after its end");
// timer ramps 0..1 and wraps, so a threshold turns it into a square wave.
@@ -114,7 +121,7 @@ int main() {
int low = 0;
for (int i = 0; i < 40; ++i) {
(Pressed(timer) ? high : low)++;
Sleep(5);
AdvanceTime(5);
}
Check(high > 5 && low > 5, "timer alternates high and low");
@@ -130,7 +137,7 @@ int main() {
high = low = 0;
for (int i = 0; i < 60; ++i) {
(Pressed(dolphinLine) ? high : low)++;
Sleep(2);
AdvanceTime(2);
}
Check(high > 5 && low > 5, "LB alternates via timer(0.01)");
@@ -192,7 +199,7 @@ int main() {
auto sm = Compile("smooth(`A`, 0)");
g_inputs["A"] = 1.0;
sm.Evaluate(Source());
Sleep(5);
AdvanceTime(5);
Check(std::isfinite(sm.Evaluate(Source())), "smooth with a zero rate stays finite");
}