Add mock Arduino for x86_64 host-side testing

Complete Arduino API mock (mock_arduino.h/cpp) enabling application
code to compile and run on PC without hardware. Includes MockSerial,
String class, GPIO/analog/timing/interrupt mocks with state tracking
and test control API.

- Arduino.h, Wire.h, SPI.h shims intercept includes in test builds
- System test template (test_system.cpp) using SimHal
- CMakeLists.txt builds mock_arduino as static lib, links both suites
- Root test.sh/test.bat with --unit/--system/--clean/--verbose flags
- test.bat auto-detects MSVC via vswhere + vcvarsall.bat
- Doctor reports nuanced compiler status (on PATH vs installed)
- Refresh pulls mock infrastructure into existing projects
- 15 tests passing: 7 unit (MockHal) + 8 system (SimHal)
This commit is contained in:
Eric Ratliff
2026-02-20 08:21:11 -06:00
parent 1ae136530f
commit aa1e9d5043
13 changed files with 1831 additions and 16 deletions

View File

@@ -0,0 +1,148 @@
#include <gtest/gtest.h>
#include "mock_arduino.h"
#include "hal.h"
#include "sim_hal.h"
#include "{{PROJECT_NAME}}_app.h"
// ============================================================================
// System Tests -- exercise full application logic against simulated hardware
//
// Unlike unit tests (which verify exact call sequences), system tests
// run the real application code against SimHal and inspect observable
// outputs: pin states, serial messages, timing behavior.
// ============================================================================
class BlinkAppSystemTest : public ::testing::Test {
protected:
void SetUp() override {
mock_arduino_reset();
sim_.setMillis(0);
}
SimHal sim_;
};
TEST_F(BlinkAppSystemTest, SetupConfiguresPins) {
BlinkApp app(&sim_);
app.begin();
EXPECT_EQ(sim_.getPinMode(13), OUTPUT);
EXPECT_EQ(sim_.getPinMode(2), INPUT_PULLUP);
}
TEST_F(BlinkAppSystemTest, LedTogglesAtCorrectInterval) {
BlinkApp app(&sim_);
app.begin();
// LED should be off initially
EXPECT_EQ(sim_.getPin(13), LOW);
// Advance past one slow interval (500ms)
sim_.advanceMillis(500);
app.update();
EXPECT_EQ(sim_.getPin(13), HIGH);
// Advance another interval
sim_.advanceMillis(500);
app.update();
EXPECT_EQ(sim_.getPin(13), LOW);
}
TEST_F(BlinkAppSystemTest, LedDoesNotToggleTooEarly) {
BlinkApp app(&sim_);
app.begin();
sim_.advanceMillis(499);
app.update();
EXPECT_EQ(sim_.getPin(13), LOW);
}
TEST_F(BlinkAppSystemTest, ButtonTogglesFastMode) {
BlinkApp app(&sim_, 13, 2);
sim_.setPin(2, HIGH); // Button not pressed (INPUT_PULLUP)
app.begin();
EXPECT_FALSE(app.fastMode());
// Press button
sim_.setPin(2, LOW);
app.update();
EXPECT_TRUE(app.fastMode());
EXPECT_EQ(app.interval(), BlinkApp::FAST_INTERVAL_MS);
// Release and press again -> back to slow
sim_.setPin(2, HIGH);
app.update();
sim_.setPin(2, LOW);
app.update();
EXPECT_FALSE(app.fastMode());
EXPECT_EQ(app.interval(), BlinkApp::SLOW_INTERVAL_MS);
}
TEST_F(BlinkAppSystemTest, FastModeBlinksFaster) {
BlinkApp app(&sim_, 13, 2);
sim_.setPin(2, HIGH);
app.begin();
// Switch to fast mode
sim_.setPin(2, LOW);
app.update();
sim_.setPin(2, HIGH);
app.update();
EXPECT_TRUE(app.fastMode());
// Clear log, then count toggles over 1 second
sim_.clearGpioLog();
for (int i = 0; i < 8; ++i) {
sim_.advanceMillis(125);
app.update();
}
// In 1 second at 125ms intervals, we expect 8 toggles
int high_count = sim_.countWrites(13, HIGH);
int low_count = sim_.countWrites(13, LOW);
EXPECT_EQ(high_count + low_count, 8);
}
TEST_F(BlinkAppSystemTest, SerialOutputOnStartup) {
BlinkApp app(&sim_);
app.begin();
std::string output = sim_.serialOutput();
EXPECT_NE(output.find("BlinkApp started"), std::string::npos);
}
TEST_F(BlinkAppSystemTest, SerialOutputOnModeChange) {
BlinkApp app(&sim_, 13, 2);
sim_.setPin(2, HIGH);
app.begin();
sim_.clearSerialOutput();
// Press button
sim_.setPin(2, LOW);
app.update();
std::string output = sim_.serialOutput();
EXPECT_NE(output.find("FAST"), std::string::npos);
}
// ============================================================================
// Timing accuracy test -- verifies blink over a longer duration
// ============================================================================
TEST_F(BlinkAppSystemTest, TenSecondBlinkCount) {
BlinkApp app(&sim_);
app.begin();
sim_.clearGpioLog();
// Run for 10 seconds at 10ms resolution
for (int i = 0; i < 1000; ++i) {
sim_.advanceMillis(10);
app.update();
}
// At 500ms intervals over 10s, expect 20 toggles
int toggles = sim_.countWrites(13, HIGH) + sim_.countWrites(13, LOW);
EXPECT_EQ(toggles, 20);
}