/* * Button Driver Tests * * Auto-generated by: anvil add button * These tests verify the Button driver mock and simulation without * any hardware. They run alongside your unit and system tests. * * To run: ./test.sh (all tests) * ./test.sh --system (skips these -- use no filter to include) * ./test.sh --unit (skips these -- use no filter to include) */ #include #include "mock_arduino.h" #include "sim_hal.h" #include "button.h" #include "button_digital.h" #include "button_mock.h" #include "button_sim.h" // --------------------------------------------------------------------------- // Mock: basic functionality // --------------------------------------------------------------------------- class ButtonMockTest : public ::testing::Test { protected: void SetUp() override { mock_arduino_reset(); } ButtonMock btn; }; TEST_F(ButtonMockTest, DefaultsToNotPressed) { EXPECT_FALSE(btn.isPressed()); } TEST_F(ButtonMockTest, SetPressedReturnsTrue) { btn.setPressed(true); EXPECT_TRUE(btn.isPressed()); } TEST_F(ButtonMockTest, SetReleasedReturnsFalse) { btn.setPressed(true); btn.setPressed(false); EXPECT_FALSE(btn.isPressed()); } TEST_F(ButtonMockTest, DefaultRawIsHigh) { EXPECT_EQ(btn.readState(), HIGH); } TEST_F(ButtonMockTest, SetRawReturnsExactValue) { btn.setRaw(LOW); EXPECT_EQ(btn.readState(), LOW); } TEST_F(ButtonMockTest, ReadCountTracking) { EXPECT_EQ(btn.readCount(), 0); btn.isPressed(); btn.isPressed(); btn.readState(); EXPECT_EQ(btn.readCount(), 3); } TEST_F(ButtonMockTest, ReadCountReset) { btn.isPressed(); btn.isPressed(); btn.resetReadCount(); EXPECT_EQ(btn.readCount(), 0); } TEST_F(ButtonMockTest, PressAndRawAreIndependent) { // setPressed controls isPressed(), setRaw controls readState() btn.setPressed(true); btn.setRaw(HIGH); EXPECT_TRUE(btn.isPressed()); EXPECT_EQ(btn.readState(), HIGH); } // --------------------------------------------------------------------------- // Digital: real implementation via SimHal // --------------------------------------------------------------------------- class ButtonDigitalTest : public ::testing::Test { protected: void SetUp() override { mock_arduino_reset(); } SimHal hal; }; TEST_F(ButtonDigitalTest, ActiveLowPressedWhenLow) { ButtonDigital btn(&hal, 2, true); // active-low hal.setPin(2, LOW); EXPECT_TRUE(btn.isPressed()); } TEST_F(ButtonDigitalTest, ActiveLowReleasedWhenHigh) { ButtonDigital btn(&hal, 2, true); // active-low hal.setPin(2, HIGH); EXPECT_FALSE(btn.isPressed()); } TEST_F(ButtonDigitalTest, ActiveHighPressedWhenHigh) { ButtonDigital btn(&hal, 2, false); // active-high hal.setPin(2, HIGH); EXPECT_TRUE(btn.isPressed()); } TEST_F(ButtonDigitalTest, ActiveHighReleasedWhenLow) { ButtonDigital btn(&hal, 2, false); // active-high hal.setPin(2, LOW); EXPECT_FALSE(btn.isPressed()); } TEST_F(ButtonDigitalTest, ReadStateReturnsRawPin) { ButtonDigital btn(&hal, 2, true); hal.setPin(2, LOW); EXPECT_EQ(btn.readState(), LOW); hal.setPin(2, HIGH); EXPECT_EQ(btn.readState(), HIGH); } TEST_F(ButtonDigitalTest, DifferentPin) { ButtonDigital btn(&hal, 7, true); hal.setPin(7, LOW); EXPECT_TRUE(btn.isPressed()); } TEST_F(ButtonDigitalTest, DefaultIsActiveLow) { ButtonDigital btn(&hal, 2); // active_low defaults to true hal.setPin(2, LOW); EXPECT_TRUE(btn.isPressed()); } // --------------------------------------------------------------------------- // Simulation: bounce and determinism // --------------------------------------------------------------------------- class ButtonSimTest : public ::testing::Test { protected: void SetUp() override { mock_arduino_reset(); } }; TEST_F(ButtonSimTest, DefaultsToNotPressed) { ButtonSim btn; EXPECT_FALSE(btn.isPressed()); } TEST_F(ButtonSimTest, PressWithNoBounceIsImmediate) { ButtonSim btn(0); // no bounce btn.press(); EXPECT_TRUE(btn.isPressed()); } TEST_F(ButtonSimTest, ReleaseWithNoBounceIsImmediate) { ButtonSim btn(0); btn.press(); btn.release(); EXPECT_FALSE(btn.isPressed()); } TEST_F(ButtonSimTest, BounceSettlesAfterEnoughReads) { ButtonSim btn(5); // 5 bouncy reads btn.press(); // Read through the bounce window for (int i = 0; i < 5; ++i) { btn.isPressed(); // may or may not bounce } // After bounce window, should be stable EXPECT_TRUE(btn.isPressed()); EXPECT_TRUE(btn.isPressed()); EXPECT_TRUE(btn.isPressed()); } TEST_F(ButtonSimTest, ReleaseBouncesSettleToReleased) { ButtonSim btn(3); btn.press(); // Clear press bounce for (int i = 0; i < 10; ++i) btn.isPressed(); btn.release(); // Read through release bounce for (int i = 0; i < 3; ++i) btn.isPressed(); // Settled to released EXPECT_FALSE(btn.isPressed()); } TEST_F(ButtonSimTest, DeterministicWithSameSeed) { ButtonSim s1(5); ButtonSim s2(5); s1.setSeed(99); s2.setSeed(99); s1.press(); s2.press(); for (int i = 0; i < 10; ++i) { EXPECT_EQ(s1.isPressed(), s2.isPressed()) << "Reading " << i << " should match with same seed"; } } TEST_F(ButtonSimTest, DifferentSeedsDifferentBounce) { ButtonSim s1(10); ButtonSim s2(10); s1.setSeed(1); s2.setSeed(2); s1.press(); s2.press(); // At least one reading during bounce should differ bool any_differ = false; for (int i = 0; i < 10; ++i) { if (s1.isPressed() != s2.isPressed()) { any_differ = true; break; } } EXPECT_TRUE(any_differ); } TEST_F(ButtonSimTest, NoBounceZeroReads) { ButtonSim btn(0); btn.press(); // With zero bounce, every read should be stable for (int i = 0; i < 20; ++i) { EXPECT_TRUE(btn.isPressed()) << "Read " << i << " should be pressed"; } } TEST_F(ButtonSimTest, ReadStateMatchesIsPressed) { ButtonSim btn(0); btn.press(); // Active-low convention: pressed -> LOW EXPECT_EQ(btn.readState(), LOW); btn.release(); EXPECT_EQ(btn.readState(), HIGH); } TEST_F(ButtonSimTest, DoublePressSameState) { ButtonSim btn(0); btn.press(); btn.press(); // second press is a no-op EXPECT_TRUE(btn.isPressed()); } TEST_F(ButtonSimTest, DoubleReleaseSameState) { ButtonSim btn(0); btn.release(); // already released, no-op EXPECT_FALSE(btn.isPressed()); } // --------------------------------------------------------------------------- // Polymorphism: all impls work through Button pointer // --------------------------------------------------------------------------- TEST(ButtonPolymorphismTest, AllImplsWorkThroughBasePointer) { mock_arduino_reset(); SimHal hal; hal.setPin(2, LOW); // pressed for active-low ButtonDigital digital_btn(&hal, 2, true); ButtonMock mock_btn; ButtonSim sim_btn(0); mock_btn.setPressed(true); sim_btn.press(); Button* buttons[] = { &digital_btn, &mock_btn, &sim_btn }; for (auto* b : buttons) { bool pressed = b->isPressed(); int raw = b->readState(); EXPECT_TRUE(pressed); (void)raw; // just verify it compiles and runs } }