#include #include "hal.h" #include "sim_hal.h" #include "blink_app.h" // ============================================================================ // System Tests -- exercise full app behavior against simulated hardware // ============================================================================ class BlinkAppSystemTest : public ::testing::Test { protected: SimHal sim_; }; // ----------------------------------------------------------------------- // Basic blink behavior // ----------------------------------------------------------------------- TEST_F(BlinkAppSystemTest, LedBlinksAtSlowRate) { BlinkApp app(&sim_); app.begin(); // Run for 2 seconds, calling update every 10ms (like a real loop) for (int i = 0; i < 200; ++i) { sim_.advanceMillis(10); app.update(); } // At 500ms intervals over 2000ms, we expect ~4 toggles // (0->500: toggle, 500->1000: toggle, 1000->1500: toggle, 1500->2000: toggle) int highs = sim_.countWrites(LED_BUILTIN, HIGH); int lows = sim_.countWrites(LED_BUILTIN, LOW); EXPECT_GE(highs, 2); EXPECT_GE(lows, 2); EXPECT_LE(highs + lows, 6); // should not over-toggle } TEST_F(BlinkAppSystemTest, LedBlinksAtFastRateAfterButtonPress) { BlinkApp app(&sim_, LED_BUILTIN, 2); app.begin(); // Button starts HIGH (INPUT_PULLUP) EXPECT_EQ(sim_.getPin(2), HIGH); // Run 1 second in slow mode for (int i = 0; i < 100; ++i) { sim_.advanceMillis(10); app.update(); } int slow_toggles = sim_.gpioLog().size(); // Press button sim_.setPin(2, LOW); sim_.advanceMillis(10); app.update(); EXPECT_TRUE(app.fastMode()); // Release button sim_.setPin(2, HIGH); sim_.advanceMillis(10); app.update(); // Clear log and run another second in fast mode sim_.clearGpioLog(); for (int i = 0; i < 100; ++i) { sim_.advanceMillis(10); app.update(); } int fast_toggles = sim_.gpioLog().size(); // Fast mode (125ms) should produce roughly 4x more toggles than slow (500ms) EXPECT_GT(fast_toggles, slow_toggles * 2); } // ----------------------------------------------------------------------- // Button edge detection // ----------------------------------------------------------------------- TEST_F(BlinkAppSystemTest, ButtonDebounceOnlyTriggersOnFallingEdge) { BlinkApp app(&sim_, LED_BUILTIN, 2); app.begin(); // Rapid button noise: HIGH-LOW-HIGH-LOW-HIGH uint8_t sequence[] = {HIGH, LOW, HIGH, LOW, HIGH}; int mode_changes = 0; bool was_fast = false; for (uint8_t val : sequence) { sim_.setPin(2, val); sim_.advanceMillis(10); app.update(); if (app.fastMode() != was_fast) { mode_changes++; was_fast = app.fastMode(); } } // Each HIGH->LOW transition is a falling edge, so 2 edges // (positions 0->1 and 2->3), toggling fast->slow->fast or slow->fast->slow EXPECT_EQ(mode_changes, 2); } TEST_F(BlinkAppSystemTest, ButtonHeldDoesNotRepeatToggle) { BlinkApp app(&sim_, LED_BUILTIN, 2); app.begin(); // Press and hold for 50 update cycles sim_.setPin(2, LOW); for (int i = 0; i < 50; ++i) { sim_.advanceMillis(10); app.update(); } // Should have toggled exactly once (to fast mode) EXPECT_TRUE(app.fastMode()); // Release and hold released for 50 cycles sim_.setPin(2, HIGH); for (int i = 0; i < 50; ++i) { sim_.advanceMillis(10); app.update(); } // Still fast -- release alone should not toggle EXPECT_TRUE(app.fastMode()); } // ----------------------------------------------------------------------- // Serial output verification // ----------------------------------------------------------------------- TEST_F(BlinkAppSystemTest, PrintsStartupMessage) { BlinkApp app(&sim_); app.begin(); EXPECT_NE(sim_.serialOutput().find("BlinkApp started"), std::string::npos); } TEST_F(BlinkAppSystemTest, PrintsModeChangeMessages) { BlinkApp app(&sim_, LED_BUILTIN, 2); app.begin(); sim_.clearSerialOutput(); // Press button sim_.setPin(2, LOW); sim_.advanceMillis(10); app.update(); EXPECT_NE(sim_.serialOutput().find("FAST"), std::string::npos); sim_.setPin(2, HIGH); sim_.advanceMillis(10); app.update(); sim_.clearSerialOutput(); // Press again sim_.setPin(2, LOW); sim_.advanceMillis(10); app.update(); EXPECT_NE(sim_.serialOutput().find("SLOW"), std::string::npos); } // ----------------------------------------------------------------------- // GPIO log / timing verification // ----------------------------------------------------------------------- TEST_F(BlinkAppSystemTest, LedToggleTimingIsCorrect) { BlinkApp app(&sim_); app.begin(); // Run for 3 seconds at 1ms resolution for (int i = 0; i < 3000; ++i) { sim_.advanceMillis(1); app.update(); } const auto& log = sim_.gpioLog(); ASSERT_GE(log.size(), 4u); // Check intervals between consecutive toggles on pin 13 for (size_t i = 1; i < log.size(); ++i) { if (log[i].pin == LED_BUILTIN && log[i - 1].pin == LED_BUILTIN) { unsigned long delta = log[i].timestamp_ms - log[i - 1].timestamp_ms; // Should be approximately 500ms (+/- 10ms for loop granularity) EXPECT_NEAR(delta, 500, 10) << "Toggle " << i << " at t=" << log[i].timestamp_ms; } } } TEST_F(BlinkAppSystemTest, PinModesAreConfiguredCorrectly) { BlinkApp app(&sim_, LED_BUILTIN, 2); app.begin(); EXPECT_EQ(sim_.getPinMode(LED_BUILTIN), OUTPUT); EXPECT_EQ(sim_.getPinMode(2), INPUT_PULLUP); }