Files
TerminalBasic/crates/tb-export/src/tests.rs

227 lines
7.4 KiB
Rust

use super::*;
use std::cell::Cell;
fn header(t: Target) -> Vec<u8> {
let mut b = vec![0; 256];
match t {
Target::WindowsAmd64 => {
b[..2].copy_from_slice(b"MZ");
b[0x3c..0x40].copy_from_slice(&64u32.to_le_bytes());
b[64..68].copy_from_slice(b"PE\0\0");
b[68..70].copy_from_slice(&0x8664u16.to_le_bytes());
b[86..88].copy_from_slice(&2u16.to_le_bytes());
b[88..90].copy_from_slice(&0x20bu16.to_le_bytes());
b[156..158].copy_from_slice(&3u16.to_le_bytes());
}
Target::MacosArm64 => {
b[..4].copy_from_slice(&[0xcf, 0xfa, 0xed, 0xfe]);
b[4..8].copy_from_slice(&0x0100000cu32.to_le_bytes());
b[12..16].copy_from_slice(&2u32.to_le_bytes());
}
Target::LinuxAmd64 | Target::LinuxArm64 => {
b[..7].copy_from_slice(b"\x7fELF\x02\x01\x01");
b[16..18].copy_from_slice(&2u16.to_le_bytes());
let cpu: u16 = if t == Target::LinuxAmd64 { 62 } else { 183 };
b[18..20].copy_from_slice(&cpu.to_le_bytes());
}
}
b.extend_from_slice(&runtime_marker(t));
b
}
struct Dir(PathBuf);
impl Dir {
fn new() -> Self {
let p = std::env::temp_dir().join(format!(
"tb-export-test-{}-{}",
std::process::id(),
NEXT.fetch_add(1, Ordering::Relaxed)
));
fs::create_dir(&p).unwrap();
Self(p)
}
}
impl Drop for Dir {
fn drop(&mut self) {
fs::remove_dir_all(&self.0).unwrap();
}
}
#[test]
fn targets_versions_integrity_and_payload_bounds_are_enforced() {
let d = Dir::new();
for t in Target::ALL {
let b = header(t);
assert_eq!(native_target(&b).unwrap(), t);
let path = d.0.join(t.runtime_name());
fs::write(&path, &b).unwrap();
prepare_template(&path, t).unwrap();
assert_eq!(template(&path, t).unwrap(), b);
let other = if t == Target::LinuxAmd64 {
Target::LinuxArm64
} else {
Target::LinuxAmd64
};
assert!(template(&path, other)
.unwrap_err()
.to_string()
.contains("Architektur"));
let meta = manifest(&b, t);
for wrong in [
meta.replace("runtime=1", "runtime=2"),
meta.replace("package=0.1.0", "package=9"),
meta.replace("tbc=4", "tbc=99"),
meta.replace("checksum=", "checksum=0"),
] {
fs::write(manifest_path(&path), wrong).unwrap();
assert!(template(&path, t).is_err());
}
fs::remove_file(manifest_path(&path)).unwrap();
assert!(template(&path, t).is_err());
fs::remove_file(&path).unwrap();
assert!(template(&path, t)
.unwrap_err()
.to_string()
.contains("fehlt"));
for len in 0..64 {
assert!(native_target(&b[..len]).is_err());
}
}
assert!(Target::parse("aarch64-pc-windows-msvc").is_err());
assert!(Target::parse("x86_64-apple-darwin").is_err());
let mut foreign_abi = header(Target::LinuxAmd64);
foreign_abi[7] = 9;
assert!(native_target(&foreign_abi).is_err());
let module = tb_vm::compile_source("TEST", "PRINT 42\nEND\n").unwrap();
for t in [Target::WindowsAmd64, Target::LinuxAmd64, Target::LinuxArm64] {
let mut b = header(t);
if t == Target::WindowsAmd64 {
let mut signed = b.clone();
signed[196..200].copy_from_slice(&5u32.to_le_bytes());
signed[232] = 1;
assert!(append_payload(&mut signed, &module, t)
.unwrap_err()
.to_string()
.contains("Signierte PE"));
b[152..156].copy_from_slice(&123u32.to_le_bytes());
}
append_payload(&mut b, &module, t).unwrap();
if t == Target::WindowsAmd64 {
assert_eq!(&b[152..156], &[0; 4]);
}
assert_eq!(embedded(&b, t).unwrap().to_tbc(), module.to_tbc());
let at = b.len() - FOOTER;
for field in [8, 12, 16, 20, 24, 32, 40] {
let mut bad = b.clone();
bad[at + field] ^= 0xff;
assert!(embedded(&bad, t).is_err(), "{field}");
}
for cut in 1..=FOOTER + 1 {
assert!(embedded(&b[..b.len() - cut], t).is_err());
}
let mut bad = b.clone();
bad[300] ^= 1;
assert!(embedded(&bad, t).is_err());
}
}
#[test]
fn publication_preserves_originals_on_conflict_failure_race_and_cancel() {
let d = Dir::new();
let out = d.0.join("result");
let source = d.0.join("source.bas");
fs::write(&source, b"source").unwrap();
fs::write(&out, b"old").unwrap();
assert!(publish(&out, false, &[], &|| false, |_| panic!(
"darf nicht schreiben"
))
.is_err());
assert!(publish(
&source,
true,
std::slice::from_ref(&source),
&|| false,
|_| panic!("Projektschutz")
)
.is_err());
assert!(publish(&out, true, &[], &|| false, |p| {
fs::write(p, b"new")?;
bail!("Finalisierungsfehler")
})
.is_err());
assert_eq!(fs::read(&out).unwrap(), b"old");
let cancel = Cell::new(false);
assert!(publish(&out, true, &[], &|| cancel.get(), |p| {
fs::write(p, b"new")?;
cancel.set(true);
Ok(())
})
.is_err());
assert_eq!(fs::read(&out).unwrap(), b"old");
assert!(publish(&out, true, &[], &|| true, |_| panic!("abgebrochen")).is_err());
assert!(publish(&out, true, &[], &|| false, |p| {
fs::write(p, b"new")?;
fs::write(&out, b"external")?;
Ok(())
})
.is_err());
assert_eq!(fs::read(&out).unwrap(), b"external");
fs::remove_file(&out).unwrap();
assert!(publish(&out, true, &[], &|| false, |p| {
fs::write(p, b"new")?;
fs::write(&out, b"racer")?;
Ok(())
})
.is_err());
assert_eq!(fs::read(&out).unwrap(), b"racer");
assert!(
publish(&d.0.join("missing/result"), false, &[], &|| false, |_| Ok(
()
))
.is_err()
);
publish(&out, true, &[], &|| false, |p| {
fs::write(p, b"approved")?;
Ok(())
})
.unwrap();
assert_eq!(fs::read(&out).unwrap(), b"approved");
assert_eq!(fs::read(&source).unwrap(), b"source");
assert_eq!(fs::read_dir(&d.0).unwrap().count(), 2);
#[cfg(unix)]
{
std::os::unix::fs::symlink(&source, d.0.join("alias")).unwrap();
assert!(
publish(&d.0.join("alias"), true, &[], &|| false, |_| panic!(
"Symlink"
))
.is_err()
);
}
}
#[test]
fn fehlgeschlagene_native_finalisierung_erhaelt_das_ziel() {
let dir = Dir::new();
let runtime = dir.0.join("tbrt");
let out = dir.0.join("program");
fs::write(&runtime, header(Target::MacosArm64)).unwrap();
prepare_template(&runtime, Target::MacosArm64).unwrap();
fs::write(&out, b"original").unwrap();
let module = tb_vm::compile_source("TEST", "END\n").unwrap();
let error = export(
Export {
module: &module,
target: Target::MacosArm64,
template: &runtime,
output: &out,
overwrite: true,
protected: &[],
},
&|| false,
)
.unwrap_err();
assert!(error.to_string().contains("Finalisierung"), "{error:#}");
assert_eq!(fs::read(&out).unwrap(), b"original");
assert_eq!(fs::read_dir(&dir.0).unwrap().count(), 3);
}