From 9d4b6ed398952b3d0dcf9aee3e9d7b430f1ddadc Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Tue, 29 Oct 2024 02:33:37 -0700 Subject: [PATCH] add tests improves some line and region coverage --- crates/rf24-rs/src/radio/rf24/auto_ack.rs | 64 ++++++- crates/rf24-rs/src/radio/rf24/details.rs | 27 +++ crates/rf24-rs/src/radio/rf24/mod.rs | 112 +++++++----- .../rf24-rs/src/radio/rf24/payload_length.rs | 2 +- crates/rf24-rs/src/radio/rf24/radio.rs | 173 ++++++++++++++---- 5 files changed, 295 insertions(+), 83 deletions(-) diff --git a/crates/rf24-rs/src/radio/rf24/auto_ack.rs b/crates/rf24-rs/src/radio/rf24/auto_ack.rs index d3bf777..183f9e5 100644 --- a/crates/rf24-rs/src/radio/rf24/auto_ack.rs +++ b/crates/rf24-rs/src/radio/rf24/auto_ack.rs @@ -122,7 +122,16 @@ mod test { vec![registers::DYNPD | commands::W_REGISTER, 3u8], vec![0xEu8, 0u8], ), - // write_ack_payload() + // read FEATURE register + ( + vec![registers::FEATURE, 0u8], + vec![0xEu8, mnemonics::EN_ACK_PAY | mnemonics::EN_DPL] + ), + ( + vec![registers::DYNPD | commands::W_REGISTER, 0x3Fu8], + vec![0xEu8, 0u8], + ), + // write_ack_payload() and also marks TX FIFO as full (ack_buf.to_vec(), vec![0u8; 3]), // read dynamic payload length invalid value (vec![commands::R_RX_PL_WID, 0u8], vec![0xEu8, 0xFFu8]), @@ -139,22 +148,39 @@ mod test { // set EN_AA register with pipe 0 disabled ( vec![registers::EN_AA | commands::W_REGISTER, 0x3Eu8], - vec![0xEu8, 0x3Fu8], + vec![0xEu8, 0u8], + ), + // read EN_AA register value + (vec![registers::EN_AA, 0u8], vec![0u8, 0x3Eu8]), + // set EN_AA register with pipe 0 disabled + ( + vec![registers::EN_AA | commands::W_REGISTER, 0x3Cu8], + vec![0xEu8, 0u8], ), ]; let mut spi_mock = SpiMock::new(&spi_expectations); let mut radio = RF24::new(pin_mock.clone(), spi_mock.clone(), delay_mock); radio.allow_ack_payloads(true).unwrap(); - let buf = [0x55; 2]; - assert!(!radio.write_ack_payload(9, &buf).unwrap()); - assert!(radio.write_ack_payload(2, &buf).unwrap()); + // do again for region coverage (should result in Ok non-op) + radio.allow_ack_payloads(true).unwrap(); + // explicitly enable dynamic payloads (again) for region coverage + radio.set_dynamic_payloads(true).unwrap(); + let buf = &ack_buf[1..3]; + // write ACK payload to invalid pipe (results in Ok non-op) + assert!(!radio.write_ack_payload(9, buf).unwrap()); + // write ACK payload to valid pipe (test will also mark TX FIFO as full) + assert!(radio.write_ack_payload(2, buf).unwrap()); assert_eq!( radio.get_dynamic_payload_length(), Err(Nrf24Error::BinaryCorruption) ); assert_eq!(radio.get_dynamic_payload_length().unwrap(), 32u8); + // disable invalid pipe number (results in Ok non-op) radio.set_auto_ack_pipe(false, 9).unwrap(); + // disable auto-ack on pipe 0 (also disables allow_ack_payloads) radio.set_auto_ack_pipe(false, 0).unwrap(); + // disable pipe 1 for region coverage + radio.set_auto_ack_pipe(false, 1).unwrap(); spi_mock.done(); pin_mock.done(); } @@ -211,6 +237,34 @@ mod test { pin_mock.done(); } + /* #[test] + fn set_auto_ack_pipe() { + // Create pin + let pin_expectations = []; + let mut pin_mock = PinMock::new(&pin_expectations); + + // create delay fn + let delay_mock = NoopDelay::new(); + + let spi_expectations = spi_test_expects![ + // read EN_AA register value + ( + vec![registers::EN_AA, 0x3Fu8], + vec![0xEu8, 0u8], + ), + // write EN_AA register value + ( + vec![registers::EN_AA | commands::W_REGISTER, 0x3Eu8], + vec![0xEu8, 0u8], + ), + ]; + let mut spi_mock = SpiMock::new(&spi_expectations); + let mut radio = RF24::new(pin_mock.clone(), spi_mock.clone(), delay_mock); + radio.set_auto_ack_pipe(false, 0).unwrap(); + spi_mock.done(); + pin_mock.done(); + } */ + #[test] pub fn allow_ask_no_ack() { // Create pin diff --git a/crates/rf24-rs/src/radio/rf24/details.rs b/crates/rf24-rs/src/radio/rf24/details.rs index 6cd2292..a04c349 100644 --- a/crates/rf24-rs/src/radio/rf24/details.rs +++ b/crates/rf24-rs/src/radio/rf24/details.rs @@ -292,3 +292,30 @@ where Ok(()) } } + +#[cfg(test)] +mod test { + use crate::radio::prelude::EsbDetails; + use crate::radio::RF24; + use embedded_hal_mock::eh1::delay::NoopDelay; + use embedded_hal_mock::eh1::digital::Mock as PinMock; + use embedded_hal_mock::eh1::spi::Mock as SpiMock; + + #[test] + fn print_nothing() { + // Create pin + let pin_expectations = []; + let mut pin_mock = PinMock::new(&pin_expectations); + + // create delay fn + let delay_mock = NoopDelay::new(); + + // create spi device + let spi_expectations = []; + let mut spi_mock = SpiMock::new(&spi_expectations); + let mut radio = RF24::new(pin_mock.clone(), spi_mock.clone(), delay_mock); + assert!(radio.print_details().is_ok()); + pin_mock.done(); + spi_mock.done(); + } +} diff --git a/crates/rf24-rs/src/radio/rf24/mod.rs b/crates/rf24-rs/src/radio/rf24/mod.rs index 315e772..56d1b39 100644 --- a/crates/rf24-rs/src/radio/rf24/mod.rs +++ b/crates/rf24-rs/src/radio/rf24/mod.rs @@ -157,13 +157,11 @@ where self.set_auto_retries(0, 0)?; let buf = [0xFF; 32]; - // use write_register() instead of openWritingPipe() to bypass + // use spi_write_buf() instead of open_tx_pipe() to bypass // truncation of the address with the current RF24::addr_width value self.spi_write_buf(registers::TX_ADDR, &buf[..5])?; self.flush_tx()?; // so we can write to top level - // use write_register() instead of write_payload() to bypass - // truncation of the payload with the current RF24::payload_size value self.spi_write_buf(commands::W_TX_PAYLOAD, &buf)?; self.set_crc_length(CrcLength::Disabled)?; @@ -241,15 +239,20 @@ mod test { pin_mock.done(); } - #[test] - pub fn start_carrier_wave() { + pub fn start_carrier_wave_parametrized(is_plus_variant: bool) { // Create pin - let pin_expectations = [ - PinTransaction::set(PinState::Low), - PinTransaction::set(PinState::High), + let mut pin_expectations = [ PinTransaction::set(PinState::Low), PinTransaction::set(PinState::High), - ]; + ] + .to_vec(); + if is_plus_variant { + pin_expectations.extend([ + PinTransaction::set(PinState::Low), + PinTransaction::set(PinState::High), + ]); + } + let mut pin_mock = PinMock::new(&pin_expectations); // create delay fn @@ -260,7 +263,7 @@ mod test { let mut address = [0xFFu8; 6]; address[0] = registers::TX_ADDR | commands::W_REGISTER; - let spi_expectations = spi_test_expects![ + let mut spi_expectations = spi_test_expects![ // as_tx() // clear PRIM_RX flag ( @@ -279,28 +282,35 @@ mod test { vec![registers::RF_SETUP | commands::W_REGISTER, 0x90u8], vec![0xEu8, 0u8], ), - // disable auto-ack - ( - vec![registers::EN_AA | commands::W_REGISTER, 0u8], - vec![0xEu8, 0u8], - ), - // disable auto-retries - ( - vec![registers::SETUP_RETR | commands::W_REGISTER, 0u8], - vec![0xEu8, 0u8], - ), - // set TX address - (address.to_vec(), vec![0u8; 6]), - // flush_tx() - (vec![commands::FLUSH_TX], vec![0xEu8]), - // set TX payload - (buf.to_vec(), vec![0u8; 33]), - // set_crc_length(disabled) - (vec![registers::CONFIG, 0u8], vec![0xEu8, 0xCu8]), - ( - vec![registers::CONFIG | commands::W_REGISTER, 0u8], - vec![0xEu8, 0u8], - ), + ] + .to_vec(); + if is_plus_variant { + spi_expectations.extend(spi_test_expects![ + // disable auto-ack + ( + vec![registers::EN_AA | commands::W_REGISTER, 0u8], + vec![0xEu8, 0u8], + ), + // disable auto-retries + ( + vec![registers::SETUP_RETR | commands::W_REGISTER, 0u8], + vec![0xEu8, 0u8], + ), + // set TX address + (address.to_vec(), vec![0u8; 6]), + // flush_tx() + (vec![commands::FLUSH_TX], vec![0xEu8]), + // set TX payload + (buf.to_vec(), vec![0u8; 33]), + // set_crc_length(disabled) + (vec![registers::CONFIG, 0u8], vec![0xEu8, 0xCu8]), + ( + vec![registers::CONFIG | commands::W_REGISTER, 0u8], + vec![0xEu8, 0u8], + ), + ]); + } + spi_expectations.extend(spi_test_expects![ // set_pa_level() // set special flags in RF_SETUP register value (vec![registers::RF_SETUP, 0u8], vec![0xEu8, 0x91u8]), @@ -313,24 +323,40 @@ mod test { vec![registers::RF_CH | commands::W_REGISTER, 125u8], vec![0xEu8, 0u8], ), - // clear the tx_df and tx_ds events - ( - vec![ - registers::STATUS | commands::W_REGISTER, - mnemonics::MASK_MAX_RT | mnemonics::MASK_TX_DS, - ], - vec![0xEu8, 0u8], - ), - // assert the REUSE_TX_PL flag - (vec![commands::REUSE_TX_PL], vec![0xEu8]), - ]; + ]); + if is_plus_variant { + spi_expectations.extend(spi_test_expects![ + // clear the tx_df and tx_ds events + ( + vec![ + registers::STATUS | commands::W_REGISTER, + mnemonics::MASK_MAX_RT | mnemonics::MASK_TX_DS, + ], + vec![0xEu8, 0u8], + ), + // assert the REUSE_TX_PL flag + (vec![commands::REUSE_TX_PL], vec![0xEu8]), + ]); + } + let mut spi_mock = SpiMock::new(&spi_expectations); let mut radio = RF24::new(pin_mock.clone(), spi_mock.clone(), delay_mock); + radio._is_plus_variant = is_plus_variant; radio.start_carrier_wave(crate::PaLevel::Max, 0xFF).unwrap(); spi_mock.done(); pin_mock.done(); } + #[test] + fn start_carrier_wave_plus_variant() { + start_carrier_wave_parametrized(true); + } + + #[test] + fn start_carrier_wave_non_plus_variant() { + start_carrier_wave_parametrized(false); + } + #[test] pub fn stop_carrier_wave() { // Create pin diff --git a/crates/rf24-rs/src/radio/rf24/payload_length.rs b/crates/rf24-rs/src/radio/rf24/payload_length.rs index 8f44d3e..db63469 100644 --- a/crates/rf24-rs/src/radio/rf24/payload_length.rs +++ b/crates/rf24-rs/src/radio/rf24/payload_length.rs @@ -72,7 +72,7 @@ mod test { use embedded_hal_mock::eh1::spi::{Mock as SpiMock, Transaction as SpiTransaction}; use std::vec; - // dynamic payloads are already tested in EsbAutoAck trait as + // NOTE: dynamic payloads are already tested in EsbAutoAck trait as // these features' behaviors are interdependent. #[test] diff --git a/crates/rf24-rs/src/radio/rf24/radio.rs b/crates/rf24-rs/src/radio/rf24/radio.rs index 4c16f11..5a9dad5 100644 --- a/crates/rf24-rs/src/radio/rf24/radio.rs +++ b/crates/rf24-rs/src/radio/rf24/radio.rs @@ -171,28 +171,32 @@ where // check if in RX mode to prevent improper radio usage return Err(Self::RadioErrorType::NotAsTxError); } - self.clear_status_flags(None)?; + self.clear_status_flags(Some(StatusFlags { + rx_dr: false, + tx_ds: true, + tx_df: true, + }))?; if self._status & 1 == 1 { // TX FIFO is full already return Ok(false); } - let mut buf_len = buf.len().min(32); + let mut buf_len = buf.len().min(32) as u8; // to avoid resizing the given buf, we'll have to use self._buf directly self._buf[0] = if !ask_no_ack { commands::W_TX_PAYLOAD } else { commands::W_TX_PAYLOAD_NO_ACK }; - self._buf[1..(buf_len + 1)].copy_from_slice(&buf[..buf_len]); + self._buf[1..(buf_len + 1) as usize].copy_from_slice(&buf[..buf_len as usize]); // ensure payload_length setting is respected - if !self._dynamic_payloads_enabled && buf_len < self._payload_length as usize { + if !self._dynamic_payloads_enabled && buf_len < self._payload_length { // pad buf with zeros - for i in buf_len..self._payload_length as usize { - self._buf[i + 1] = 0; + for i in (buf_len + 1)..(self._payload_length + 1) { + self._buf[i as usize] = 0; } - buf_len = self._payload_length as usize; + buf_len = self._payload_length; } - self.spi_transfer(buf_len as u8 + 1)?; + self.spi_transfer(buf_len + 1)?; if start_tx { self._ce_pin.set_high().map_err(Nrf24Error::Gpo)?; } @@ -218,13 +222,12 @@ where /// The nRF24L01 will repeatedly use the last byte from the last /// payload even when [`RF24::read()`] is called with an empty RX FIFO. fn read(&mut self, buf: &mut [u8], len: Option) -> Result { - let buf_len = (buf.len() as u8) - .min(len.unwrap_or(if self._dynamic_payloads_enabled { + let buf_len = + (buf.len().min(32) as u8).min(len.unwrap_or(if self._dynamic_payloads_enabled { self.get_dynamic_payload_length()? } else { self._payload_length - })) - .min(32); + })); if buf_len == 0 { return Ok(0); } @@ -287,8 +290,7 @@ mod test { use embedded_hal_mock::eh1::spi::{Mock as SpiMock, Transaction as SpiTransaction}; use std::vec; - #[test] - pub fn init() { + pub fn init_parametrized(corrupted_binary: bool, is_plus_variant: bool, no_por: bool) { // Create pin let pin_expectations = [PinTransaction::set(PinState::Low)]; let mut pin_mock = PinMock::new(&pin_expectations); @@ -296,7 +298,7 @@ mod test { // create delay fn let delay_mock = NoopDelay::new(); - let spi_expectations = spi_test_expects![ + let mut spi_expectations = spi_test_expects![ // set_auto_retries() ( vec![registers::SETUP_RETR | commands::W_REGISTER, 0x5Fu8], @@ -309,20 +311,51 @@ mod test { ), // we're mocking a non-plus variant here for added coverage // read FEATURE register - (vec![registers::FEATURE, 0u8], vec![0xEu8, 0u8]), - // toggle_features() - (vec![commands::ACTIVATE, 0x73u8], vec![0xEu8, 0u8]), - // read FEATURE register - (vec![registers::FEATURE, 0u8], vec![0xEu8, 7u8]), - // toggle_features() - (vec![commands::ACTIVATE, 0x73u8], vec![0xEu8, 0u8]), - // we're also mocking a non-plus radio that didn't reset on boot, - // so lib wil clear the FEATURE register - // write FEATURE register ( - vec![registers::FEATURE | commands::W_REGISTER, 0u8], - vec![0xEu8, 0u8], + vec![registers::FEATURE, 0u8], + vec![0xEu8, if no_por { 0x7u8 } else { 0u8 }] ), + // toggle_features() + (vec![commands::ACTIVATE, 0x73u8], vec![0xEu8, 0u8]), + ] + .to_vec(); + if is_plus_variant { + // mocking a plus variant + spi_expectations.extend(spi_test_expects![ + // read FEATURE register + ( + vec![registers::FEATURE, 0u8], + vec![0xEu8, if no_por { 0x7u8 } else { 0u8 }] + ), + ]); + if no_por { + spi_expectations.extend(spi_test_expects![ + // write FEATURE register + ( + vec![registers::FEATURE | commands::W_REGISTER, 0u8], + vec![0xEu8, 0u8], + ), + ]); + } + } else { + // mocking a non-plus variant + spi_expectations.extend(spi_test_expects![ + // read FEATURE register + (vec![registers::FEATURE, 0u8], vec![0xEu8, 7u8]), + // toggle_features() + (vec![commands::ACTIVATE, 0x73u8], vec![0xEu8, 0u8]), + // we're also mocking a non-plus radio that didn't reset on boot, + // so lib wil clear the FEATURE register + // write FEATURE register + ( + vec![registers::FEATURE | commands::W_REGISTER, 0u8], + vec![0xEu8, 0u8], + ), + ]); + } + + let config_result = if corrupted_binary { 0xFFu8 } else { 14u8 }; + spi_expectations.extend(spi_test_expects![ // disable dynamic payloads ( vec![registers::DYNPD | commands::W_REGISTER, 0u8], @@ -393,16 +426,36 @@ mod test { vec![0xEu8, 0u8], ), // read CONFIG to test for binary corruption on SPI lines - (vec![registers::CONFIG, 0u8], vec![0xEu8, 14u8]), - ]; + (vec![registers::CONFIG, 0u8], vec![0xEu8, config_result]), + ]); let mut spi_mock = SpiMock::new(&spi_expectations); let mut radio = RF24::new(pin_mock.clone(), spi_mock.clone(), delay_mock); - radio.init().unwrap(); - assert!(!radio.is_plus_variant()); + let result = radio.init(); + if corrupted_binary { + assert!(result.is_err()); + } else { + assert!(result.is_ok()); + } + assert_eq!(radio.is_plus_variant(), is_plus_variant); spi_mock.done(); pin_mock.done(); } + #[test] + fn init_bin_corrupt_plus_variant() { + init_parametrized(true, true, false); + } + + #[test] + fn init_bin_correct_non_plus_variant() { + init_parametrized(false, false, false); + } + + #[test] + fn init_no_power_on_reset() { + init_parametrized(false, true, true); + } + #[test] pub fn as_rx() { // Create pin @@ -538,6 +591,7 @@ mod test { PinTransaction::set(PinState::Low), PinTransaction::set(PinState::High), PinTransaction::set(PinState::Low), + PinTransaction::set(PinState::Low), ]; let mut pin_mock = PinMock::new(&pin_expectations); @@ -557,7 +611,7 @@ mod test { ( vec![ registers::STATUS | commands::W_REGISTER, - mnemonics::MASK_MAX_RT | mnemonics::MASK_RX_DR | mnemonics::MASK_TX_DS, + mnemonics::MASK_MAX_RT | mnemonics::MASK_TX_DS, ], vec![0xEu8, 0u8], ), @@ -571,10 +625,12 @@ mod test { ( vec![ registers::STATUS | commands::W_REGISTER, - mnemonics::MASK_MAX_RT | mnemonics::MASK_RX_DR | mnemonics::MASK_TX_DS, + mnemonics::MASK_MAX_RT | mnemonics::MASK_TX_DS, ], vec![0xFu8, 0u8], ), + // flush_tx() + (vec![commands::FLUSH_TX], vec![0xEu8]), ]; let mut spi_mock = SpiMock::new(&spi_expectations); let mut radio = RF24::new(pin_mock.clone(), spi_mock.clone(), delay_mock); @@ -583,7 +639,55 @@ mod test { // again using simulated full TX FIFO assert!(!radio.send(&payload, false).unwrap()); radio._config_reg |= 1; // simulate RX mode - assert!(!radio.send(&payload, false).unwrap()); + assert!(radio.send(&payload, false).is_err()); + spi_mock.done(); + pin_mock.done(); + } + + #[test] + fn ask_no_ack() { + // Create pin + let pin_expectations = []; + let mut pin_mock = PinMock::new(&pin_expectations); + + // create delay fn + let delay_mock = NoopDelay::new(); + + let mut buf = [0u8; 33]; + buf[0] = commands::W_TX_PAYLOAD_NO_ACK; + let payload = [0x55; 8]; + buf[1..9].copy_from_slice(&payload); + let mut dyn_buf = [0x55; 9]; + dyn_buf[0] = commands::W_TX_PAYLOAD_NO_ACK; + + let spi_expectations = spi_test_expects![ + // clear_status_flags() + ( + vec![ + registers::STATUS | commands::W_REGISTER, + mnemonics::MASK_MAX_RT | mnemonics::MASK_TX_DS, + ], + vec![0xEu8, 0u8], + ), + // write payload + (buf.to_vec(), vec![0u8; 33]), + // clear_status_flags() + ( + vec![ + registers::STATUS | commands::W_REGISTER, + mnemonics::MASK_MAX_RT | mnemonics::MASK_TX_DS, + ], + vec![0xEu8, 0u8], + ), + // write dynamically sized payload + (dyn_buf.to_vec(), vec![0u8; 9]), + ]; + let mut spi_mock = SpiMock::new(&spi_expectations); + let mut radio = RF24::new(pin_mock.clone(), spi_mock.clone(), delay_mock); + assert!(radio.write(&payload, true, false).unwrap()); + // upload a dynamically sized payload + radio._dynamic_payloads_enabled = true; + assert!(radio.write(&payload, true, false).unwrap()); spi_mock.done(); pin_mock.done(); } @@ -631,6 +735,7 @@ mod test { let mut payload = [0u8; 32]; assert_eq!(32u8, radio.read(&mut payload, None).unwrap()); assert_eq!(payload, [0x55u8; 32]); + assert_eq!(0u8, radio.read(&mut payload, Some(0)).unwrap()); radio._dynamic_payloads_enabled = true; assert_eq!(32u8, radio.read(&mut payload, None).unwrap()); assert_eq!(payload, [0xAA; 32]);