Arduino CLI build system with HAL-based test architecture
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
This commit is contained in:
201
test/test_blink_system.cpp
Normal file
201
test/test_blink_system.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
#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);
|
||||
}
|
||||
Reference in New Issue
Block a user