Skip to content

Commit

Permalink
cleanup simulation framework
Browse files Browse the repository at this point in the history
  • Loading branch information
Florian Guggi committed Dec 26, 2023
1 parent e44f4f0 commit 0c4447a
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 114 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -24,4 +24,4 @@ clean:

rebuild_student_archive:
rm -f tests/student_program.zip
cd tests/test_data; zip -r ../student_program.zip *
cd tests/test_data; zip -r ../student_program.zip *
10 changes: 7 additions & 3 deletions src/command/common.rs
Original file line number Diff line number Diff line change
@@ -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<u8>, n: usize) -> Result<(), CommandError> {
pub fn check_length(
com: &mut impl CommunicationHandle,
vec: &Vec<u8>,
n: usize,
) -> Result<(), CommandError> {
let actual_len = vec.len();
if actual_len != n {
log::error!("Command came with {actual_len} bytes, should have {n}");
Expand Down
4 changes: 2 additions & 2 deletions src/communication/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}
}
Expand All @@ -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 {
Expand Down
5 changes: 1 addition & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
8 changes: 3 additions & 5 deletions tests/simulation/command_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand All @@ -23,6 +22,5 @@ fn simulate_archive_is_stored_correctly() -> Result<(), std::io::Error> {
.unwrap()
);

scheduler.kill()?;
Ok(())
}
27 changes: 27 additions & 0 deletions tests/simulation/full_run.rs
Original file line number Diff line number Diff line change
@@ -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<_>>(), vec![0xde, 0xad]);

assert_eq!(simulate_get_status(&mut com).unwrap(), [0]);
}
21 changes: 10 additions & 11 deletions tests/simulation/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,27 @@ 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(())
}

#[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");
Expand Down
179 changes: 92 additions & 87 deletions tests/simulation/mod.rs
Original file line number Diff line number Diff line change
@@ -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<T: Read, U: Write> {
cobc_in: T,
cobc_out: U,
}

impl SimulationComHandle<ChildStdout, ChildStdin> {
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<T: Read, U: Write> Read for SimulationComHandle<T, U> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.cobc_in.read(buf)
}
}

impl<T: Read, U: Write> Write for SimulationComHandle<T, U> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.cobc_out.write(buf)
}

fn flush(&mut self) -> std::io::Result<()> {
self.cobc_out.flush()
}
}

impl<T: Read, U: Write> CommunicationHandle for SimulationComHandle<T, U> {
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!(
Expand All @@ -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<PoisonedChild, std::io::Error> {
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<Vec<u8>> {
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<Vec<u8>, 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<u8>) -> 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<Vec<u8>> {
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<Vec<u8>, 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)
}

Expand Down

0 comments on commit 0c4447a

Please sign in to comment.