Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 150 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
name: FluidNC Continuous Integration
on:
pull_request:
push:
workflow_dispatch:
jobs:
build:
strategy:
matrix:
os:
- ubuntu-latest
# - macos-latest
# - windows-latest
pio_env:
- wifi
# - bt
# - noradio
# - wifibt
# - debug
pio_env_variant:
- ""
- "_s3"
include:
- os: ubuntu-latest
pio_env: wifi
pio_env_variant: ""
mcu: esp32
- os: ubuntu-latest
pio_env: wifi
pio_env_variant: "_s3"
mcu: esp32s3
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
Expand All @@ -44,6 +41,7 @@ jobs:
python tools/stack_trace_decoder/generate_addrinfo.py \
.pio/build/${{ matrix.pio_env }}${{ matrix.pio_env_variant }}/firmware.elf \
-o .pio/build/${{ matrix.pio_env }}${{ matrix.pio_env_variant }}/firmware.addrinfo \
--mcu ${{ matrix.mcu }} \
--build ${{ matrix.pio_env }}${{ matrix.pio_env_variant }} \
--verbose
- if: matrix.os == 'ubuntu-latest'
Expand Down Expand Up @@ -101,3 +99,142 @@ jobs:
- if: matrix.os != 'windows-latest'
name: Run tests
run: pio test -e ${{ matrix.pio_env }} -vv

asan_host:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.9"
cache: "pip"
- name: Install PlatformIO
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Cache PlatformIO
uses: actions/cache@v3
with:
path: ~/.platformio
key: platformio-${{ runner.os }}
- name: Run ASan unit tests
run: |
set -euo pipefail
pio test -e tests_asan -vv
- name: Run ASan machine-bus integration suite
run: |
set -euo pipefail
ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 pio test -e integration_asan -f test_integration_machine_buses -vv

integration_host:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.9"
cache: "pip"
- name: Install PlatformIO
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Cache PlatformIO
uses: actions/cache@v3
with:
path: ~/.platformio
key: platformio-${{ runner.os }}
- name: Run integration suites
run: |
set -euo pipefail
pio test -e integration -vv --junit-output-path integration-junit.xml
- name: Upload integration JUnit report
uses: actions/upload-artifact@v4
if: always()
with:
name: integration-junit
path: integration-junit.xml

coverage_guard_tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.9"
cache: "pip"
- name: Install test dependencies
run: |
set -euo pipefail
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run coverage guard self-tests
run: |
set -euo pipefail
python -m unittest discover -s tools -p "test_coverage_guard.py" -v
Comment thread
540lyle marked this conversation as resolved.
- name: Run build manifest self-tests
run: |
set -euo pipefail
python -m unittest discover -s tools -p "test_build_manifests.py" -v

coverage_reports:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.9"
cache: "pip"
- name: Install coverage dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install gcovr
- name: Cache PlatformIO
uses: actions/cache@v3
with:
path: ~/.platformio
key: platformio-${{ runner.os }}
- name: Generate coverage reports
run: |
set -euo pipefail
python coverage.py
test -f coverage-summary.json
test -f coverage-gaps.json
- name: Enforce coverage guardrails
run: |
set -euo pipefail
python tools/coverage_guard.py --summary coverage-summary.json --gaps coverage-gaps.json
- name: Upload coverage artifacts
uses: actions/upload-artifact@v4
with:
name: coverage-reports
path: |
coverage.txt
coverage-branches.txt
coverage-summary.json
coverage-gaps.json

posix_host_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.9"
cache: "pip"
- name: Install PlatformIO
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Cache PlatformIO
uses: actions/cache@v3
with:
path: ~/.platformio
key: platformio-${{ runner.os }}
- name: Build host posix target
run: pio run -e posix
66 changes: 66 additions & 0 deletions FluidNC/capture/ArduinoOTA.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#pragma once

#include <functional>

typedef int ota_error_t;

static constexpr ota_error_t OTA_AUTH_ERROR = 1;
static constexpr ota_error_t OTA_BEGIN_ERROR = 2;
static constexpr ota_error_t OTA_CONNECT_ERROR = 3;
static constexpr ota_error_t OTA_RECEIVE_ERROR = 4;
static constexpr ota_error_t OTA_END_ERROR = 5;
static constexpr int U_FLASH = 0;
static constexpr int U_FS = 100;

class ArduinoOTAClass {
public:
bool mdnsEnabled = true;
const char* hostname = nullptr;
int command = U_FLASH;
int beginCalls = 0;
int endCalls = 0;
int handleCalls = 0;
std::function<void()> onStartHandler;
std::function<void()> onEndHandler;
std::function<void(unsigned int, unsigned int)> onProgressHandler;
std::function<void(ota_error_t)> onErrorHandler;

ArduinoOTAClass& setMdnsEnabled(bool enabled) {
mdnsEnabled = enabled;
return *this;
}
ArduinoOTAClass& setHostname(const char* next) {
hostname = next;
return *this;
}
ArduinoOTAClass& onStart(std::function<void()> handler) {
onStartHandler = std::move(handler);
return *this;
}
ArduinoOTAClass& onEnd(std::function<void()> handler) {
onEndHandler = std::move(handler);
return *this;
}
ArduinoOTAClass& onProgress(std::function<void(unsigned int, unsigned int)> handler) {
onProgressHandler = std::move(handler);
return *this;
}
ArduinoOTAClass& onError(std::function<void(ota_error_t)> handler) {
onErrorHandler = std::move(handler);
return *this;
}
void begin() {
++beginCalls;
}
void end() {
++endCalls;
}
void handle() {
++handleCalls;
}
int getCommand() const {
return command;
}
};

inline ArduinoOTAClass ArduinoOTA;
28 changes: 21 additions & 7 deletions FluidNC/capture/Platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,27 @@

#define PACK(__Declaration__) __pragma(pack(push, 1)) __Declaration__ __pragma(pack(pop))

#define MAX_N_SDCARD 1
#define MAX_N_UARTS 2
#define MAX_N_I2SO 0
#define MAX_N_I2C 0
#define MAX_N_SPI 1
#define MAX_N_DACS 0
#define MAX_N_RMT 0
#ifndef MAX_N_SDCARD
# define MAX_N_SDCARD 1
#endif
#ifndef MAX_N_UARTS
# define MAX_N_UARTS 2
#endif
#ifndef MAX_N_I2SO
# define MAX_N_I2SO 0
#endif
#ifndef MAX_N_I2C
# define MAX_N_I2C 0
#endif
#ifndef MAX_N_SPI
# define MAX_N_SPI 1
#endif
#ifndef MAX_N_DACS
# define MAX_N_DACS 0
#endif
#ifndef MAX_N_RMT
# define MAX_N_RMT 0
#endif

#define DEFAULT_STEPPING_ENGINE Stepping::TIMED

Expand Down
14 changes: 13 additions & 1 deletion FluidNC/capture/PwmPin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,23 @@
#include "Driver/PwmPin.h"
#include "Config.h"

int g_pwmConstructCalls = 0;
int g_pwmSetDutyCalls = 0;
pinnum_t g_pwmLastPin = INVALID_PINNUM;
uint32_t g_pwmLastFrequency = 0;
uint32_t g_pwmLastDuty = 0;

PwmPin::PwmPin(pinnum_t gpio, bool invert, uint32_t frequency) : _gpio(gpio), _frequency(frequency) {
++g_pwmConstructCalls;
g_pwmLastPin = gpio;
g_pwmLastFrequency = frequency;
_period = 1000000 / frequency;
}

// cppcheck-suppress unusedFunction
void PwmPin::setDuty(uint32_t duty) {}
void PwmPin::setDuty(uint32_t duty) {
++g_pwmSetDutyCalls;
g_pwmLastDuty = duty;
}

PwmPin::~PwmPin() {}
74 changes: 74 additions & 0 deletions FluidNC/capture/Stage1HostSupport.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include "Stage1HostSupport.h"

#include "ArduinoOTA.h"
#include "WiFi.h"
#include "WiFiClientSecure.h"
#include "mdns.h"

namespace Stage1HostSupport {
I2CState g_i2c;
SPIState g_spi;
I2SOState g_i2so;

void resetBusState() {
g_i2c = {};
g_spi = {};
g_i2so = {};
}

void resetWebUiState() {
WiFi.setMode(WIFI_OFF);
WiFi.setHostname("fluidnc-host");

g_mdnsInitResult = 0;
g_mdnsHostnameSetResult = 0;
g_mdnsFreeCalls = 0;
g_mdnsAddedServices.clear();
g_mdnsRemovedServices.clear();

g_wifiClientConnectResult = true;
g_wifiClientConnected = false;
g_wifiClientStopCalls = 0;
g_wifiClientSetInsecureCalls = 0;
g_wifiClientWrites.clear();
g_wifiClientReadLines.clear();
g_wifiClientLastErrorCode = 0;
g_wifiClientLastErrorText.clear();

ArduinoOTA.mdnsEnabled = true;
ArduinoOTA.hostname = nullptr;
ArduinoOTA.command = U_FLASH;
ArduinoOTA.beginCalls = 0;
ArduinoOTA.endCalls = 0;
ArduinoOTA.handleCalls = 0;
ArduinoOTA.onStartHandler = nullptr;
ArduinoOTA.onEndHandler = nullptr;
ArduinoOTA.onProgressHandler = nullptr;
ArduinoOTA.onErrorHandler = nullptr;
}
}

bool i2c_master_init(objnum_t bus_number, pinnum_t sda_pin, pinnum_t scl_pin, uint32_t frequency) {
++Stage1HostSupport::g_i2c.initCalls;
Stage1HostSupport::g_i2c.initBus = bus_number;
Stage1HostSupport::g_i2c.initSda = sda_pin;
Stage1HostSupport::g_i2c.initScl = scl_pin;
Stage1HostSupport::g_i2c.initFrequency = frequency;
return Stage1HostSupport::g_i2c.initError;
}

int i2c_write(objnum_t bus_number, uint8_t address, const uint8_t* data, size_t count) {
Stage1HostSupport::g_i2c.lastWriteBus = bus_number;
Stage1HostSupport::g_i2c.lastWriteAddress = address;
Stage1HostSupport::g_i2c.lastWriteData.assign(data, data + count);
return Stage1HostSupport::g_i2c.writeResult;
}

int i2c_read(objnum_t bus_number, uint8_t address, uint8_t* data, size_t count) {
Stage1HostSupport::g_i2c.lastReadBus = bus_number;
Stage1HostSupport::g_i2c.lastReadAddress = address;
for (size_t i = 0; i < count; ++i) {
data[i] = i < Stage1HostSupport::g_i2c.readData.size() ? Stage1HostSupport::g_i2c.readData[i] : 0;
}
return Stage1HostSupport::g_i2c.readResult;
}
Loading
Loading