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,579 @@
/*
* mock_arduino.cpp -- Implementation of the Arduino API mock.
*
* This file provides function bodies for all Arduino core functions
* and the test control API. Linked into the test executable via cmake.
*
* Generated by Anvil -- https://github.com/nexus-workshops/anvil
*/
#include "mock_arduino.h"
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cctype>
#include <sstream>
// ============================================================================
// Global state
// ============================================================================
MockArduinoState _mock_arduino;
MockSerial Serial;
// Wire mock (defined in Wire.h shim)
#include "Wire.h"
MockWire Wire;
// SPI mock (defined in SPI.h shim)
#include "SPI.h"
MockSPI SPI;
// ============================================================================
// Test control API
// ============================================================================
void mock_arduino_reset() {
memset(_mock_arduino.pin_modes, 0, sizeof(_mock_arduino.pin_modes));
memset(_mock_arduino.pin_digital, 0, sizeof(_mock_arduino.pin_digital));
memset(_mock_arduino.pin_analog, 0, sizeof(_mock_arduino.pin_analog));
memset(_mock_arduino.pin_pwm, 0, sizeof(_mock_arduino.pin_pwm));
_mock_arduino.millis_value = 0;
_mock_arduino.micros_value = 0;
_mock_arduino.serial_output.clear();
_mock_arduino.serial_input.clear();
_mock_arduino.serial_baud = 0;
_mock_arduino.serial_begun = false;
for (int i = 0; i < 8; ++i) {
_mock_arduino.interrupts[i].callback = nullptr;
_mock_arduino.interrupts[i].mode = 0;
_mock_arduino.interrupts[i].attached = false;
}
_mock_arduino.interrupts_enabled = true;
_mock_arduino.gpio_log.clear();
_mock_arduino.delay_log.clear();
}
void mock_arduino_advance_millis(unsigned long ms) {
_mock_arduino.millis_value += ms;
_mock_arduino.micros_value += ms * 1000;
}
void mock_arduino_set_millis(unsigned long ms) {
_mock_arduino.millis_value = ms;
_mock_arduino.micros_value = ms * 1000;
}
void mock_arduino_set_digital(uint8_t pin, int value) {
if (pin < MOCK_ARDUINO_MAX_PINS) {
_mock_arduino.pin_digital[pin] = value;
}
}
void mock_arduino_set_analog(uint8_t pin, int value) {
if (pin < MOCK_ARDUINO_MAX_PINS) {
_mock_arduino.pin_analog[pin] = value;
}
}
int mock_arduino_get_digital(uint8_t pin) {
if (pin < MOCK_ARDUINO_MAX_PINS) return _mock_arduino.pin_digital[pin];
return LOW;
}
int mock_arduino_get_pwm(uint8_t pin) {
if (pin < MOCK_ARDUINO_MAX_PINS) return _mock_arduino.pin_pwm[pin];
return 0;
}
uint8_t mock_arduino_get_pin_mode(uint8_t pin) {
if (pin < MOCK_ARDUINO_MAX_PINS) return _mock_arduino.pin_modes[pin];
return 0;
}
const std::string& mock_arduino_serial_output() {
return _mock_arduino.serial_output;
}
void mock_arduino_inject_serial(const std::string& data) {
for (char c : data) {
_mock_arduino.serial_input.push_back(static_cast<uint8_t>(c));
}
}
void mock_arduino_clear_serial() {
_mock_arduino.serial_output.clear();
_mock_arduino.serial_input.clear();
}
const std::vector<MockArduinoState::GpioEvent>& mock_arduino_gpio_log() {
return _mock_arduino.gpio_log;
}
void mock_arduino_clear_gpio_log() {
_mock_arduino.gpio_log.clear();
}
int mock_arduino_count_writes(uint8_t pin, int value) {
int count = 0;
for (const auto& e : _mock_arduino.gpio_log) {
if (e.pin == pin && e.value == value && !e.is_analog) ++count;
}
return count;
}
// ============================================================================
// Arduino digital I/O
// ============================================================================
void pinMode(uint8_t pin, uint8_t mode) {
if (pin < MOCK_ARDUINO_MAX_PINS) {
_mock_arduino.pin_modes[pin] = mode;
if (mode == INPUT_PULLUP) {
_mock_arduino.pin_digital[pin] = HIGH;
}
}
}
void digitalWrite(uint8_t pin, uint8_t val) {
if (pin < MOCK_ARDUINO_MAX_PINS) {
_mock_arduino.pin_digital[pin] = val;
_mock_arduino.gpio_log.push_back({
_mock_arduino.millis_value, pin, val, false
});
}
}
int digitalRead(uint8_t pin) {
if (pin < MOCK_ARDUINO_MAX_PINS) return _mock_arduino.pin_digital[pin];
return LOW;
}
// ============================================================================
// Arduino analog I/O
// ============================================================================
int analogRead(uint8_t pin) {
if (pin < MOCK_ARDUINO_MAX_PINS) return _mock_arduino.pin_analog[pin];
return 0;
}
void analogWrite(uint8_t pin, int val) {
if (pin < MOCK_ARDUINO_MAX_PINS) {
_mock_arduino.pin_pwm[pin] = val;
_mock_arduino.pin_digital[pin] = (val > 0) ? HIGH : LOW;
_mock_arduino.gpio_log.push_back({
_mock_arduino.millis_value, pin, val, true
});
}
}
// ============================================================================
// Arduino timing
// ============================================================================
unsigned long millis() {
return _mock_arduino.millis_value;
}
unsigned long micros() {
return _mock_arduino.micros_value;
}
void delay(unsigned long ms) {
_mock_arduino.delay_log.push_back(ms);
_mock_arduino.millis_value += ms;
_mock_arduino.micros_value += ms * 1000;
}
void delayMicroseconds(unsigned long us) {
_mock_arduino.micros_value += us;
}
// ============================================================================
// Arduino math helpers
// ============================================================================
long map(long value, long fromLow, long fromHigh, long toLow, long toHigh) {
if (fromHigh == fromLow) return toLow;
return (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow;
}
long constrain(long value, long low, long high) {
if (value < low) return low;
if (value > high) return high;
return value;
}
static unsigned long _random_seed = 1;
long random(long max) {
if (max <= 0) return 0;
_random_seed = _random_seed * 1103515245 + 12345;
return (long)((_random_seed >> 16) % (unsigned long)max);
}
long random(long min, long max) {
if (max <= min) return min;
return random(max - min) + min;
}
void randomSeed(unsigned long seed) {
_random_seed = seed;
}
// ============================================================================
// Interrupts
// ============================================================================
void attachInterrupt(uint8_t interrupt, void (*callback)(), int mode) {
if (interrupt < 8) {
_mock_arduino.interrupts[interrupt].callback = callback;
_mock_arduino.interrupts[interrupt].mode = mode;
_mock_arduino.interrupts[interrupt].attached = true;
}
}
void detachInterrupt(uint8_t interrupt) {
if (interrupt < 8) {
_mock_arduino.interrupts[interrupt].callback = nullptr;
_mock_arduino.interrupts[interrupt].attached = false;
}
}
void noInterrupts() {
_mock_arduino.interrupts_enabled = false;
}
void interrupts() {
_mock_arduino.interrupts_enabled = true;
}
uint8_t digitalPinToInterrupt(uint8_t pin) {
// Uno/Nano: pin 2 = INT0, pin 3 = INT1
switch (pin) {
case 2: return 0;
case 3: return 1;
default: return 255; // NOT_AN_INTERRUPT
}
}
// ============================================================================
// MockSerial implementation
// ============================================================================
void MockSerial::begin(unsigned long baud) {
_mock_arduino.serial_baud = baud;
_mock_arduino.serial_begun = true;
}
void MockSerial::end() {
_mock_arduino.serial_begun = false;
}
size_t MockSerial::print(const char* str) {
if (!str) return 0;
_mock_arduino.serial_output += str;
return strlen(str);
}
size_t MockSerial::print(char c) {
_mock_arduino.serial_output += c;
return 1;
}
static std::string long_to_string(long val, int base) {
if (base == 16) {
char buf[32];
snprintf(buf, sizeof(buf), "%lx", val);
return buf;
}
if (base == 8) {
char buf[32];
snprintf(buf, sizeof(buf), "%lo", val);
return buf;
}
if (base == 2) {
if (val == 0) return "0";
std::string result;
unsigned long v = (unsigned long)val;
while (v > 0) {
result = (char)('0' + (v & 1)) + result;
v >>= 1;
}
return result;
}
return std::to_string(val);
}
static std::string ulong_to_string(unsigned long val, int base) {
return long_to_string((long)val, base);
}
size_t MockSerial::print(int val, int base) {
std::string s = long_to_string(val, base);
_mock_arduino.serial_output += s;
return s.size();
}
size_t MockSerial::print(unsigned int val, int base) {
std::string s = ulong_to_string(val, base);
_mock_arduino.serial_output += s;
return s.size();
}
size_t MockSerial::print(long val, int base) {
std::string s = long_to_string(val, base);
_mock_arduino.serial_output += s;
return s.size();
}
size_t MockSerial::print(unsigned long val, int base) {
std::string s = ulong_to_string(val, base);
_mock_arduino.serial_output += s;
return s.size();
}
size_t MockSerial::print(double val, int decimals) {
char buf[64];
snprintf(buf, sizeof(buf), "%.*f", decimals, val);
_mock_arduino.serial_output += buf;
return strlen(buf);
}
size_t MockSerial::print(const std::string& str) {
_mock_arduino.serial_output += str;
return str.size();
}
size_t MockSerial::println(const char* str) {
size_t n = print(str);
_mock_arduino.serial_output += "\n";
return n + 1;
}
size_t MockSerial::println(char c) {
size_t n = print(c);
_mock_arduino.serial_output += "\n";
return n + 1;
}
size_t MockSerial::println(int val, int base) {
size_t n = print(val, base);
_mock_arduino.serial_output += "\n";
return n + 1;
}
size_t MockSerial::println(unsigned int val, int base) {
size_t n = print(val, base);
_mock_arduino.serial_output += "\n";
return n + 1;
}
size_t MockSerial::println(long val, int base) {
size_t n = print(val, base);
_mock_arduino.serial_output += "\n";
return n + 1;
}
size_t MockSerial::println(unsigned long val, int base) {
size_t n = print(val, base);
_mock_arduino.serial_output += "\n";
return n + 1;
}
size_t MockSerial::println(double val, int decimals) {
size_t n = print(val, decimals);
_mock_arduino.serial_output += "\n";
return n + 1;
}
size_t MockSerial::println(const std::string& str) {
size_t n = print(str);
_mock_arduino.serial_output += "\n";
return n + 1;
}
size_t MockSerial::write(uint8_t b) {
_mock_arduino.serial_output += static_cast<char>(b);
return 1;
}
size_t MockSerial::write(const uint8_t* buf, size_t len) {
for (size_t i = 0; i < len; ++i) {
_mock_arduino.serial_output += static_cast<char>(buf[i]);
}
return len;
}
int MockSerial::available() {
return static_cast<int>(_mock_arduino.serial_input.size());
}
int MockSerial::read() {
if (_mock_arduino.serial_input.empty()) return -1;
int c = _mock_arduino.serial_input.front();
_mock_arduino.serial_input.erase(_mock_arduino.serial_input.begin());
return c;
}
int MockSerial::peek() {
if (_mock_arduino.serial_input.empty()) return -1;
return _mock_arduino.serial_input.front();
}
void MockSerial::flush() {
// No-op in mock (real Arduino waits for TX buffer to drain)
}
// ============================================================================
// String class implementation
// ============================================================================
String::String(int val, int base) {
data_ = long_to_string(val, base);
}
String::String(unsigned int val, int base) {
data_ = ulong_to_string(val, base);
}
String::String(long val, int base) {
data_ = long_to_string(val, base);
}
String::String(unsigned long val, int base) {
data_ = ulong_to_string(val, base);
}
String::String(double val, int decimals) {
char buf[64];
snprintf(buf, sizeof(buf), "%.*f", decimals, val);
data_ = buf;
}
char String::charAt(unsigned int index) const {
if (index < data_.size()) return data_[index];
return 0;
}
void String::setCharAt(unsigned int index, char c) {
if (index < data_.size()) data_[index] = c;
}
bool String::equalsIgnoreCase(const String& other) const {
if (data_.size() != other.data_.size()) return false;
for (size_t i = 0; i < data_.size(); ++i) {
if (tolower((unsigned char)data_[i]) !=
tolower((unsigned char)other.data_[i])) return false;
}
return true;
}
int String::compareTo(const String& other) const {
return data_.compare(other.data_);
}
int String::indexOf(char ch, unsigned int fromIndex) const {
auto pos = data_.find(ch, fromIndex);
return (pos == std::string::npos) ? -1 : (int)pos;
}
int String::indexOf(const String& str, unsigned int fromIndex) const {
auto pos = data_.find(str.data_, fromIndex);
return (pos == std::string::npos) ? -1 : (int)pos;
}
int String::lastIndexOf(char ch) const {
auto pos = data_.rfind(ch);
return (pos == std::string::npos) ? -1 : (int)pos;
}
String String::substring(unsigned int from, unsigned int to) const {
if (from >= data_.size()) return String();
if (to > data_.size()) to = (unsigned int)data_.size();
return String(data_.substr(from, to - from));
}
void String::toLowerCase() {
for (char& c : data_) c = (char)tolower((unsigned char)c);
}
void String::toUpperCase() {
for (char& c : data_) c = (char)toupper((unsigned char)c);
}
void String::trim() {
size_t start = data_.find_first_not_of(" \t\r\n");
size_t end = data_.find_last_not_of(" \t\r\n");
if (start == std::string::npos) {
data_.clear();
} else {
data_ = data_.substr(start, end - start + 1);
}
}
void String::replace(const String& from, const String& to) {
size_t pos = 0;
while ((pos = data_.find(from.data_, pos)) != std::string::npos) {
data_.replace(pos, from.data_.size(), to.data_);
pos += to.data_.size();
}
}
void String::remove(unsigned int index, unsigned int count) {
if (index < data_.size()) {
data_.erase(index, count);
}
}
long String::toInt() const {
return atol(data_.c_str());
}
float String::toFloat() const {
return (float)atof(data_.c_str());
}
double String::toDouble() const {
return atof(data_.c_str());
}
String& String::concat(const String& other) {
data_ += other.data_;
return *this;
}
String& String::concat(const char* s) {
if (s) data_ += s;
return *this;
}
String& String::concat(char c) {
data_ += c;
return *this;
}
String& String::concat(int val) {
data_ += std::to_string(val);
return *this;
}
String& String::concat(unsigned long val) {
data_ += std::to_string(val);
return *this;
}
String String::operator+(const String& rhs) const {
return String(data_ + rhs.data_);
}
char String::operator[](unsigned int index) const {
if (index < data_.size()) return data_[index];
return 0;
}
char& String::operator[](unsigned int index) {
return data_[index];
}