Phase 6: Native Executables implementieren und Change archivieren
This commit is contained in:
226
crates/tb-export/src/tests.rs
Normal file
226
crates/tb-export/src/tests.rs
Normal file
@@ -0,0 +1,226 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user