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:
579
templates/basic/test/mocks/mock_arduino.cpp
Normal file
579
templates/basic/test/mocks/mock_arduino.cpp
Normal 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];
|
||||
}
|
||||
Reference in New Issue
Block a user