Test all feature sets (#2901)

* Mark interconnect as unstable

* Explicitly set unstable feature in HIL tests

* WIP append feature set name to artifact

* Add name to feature sets, build all combinations

* Fix tests

* Provide a looping executor for stable async tests

* Fix usb serial jtag

* Hide interconnect types
This commit is contained in:
Dániel Buga 2025-01-09 14:58:14 +01:00 committed by GitHub
parent 2b80e4d123
commit 848029b152
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 307 additions and 191 deletions

View File

@ -121,26 +121,10 @@ jobs:
- name: Build tests
run: cargo xtask build-tests ${{ matrix.target.soc }}
- name: Prepare artifact
run: |
# Create the 'tests' directory if it doesn't exist
mkdir -p tests
# Find ELF files in the specified path and move them to 'tests'
find "hil-test/target/${{ matrix.target.rust-target }}/release/deps/" -type f -exec file {} + | \
grep ELF | \
awk -F: '{print $1}' | \
xargs -I {} mv {} tests
# Rename files in 'tests' by removing everything after the first dash
for file in tests/*-*; do
base_name="$(basename "$file" | cut -d'-' -f1)"
mv "$file" "tests/$base_name"
done
- uses: actions/upload-artifact@v4
with:
name: tests-${{ matrix.target.soc }}
path: /home/runner/work/esp-hal/esp-hal/tests
path: /home/runner/work/esp-hal/esp-hal/target/tests/${{ matrix.target.soc }}
if-no-files-found: error
overwrite: true

View File

@ -212,6 +212,7 @@ fn disconnect_peripheral_output_from_pin(pin: &mut AnyPin, signal: gpio::OutputS
/// A configurable input signal between a peripheral and a GPIO pin.
///
/// Multiple input signals can be connected to one pin.
#[instability::unstable]
pub struct InputSignal {
pin: AnyPin,
is_inverted: bool,
@ -347,6 +348,7 @@ impl DirectInputSignal {
/// A configurable output signal between a peripheral and a GPIO pin.
///
/// Multiple pins can be connected to one output signal.
#[instability::unstable]
pub struct OutputSignal {
pin: AnyPin,
is_inverted: bool,
@ -506,6 +508,7 @@ enum InputConnectionInner {
/// This is mainly intended for internal use, but it can be used to connect
/// peripherals within the MCU without external hardware.
#[derive(Clone)]
#[doc(hidden)] // FIXME: replace with `#[unstable]` when we can mark delegated methods https://github.com/Kobzol/rust-delegate/issues/77
pub struct InputConnection(InputConnectionInner);
impl Peripheral for InputConnection {
@ -614,6 +617,7 @@ enum OutputConnectionInner {
///
/// This is mainly intended for internal use, but it can be used to connect
/// peripherals within the MCU without external hardware.
#[doc(hidden)] // FIXME: replace with `#[unstable]` when we can mark delegated methods https://github.com/Kobzol/rust-delegate/issues/77
pub struct OutputConnection(OutputConnectionInner);
impl Sealed for OutputConnection {}

View File

@ -79,23 +79,20 @@ use crate::{
private::{self, Sealed},
};
pub mod interconnect;
mod placeholder;
pub use placeholder::NoPin;
#[cfg(soc_etm)]
crate::unstable_module! {
pub mod interconnect;
#[cfg(soc_etm)]
pub mod etm;
}
#[cfg(lp_io)]
crate::unstable_module! {
#[cfg(lp_io)]
pub mod lp_io;
}
#[cfg(all(rtc_io, not(esp32)))]
crate::unstable_module! {
#[cfg(all(rtc_io, not(esp32)))]
pub mod rtc_io;
}
@ -820,6 +817,7 @@ where
///
/// Peripheral signals allow connecting peripherals together without using
/// external hardware.
#[instability::unstable]
pub fn split(self) -> (interconnect::InputSignal, interconnect::OutputSignal) {
(
interconnect::InputSignal::new(self.degrade_pin(private::Internal)),
@ -1296,6 +1294,8 @@ impl<'d> Output<'d> {
///
/// Peripheral signals allow connecting peripherals together without using
/// external hardware.
#[inline]
#[instability::unstable]
pub fn split(self) -> (interconnect::InputSignal, interconnect::OutputSignal) {
self.pin.split()
}
@ -1305,6 +1305,7 @@ impl<'d> Output<'d> {
///
/// The input signal can be passed to peripherals in place of an input pin.
#[inline]
#[instability::unstable]
pub fn peripheral_input(&self) -> interconnect::InputSignal {
self.pin.peripheral_input()
}
@ -1315,6 +1316,7 @@ impl<'d> Output<'d> {
/// The output signal can be passed to peripherals in place of an output
/// pin.
#[inline]
#[instability::unstable]
pub fn into_peripheral_output(self) -> interconnect::OutputSignal {
self.pin.into_peripheral_output()
}
@ -1439,6 +1441,7 @@ impl<'d> Input<'d> {
///
/// The input signal can be passed to peripherals in place of an input pin.
#[inline]
#[instability::unstable]
pub fn peripheral_input(&self) -> interconnect::InputSignal {
self.pin.peripheral_input()
}
@ -1565,6 +1568,8 @@ impl<'d> Input<'d> {
///
/// Peripheral signals allow connecting peripherals together without using
/// external hardware.
#[inline]
#[instability::unstable]
pub fn split(self) -> (interconnect::InputSignal, interconnect::OutputSignal) {
self.pin.split()
}
@ -1575,6 +1580,7 @@ impl<'d> Input<'d> {
/// The output signal can be passed to peripherals in place of an output
/// pin.
#[inline]
#[instability::unstable]
pub fn into_peripheral_output(self) -> interconnect::OutputSignal {
self.pin.into_peripheral_output()
}
@ -1654,6 +1660,8 @@ impl<'d> OutputOpenDrain<'d> {
///
/// Peripheral signals allow connecting peripherals together without using
/// external hardware.
#[inline]
#[instability::unstable]
pub fn split(self) -> (interconnect::InputSignal, interconnect::OutputSignal) {
self.pin.split()
}
@ -1663,6 +1671,7 @@ impl<'d> OutputOpenDrain<'d> {
///
/// The input signal can be passed to peripherals in place of an input pin.
#[inline]
#[instability::unstable]
pub fn peripheral_input(&self) -> interconnect::InputSignal {
self.pin.peripheral_input()
}
@ -1673,6 +1682,7 @@ impl<'d> OutputOpenDrain<'d> {
/// The output signal can be passed to peripherals in place of an output
/// pin.
#[inline]
#[instability::unstable]
pub fn into_peripheral_output(self) -> interconnect::OutputSignal {
self.pin.into_peripheral_output()
}
@ -1797,6 +1807,7 @@ impl<'d> Flex<'d> {
///
/// The input signal can be passed to peripherals in place of an input pin.
#[inline]
#[instability::unstable]
pub fn peripheral_input(&self) -> interconnect::InputSignal {
self.pin.degrade_pin(private::Internal).split().0
}
@ -1963,6 +1974,8 @@ impl<'d> Flex<'d> {
///
/// Peripheral signals allow connecting peripherals together without using
/// external hardware.
#[inline]
#[instability::unstable]
pub fn split(self) -> (interconnect::InputSignal, interconnect::OutputSignal) {
assert!(self.pin.is_output());
self.pin.degrade_pin(private::Internal).split()
@ -1974,6 +1987,7 @@ impl<'d> Flex<'d> {
/// The output signal can be passed to peripherals in place of an output
/// pin.
#[inline]
#[instability::unstable]
pub fn into_peripheral_output(self) -> interconnect::OutputSignal {
self.split().1
}
@ -2028,6 +2042,7 @@ pub(crate) mod internal {
/// using external hardware.
#[inline]
#[allow(unused_braces, reason = "False positive")]
#[instability::unstable]
pub fn split(self) -> (interconnect::InputSignal, interconnect::OutputSignal) {
handle_gpio_input!(self, target, { target.split() })
}

View File

@ -197,6 +197,7 @@ cfg-if = "1.0.0"
critical-section = "1.1.3"
defmt = "0.3.8"
defmt-rtt = { version = "0.4.1", optional = true }
embassy-executor = "0.6.0"
embassy-futures = "0.1.1"
embassy-sync = "0.6.0"
embassy-time = "0.3.2"
@ -206,7 +207,7 @@ embedded-hal-async = "1.0.0"
embedded-hal-nb = "1.0.0"
esp-alloc = { path = "../esp-alloc", optional = true }
esp-backtrace = { path = "../esp-backtrace", default-features = false, features = ["exception-handler", "defmt", "semihosting"] }
esp-hal = { path = "../esp-hal", features = ["digest", "unstable"], optional = true }
esp-hal = { path = "../esp-hal", features = ["digest"], optional = true }
esp-hal-embassy = { path = "../esp-hal-embassy", optional = true }
esp-wifi = { path = "../esp-wifi", optional = true, features = ["wifi"] }
portable-atomic = "1.9.0"
@ -220,7 +221,7 @@ digest = { version = "0.10.7", default-features = false }
elliptic-curve = { version = "0.13.8", default-features = false, features = ["sec1"] }
embassy-executor = { version = "0.6.0", default-features = false }
# Add the `embedded-test/defmt` feature for more verbose testing
embedded-test = { version = "0.5.0", git = "https://github.com/probe-rs/embedded-test.git", rev = "7109473", default-features = false }
embedded-test = { version = "0.5.0", git = "https://github.com/probe-rs/embedded-test.git", rev = "7109473", default-features = false, features = ["embassy", "external-executor"] }
fugit = "0.3.7"
hex-literal = "0.4.1"
nb = "1.1.0"
@ -234,7 +235,8 @@ esp-build = { path = "../esp-build" }
esp-metadata = { path = "../esp-metadata" }
[features]
default = ["embassy"]
default = []
unstable = ["esp-hal/unstable"]
defmt = ["dep:defmt-rtt", "esp-hal/defmt", "embedded-test/defmt"]
@ -286,14 +288,14 @@ esp32s3 = [
]
# Async & Embassy:
embassy = [
"embedded-test/embassy",
"embedded-test/external-executor",
"dep:esp-hal-embassy",
]
generic-queue = [
"embassy",
"embassy-time/generic-queue-64"
]
integrated-timers = [
"embassy",
"esp-hal-embassy/integrated-timers",
]
octal-psram = ["esp-hal/octal-psram", "esp-alloc"]

View File

@ -82,3 +82,42 @@ macro_rules! unconnected_pin {
}
}};
}
// A simple looping executor to test async code without esp-hal-embassy (which
// needs `esp-hal/unstable`).
#[cfg(not(feature = "embassy"))]
mod executor {
use core::marker::PhantomData;
use embassy_executor::{raw, Spawner};
#[export_name = "__pender"]
fn __pender(_: *mut ()) {}
pub struct Executor {
inner: raw::Executor,
not_send: PhantomData<*mut ()>,
}
impl Executor {
pub fn new() -> Self {
Self {
inner: raw::Executor::new(core::ptr::null_mut()),
not_send: PhantomData,
}
}
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
init(self.inner.spawner());
loop {
unsafe { self.inner.poll() };
}
}
}
}
#[cfg(feature = "embassy")]
pub use esp_hal_embassy::Executor;
#[cfg(not(feature = "embassy"))]
pub use executor::Executor;

View File

@ -1,6 +1,7 @@
//! AES Test
//% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,6 +1,7 @@
//! AES DMA Test
//% CHIPS: esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,6 +1,7 @@
//! Clock Monitor Test
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,6 +1,7 @@
//! CRC and MD5 Tests
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,6 +1,7 @@
//! Ensure invariants of locks are upheld.
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
// TODO: add multi-core tests

View File

@ -1,6 +1,7 @@
//! Delay Test
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -4,6 +4,7 @@
//! `embedded_hal_async::delay::DelayNs` trait.
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]
@ -69,7 +70,7 @@ async fn test_async_delay_ms(mut timer: impl DelayNs, duration: u32) {
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 2, executor = esp_hal_embassy::Executor::new())]
#[embedded_test::tests(default_timeout = 2, executor = hil_test::Executor::new())]
mod tests {
use super::*;

View File

@ -1,6 +1,7 @@
//! DMA macro tests
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,6 +1,7 @@
//! DMA Mem2Mem Tests
//% CHIPS: esp32c2 esp32c3 esp32c6 esp32h2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,6 +1,7 @@
//! ECC Test
//% CHIPS: esp32c2 esp32c6 esp32h2
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -2,8 +2,8 @@
//! code.
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: integrated-timers
//% FEATURES: generic-queue
//% FEATURES(integrated): unstable embassy integrated-timers
//% FEATURES(generic): unstable embassy generic-queue
#![no_std]
#![no_main]
@ -48,7 +48,7 @@ struct Context {
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = esp_hal_embassy::Executor::new())]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod test {
use super::*;

View File

@ -1,8 +1,8 @@
//! Reproduction and regression test for a sneaky issue.
//% CHIPS: esp32 esp32s2 esp32s3 esp32c3 esp32c6 esp32h2
//% FEATURES: integrated-timers
//% FEATURES: generic-queue
//% FEATURES(integrated): unstable embassy integrated-timers
//% FEATURES(generic): unstable embassy generic-queue
#![no_std]
#![no_main]
@ -75,7 +75,7 @@ async fn interrupt_driven_task(mut i2s_tx: esp_hal::i2s::master::I2sTx<'static,
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = esp_hal_embassy::Executor::new())]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod test {
use super::*;

View File

@ -1,8 +1,8 @@
//! Embassy timer and executor Test
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: integrated-timers
//% FEATURES: generic-queue
//% FEATURES(integrated): unstable embassy integrated-timers
//% FEATURES(generic): unstable embassy generic-queue
#![no_std]
#![no_main]
@ -122,7 +122,7 @@ fn set_up_embassy_with_systimer(peripherals: Peripherals) {
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = esp_hal_embassy::Executor::new())]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod test {
use super::*;
use crate::test_cases::*;

View File

@ -1,7 +1,7 @@
//! Cp0Disable exception regression test
//% CHIPS: esp32 esp32s2 esp32s3
//% FEATURES: esp-wifi esp-alloc
//% FEATURES: unstable esp-wifi esp-alloc
#![no_std]
#![no_main]

View File

@ -1,6 +1,7 @@
//! time::now Test
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,7 +1,8 @@
//! GPIO Test
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: generic-queue
//% FEATURES: unstable embassy generic-queue
//% FEATURES(stable):
#![no_std]
#![no_main]
@ -9,14 +10,22 @@
use core::cell::RefCell;
use critical_section::Mutex;
#[cfg(feature = "unstable")]
use embassy_time::{Duration, Timer};
use esp_hal::{
delay::Delay,
gpio::{AnyPin, Input, Io, Level, Output, Pin, Pull},
interrupt::InterruptConfigurable,
gpio::{AnyPin, Input, Level, Output, OutputOpenDrain, Pin, Pull},
macros::handler,
};
#[cfg(feature = "unstable")]
use esp_hal::{
gpio::{Event, Flex, Io},
interrupt::InterruptConfigurable,
timer::timg::TimerGroup,
};
use hil_test as _;
#[cfg(feature = "unstable")]
use portable_atomic::{AtomicUsize, Ordering};
static COUNTER: Mutex<RefCell<u32>> = Mutex::new(RefCell::new(0));
static INPUT_PIN: Mutex<RefCell<Option<Input>>> = Mutex::new(RefCell::new(None));
@ -39,27 +48,28 @@ pub fn interrupt_handler() {
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = esp_hal_embassy::Executor::new())]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod tests {
use embassy_time::{Duration, Timer};
use esp_hal::gpio::{Event, Flex, OutputOpenDrain};
use portable_atomic::{AtomicUsize, Ordering};
use super::*;
#[init]
fn init() -> Context {
let peripherals = esp_hal::init(esp_hal::Config::default());
let mut io = Io::new(peripherals.IO_MUX);
io.set_interrupt_handler(interrupt_handler);
let delay = Delay::new();
let (gpio1, gpio2) = hil_test::common_test_pins!(peripherals);
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_hal_embassy::init(timg0.timer0);
#[cfg(feature = "unstable")]
{
// Interrupts are unstable
let mut io = Io::new(peripherals.IO_MUX);
io.set_interrupt_handler(interrupt_handler);
// Timers are unstable
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_hal_embassy::init(timg0.timer0);
}
Context {
test_gpio1: gpio1.degrade(),
@ -69,6 +79,7 @@ mod tests {
}
#[test]
#[cfg(feature = "unstable")] // Timers are unstable
async fn async_edge(ctx: Context) {
let counter = AtomicUsize::new(0);
let Context {
@ -147,53 +158,6 @@ mod tests {
assert_eq!(test_gpio2.is_set_high(), true);
}
#[test]
fn gpio_output_embedded_hal_0_2(ctx: Context) {
let test_gpio1 = Input::new(ctx.test_gpio1, Pull::Down);
let mut test_gpio2 = Output::new(ctx.test_gpio2, Level::Low);
fn set<T>(pin: &mut T, state: bool)
where
T: embedded_hal::digital::OutputPin,
{
if state {
pin.set_high().ok();
} else {
pin.set_low().ok();
}
}
fn toggle<T>(pin: &mut T)
where
T: embedded_hal::digital::StatefulOutputPin,
{
pin.toggle().ok();
}
// `StatefulOutputPin`:
assert_eq!(test_gpio2.is_set_low(), true);
assert_eq!(test_gpio2.is_set_high(), false);
assert_eq!(test_gpio1.is_low(), true);
assert_eq!(test_gpio1.is_high(), false);
set(&mut test_gpio2, true);
assert_eq!(test_gpio2.is_set_low(), false);
assert_eq!(test_gpio2.is_set_high(), true);
assert_eq!(test_gpio1.is_low(), false);
assert_eq!(test_gpio1.is_high(), true);
// `ToggleableOutputPin`:
toggle(&mut test_gpio2);
assert_eq!(test_gpio2.is_set_low(), true);
assert_eq!(test_gpio2.is_set_high(), false);
assert_eq!(test_gpio1.is_low(), true);
assert_eq!(test_gpio1.is_high(), false);
toggle(&mut test_gpio2);
assert_eq!(test_gpio2.is_set_low(), false);
assert_eq!(test_gpio2.is_set_high(), true);
assert_eq!(test_gpio1.is_low(), false);
assert_eq!(test_gpio1.is_high(), true);
}
#[test]
fn gpio_output_embedded_hal_1_0(ctx: Context) {
let test_gpio1 = Input::new(ctx.test_gpio1, Pull::Down);
@ -242,6 +206,7 @@ mod tests {
}
#[test]
#[cfg(feature = "unstable")] // Interrupts are unstable
fn gpio_interrupt(ctx: Context) {
let mut test_gpio1 = Input::new(ctx.test_gpio1, Pull::Down);
let mut test_gpio2 = Output::new(ctx.test_gpio2, Level::Low);
@ -325,6 +290,7 @@ mod tests {
}
#[test]
#[cfg(feature = "unstable")]
fn gpio_flex(ctx: Context) {
let mut test_gpio1 = Flex::new(ctx.test_gpio1);
let mut test_gpio2 = Flex::new(ctx.test_gpio2);
@ -401,6 +367,7 @@ mod tests {
}
#[test]
#[cfg(feature = "unstable")]
fn interrupt_executor_is_not_frozen(ctx: Context) {
use esp_hal::interrupt::{software::SoftwareInterrupt, Priority};
use esp_hal_embassy::InterruptExecutor;

View File

@ -6,7 +6,7 @@
//! async API works for user handlers automatically.
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: integrated-timers
//% FEATURES: unstable integrated-timers
#![no_std]
#![no_main]
@ -66,7 +66,7 @@ async fn drive_pins(gpio1: impl Into<AnyPin>, gpio2: impl Into<AnyPin>) -> usize
}
#[cfg(test)]
#[embedded_test::tests(executor = esp_hal_embassy::Executor::new())]
#[embedded_test::tests(executor = hil_test::Executor::new())]
mod tests {
use super::*;

View File

@ -1,6 +1,7 @@
//! I2C test
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -4,7 +4,7 @@
//! with loopback mode enabled).
//% CHIPS: esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: generic-queue
//% FEATURES: unstable generic-queue
// FIXME: re-enable on ESP32 when it no longer fails spuriously
#![no_std]
@ -97,7 +97,7 @@ fn enable_loopback() {
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = esp_hal_embassy::Executor::new())]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod tests {
use super::*;

View File

@ -1,6 +1,7 @@
//! Initialization tests
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -3,6 +3,7 @@
//! "Disabled" for now - see https://github.com/esp-rs/esp-hal/pull/1635#issuecomment-2137405251
//% CHIPS: esp32c2 esp32c3 esp32c6 esp32h2
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,6 +1,7 @@
//! LCD_CAM Camera and DPI tests
//% CHIPS: esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,6 +1,7 @@
//! lcd_cam i8080 tests
//% CHIPS: esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,7 +1,7 @@
//! lcd_cam i8080 tests
//% CHIPS: esp32s3
//% FEATURES: generic-queue
//% FEATURES: unstable generic-queue
#![no_std]
#![no_main]
@ -28,7 +28,7 @@ struct Context<'d> {
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = esp_hal_embassy::Executor::new())]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod tests {
use super::*;

View File

@ -1,6 +1,8 @@
//! PARL_IO TX test
//% CHIPS: esp32c6 esp32h2
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,7 +1,7 @@
//! PARL_IO TX async test
//% CHIPS: esp32c6 esp32h2
//% FEATURES: generic-queue
//% FEATURES: unstable generic-queue
#![no_std]
#![no_main]
@ -43,7 +43,7 @@ struct Context {
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = esp_hal_embassy::Executor::new())]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod tests {
use defmt::info;

View File

@ -1,6 +1,7 @@
//! PCNT tests
//% CHIPS: esp32 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,6 +1,7 @@
//! QSPI Test Suite
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,6 +1,7 @@
//! RMT Loopback Test
//% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,6 +1,7 @@
//! RSA Test
//% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,6 +1,7 @@
//! Async RSA Test
//% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]
@ -48,7 +49,7 @@ const fn compute_mprime(modulus: &U512) -> u32 {
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 5, executor = esp_hal_embassy::Executor::new())]
#[embedded_test::tests(default_timeout = 5, executor = hil_test::Executor::new())]
mod tests {
use super::*;

View File

@ -1,19 +1,21 @@
//! SHA Test
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]
use digest::{Digest, Update};
#[cfg(not(feature = "esp32"))]
use esp_hal::clock::CpuClock;
#[cfg(not(feature = "esp32"))]
use esp_hal::sha::Sha224;
#[cfg(any(feature = "esp32", feature = "esp32s2", feature = "esp32s3"))]
use esp_hal::sha::{Sha384, Sha512};
#[cfg(any(feature = "esp32s2", feature = "esp32s3"))]
use esp_hal::sha::{Sha512_224, Sha512_256};
use esp_hal::{
clock::CpuClock,
rng::Rng,
sha::{Sha, Sha1, Sha256, ShaAlgorithm, ShaDigest},
};

View File

@ -1,7 +1,7 @@
//! SPI Full Duplex test suite.
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: generic-queue
//% FEATURES: unstable generic-queue
// FIXME: add async test cases that don't rely on PCNT
@ -48,7 +48,7 @@ struct Context {
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = esp_hal_embassy::Executor::new())]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod tests {
use super::*;

View File

@ -1,6 +1,7 @@
//! SPI Half Duplex Read Test
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,6 +1,7 @@
//! SPI Half Duplex Write Test
//% CHIPS: esp32 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,6 +1,7 @@
//! SPI Half Duplex Write Test
//% FEATURES: octal-psram
//% CHIPS: esp32s3
//% FEATURES: unstable octal-psram
#![no_std]
#![no_main]

View File

@ -4,6 +4,7 @@
//! testing Mode 1.
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]
@ -94,7 +95,7 @@ impl BitbangSpi {
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 10, executor = esp_hal_embassy::Executor::new())]
#[embedded_test::tests(default_timeout = 10, executor = hil_test::Executor::new())]
mod tests {
use super::*;

View File

@ -2,6 +2,7 @@
// esp32 disabled as it does not have a systimer
//% CHIPS: esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,6 +1,7 @@
//! TWAI test
//% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,6 +1,7 @@
//! UART Test
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,7 +1,7 @@
//! UART Test
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: generic-queue
//% FEATURES: unstable embassy generic-queue
#![no_std]
#![no_main]
@ -17,7 +17,7 @@ struct Context {
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = esp_hal_embassy::Executor::new())]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod tests {
use super::*;

View File

@ -1,6 +1,7 @@
//! Misc UART TX/RX regression tests
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,6 +1,7 @@
//! UART TX/RX Test
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,7 +1,7 @@
//! UART TX/RX Async Test
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: generic-queue
//% FEATURES: unstable generic-queue
#![no_std]
#![no_main]
@ -18,7 +18,7 @@ struct Context {
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = esp_hal_embassy::Executor::new())]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod tests {
use super::*;

View File

@ -1,6 +1,7 @@
//! USB Serial JTAG tests
//% CHIPS: esp32c3 esp32c6 esp32h2 esp32s3
//% FEATURES: unstable
#![no_std]
#![no_main]
@ -8,16 +9,13 @@
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use esp_hal::{timer::timg::TimerGroup, usb_serial_jtag::UsbSerialJtag};
use esp_hal::usb_serial_jtag::UsbSerialJtag;
use hil_test as _;
#[test]
fn creating_peripheral_does_not_break_debug_connection() {
let peripherals = esp_hal::init(esp_hal::Config::default());
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_hal_embassy::init(timg0.timer0);
_ = UsbSerialJtag::new(peripherals.USB_DEVICE)
.into_async()
.split();

View File

@ -17,5 +17,6 @@ log = "0.4.22"
minijinja = "2.5.0"
semver = { version = "1.0.23", features = ["serde"] }
serde = { version = "1.0.215", features = ["derive"] }
serde_json = "1.0.70"
strum = { version = "0.26.3", features = ["derive"] }
toml_edit = "0.22.22"

View File

@ -2,27 +2,39 @@
use std::{
ffi::OsStr,
path::Path,
path::{Path, PathBuf},
process::{Command, Stdio},
};
use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
use crate::windows_safe_path;
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub enum CargoAction {
Build,
Build(PathBuf),
Run,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Artifact {
pub executable: PathBuf,
}
/// Execute cargo with the given arguments and from the specified directory.
pub fn run(args: &[String], cwd: &Path) -> Result<()> {
run_with_env::<[(&str, &str); 0], _, _>(args, cwd, [])
run_with_env::<[(&str, &str); 0], _, _>(args, cwd, [], false)?;
Ok(())
}
/// Execute cargo with the given arguments and from the specified directory.
pub fn run_with_env<I, K, V>(args: &[String], cwd: &Path, envs: I) -> Result<()>
pub fn run_and_capture(args: &[String], cwd: &Path) -> Result<String> {
run_with_env::<[(&str, &str); 0], _, _>(args, cwd, [], true)
}
/// Execute cargo with the given arguments and from the specified directory.
pub fn run_with_env<I, K, V>(args: &[String], cwd: &Path, envs: I, capture: bool) -> Result<String>
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
@ -38,19 +50,26 @@ where
// using now or in future!
let cwd = windows_safe_path(cwd);
let status = Command::new(get_cargo())
let output = Command::new(get_cargo())
.args(args)
.current_dir(cwd)
.envs(envs)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.stdin(Stdio::inherit())
.status()?;
.stdout(if capture {
Stdio::piped()
} else {
Stdio::inherit()
})
.stderr(if capture {
Stdio::piped()
} else {
Stdio::inherit()
})
.output()?;
// Make sure that we return an appropriate exit code here, as Github Actions
// requires this in order to function correctly:
if status.success() {
Ok(())
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
bail!("Failed to execute cargo subcommand")
}
@ -145,16 +164,16 @@ impl CargoArgsBuilder {
}
#[must_use]
pub fn build(self) -> Vec<String> {
pub fn build(&self) -> Vec<String> {
let mut args = vec![];
if let Some(toolchain) = self.toolchain {
if let Some(ref toolchain) = self.toolchain {
args.push(format!("+{toolchain}"));
}
args.push(self.subcommand);
args.push(self.subcommand.clone());
if let Some(target) = self.target {
if let Some(ref target) = self.target {
args.push(format!("--target={target}"));
}
@ -162,8 +181,8 @@ impl CargoArgsBuilder {
args.push(format!("--features={}", self.features.join(",")));
}
for arg in self.args {
args.push(arg);
for arg in self.args.iter() {
args.push(arg.clone());
}
args

View File

@ -58,6 +58,7 @@ pub enum Package {
pub struct Metadata {
example_path: PathBuf,
chip: Chip,
feature_set_name: String,
feature_set: Vec<String>,
tag: Option<String>,
description: Option<String>,
@ -67,6 +68,7 @@ impl Metadata {
pub fn new(
example_path: &Path,
chip: Chip,
feature_set_name: String,
feature_set: Vec<String>,
tag: Option<String>,
description: Option<String>,
@ -74,6 +76,7 @@ impl Metadata {
Self {
example_path: example_path.to_path_buf(),
chip,
feature_set_name,
feature_set,
tag,
description,
@ -161,6 +164,7 @@ pub fn build_documentation(workspace: &Path, package: Package, chip: Chip) -> Re
&args,
&package_path,
[("RUSTDOCFLAGS", "--cfg docsrs --cfg not_really_docsrs")],
false,
)?;
let docs_path = windows_safe_path(
@ -212,7 +216,7 @@ fn apply_feature_rules(package: &Package, config: &Config) -> Vec<String> {
}
/// Load all examples at the given path, and parse their metadata.
pub fn load_examples(path: &Path, action: CargoAction) -> Result<Vec<Metadata>> {
pub fn load_examples(path: &Path) -> Result<Vec<Metadata>> {
let mut examples = Vec::new();
for entry in fs::read_dir(path)? {
@ -248,7 +252,7 @@ pub fn load_examples(path: &Path, action: CargoAction) -> Result<Vec<Metadata>>
.split_ascii_whitespace()
.map(|s| Chip::from_str(s, false).unwrap())
.collect::<Vec<_>>();
} else if key == "FEATURES" {
} else if let Some(feature_set_name) = key.strip_prefix("FEATURES") {
// Base feature set required to run the example.
// If multiple are specified, we compile the same example multiple times.
let mut values = value
@ -259,7 +263,20 @@ pub fn load_examples(path: &Path, action: CargoAction) -> Result<Vec<Metadata>>
// Sort the features so they are in a deterministic order:
values.sort();
feature_sets.push(values);
let feature_set_name = feature_set_name.trim_matches(&['(', ')']).to_string();
if feature_sets
.iter()
.any(|(name, _)| name == &feature_set_name)
{
bail!(
"Duplicate feature set name '{}' in {}",
feature_set_name,
path.display()
);
}
feature_sets.push((feature_set_name, values));
} else if key.starts_with("CHIP-FEATURES(") {
// Additional features required for specific chips.
// These are appended to the base feature set(s).
@ -289,15 +306,10 @@ pub fn load_examples(path: &Path, action: CargoAction) -> Result<Vec<Metadata>>
}
if feature_sets.is_empty() {
feature_sets.push(Vec::new());
feature_sets.push((String::new(), Vec::new()));
}
if action == CargoAction::Build {
// Only build the first feature set for each example.
// Rebuilding with a different feature set just wastes time because the latter
// one will overwrite the former one(s).
feature_sets.truncate(1);
}
for feature_set in feature_sets {
for (feature_set_name, feature_set) in feature_sets {
for chip in &chips {
let mut feature_set = feature_set.clone();
if let Some(chip_features) = chip_features.get(chip) {
@ -310,6 +322,7 @@ pub fn load_examples(path: &Path, action: CargoAction) -> Result<Vec<Metadata>>
examples.push(Metadata::new(
&path,
*chip,
feature_set_name.clone(),
feature_set.clone(),
tag.clone(),
description.clone(),
@ -331,7 +344,7 @@ pub fn execute_app(
target: &str,
app: &Metadata,
action: CargoAction,
mut repeat: usize,
repeat: usize,
debug: bool,
) -> Result<()> {
log::info!(
@ -348,52 +361,79 @@ pub fn execute_app(
let package = app.example_path().strip_prefix(package_path)?;
log::info!("Package: {}", package.display());
let (bin, subcommand) = if action == CargoAction::Build {
repeat = 1; // Do not repeat builds in a loop
let bin = if package.starts_with("src/bin") {
format!("--bin={}", app.name())
} else if package.starts_with("tests") {
format!("--test={}", app.name())
} else {
format!("--example={}", app.name())
};
(bin, "build")
} else if package.starts_with("src/bin") {
(format!("--bin={}", app.name()), "run")
} else if package.starts_with("tests") {
(format!("--test={}", app.name()), "test")
} else {
(format!("--example={}", app.name()), "run")
};
let mut builder = CargoArgsBuilder::default()
.subcommand(subcommand)
.target(target)
.features(&features)
.arg(bin);
.features(&features);
let bin_arg = if package.starts_with("src/bin") {
format!("--bin={}", app.name())
} else if package.starts_with("tests") {
format!("--test={}", app.name())
} else {
format!("--example={}", app.name())
};
builder.add_arg(bin_arg);
let subcommand = if matches!(action, CargoAction::Build(_)) {
"build"
} else if package.starts_with("tests") {
"test"
} else {
"run"
};
builder = builder.subcommand(subcommand);
if !debug {
builder.add_arg("--release");
}
if subcommand == "test" && chip == Chip::Esp32c2 {
builder.add_arg("--").add_arg("--speed").add_arg("15000");
}
// If targeting an Xtensa device, we must use the '+esp' toolchain modifier:
if target.starts_with("xtensa") {
builder = builder.toolchain("esp");
builder.add_arg("-Zbuild-std=core,alloc");
}
if subcommand == "test" && chip == Chip::Esp32c2 {
builder.add_arg("--").add_arg("--speed").add_arg("15000");
}
let args = builder.build();
log::debug!("{args:#?}");
for i in 0..repeat {
if repeat != 1 {
log::info!("Run {}/{}", i + 1, repeat);
}
if let CargoAction::Build(out_dir) = action {
cargo::run(&args, package_path)?;
// Now that the build has succeeded and we printed the output, we can
// rerun the build again quickly enough to capture JSON. We'll use this to
// copy the binary to the output directory.
builder.add_arg("--message-format=json");
let args = builder.build();
let output = cargo::run_and_capture(&args, package_path)?;
for line in output.lines() {
if let Ok(artifact) = serde_json::from_str::<cargo::Artifact>(line) {
let out_dir = out_dir.join(&chip.to_string());
std::fs::create_dir_all(&out_dir)?;
let basename = app.name();
let name = if app.feature_set_name.is_empty() {
basename
} else {
format!("{}_{}", basename, app.feature_set_name)
};
let output_file = out_dir.join(name);
std::fs::copy(artifact.executable, &output_file)?;
log::info!("Output ready: {}", output_file.display());
}
}
} else {
for i in 0..repeat {
if repeat != 1 {
log::info!("Run {}/{}", i + 1, repeat);
}
cargo::run(&args, package_path)?;
}
}
Ok(())

View File

@ -181,12 +181,20 @@ fn main() -> Result<()> {
let workspace = std::env::current_dir()?;
let out_path = Path::new("target");
match Cli::parse() {
Cli::BuildDocumentation(args) => build_documentation(&workspace, args),
Cli::BuildDocumentationIndex(args) => build_documentation_index(&workspace, args),
Cli::BuildExamples(args) => examples(&workspace, args, CargoAction::Build),
Cli::BuildExamples(args) => examples(
&workspace,
args,
CargoAction::Build(out_path.join("examples")),
),
Cli::BuildPackage(args) => build_package(&workspace, args),
Cli::BuildTests(args) => tests(&workspace, args, CargoAction::Build),
Cli::BuildTests(args) => {
tests(&workspace, args, CargoAction::Build(out_path.join("tests")))
}
Cli::BumpVersion(args) => bump_version(&workspace, args),
Cli::FmtPackages(args) => fmt_packages(&workspace, args),
Cli::GenerateEfuseFields(args) => generate_efuse_src(&workspace, args),
@ -227,7 +235,7 @@ fn examples(workspace: &Path, mut args: ExampleArgs, action: CargoAction) -> Res
};
// Load all examples which support the specified chip and parse their metadata:
let mut examples = xtask::load_examples(&example_path, action)?
let mut examples = xtask::load_examples(&example_path)?
.iter()
.filter_map(|example| {
if example.supports_chip(args.chip) {
@ -243,13 +251,18 @@ fn examples(workspace: &Path, mut args: ExampleArgs, action: CargoAction) -> Res
// Execute the specified action:
match action {
CargoAction::Build => build_examples(args, examples, &package_path),
CargoAction::Build(out_path) => build_examples(args, examples, &package_path, out_path),
CargoAction::Run if args.example.is_some() => run_example(args, examples, &package_path),
CargoAction::Run => run_examples(args, examples, &package_path),
}
}
fn build_examples(args: ExampleArgs, examples: Vec<Metadata>, package_path: &Path) -> Result<()> {
fn build_examples(
args: ExampleArgs,
examples: Vec<Metadata>,
package_path: &Path,
out_path: PathBuf,
) -> Result<()> {
// Determine the appropriate build target for the given package and chip:
let target = target_triple(args.package, &args.chip)?;
@ -265,7 +278,7 @@ fn build_examples(args: ExampleArgs, examples: Vec<Metadata>, package_path: &Pat
args.chip,
target,
example,
CargoAction::Build,
CargoAction::Build(out_path.clone()),
1,
args.debug,
)?;
@ -282,7 +295,7 @@ fn build_examples(args: ExampleArgs, examples: Vec<Metadata>, package_path: &Pat
args.chip,
target,
example,
CargoAction::Build,
CargoAction::Build(out_path.clone()),
1,
args.debug,
)
@ -400,7 +413,7 @@ fn tests(workspace: &Path, args: TestArgs, action: CargoAction) -> Result<()> {
let target = target_triple(Package::HilTest, &args.chip)?;
// Load all tests which support the specified chip and parse their metadata:
let mut tests = xtask::load_examples(&package_path.join("tests"), action)?
let mut tests = xtask::load_examples(&package_path.join("tests"))?
.into_iter()
.filter(|example| example.supports_chip(args.chip))
.collect::<Vec<_>>();
@ -420,7 +433,7 @@ fn tests(workspace: &Path, args: TestArgs, action: CargoAction) -> Result<()> {
args.chip,
target,
test,
action,
action.clone(),
args.repeat.unwrap_or(1),
false,
)?;
@ -436,7 +449,7 @@ fn tests(workspace: &Path, args: TestArgs, action: CargoAction) -> Result<()> {
args.chip,
target,
&test,
action,
action.clone(),
args.repeat.unwrap_or(1),
false,
)