use std::fs;
use rstest::rstest;
const CRATE: &str = "test-flip-link-app";
const FILES: [&str; 4] = ["crash", "exception", "hello", "panic"];
const TARGET: &str = "thumbv7m-none-eabi";
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
#[rstest]
#[case::normal(true)]
#[case::custom_linkerscript(false)]
fn should_link_example_firmware(#[case] default_features: bool) {
cargo::check_flip_link();
let cmd = cargo::build_example_firmware(CRATE, default_features);
cmd.success();
}
#[test]
fn should_verify_memory_layout() -> Result<()> {
cargo::check_flip_link();
cargo::build_example_firmware(CRATE, true).success();
for elf_path in elf::paths() {
let elf = fs::read(&elf_path)?;
let object = object::File::parse(&*elf)?;
let (bss, data, uninit, vector_table) = elf::get_sections(&object)?;
let initial_sp = elf::compute_initial_sp(&vector_table)?;
let bounds = elf::get_bounds(&[data, bss, uninit])?;
assert!(initial_sp <= *bounds.start(),);
}
Ok(())
}
mod cargo {
use std::process::Command;
use assert_cmd::{assert::Assert, prelude::*};
#[must_use]
pub fn build_example_firmware(rel_path: &str, default_features: bool) -> Assert {
let mut firmware_dir = std::env::current_dir().unwrap();
firmware_dir.push(rel_path);
let default_features = match default_features {
false => "--no-default-features",
true => "-v",
};
Command::new("cargo")
.args(&["build", "--examples", default_features])
.current_dir(firmware_dir)
.unwrap()
.assert()
}
pub fn check_flip_link() {
Command::new("which")
.arg("flip-link")
.unwrap()
.assert()
.success();
}
}
mod elf {
use std::{convert::TryInto, ops::RangeInclusive, path::PathBuf};
use object::{File, Object, ObjectSection, Section};
use super::*;
pub fn compute_initial_sp(vector_table: &Section) -> Result<u64> {
let data = vector_table.uncompressed_data()?;
let sp = u32::from_le_bytes(data[..4].try_into()?);
Ok(sp as u64)
}
pub fn get_bounds(sections: &[Section]) -> Result<RangeInclusive<u64>> {
let addresses = sections
.iter()
.flat_map(|sec| vec![sec.address(), sec.address() + sec.size()])
.collect::<Vec<_>>();
let min = *addresses.iter().min().ok_or(format!("empty iterator"))?;
let max = *addresses.iter().max().ok_or(format!("empty iterator"))?;
Ok(min..=max)
}
pub fn get_sections<'data, 'file>(
object: &'file File<'data>,
) -> Result<(
Section<'data, 'file>,
Section<'data, 'file>,
Section<'data, 'file>,
Section<'data, 'file>,
)> {
let get_section = |section_name| {
object
.section_by_name(section_name)
.ok_or(format!("error getting section `{}`", section_name))
};
Ok((
get_section(".bss")?,
get_section(".data")?,
get_section(".uninit")?,
get_section(".vector_table")?,
))
}
pub fn paths() -> Vec<PathBuf> {
FILES
.iter()
.map(|file_name| format!("{}/target/{}/debug/examples/{}", CRATE, TARGET, file_name))
.map(|file_path| PathBuf::from(file_path))
.collect()
}
}