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)
353 lines
11 KiB
C++
353 lines
11 KiB
C++
#ifndef MOCK_ARDUINO_H
|
|
#define MOCK_ARDUINO_H
|
|
|
|
/*
|
|
* mock_arduino.h -- Arduino API mock for host-side (x86_64) testing.
|
|
*
|
|
* Provides the core Arduino vocabulary (types, constants, functions,
|
|
* Serial) so that application code compiles and runs on a PC without
|
|
* any AVR toolchain. All state is captured in a global MockArduino
|
|
* instance that tests can inspect and manipulate.
|
|
*
|
|
* This file replaces <Arduino.h> in the test build. The CMakeLists.txt
|
|
* adds the mocks/ directory to the include path, so any code that does
|
|
* #include <Arduino.h> will pick up this file instead.
|
|
*
|
|
* Usage in tests:
|
|
*
|
|
* #include "mock_arduino.h"
|
|
*
|
|
* TEST(MyTest, ReadsAnalogSensor) {
|
|
* mock_arduino_reset();
|
|
* mock_arduino_set_analog(A0, 512);
|
|
* int val = analogRead(A0);
|
|
* EXPECT_EQ(val, 512);
|
|
* }
|
|
*
|
|
* Generated by Anvil -- https://github.com/nexus-workshops/anvil
|
|
*/
|
|
|
|
#include <cstdint>
|
|
#include <cstddef>
|
|
#include <cstring>
|
|
#include <cmath>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <functional>
|
|
|
|
// ============================================================================
|
|
// Arduino types
|
|
// ============================================================================
|
|
|
|
typedef uint8_t byte;
|
|
typedef uint16_t word;
|
|
typedef bool boolean;
|
|
|
|
// ============================================================================
|
|
// Pin mode and logic constants
|
|
// ============================================================================
|
|
|
|
#ifndef INPUT
|
|
#define INPUT 0x0
|
|
#define OUTPUT 0x1
|
|
#define INPUT_PULLUP 0x2
|
|
#endif
|
|
|
|
#ifndef LOW
|
|
#define LOW 0x0
|
|
#define HIGH 0x1
|
|
#endif
|
|
|
|
#ifndef LED_BUILTIN
|
|
#define LED_BUILTIN 13
|
|
#endif
|
|
|
|
// Analog pins (map to digital pin numbers, Uno layout by default)
|
|
#ifndef A0
|
|
#define A0 14
|
|
#define A1 15
|
|
#define A2 16
|
|
#define A3 17
|
|
#define A4 18
|
|
#define A5 19
|
|
#define A6 20
|
|
#define A7 21
|
|
// Extended for Mega
|
|
#define A8 54
|
|
#define A9 55
|
|
#define A10 56
|
|
#define A11 57
|
|
#define A12 58
|
|
#define A13 59
|
|
#define A14 60
|
|
#define A15 61
|
|
#endif
|
|
|
|
// ============================================================================
|
|
// Math constants
|
|
// ============================================================================
|
|
|
|
#ifndef PI
|
|
#define PI 3.14159265358979323846
|
|
#define HALF_PI 1.57079632679489661923
|
|
#define TWO_PI 6.28318530717958647692
|
|
#define DEG_TO_RAD 0.017453292519943295
|
|
#define RAD_TO_DEG 57.29577951308232
|
|
#endif
|
|
|
|
// ============================================================================
|
|
// Interrupt constants
|
|
// ============================================================================
|
|
|
|
#ifndef CHANGE
|
|
#define CHANGE 1
|
|
#define FALLING 2
|
|
#define RISING 3
|
|
#endif
|
|
|
|
// ============================================================================
|
|
// MockArduino -- global state for all Arduino functions
|
|
// ============================================================================
|
|
|
|
static const int MOCK_ARDUINO_MAX_PINS = 70; // Enough for Mega
|
|
|
|
struct MockArduinoState {
|
|
// Pin state
|
|
uint8_t pin_modes[MOCK_ARDUINO_MAX_PINS];
|
|
int pin_digital[MOCK_ARDUINO_MAX_PINS]; // digital values
|
|
int pin_analog[MOCK_ARDUINO_MAX_PINS]; // analog read values (0-1023)
|
|
int pin_pwm[MOCK_ARDUINO_MAX_PINS]; // analogWrite values (0-255)
|
|
|
|
// Timing
|
|
unsigned long millis_value;
|
|
unsigned long micros_value;
|
|
|
|
// Serial capture
|
|
std::string serial_output;
|
|
std::vector<uint8_t> serial_input;
|
|
unsigned long serial_baud;
|
|
bool serial_begun;
|
|
|
|
// Interrupt tracking
|
|
struct InterruptHandler {
|
|
void (*callback)();
|
|
int mode;
|
|
bool attached;
|
|
};
|
|
InterruptHandler interrupts[8]; // INT0-INT7
|
|
bool interrupts_enabled;
|
|
|
|
// GPIO event log
|
|
struct GpioEvent {
|
|
unsigned long timestamp;
|
|
uint8_t pin;
|
|
int value;
|
|
bool is_analog; // true = analogWrite, false = digitalWrite
|
|
};
|
|
std::vector<GpioEvent> gpio_log;
|
|
|
|
// Delay log (to verify timing behavior)
|
|
std::vector<unsigned long> delay_log;
|
|
};
|
|
|
|
// Global state -- tests access this directly
|
|
extern MockArduinoState _mock_arduino;
|
|
|
|
// ============================================================================
|
|
// Test control API -- call from tests to set up / inspect state
|
|
// ============================================================================
|
|
|
|
// Reset all state to defaults
|
|
void mock_arduino_reset();
|
|
|
|
// Clock control
|
|
void mock_arduino_advance_millis(unsigned long ms);
|
|
void mock_arduino_set_millis(unsigned long ms);
|
|
|
|
// Input injection
|
|
void mock_arduino_set_digital(uint8_t pin, int value);
|
|
void mock_arduino_set_analog(uint8_t pin, int value);
|
|
|
|
// Output inspection
|
|
int mock_arduino_get_digital(uint8_t pin);
|
|
int mock_arduino_get_pwm(uint8_t pin);
|
|
uint8_t mock_arduino_get_pin_mode(uint8_t pin);
|
|
|
|
// Serial inspection
|
|
const std::string& mock_arduino_serial_output();
|
|
void mock_arduino_inject_serial(const std::string& data);
|
|
void mock_arduino_clear_serial();
|
|
|
|
// GPIO log
|
|
const std::vector<MockArduinoState::GpioEvent>& mock_arduino_gpio_log();
|
|
void mock_arduino_clear_gpio_log();
|
|
int mock_arduino_count_writes(uint8_t pin, int value);
|
|
|
|
// ============================================================================
|
|
// Arduino core functions -- called by application code
|
|
// ============================================================================
|
|
|
|
// Digital I/O
|
|
void pinMode(uint8_t pin, uint8_t mode);
|
|
void digitalWrite(uint8_t pin, uint8_t val);
|
|
int digitalRead(uint8_t pin);
|
|
|
|
// Analog I/O
|
|
int analogRead(uint8_t pin);
|
|
void analogWrite(uint8_t pin, int val);
|
|
|
|
// Timing
|
|
unsigned long millis();
|
|
unsigned long micros();
|
|
void delay(unsigned long ms);
|
|
void delayMicroseconds(unsigned long us);
|
|
|
|
// Math helpers
|
|
long map(long value, long fromLow, long fromHigh, long toLow, long toHigh);
|
|
long constrain(long value, long low, long high);
|
|
long random(long max);
|
|
long random(long min, long max);
|
|
void randomSeed(unsigned long seed);
|
|
|
|
// Interrupts
|
|
void attachInterrupt(uint8_t interrupt, void (*callback)(), int mode);
|
|
void detachInterrupt(uint8_t interrupt);
|
|
void noInterrupts();
|
|
void interrupts();
|
|
uint8_t digitalPinToInterrupt(uint8_t pin);
|
|
|
|
// Bit manipulation (Arduino built-ins)
|
|
#ifndef bitRead
|
|
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
|
|
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
|
|
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
|
|
#define bitWrite(value, bit, b) ((b) ? bitSet(value, bit) : bitClear(value, bit))
|
|
#define bit(n) (1UL << (n))
|
|
#define lowByte(w) ((uint8_t)((w) & 0xFF))
|
|
#define highByte(w) ((uint8_t)((w) >> 8))
|
|
#endif
|
|
|
|
// ============================================================================
|
|
// MockSerial -- replaces Serial global
|
|
// ============================================================================
|
|
|
|
class MockSerial {
|
|
public:
|
|
void begin(unsigned long baud);
|
|
void end();
|
|
|
|
size_t print(const char* str);
|
|
size_t print(char c);
|
|
size_t print(int val, int base = 10);
|
|
size_t print(unsigned int val, int base = 10);
|
|
size_t print(long val, int base = 10);
|
|
size_t print(unsigned long val, int base = 10);
|
|
size_t print(double val, int decimals = 2);
|
|
size_t print(const std::string& str);
|
|
|
|
size_t println(const char* str = "");
|
|
size_t println(char c);
|
|
size_t println(int val, int base = 10);
|
|
size_t println(unsigned int val, int base = 10);
|
|
size_t println(long val, int base = 10);
|
|
size_t println(unsigned long val, int base = 10);
|
|
size_t println(double val, int decimals = 2);
|
|
size_t println(const std::string& str);
|
|
|
|
size_t write(uint8_t b);
|
|
size_t write(const uint8_t* buf, size_t len);
|
|
|
|
int available();
|
|
int read();
|
|
int peek();
|
|
void flush();
|
|
|
|
explicit operator bool() const { return _mock_arduino.serial_begun; }
|
|
};
|
|
|
|
// Global Serial instance (matches Arduino's global)
|
|
extern MockSerial Serial;
|
|
|
|
// ============================================================================
|
|
// Minimal String class (Arduino-compatible subset)
|
|
// ============================================================================
|
|
|
|
class String {
|
|
public:
|
|
String() : data_() {}
|
|
String(const char* s) : data_(s ? s : "") {}
|
|
String(const std::string& s) : data_(s) {}
|
|
String(char c) : data_(1, c) {}
|
|
String(int val, int base = 10);
|
|
String(unsigned int val, int base = 10);
|
|
String(long val, int base = 10);
|
|
String(unsigned long val, int base = 10);
|
|
String(double val, int decimals = 2);
|
|
|
|
// Access
|
|
unsigned int length() const { return (unsigned int)data_.size(); }
|
|
const char* c_str() const { return data_.c_str(); }
|
|
char charAt(unsigned int index) const;
|
|
void setCharAt(unsigned int index, char c);
|
|
|
|
// Comparison
|
|
bool equals(const String& other) const { return data_ == other.data_; }
|
|
bool equalsIgnoreCase(const String& other) const;
|
|
int compareTo(const String& other) const;
|
|
|
|
// Search
|
|
int indexOf(char ch, unsigned int fromIndex = 0) const;
|
|
int indexOf(const String& str, unsigned int fromIndex = 0) const;
|
|
int lastIndexOf(char ch) const;
|
|
|
|
// Modification
|
|
String substring(unsigned int from, unsigned int to = 0xFFFFFFFF) const;
|
|
void toLowerCase();
|
|
void toUpperCase();
|
|
void trim();
|
|
void replace(const String& from, const String& to);
|
|
void remove(unsigned int index, unsigned int count = 1);
|
|
|
|
// Conversion
|
|
long toInt() const;
|
|
float toFloat() const;
|
|
double toDouble() const;
|
|
|
|
// Concatenation
|
|
String& concat(const String& other);
|
|
String& concat(const char* s);
|
|
String& concat(char c);
|
|
String& concat(int val);
|
|
String& concat(unsigned long val);
|
|
|
|
// Operators
|
|
String& operator+=(const String& rhs) { return concat(rhs); }
|
|
String& operator+=(const char* rhs) { return concat(rhs); }
|
|
String& operator+=(char rhs) { return concat(rhs); }
|
|
String operator+(const String& rhs) const;
|
|
|
|
bool operator==(const String& rhs) const { return data_ == rhs.data_; }
|
|
bool operator==(const char* rhs) const { return data_ == (rhs ? rhs : ""); }
|
|
bool operator!=(const String& rhs) const { return data_ != rhs.data_; }
|
|
bool operator<(const String& rhs) const { return data_ < rhs.data_; }
|
|
|
|
char operator[](unsigned int index) const;
|
|
char& operator[](unsigned int index);
|
|
|
|
// Interop with std::string
|
|
operator std::string() const { return data_; }
|
|
|
|
private:
|
|
std::string data_;
|
|
};
|
|
|
|
// ============================================================================
|
|
// Arduino.h compatibility alias
|
|
// ============================================================================
|
|
|
|
// So that #include <Arduino.h> resolves to this file via include path,
|
|
// we provide this comment as a note. The CMakeLists.txt adds the mocks/
|
|
// directory to the include path, so #include <Arduino.h> becomes:
|
|
// #include "mock_arduino.h" (via the Arduino.h shim in this directory)
|
|
|
|
#endif // MOCK_ARDUINO_H
|