Anvil v1.0.0 -- Arduino build tool with HAL and test scaffolding
Single-binary CLI that scaffolds testable Arduino projects, compiles, uploads, and monitors serial output. Templates embed a hardware abstraction layer, Google Mock infrastructure, and CMake-based host tests so application logic can be verified without hardware. Commands: new, doctor, setup, devices, build, upload, monitor 39 Rust tests (21 unit, 18 integration) Cross-platform: Linux and Windows
This commit is contained in:
45
templates/basic/test/mocks/mock_hal.h
Normal file
45
templates/basic/test/mocks/mock_hal.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef MOCK_HAL_H
|
||||
#define MOCK_HAL_H
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "hal.h"
|
||||
|
||||
/*
|
||||
* StrictMock-friendly HAL mock for unit tests.
|
||||
*
|
||||
* Use this when you want to verify exact call sequences:
|
||||
* EXPECT_CALL(mock, digitalWrite(13, HIGH)).Times(1);
|
||||
*/
|
||||
class MockHal : public Hal {
|
||||
public:
|
||||
// GPIO
|
||||
MOCK_METHOD(void, pinMode, (uint8_t pin, uint8_t mode), (override));
|
||||
MOCK_METHOD(void, digitalWrite, (uint8_t pin, uint8_t value), (override));
|
||||
MOCK_METHOD(uint8_t, digitalRead, (uint8_t pin), (override));
|
||||
MOCK_METHOD(int, analogRead, (uint8_t pin), (override));
|
||||
MOCK_METHOD(void, analogWrite, (uint8_t pin, int value), (override));
|
||||
|
||||
// Timing
|
||||
MOCK_METHOD(unsigned long, millis, (), (override));
|
||||
MOCK_METHOD(unsigned long, micros, (), (override));
|
||||
MOCK_METHOD(void, delay, (unsigned long ms), (override));
|
||||
MOCK_METHOD(void, delayMicroseconds, (unsigned long us), (override));
|
||||
|
||||
// Serial
|
||||
MOCK_METHOD(void, serialBegin, (unsigned long baud), (override));
|
||||
MOCK_METHOD(void, serialPrint, (const char* msg), (override));
|
||||
MOCK_METHOD(void, serialPrintln, (const char* msg), (override));
|
||||
MOCK_METHOD(int, serialAvailable, (), (override));
|
||||
MOCK_METHOD(int, serialRead, (), (override));
|
||||
|
||||
// I2C
|
||||
MOCK_METHOD(void, i2cBegin, (), (override));
|
||||
MOCK_METHOD(void, i2cBeginTransmission, (uint8_t addr), (override));
|
||||
MOCK_METHOD(size_t, i2cWrite, (uint8_t data), (override));
|
||||
MOCK_METHOD(uint8_t, i2cEndTransmission, (), (override));
|
||||
MOCK_METHOD(uint8_t, i2cRequestFrom, (uint8_t addr, uint8_t count), (override));
|
||||
MOCK_METHOD(int, i2cAvailable, (), (override));
|
||||
MOCK_METHOD(int, i2cRead, (), (override));
|
||||
};
|
||||
|
||||
#endif // MOCK_HAL_H
|
||||
256
templates/basic/test/mocks/sim_hal.h
Normal file
256
templates/basic/test/mocks/sim_hal.h
Normal file
@@ -0,0 +1,256 @@
|
||||
#ifndef SIM_HAL_H
|
||||
#define SIM_HAL_H
|
||||
|
||||
#include "hal.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
/*
|
||||
* Simulated HAL for system tests.
|
||||
*
|
||||
* Unlike MockHal (which verifies call expectations), SimHal actually
|
||||
* maintains state: pin values, a virtual clock, serial output capture,
|
||||
* and pluggable I2C device simulators.
|
||||
*
|
||||
* This lets you write system tests that exercise full application logic
|
||||
* against simulated hardware:
|
||||
*
|
||||
* SimHal sim;
|
||||
* BlinkApp app(&sim);
|
||||
* app.begin();
|
||||
*
|
||||
* sim.setPin(2, LOW); // simulate button press
|
||||
* sim.advanceMillis(600); // advance clock
|
||||
* app.update();
|
||||
*
|
||||
* EXPECT_EQ(sim.getPin(13), HIGH); // check LED state
|
||||
*/
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// I2C device simulator interface
|
||||
// --------------------------------------------------------------------
|
||||
class I2cDeviceSim {
|
||||
public:
|
||||
virtual ~I2cDeviceSim() = default;
|
||||
|
||||
// Called when master writes bytes to this device
|
||||
virtual void onReceive(const uint8_t* data, size_t len) = 0;
|
||||
|
||||
// Called when master requests bytes; fill response buffer
|
||||
virtual size_t onRequest(uint8_t* buf, size_t max_len) = 0;
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Simulated HAL
|
||||
// --------------------------------------------------------------------
|
||||
class SimHal : public Hal {
|
||||
public:
|
||||
static const int NUM_PINS = 20; // D0-D13 + A0-A5
|
||||
|
||||
SimHal() : clock_ms_(0), clock_us_(0) {
|
||||
memset(pin_modes_, 0, sizeof(pin_modes_));
|
||||
memset(pin_values_, 0, sizeof(pin_values_));
|
||||
}
|
||||
|
||||
// -- GPIO ---------------------------------------------------------------
|
||||
void pinMode(uint8_t pin, uint8_t mode) override {
|
||||
if (pin < NUM_PINS) {
|
||||
pin_modes_[pin] = mode;
|
||||
// INPUT_PULLUP defaults to HIGH
|
||||
if (mode == INPUT_PULLUP) {
|
||||
pin_values_[pin] = HIGH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void digitalWrite(uint8_t pin, uint8_t value) override {
|
||||
if (pin < NUM_PINS) {
|
||||
pin_values_[pin] = value;
|
||||
gpio_log_.push_back({clock_ms_, pin, value});
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t digitalRead(uint8_t pin) override {
|
||||
if (pin < NUM_PINS) return pin_values_[pin];
|
||||
return LOW;
|
||||
}
|
||||
|
||||
int analogRead(uint8_t pin) override {
|
||||
if (pin < NUM_PINS) return analog_values_[pin];
|
||||
return 0;
|
||||
}
|
||||
|
||||
void analogWrite(uint8_t pin, int value) override {
|
||||
if (pin < NUM_PINS) pin_values_[pin] = (value > 0) ? HIGH : LOW;
|
||||
}
|
||||
|
||||
// -- Timing -------------------------------------------------------------
|
||||
unsigned long millis() override { return clock_ms_; }
|
||||
unsigned long micros() override { return clock_us_; }
|
||||
void delay(unsigned long ms) override { advanceMillis(ms); }
|
||||
void delayMicroseconds(unsigned long us) override { clock_us_ += us; }
|
||||
|
||||
// -- Serial -------------------------------------------------------------
|
||||
void serialBegin(unsigned long baud) override { (void)baud; }
|
||||
void serialPrint(const char* msg) override {
|
||||
serial_output_ += msg;
|
||||
}
|
||||
void serialPrintln(const char* msg) override {
|
||||
serial_output_ += msg;
|
||||
serial_output_ += "\n";
|
||||
}
|
||||
int serialAvailable() override {
|
||||
return static_cast<int>(serial_input_.size());
|
||||
}
|
||||
int serialRead() override {
|
||||
if (serial_input_.empty()) return -1;
|
||||
int c = serial_input_.front();
|
||||
serial_input_.erase(serial_input_.begin());
|
||||
return c;
|
||||
}
|
||||
|
||||
// -- I2C ----------------------------------------------------------------
|
||||
void i2cBegin() override {}
|
||||
|
||||
void i2cBeginTransmission(uint8_t addr) override {
|
||||
i2c_addr_ = addr;
|
||||
i2c_tx_buf_.clear();
|
||||
}
|
||||
|
||||
size_t i2cWrite(uint8_t data) override {
|
||||
i2c_tx_buf_.push_back(data);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t i2cEndTransmission() override {
|
||||
auto it = i2c_devices_.find(i2c_addr_);
|
||||
if (it == i2c_devices_.end()) return 2; // NACK on address
|
||||
it->second->onReceive(i2c_tx_buf_.data(), i2c_tx_buf_.size());
|
||||
return 0; // success
|
||||
}
|
||||
|
||||
uint8_t i2cRequestFrom(uint8_t addr, uint8_t count) override {
|
||||
i2c_rx_buf_.clear();
|
||||
auto it = i2c_devices_.find(addr);
|
||||
if (it == i2c_devices_.end()) return 0;
|
||||
uint8_t tmp[256];
|
||||
size_t n = it->second->onRequest(tmp, count);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
i2c_rx_buf_.push_back(tmp[i]);
|
||||
}
|
||||
return static_cast<uint8_t>(n);
|
||||
}
|
||||
|
||||
int i2cAvailable() override {
|
||||
return static_cast<int>(i2c_rx_buf_.size());
|
||||
}
|
||||
|
||||
int i2cRead() override {
|
||||
if (i2c_rx_buf_.empty()) return -1;
|
||||
int val = i2c_rx_buf_.front();
|
||||
i2c_rx_buf_.erase(i2c_rx_buf_.begin());
|
||||
return val;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Test control API (not part of Hal interface)
|
||||
// ====================================================================
|
||||
|
||||
// -- Clock control ------------------------------------------------------
|
||||
void advanceMillis(unsigned long ms) {
|
||||
clock_ms_ += ms;
|
||||
clock_us_ += ms * 1000;
|
||||
}
|
||||
|
||||
void setMillis(unsigned long ms) {
|
||||
clock_ms_ = ms;
|
||||
clock_us_ = ms * 1000;
|
||||
}
|
||||
|
||||
// -- GPIO control -------------------------------------------------------
|
||||
void setPin(uint8_t pin, uint8_t value) {
|
||||
if (pin < NUM_PINS) pin_values_[pin] = value;
|
||||
}
|
||||
|
||||
uint8_t getPin(uint8_t pin) const {
|
||||
if (pin < NUM_PINS) return pin_values_[pin];
|
||||
return LOW;
|
||||
}
|
||||
|
||||
uint8_t getPinMode(uint8_t pin) const {
|
||||
if (pin < NUM_PINS) return pin_modes_[pin];
|
||||
return 0;
|
||||
}
|
||||
|
||||
void setAnalog(uint8_t pin, int value) {
|
||||
analog_values_[pin] = value;
|
||||
}
|
||||
|
||||
// -- GPIO history -------------------------------------------------------
|
||||
struct GpioEvent {
|
||||
unsigned long timestamp_ms;
|
||||
uint8_t pin;
|
||||
uint8_t value;
|
||||
};
|
||||
|
||||
const std::vector<GpioEvent>& gpioLog() const { return gpio_log_; }
|
||||
|
||||
void clearGpioLog() { gpio_log_.clear(); }
|
||||
|
||||
// Count how many times a pin was set to a specific value
|
||||
int countWrites(uint8_t pin, uint8_t value) const {
|
||||
int count = 0;
|
||||
for (const auto& e : gpio_log_) {
|
||||
if (e.pin == pin && e.value == value) ++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// -- Serial control -----------------------------------------------------
|
||||
const std::string& serialOutput() const { return serial_output_; }
|
||||
void clearSerialOutput() { serial_output_.clear(); }
|
||||
|
||||
void injectSerialInput(const std::string& data) {
|
||||
for (char c : data) {
|
||||
serial_input_.push_back(static_cast<uint8_t>(c));
|
||||
}
|
||||
}
|
||||
|
||||
// -- I2C device registration --------------------------------------------
|
||||
void attachI2cDevice(uint8_t addr, I2cDeviceSim* device) {
|
||||
i2c_devices_[addr] = device;
|
||||
}
|
||||
|
||||
void detachI2cDevice(uint8_t addr) {
|
||||
i2c_devices_.erase(addr);
|
||||
}
|
||||
|
||||
private:
|
||||
// GPIO
|
||||
uint8_t pin_modes_[NUM_PINS];
|
||||
uint8_t pin_values_[NUM_PINS];
|
||||
std::map<uint8_t, int> analog_values_;
|
||||
std::vector<GpioEvent> gpio_log_;
|
||||
|
||||
// Timing
|
||||
unsigned long clock_ms_;
|
||||
unsigned long clock_us_;
|
||||
|
||||
// Serial
|
||||
std::string serial_output_;
|
||||
std::vector<uint8_t> serial_input_;
|
||||
|
||||
// I2C
|
||||
uint8_t i2c_addr_ = 0;
|
||||
std::vector<uint8_t> i2c_tx_buf_;
|
||||
std::vector<uint8_t> i2c_rx_buf_;
|
||||
std::map<uint8_t, I2cDeviceSim*> i2c_devices_;
|
||||
};
|
||||
|
||||
#endif // SIM_HAL_H
|
||||
Reference in New Issue
Block a user