New button template
Some checks failed
CI / Test (Linux) (push) Has been cancelled
CI / Test (Windows MSVC) (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Format (push) Has been cancelled

This commit is contained in:
Eric Ratliff
2026-02-22 17:06:02 -06:00
parent 578b5f02c0
commit e12608370a
9 changed files with 898 additions and 5 deletions

View File

@@ -0,0 +1,283 @@
/*
* 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 <gtest/gtest.h>
#include <gmock/gmock.h>
#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<MockHal> 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);
}

View File

@@ -0,0 +1,31 @@
/*
* test_system.cpp -- Your system tests go here.
*
* This file is YOURS. Anvil will never overwrite it.
* The button example tests are in test_button_app.cpp.
*
* System tests use SimHal and ButtonSim to exercise real application
* logic against simulated hardware. See test_button_app.cpp for examples.
*/
#include <gtest/gtest.h>
#include "mock_arduino.h"
#include "hal.h"
#include "sim_hal.h"
#include "button_sim.h"
#include "{{PROJECT_NAME}}_app.h"
// Example: add your own system tests below
// TEST(MySystemTests, DescribeWhatItTests) {
// mock_arduino_reset();
// SimHal sim;
// ButtonSim btn(0); // 0 = no bounce
//
// ButtonApp app(&sim, &btn);
// app.begin();
//
// btn.press();
// app.update();
// EXPECT_EQ(app.pressCount(), 1);
// }

View File

@@ -0,0 +1,34 @@
/*
* test_unit.cpp -- Your unit tests go here.
*
* This file is YOURS. Anvil will never overwrite it.
* The button example tests are in test_button_app.cpp.
*
* Unit tests use MockHal and ButtonMock to verify exact behavior
* without real hardware. See test_button_app.cpp for examples.
*/
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "hal.h"
#include "mock_hal.h"
#include "button_mock.h"
#include "{{PROJECT_NAME}}_app.h"
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::Return;
// Example: add your own tests below
// TEST(MyTests, DescribeWhatItTests) {
// ::testing::NiceMock<MockHal> mock;
// ButtonMock btn;
//
// ButtonApp app(&mock, &btn);
// app.begin();
//
// btn.setPressed(true);
// app.update();
// EXPECT_EQ(app.pressCount(), 1);
// }