Spezifikationsabgleich und Regressionsnachweise abschließen
This commit is contained in:
188
crates/tb-cli/tests/foreign.rs
Normal file
188
crates/tb-cli/tests/foreign.rs
Normal file
@@ -0,0 +1,188 @@
|
||||
//! Optionaler öffentlicher Bestand; kein Netzwerk und keine GUI.
|
||||
use std::{path::PathBuf, process::Command, time::Duration};
|
||||
use tb_runtime::host::{CaptureHost, Ereignis, Host};
|
||||
use tb_vm::{
|
||||
bytecode::CompiledModule,
|
||||
interp::{RunEvent, Vm},
|
||||
};
|
||||
#[path = "../../../tests/support/prozessfrist.rs"]
|
||||
mod prozessfrist;
|
||||
|
||||
#[derive(Default)]
|
||||
struct PruefHost {
|
||||
inner: CaptureHost,
|
||||
dialogantwort: Option<Ereignis>,
|
||||
dialog_gesehen: bool,
|
||||
}
|
||||
impl Host for PruefHost {
|
||||
fn present(&mut self, screen: &tb_runtime::screen::TextScreen) {
|
||||
self.dialog_gesehen |= tb_runtime::snapshot::text(screen).contains("Save changes to");
|
||||
self.inner.present(screen);
|
||||
}
|
||||
fn next_event(&mut self, block: bool) -> Option<Ereignis> {
|
||||
if block {
|
||||
if let Some(event) = self.dialogantwort.take() {
|
||||
return Some(event);
|
||||
}
|
||||
}
|
||||
self.inner.next_event(block)
|
||||
}
|
||||
fn jetzt_ms(&mut self) -> u64 {
|
||||
self.inner.jetzt_ms()
|
||||
}
|
||||
fn warten(&mut self, deadline: Option<u64>) -> Option<Ereignis> {
|
||||
self.inner.warten(deadline)
|
||||
}
|
||||
}
|
||||
|
||||
const REVISION: &str = "1cdd2b32b829fe1721d0b6aecc433abc47a96fb6";
|
||||
|
||||
#[test]
|
||||
#[ignore = "TB_VBDOS_REPO muss auf cout/vbdos in der dokumentierten Revision zeigen"]
|
||||
fn oeffentliche_formularprogramme_sind_reproduzierbar_bedienbar() {
|
||||
let _frist =
|
||||
prozessfrist::Prozessfrist::neu("öffentlicher VBDOS-Bestand", Duration::from_secs(60));
|
||||
let repo = PathBuf::from(std::env::var_os("TB_VBDOS_REPO").expect("TB_VBDOS_REPO fehlt"));
|
||||
let rev = Command::new("git")
|
||||
.arg("-C")
|
||||
.arg(&repo)
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(rev.status.success());
|
||||
assert_eq!(String::from_utf8_lossy(&rev.stdout).trim(), REVISION);
|
||||
let original = std::env::current_dir().unwrap();
|
||||
let base = std::env::temp_dir().join(format!("tb-foreign-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&base).unwrap();
|
||||
let archive = base.join("source.tar");
|
||||
assert!(Command::new("git")
|
||||
.arg("-C")
|
||||
.arg(&repo)
|
||||
.args(["archive", "--format=tar", "-o"])
|
||||
.arg(&archive)
|
||||
.arg(REVISION)
|
||||
.status()
|
||||
.unwrap()
|
||||
.success());
|
||||
for (entry, keys) in [
|
||||
("graphics/graphics.mak", vec![("x", 4)]),
|
||||
("microsoft/check.mak", vec![("f", 4), ("x", 0)]),
|
||||
("microsoft/qlbview.mak", vec![("\u{1b}", 0)]),
|
||||
("microsoft/seek.mak", vec![("x", 4)]),
|
||||
(
|
||||
"microsoft/spindemo.mak",
|
||||
vec![("\t", 0), ("\t", 0), ("\0H", 0)],
|
||||
),
|
||||
("microsoft/notepad.frm", vec![("f", 4), ("x", 0)]),
|
||||
("misc/mentors/mentors.frm", vec![("f", 4), ("x", 0)]),
|
||||
] {
|
||||
let mut results = Vec::new();
|
||||
for repeat in 0..2 {
|
||||
let dir = base.join(format!("run-{repeat}"));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
assert!(Command::new("tar")
|
||||
.arg("-xf")
|
||||
.arg(&archive)
|
||||
.arg("-C")
|
||||
.arg(&dir)
|
||||
.status()
|
||||
.unwrap()
|
||||
.success());
|
||||
let path = dir.join(entry);
|
||||
let build = Command::new(env!("CARGO_BIN_EXE_tbc"))
|
||||
.arg("build")
|
||||
.arg(&path)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(
|
||||
build.status.success() && build.stderr.is_empty(),
|
||||
"{entry}: {build:?}"
|
||||
);
|
||||
let module =
|
||||
CompiledModule::from_tbc(&std::fs::read(path.with_extension("tbc")).unwrap())
|
||||
.unwrap();
|
||||
let mut vm = Vm::new(module);
|
||||
vm.rt.screen.resize(100, 30);
|
||||
vm.forms.resize(100, 30);
|
||||
std::env::set_current_dir(path.parent().unwrap()).unwrap();
|
||||
let mut host = PruefHost::default();
|
||||
assert_eq!(vm.run(&mut host), RunEvent::Ended, "{entry}: Start");
|
||||
assert_eq!(
|
||||
vm.run_visible_forms(&mut host),
|
||||
RunEvent::Ended,
|
||||
"{entry}: initiale Ereignisse"
|
||||
);
|
||||
assert!(
|
||||
vm.forms.has_visible_forms(),
|
||||
"{entry}: kein sichtbares Startformular"
|
||||
);
|
||||
let text1 = vm
|
||||
.forms
|
||||
.objects
|
||||
.iter()
|
||||
.position(|o| o.description.name.eq_ignore_ascii_case("Text1"));
|
||||
let before = tb_runtime::snapshot::snapshot(&vm.rt.screen);
|
||||
let mut states = Vec::new();
|
||||
for (key, shift) in &keys {
|
||||
vm.rt.ende = false;
|
||||
if entry == "microsoft/notepad.frm" && *key == "x" {
|
||||
host.dialogantwort = Some(Ereignis::Taste("n".into(), 0));
|
||||
}
|
||||
host.inner.ereignis(Ereignis::Taste((*key).into(), *shift));
|
||||
assert_eq!(
|
||||
vm.run_visible_forms(&mut host),
|
||||
RunEvent::Ended,
|
||||
"{entry}: Taste {key:?}"
|
||||
);
|
||||
states.push((
|
||||
vm.forms.has_visible_forms(),
|
||||
vm.forms.menu_is_open(),
|
||||
vm.forms.active_form(),
|
||||
tb_runtime::snapshot::snapshot(&vm.rt.screen),
|
||||
));
|
||||
}
|
||||
if entry.contains("spindemo") {
|
||||
let text_id =
|
||||
tb_frontend::forms::property(tb_frontend::forms::ObjectClass::TextBox, "TEXT")
|
||||
.unwrap()
|
||||
.0;
|
||||
assert_eq!(
|
||||
vm.forms.get(text1.unwrap() as u16, text_id).unwrap(),
|
||||
tb_ui::forms::PropertyValue::String(" 1".into()),
|
||||
"{entry}: Spin-Handler hat Text1 nicht aktualisiert"
|
||||
);
|
||||
assert!(
|
||||
states[2].3 != states[1].3,
|
||||
"Pfeiltaste muss den sichtbaren Wert ändern"
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
!vm.forms.has_visible_forms(),
|
||||
"{entry}: Exit/Cancel hat das Formular nicht entladen"
|
||||
);
|
||||
}
|
||||
if keys.len() == 2 {
|
||||
assert!(
|
||||
states[0].1 && !states[1].1,
|
||||
"{entry}: Menü öffnen und Exit auswählen"
|
||||
);
|
||||
}
|
||||
if entry == "microsoft/notepad.frm" {
|
||||
assert!(
|
||||
host.dialog_gesehen && host.dialogantwort.is_none(),
|
||||
"Notepad: Save-Dialog mit N beantworten"
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
states.iter().any(|s| !s.0 || s.3 != before),
|
||||
"{entry}: keine sichtbare Zustandsänderung"
|
||||
);
|
||||
eprintln!("{entry}: Lauf {} bestanden", repeat + 1);
|
||||
results.push(states);
|
||||
std::env::set_current_dir(&original).unwrap();
|
||||
std::fs::remove_dir_all(&dir).unwrap();
|
||||
}
|
||||
assert_eq!(results[0], results[1], "{entry}: nicht reproduzierbar");
|
||||
}
|
||||
std::fs::remove_dir_all(base).unwrap();
|
||||
}
|
||||
1084
crates/tb-cli/tests/inventar.rs
Normal file
1084
crates/tb-cli/tests/inventar.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -128,3 +128,58 @@ fn cli_meldet_physische_quellorte_auch_nach_verschachtelten_includes() {
|
||||
assert!(tbc(&dir, "run", "empty.bas").status.success());
|
||||
std::fs::remove_dir_all(dir).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn erlaubte_grafik_und_benannte_nonfeatures_werden_per_cli_geprueft() {
|
||||
let dir = std::env::temp_dir().join(format!("tb-graphics-check-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let path = dir.join("probe.bas");
|
||||
for mode in 0..=13 {
|
||||
std::fs::write(
|
||||
&path,
|
||||
format!("SCREEN {mode}\nLINE (1,1)-(4,4),1\nPAINT (2,2),1\nVIEW (0,0)-(10,10)\n"),
|
||||
)
|
||||
.unwrap();
|
||||
let out = tbc(&dir, "check", "probe.bas");
|
||||
assert!(
|
||||
out.status.success() && out.stderr.is_empty(),
|
||||
"SCREEN {mode}: {out:?}"
|
||||
);
|
||||
}
|
||||
for (name, source) in [
|
||||
("POKE", "POKE 1,2"),
|
||||
("PEEK", "PRINT PEEK(1)"),
|
||||
("CIRCLE", "CIRCLE (1,1),1"),
|
||||
("WINDOW", "WINDOW (0,0)-(10,10)"),
|
||||
("IOCTL", "IOCTL #1, \"x\""),
|
||||
("PSET", "PSET (1,1),2"),
|
||||
("PSET", "PSET STEP (1,1),2"),
|
||||
("PRESET", "PRESET (1,1),2"),
|
||||
("PRESET", "PRESET STEP (1,1),2"),
|
||||
("CIRCLE", "CIRCLE STEP (1,1),2"),
|
||||
("WINDOW", "WINDOW SCREEN (0,0)-(10,10)"),
|
||||
("WINDOW", "WINDOW"),
|
||||
("PALETTE", "DIM a%(15)\nPALETTE USING a%(0)"),
|
||||
("PALETTE", "PALETTE"),
|
||||
("PALETTE", "IF 1 THEN PALETTE USING a%(0)"),
|
||||
] {
|
||||
std::fs::write(&path, source).unwrap();
|
||||
let out = tbc(&dir, "check", "probe.bas");
|
||||
let message = String::from_utf8_lossy(&out.stderr);
|
||||
assert!(
|
||||
!out.status.success()
|
||||
&& message.contains("Feature unavailable")
|
||||
&& message.contains(name),
|
||||
"{out:?}"
|
||||
);
|
||||
}
|
||||
// Bibliotheksnamen sind keine reservierten Variablennamen.
|
||||
std::fs::write(
|
||||
&path,
|
||||
"DIM PSET(2), PALETTE(2)\nPSET(1)=3\nPALETTE(1)=4\nPRINT PSET(1),PALETTE(1)",
|
||||
)
|
||||
.unwrap();
|
||||
let out = tbc(&dir, "check", "probe.bas");
|
||||
assert!(out.status.success(), "{out:?}");
|
||||
std::fs::remove_dir_all(dir).unwrap();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user