/* * test_button_app.cpp -- Button application example tests. * * THIS FILE IS MANAGED BY ANVIL and will be updated by `anvil refresh`. * Do not edit -- put your own tests in test_unit.cpp and test_system.cpp. */ #include #include #include "mock_arduino.h" #include "hal.h" #include "mock_hal.h" #include "sim_hal.h" #include "button_mock.h" #include "button_sim.h" #include "{{PROJECT_NAME}}_app.h" using ::testing::_; using ::testing::AnyNumber; using ::testing::Return; using ::testing::HasSubstr; // ============================================================================ // Unit Tests -- verify ButtonApp behavior with mock button // ============================================================================ class ButtonUnitTest : public ::testing::Test { protected: void SetUp() override { ON_CALL(mock_, millis()).WillByDefault(Return(0)); EXPECT_CALL(mock_, serialBegin(_)).Times(AnyNumber()); EXPECT_CALL(mock_, serialPrint(_)).Times(AnyNumber()); EXPECT_CALL(mock_, serialPrintln(_)).Times(AnyNumber()); EXPECT_CALL(mock_, millis()).Times(AnyNumber()); EXPECT_CALL(mock_, pinMode(_, _)).Times(AnyNumber()); } ::testing::NiceMock mock_; ButtonMock btn_; }; TEST_F(ButtonUnitTest, BeginPrintsReadyMessage) { ButtonApp app(&mock_, &btn_); EXPECT_CALL(mock_, serialBegin(115200)).Times(1); EXPECT_CALL(mock_, serialPrintln(HasSubstr("ButtonApp ready"))).Times(1); app.begin(); } TEST_F(ButtonUnitTest, BeginSetsPinMode) { ButtonApp app(&mock_, &btn_); EXPECT_CALL(mock_, pinMode(2, INPUT_PULLUP)).Times(1); app.begin(); } TEST_F(ButtonUnitTest, NoPressNoCount) { ButtonApp app(&mock_, &btn_); app.begin(); // Button not pressed, update should not increment count btn_.setPressed(false); app.update(); app.update(); app.update(); EXPECT_EQ(app.pressCount(), 0); } TEST_F(ButtonUnitTest, SinglePressIncrementsCount) { ButtonApp app(&mock_, &btn_); btn_.setPressed(false); app.begin(); btn_.setPressed(true); app.update(); EXPECT_EQ(app.pressCount(), 1); } TEST_F(ButtonUnitTest, HoldDoesNotRepeat) { ButtonApp app(&mock_, &btn_); btn_.setPressed(false); app.begin(); btn_.setPressed(true); app.update(); // rising edge -> count = 1 app.update(); // still pressed -> no increment app.update(); // still pressed -> no increment EXPECT_EQ(app.pressCount(), 1); } TEST_F(ButtonUnitTest, ReleaseAndPressAgain) { ButtonApp app(&mock_, &btn_); btn_.setPressed(false); app.begin(); // First press btn_.setPressed(true); app.update(); EXPECT_EQ(app.pressCount(), 1); // Release btn_.setPressed(false); app.update(); // Second press btn_.setPressed(true); app.update(); EXPECT_EQ(app.pressCount(), 2); } TEST_F(ButtonUnitTest, PrintsMessageOnPress) { ButtonApp app(&mock_, &btn_); btn_.setPressed(false); app.begin(); EXPECT_CALL(mock_, serialPrintln(HasSubstr("Button pressed!"))).Times(1); btn_.setPressed(true); app.update(); } TEST_F(ButtonUnitTest, PrintsCountInMessage) { ButtonApp app(&mock_, &btn_); btn_.setPressed(false); app.begin(); EXPECT_CALL(mock_, serialPrintln(HasSubstr("count: 1"))).Times(1); btn_.setPressed(true); app.update(); } TEST_F(ButtonUnitTest, DoesNotPrintOnRelease) { ButtonApp app(&mock_, &btn_); btn_.setPressed(false); app.begin(); btn_.setPressed(true); app.update(); // Release should not trigger another print EXPECT_CALL(mock_, serialPrintln(HasSubstr("Button pressed!"))).Times(0); btn_.setPressed(false); app.update(); } TEST_F(ButtonUnitTest, TenPresses) { ButtonApp app(&mock_, &btn_); btn_.setPressed(false); app.begin(); for (int i = 0; i < 10; ++i) { btn_.setPressed(true); app.update(); btn_.setPressed(false); app.update(); } EXPECT_EQ(app.pressCount(), 10); } TEST_F(ButtonUnitTest, StartupWithButtonAlreadyPressed) { // If the button is held during startup, begin() reads it as pressed. // The first update() should NOT trigger a press (no rising edge). btn_.setPressed(true); ButtonApp app(&mock_, &btn_); app.begin(); app.update(); // still pressed, no edge EXPECT_EQ(app.pressCount(), 0); // Release and press again -- THIS should trigger btn_.setPressed(false); app.update(); btn_.setPressed(true); app.update(); EXPECT_EQ(app.pressCount(), 1); } // ============================================================================ // System Tests -- exercise ButtonApp with simulated button and hardware // ============================================================================ class ButtonSystemTest : public ::testing::Test { protected: void SetUp() override { mock_arduino_reset(); sim_.setMillis(0); } SimHal sim_; ButtonSim btn_{0}; // no bounce for predictable tests }; TEST_F(ButtonSystemTest, StartsAndPrintsToSerial) { ButtonApp app(&sim_, &btn_); app.begin(); std::string output = sim_.serialOutput(); EXPECT_NE(output.find("ButtonApp ready"), std::string::npos); } TEST_F(ButtonSystemTest, PressShowsInSerialOutput) { ButtonApp app(&sim_, &btn_); app.begin(); btn_.press(); app.update(); std::string output = sim_.serialOutput(); EXPECT_NE(output.find("Button pressed!"), std::string::npos); EXPECT_NE(output.find("count: 1"), std::string::npos); } TEST_F(ButtonSystemTest, MultiplePressesCountCorrectly) { ButtonApp app(&sim_, &btn_); app.begin(); for (int i = 0; i < 5; ++i) { btn_.press(); app.update(); btn_.release(); app.update(); } EXPECT_EQ(app.pressCount(), 5); std::string output = sim_.serialOutput(); EXPECT_NE(output.find("count: 5"), std::string::npos); } TEST_F(ButtonSystemTest, HoldOnlyCountsOnce) { ButtonApp app(&sim_, &btn_); app.begin(); btn_.press(); for (int i = 0; i < 100; ++i) { app.update(); } EXPECT_EQ(app.pressCount(), 1); } TEST_F(ButtonSystemTest, RapidPressReleaseCycle) { ButtonApp app(&sim_, &btn_); app.begin(); // Simulate rapid button mashing for (int i = 0; i < 50; ++i) { btn_.press(); app.update(); btn_.release(); app.update(); } EXPECT_EQ(app.pressCount(), 50); } TEST_F(ButtonSystemTest, BouncyButtonWithSettling) { ButtonSim bouncy_btn(5); // 5 reads of bounce bouncy_btn.setSeed(42); ButtonApp app(&sim_, &bouncy_btn); app.begin(); bouncy_btn.press(); // During bounce, we may get spurious edges. // After settling, one press should eventually register. for (int i = 0; i < 20; ++i) { app.update(); } // At least one press should have registered EXPECT_GE(app.pressCount(), 1); }