Build and upload tool (arduino-build.sh): - Compile, upload, and monitor via arduino-cli - Device discovery with USB ID identification (--devices) - Persistent reconnecting serial monitor (--watch) - Split compile/upload workflow (--verify, --upload-only) - First-time setup wizard (--setup) - Comprehensive --help with troubleshooting and RedBoard specs Testable application architecture: - Hardware abstraction layer (lib/hal/) decouples logic from Arduino API - Google Mock HAL for unit tests (exact call verification) - Simulated HAL for system tests (GPIO state, virtual clock, I2C devices) - Example I2C temperature sensor simulator (TMP102) - Host-side test suite via CMake + Google Test (21 tests) Example sketch: - blink/ -- LED blink with button-controlled speed, wired through HAL
202 lines
5.6 KiB
C++
202 lines
5.6 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#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);
|
|
}
|