/ firmware / tests / sd.rs
sd.rs
  1  //! `describe("SD Card")`
  2  //!
  3  //! Tests the SD card filesystem through the `firmware::filesystems::sd`
  4  //! infrastructure. Mirrors the C++ test suite in `hardware/storage.cpp`.
  5  
  6  #![no_std]
  7  #![no_main]
  8  
  9  extern crate alloc;
 10  
 11  #[path = "common/mod.rs"]
 12  mod common;
 13  
 14  use defmt::info;
 15  use firmware::filesystems::sd;
 16  
 17  use common::Device;
 18  
 19  esp_bootloader_esp_idf::esp_app_desc!();
 20  
 21  #[cfg(test)]
 22  #[embedded_test::setup]
 23  fn setup() {
 24      rtt_target::rtt_init_defmt!();
 25  }
 26  
 27  #[cfg(test)]
 28  #[embedded_test::tests(default_timeout = 15, executor = esp_rtos::embassy::Executor::new())]
 29  mod tests {
 30      use super::*;
 31  
 32      #[init]
 33      fn init() -> Device {
 34          info!("=== SD Card — describe block ===");
 35          common::setup::boot_device()
 36      }
 37  
 38      /// `it("user observes that the SD card reports its size")`
 39      #[test]
 40      async fn user_observes_sd_card_reports_its_size(_device: Device) -> Result<(), &'static str> {
 41          let snapshot = sd::snapshot();
 42          info!("SD card size={=u32} MiB", snapshot.sd_card_size_mb);
 43          defmt::assert!(snapshot.sd_card_size_mb > 0, "SD card size is 0");
 44          Ok(())
 45      }
 46  
 47      /// `it("user writes a file, reads it back, and deletes it")`
 48      #[test]
 49      async fn user_writes_reads_and_deletes_a_file(_device: Device) -> Result<(), &'static str> {
 50          let name = "RT_TEST.BIN";
 51          let payload = b"sd-card round-trip test payload \xde\xad\xbe\xef\nline two\n";
 52  
 53          sd::write_file_at("", name, payload).map_err(|_| "write failed")?;
 54  
 55          let readback = sd::read_file_at::<256>("", name).map_err(|_| "read failed")?;
 56          defmt::assert_eq!(readback.as_slice(), payload.as_slice(), "content mismatch");
 57  
 58          sd::delete_file(name).map_err(|_| "delete failed")?;
 59  
 60          let after_delete = sd::file_size(name);
 61          defmt::assert!(after_delete.is_err(), "file still exists after delete");
 62  
 63          info!("write/read/delete roundtrip verified");
 64          Ok(())
 65      }
 66  
 67      /// `it("user appends to a file without truncating")`
 68      #[test]
 69      async fn user_appends_to_a_file(_device: Device) -> Result<(), &'static str> {
 70          let name = "APPEND.TMP";
 71          let _ = sd::delete_file(name);
 72  
 73          sd::write_file_at("", name, b"hello").map_err(|_| "initial write failed")?;
 74  
 75          sd::write_file_chunk(name, 5, b" world").map_err(|_| "append write failed")?;
 76  
 77          let readback = sd::read_file_at::<256>("", name).map_err(|_| "read failed")?;
 78          defmt::assert_eq!(
 79              readback.as_slice(),
 80              b"hello world",
 81              "append content mismatch"
 82          );
 83  
 84          sd::delete_file(name).map_err(|_| "cleanup delete failed")?;
 85  
 86          info!("append mode verified");
 87          Ok(())
 88      }
 89  
 90      /// `it("user lists a directory and finds created files")`
 91      #[test]
 92      async fn user_lists_directory_and_finds_files(_device: Device) -> Result<(), &'static str> {
 93          sd::touch_at("", "LST_A.TMP").map_err(|_| "touch a failed")?;
 94          sd::touch_at("", "LST_B.TMP").map_err(|_| "touch b failed")?;
 95  
 96          let entries = sd::list_directory_at("/").map_err(|_| "list failed")?;
 97  
 98          let found_a = entries.iter().any(|e| e.name.as_str() == "LST_A.TMP");
 99          let found_b = entries.iter().any(|e| e.name.as_str() == "LST_B.TMP");
100  
101          defmt::assert!(found_a, "LST_A.TMP not found in listing");
102          defmt::assert!(found_b, "LST_B.TMP not found in listing");
103  
104          info!("directory listing found {=usize} entries", entries.len());
105  
106          sd::delete_file("LST_A.TMP").map_err(|_| "cleanup a failed")?;
107          sd::delete_file("LST_B.TMP").map_err(|_| "cleanup b failed")?;
108  
109          Ok(())
110      }
111  
112      /// `it("user creates a directory and verifies it exists")`
113      #[test]
114      async fn user_creates_a_directory(_device: Device) -> Result<(), &'static str> {
115          let dir_name = "TSTDIR";
116          if !sd::directory_exists(dir_name) {
117              sd::create_directory(dir_name).map_err(|_| "mkdir failed")?;
118          }
119  
120          defmt::assert!(
121              sd::directory_exists(dir_name),
122              "directory not found after create"
123          );
124  
125          sd::delete_at("/", dir_name).map_err(|_| "rmdir failed")?;
126  
127          info!("directory create/delete verified");
128          Ok(())
129      }
130  
131      /// `it("user copies a file and reads the copy")`
132      #[test]
133      async fn user_copies_a_file(_device: Device) -> Result<(), &'static str> {
134          let payload = b"copy test data";
135          sd::write_file_at("", "CP_SRC.TMP", payload).map_err(|_| "write src failed")?;
136  
137          sd::copy_file("CP_SRC.TMP", "CP_DST.TMP").map_err(|_| "copy failed")?;
138  
139          let readback = sd::read_file_at::<256>("", "CP_DST.TMP").map_err(|_| "read dst failed")?;
140          defmt::assert_eq!(
141              readback.as_slice(),
142              payload.as_slice(),
143              "copy content mismatch"
144          );
145  
146          let _ = sd::delete_file("CP_SRC.TMP");
147          let _ = sd::delete_file("CP_DST.TMP");
148  
149          info!("file copy verified");
150          Ok(())
151      }
152  }