From 0c4447af105975ad153fdf87cb4d94acd6522f43 Mon Sep 17 00:00:00 2001 From: Florian Guggi Date: Sat, 16 Dec 2023 23:15:03 +0100 Subject: [PATCH] cleanup simulation framework --- Makefile | 4 +- src/command/common.rs | 10 +- src/communication/mod.rs | 4 +- src/main.rs | 5 +- tests/simulation/command_execution.rs | 8 +- tests/simulation/full_run.rs | 27 ++++ tests/simulation/logging.rs | 21 ++- tests/simulation/mod.rs | 179 +++++++++++++------------- 8 files changed, 144 insertions(+), 114 deletions(-) create mode 100644 tests/simulation/full_run.rs diff --git a/Makefile b/Makefile index c80ec69..cc7d0fe 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ coverage: build_with_cov firefox ./target/debug/coverage/index.html& sw_test: - RUST_LOG=info cargo test --release --features mock + cargo build --release && RUST_LOG=info cargo test --release --features mock packs: cargo test build_pack --features rpi @@ -24,4 +24,4 @@ clean: rebuild_student_archive: rm -f tests/student_program.zip - cd tests/test_data; zip -r ../student_program.zip * \ No newline at end of file + cd tests/test_data; zip -r ../student_program.zip * diff --git a/src/command/common.rs b/src/command/common.rs index 2b33620..f512657 100644 --- a/src/command/common.rs +++ b/src/command/common.rs @@ -1,8 +1,12 @@ -use std::time::Duration; -use crate::communication::{CommunicationHandle, CEPPacket}; use super::{CommandError, CommandResult, SyncExecutionContext}; +use crate::communication::{CEPPacket, CommunicationHandle}; +use std::time::Duration; -pub fn check_length(com: &mut impl CommunicationHandle, vec: &Vec, n: usize) -> Result<(), CommandError> { +pub fn check_length( + com: &mut impl CommunicationHandle, + vec: &Vec, + n: usize, +) -> Result<(), CommandError> { let actual_len = vec.len(); if actual_len != n { log::error!("Command came with {actual_len} bytes, should have {n}"); diff --git a/src/communication/mod.rs b/src/communication/mod.rs index 7f74bd0..8bd501c 100644 --- a/src/communication/mod.rs +++ b/src/communication/mod.rs @@ -120,7 +120,7 @@ pub trait CommunicationHandle: Read + Write { match self.receive_packet()? { CEPPacket::Ack => Ok(()), CEPPacket::Nack => Err(CommunicationError::NotAcknowledged), - _ => Err(CommunicationError::PacketInvalidError) + _ => Err(CommunicationError::PacketInvalidError), } } } @@ -147,7 +147,7 @@ pub enum CommunicationError { /// Signals that a receive timed out TimedOut, /// Nack was received when Ack was expected - NotAcknowledged + NotAcknowledged, } impl std::fmt::Display for CommunicationError { diff --git a/src/main.rs b/src/main.rs index d58bcd1..9e40868 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,7 @@ #![allow(non_snake_case)] use core::time; use rppal::gpio::Gpio; -use std::{ - thread, - time::Duration, -}; +use std::{thread, time::Duration}; use STS1_EDU_Scheduler::communication::CommunicationHandle; use simplelog as sl; diff --git a/tests/simulation/command_execution.rs b/tests/simulation/command_execution.rs index f42c722..d306168 100644 --- a/tests/simulation/command_execution.rs +++ b/tests/simulation/command_execution.rs @@ -2,11 +2,10 @@ use crate::simulation::*; #[test] fn simulate_archive_is_stored_correctly() -> Result<(), std::io::Error> { - let (mut scheduler, mut serial_port) = start_scheduler("archive_is_stored_correctly")?; - let mut cobc_in = serial_port.stdout.take().unwrap(); - let mut cobc_out = serial_port.stdin.take().unwrap(); + let (mut com, _socat) = SimulationComHandle::with_socat_proc("archive_is_stored_correctly"); + let _sched = start_scheduler("archive_is_stored_correctly")?; - simulate_test_store_archive(&mut cobc_in, &mut cobc_out, 1)?; + simulate_test_store_archive(&mut com, 1).unwrap(); std::thread::sleep(std::time::Duration::from_millis(400)); assert_eq!( @@ -23,6 +22,5 @@ fn simulate_archive_is_stored_correctly() -> Result<(), std::io::Error> { .unwrap() ); - scheduler.kill()?; Ok(()) } diff --git a/tests/simulation/full_run.rs b/tests/simulation/full_run.rs new file mode 100644 index 0000000..441fa9b --- /dev/null +++ b/tests/simulation/full_run.rs @@ -0,0 +1,27 @@ +use crate::simulation::*; +use std::{io::Cursor, time::Duration}; + +#[test] +fn full_run() { + let (mut com, _socat) = SimulationComHandle::with_socat_proc("full_run"); + let _sched = start_scheduler("full_run").unwrap(); + + // store and execute program + simulate_test_store_archive(&mut com, 1).unwrap(); + simulate_execute_program(&mut com, 1, 3, 3).unwrap(); + std::thread::sleep(Duration::from_secs(1)); + + // read program finished and result ready + assert_eq!(simulate_get_status(&mut com).unwrap(), [1, 1, 0, 3, 0, 0, 0, 0]); + assert_eq!(simulate_get_status(&mut com).unwrap(), [2, 1, 0, 3, 0, 0, 0]); + + // Check result + let result = simulate_return_result(&mut com, 1, 3).unwrap(); + let mut result_archive = zip::ZipArchive::new(Cursor::new(result)).unwrap(); + com.send_packet(&CEPPacket::Ack).unwrap(); + + let result_file = result_archive.by_name(&"3").unwrap(); + assert_eq!(result_file.bytes().map(|b| b.unwrap()).collect::>(), vec![0xde, 0xad]); + + assert_eq!(simulate_get_status(&mut com).unwrap(), [0]); +} diff --git a/tests/simulation/logging.rs b/tests/simulation/logging.rs index 08cd64a..332af4c 100644 --- a/tests/simulation/logging.rs +++ b/tests/simulation/logging.rs @@ -2,9 +2,9 @@ use crate::simulation::*; #[test] fn logfile_is_created() -> Result<(), std::io::Error> { - let (mut scheduler, _) = start_scheduler("log_created")?; + let (_, _proc) = SimulationComHandle::with_socat_proc("log_created"); + let _sched = start_scheduler("log_created")?; std::thread::sleep(std::time::Duration::from_millis(400)); - scheduler.kill().unwrap(); assert!(std::path::Path::new("./tests/tmp/log_created/log").exists()); Ok(()) @@ -12,18 +12,17 @@ fn logfile_is_created() -> Result<(), std::io::Error> { #[test] fn logfile_is_cleared_after_sent() -> std::io::Result<()> { - let (mut scheduler, mut serial_port) = start_scheduler("log_is_cleared_after_sent")?; - let mut cobc_in = serial_port.stdout.take().unwrap(); - let mut cobc_out = serial_port.stdin.take().unwrap(); + let (mut com, _socat) = SimulationComHandle::with_socat_proc("log_is_cleared_after_sent"); + let _sched = start_scheduler("log_is_cleared_after_sent")?; - simulate_test_store_archive(&mut cobc_in, &mut cobc_out, 1).unwrap(); - simulate_execute_program(&mut cobc_in, &mut cobc_out, 1, 0, 3).unwrap(); - std::thread::sleep(std::time::Duration::from_millis(100)); - let _ = simulate_return_result(&mut cobc_in, &mut cobc_out, 1, 0).unwrap(); - cobc_out.write_all(&CEPPacket::Ack.serialize()).unwrap(); + simulate_test_store_archive(&mut com, 1).unwrap(); + com.send_packet(&CEPPacket::Data(execute_program(1, 0, 3))).unwrap(); + com.await_ack(&Duration::MAX).unwrap(); std::thread::sleep(std::time::Duration::from_millis(100)); - scheduler.kill().unwrap(); + let _ = simulate_return_result(&mut com, 1, 0).unwrap(); + com.send_packet(&CEPPacket::Ack).unwrap(); + std::thread::sleep(std::time::Duration::from_millis(100)); let log_metadata = std::fs::metadata("./tests/tmp/log_is_cleared_after_sent/log")?; assert!(log_metadata.len() < 50, "Logfile is not empty"); diff --git a/tests/simulation/mod.rs b/tests/simulation/mod.rs index 5e74d2a..6d457bc 100644 --- a/tests/simulation/mod.rs +++ b/tests/simulation/mod.rs @@ -1,12 +1,65 @@ mod command_execution; +mod full_run; mod logging; use std::{ io::{Read, Write}, - process::{Child, Stdio}, + process::{Child, ChildStdin, ChildStdout, Stdio}, + time::Duration, }; +use STS1_EDU_Scheduler::communication::{CEPPacket, CommunicationError, CommunicationHandle}; -use STS1_EDU_Scheduler::communication::CEPPacket; +pub struct SimulationComHandle { + cobc_in: T, + cobc_out: U, +} + +impl SimulationComHandle { + fn with_socat_proc(unique: &str) -> (Self, PoisonedChild) { + let mut proc = std::process::Command::new("socat") + .arg("stdio") + .arg(format!("pty,raw,echo=0,link=/tmp/ttySTS1-{},b921600,wait-slave", unique)) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + + loop { + if std::path::Path::new(&format!("/tmp/ttySTS1-{unique}")).exists() { + break; + } + std::thread::sleep(Duration::from_millis(50)); + } + + ( + Self { cobc_in: proc.stdout.take().unwrap(), cobc_out: proc.stdin.take().unwrap() }, + PoisonedChild(proc), + ) + } +} + +impl Read for SimulationComHandle { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.cobc_in.read(buf) + } +} + +impl Write for SimulationComHandle { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.cobc_out.write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.cobc_out.flush() + } +} + +impl CommunicationHandle for SimulationComHandle { + const INTEGRITY_ACK_TIMEOUT: Duration = Duration::MAX; + const UNLIMITED_TIMEOUT: Duration = Duration::MAX; + + fn set_timeout(&mut self, _timeout: &std::time::Duration) {} +} fn get_config_str(unique: &str) -> String { format!( @@ -22,120 +75,72 @@ fn get_config_str(unique: &str) -> String { ) } -pub fn start_scheduler(unique: &str) -> Result<(Child, Child), std::io::Error> { +/// A simple wrapper that ensures child processes are killed when dropped +struct PoisonedChild(pub Child); +impl Drop for PoisonedChild { + fn drop(&mut self) { + self.0.kill().unwrap(); + } +} + +fn start_scheduler(unique: &str) -> Result { let test_dir = format!("./tests/tmp/{}", unique); let scheduler_bin = std::fs::canonicalize("./target/release/STS1_EDU_Scheduler")?; let _ = std::fs::remove_dir_all(&test_dir); std::fs::create_dir_all(&test_dir)?; std::fs::write(format!("{}/config.toml", &test_dir), get_config_str(unique))?; - let serial_port = std::process::Command::new("socat") - .arg("stdio") - .arg(format!("pty,raw,echo=0,link=/tmp/ttySTS1-{},b921600,wait-slave", unique)) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn()?; - std::thread::sleep(std::time::Duration::from_millis(100)); - let scheduler = std::process::Command::new(scheduler_bin).current_dir(test_dir).spawn().unwrap(); - Ok((scheduler, serial_port)) -} - -pub fn receive_ack(reader: &mut impl std::io::Read) -> Result<(), std::io::Error> { - let mut buffer = [0; 1]; - reader.read_exact(&mut buffer).unwrap(); - - if buffer[0] == CEPPacket::Ack.serialize()[0] { - Ok(()) - } else { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - format!("received {:#x} instead of Ack", buffer[0]), - )) - } + Ok(PoisonedChild(scheduler)) } pub fn simulate_test_store_archive( - cobc_in: &mut impl std::io::Read, - cobc_out: &mut impl std::io::Write, + com: &mut impl CommunicationHandle, program_id: u16, -) -> std::io::Result<()> { - let archive = std::fs::read("tests/student_program.zip")?; - cobc_out.write_all(&CEPPacket::Data(store_archive(program_id)).serialize())?; - receive_ack(cobc_in)?; - cobc_out.write_all(&CEPPacket::Data(archive).serialize())?; - receive_ack(cobc_in)?; - cobc_out.write_all(&CEPPacket::Eof.serialize())?; - receive_ack(cobc_in)?; - receive_ack(cobc_in)?; +) -> Result<(), CommunicationError> { + let archive = std::fs::read("tests/student_program.zip").unwrap(); + com.send_packet(&CEPPacket::Data(store_archive(program_id)))?; + com.send_multi_packet(&archive)?; + com.await_ack(&Duration::MAX)?; Ok(()) } pub fn simulate_execute_program( - cobc_in: &mut impl std::io::Read, - cobc_out: &mut impl std::io::Write, + com: &mut impl CommunicationHandle, program_id: u16, timestamp: u32, timeout: u16, -) -> std::io::Result<()> { - cobc_out - .write_all(&CEPPacket::Data(execute_program(program_id, timestamp, timeout)).serialize())?; - receive_ack(cobc_in)?; - receive_ack(cobc_in)?; +) -> Result<(), CommunicationError> { + com.send_packet(&CEPPacket::Data(execute_program(program_id, timestamp, timeout)))?; + com.await_ack(&Duration::MAX)?; + Ok(()) } -pub fn simulate_return_result( - cobc_in: &mut impl std::io::Read, - cobc_out: &mut impl std::io::Write, - program_id: u16, - timestamp: u32, -) -> std::io::Result> { - cobc_out.write_all(&CEPPacket::Data(return_result(program_id, timestamp)).serialize())?; - receive_ack(cobc_in)?; +pub fn simulate_get_status( + com: &mut impl CommunicationHandle, +) -> Result, CommunicationError> { + com.send_packet(&CEPPacket::Data(get_status()))?; + let response = com.receive_packet()?; - let data = read_multi_data_packets(cobc_in, cobc_out)?; - Ok(data) -} - -/// Reads a data packet from input and returns the data content (does not check CRC) -pub fn read_data_packet(input: &mut impl std::io::Read, data: &mut Vec) -> std::io::Result<()> { - let mut header = [0; 3]; - input.read_exact(&mut header)?; - if header[0] != 0x8B { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Expected data header (0x8B), received {:#04x}", header[0]), - )); + if let CEPPacket::Data(data) = response { + Ok(data) + } else { + Err(CommunicationError::PacketInvalidError) } - - let data_len = u16::from_le_bytes([header[1], header[2]]); - input.take((data_len + 4).into()).read_to_end(data)?; - - Ok(()) } -/// Reads a multi packet round without checking the CRC and returns the concatenated contents -pub fn read_multi_data_packets( - input: &mut impl std::io::Read, - output: &mut impl std::io::Write, -) -> std::io::Result> { - let mut eof_byte = [0; 1]; - let mut data = Vec::new(); - loop { - read_data_packet(input, &mut data)?; - output.write_all(&CEPPacket::Ack.serialize())?; - - input.read_exact(&mut eof_byte)?; - if eof_byte[0] == CEPPacket::Eof.serialize()[0] { - break; - } - } +pub fn simulate_return_result( + com: &mut impl CommunicationHandle, + program_id: u16, + timestamp: u32, +) -> Result, CommunicationError> { + com.send_packet(&CEPPacket::Data(return_result(program_id, timestamp)))?; + let data = com.receive_multi_packet(|| false)?; - output.write_all(&CEPPacket::Ack.serialize())?; Ok(data) }