diff --git a/PLAN.md b/PLAN.md index 2aa7635..2c97aab 100644 --- a/PLAN.md +++ b/PLAN.md @@ -509,7 +509,7 @@ und keinen nativen Linker. Systemvoraussetzungen zur EXE-Finalisierung (insbesondere Signierung auf macOS) werden separat geprüft und dokumentiert. Gesamtproposal und Abhängigkeiten: -`openspec/changes/phase-6-01-kompatibilitaet-und-leistungsabnahme/phase-6-uebersicht.md`. +`openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/phase-6-uebersicht.md`. - [ ] Kompatibilitäts-Testsuite ausbauen (Snapshot-Tests der Bildschirmausgabe) - [ ] Plattformtests: Windows Terminal, Linux (mind. 2 Emulatoren), macOS — diff --git a/crates/tb-cli/tests/compat.rs b/crates/tb-cli/tests/compat.rs index 2a34724..eca9385 100644 --- a/crates/tb-cli/tests/compat.rs +++ b/crates/tb-cli/tests/compat.rs @@ -206,6 +206,9 @@ fn ereignis_direktive(name: &str, src: &str) -> Vec { ms.parse() .unwrap_or_else(|_| panic!("{name}: ungültige Zeit {ms:?}")), )); + } else if let Some(size) = teil.strip_prefix("resize:") { + let (cols, rows) = groesse_lesen(name, size); + out.push(Ereignisschritt::Ereignis(Ereignis::Groesse { cols, rows })); } else if let Some(mouse) = teil.strip_prefix("mouse:") { let fields: Vec<_> = mouse.split(',').map(str::trim).collect(); let [kind, button, shift, row, col] = fields.as_slice() else { @@ -516,15 +519,22 @@ fn assert_output_matches(file: &str, want: &str, got: &str) { (Some(w), Some(g)) => { for (z, (wz, gz)) in w.lines().zip(g.lines()).enumerate() { if wz != gz { - // Je Zelle zwei Hexziffern → Spalte aus dem Byteoffset. - let spalte = wz + // Der Snapshot lässt Fortsetzungszellen breiter Zeichen aus. + let index = wz .chars() .zip(gz.chars()) .position(|(a, b)| a != b) - .map(|i| i / 2 + 1) - .unwrap_or(0); - let paar = - |s: &str| s.chars().skip((spalte - 1) * 2).take(2).collect::(); + .map(|i| i / 2) + .unwrap_or_else(|| wz.len().min(gz.len()) / 2); + let prefix: String = want_text + .lines() + .nth(z) + .unwrap_or("") + .chars() + .take(index) + .collect(); + let spalte = ratatui::text::Line::raw(prefix).width() + 1; + let paar = |s: &str| s.chars().skip(index * 2).take(2).collect::(); panic!( "{file}: Farbabweichung in Zeile {}, Spalte {spalte}:\n \ Soll: {}\n Ist: {}", @@ -596,9 +606,9 @@ fn uhr_direktive_wird_gelesen() { fn ereignisfolge_liefert_taste_maus_und_zeit_in_reihenfolge() { let steps = ereignis_direktive( "form.frm", - "' tb-events: key:%o | mouse:down,1,0,5,7 | time:250ms | key:", + "' tb-events: key:%o | mouse:down,1,0,5,7 | time:250ms | key: | resize:100x30", ); - assert_eq!(steps.len(), 4); + assert_eq!(steps.len(), 5); assert!( matches!(&steps[0], Ereignisschritt::Ereignis(tb_runtime::host::Ereignis::Taste(key, shift)) if key == "o" && *shift == 4) ); @@ -609,6 +619,13 @@ fn ereignisfolge_liefert_taste_maus_und_zeit_in_reihenfolge() { assert!( matches!(&steps[3], Ereignisschritt::Ereignis(tb_runtime::host::Ereignis::Taste(key, _)) if key == tb_runtime::host::taste::ENTER) ); + assert!(matches!( + steps[4], + Ereignisschritt::Ereignis(tb_runtime::host::Ereignis::Groesse { + cols: 100, + rows: 30 + }) + )); } #[test] @@ -786,6 +803,38 @@ fn farbabweichung_nennt_zeile_spalte_soll_und_ist() { assert!(m.contains("Zeile 1"), "{m}"); assert!(m.contains("Spalte 2"), "{m}"); assert!(m.contains("70") && m.contains("e1"), "{m}"); + let kurz = format!("ab\n{marke}\n70\n"); + let m = meldung(&want, &kurz); + assert!(m.contains("Spalte 2") && m.contains("Soll: 70"), "{m}"); + let m = meldung( + &format!("中a\n{marke}\n7070\n"), + &format!("中a\n{marke}\n70e1\n"), + ); + assert!(m.contains("Spalte 3") && m.contains("Ist: e1"), "{m}"); +} + +#[test] +fn release_formular_vergleicht_zeichen_und_attribute_unabhaengig() { + let path = compat_dir().join("release-formular.frm"); + let want = std::fs::read_to_string(path.with_extension("out")).unwrap(); + let got = run_corpus_file(&path, None); + assert_output_matches("release-formular.frm", &want, &got); + assert_eq!(got, run_corpus_file(&path, None)); + let falscher_text = want.replacen("Click", "Falsch", 1); + assert!(meldung(&falscher_text, &got).contains("Abweichung in Zeile 1")); + let (text, attr) = want + .split_once(tb_runtime::snapshot::ATTRIBUT_MARKE) + .unwrap(); + let falsche_farbe = format!( + "{text}{}{}", + tb_runtime::snapshot::ATTRIBUT_MARKE, + attr.replacen("e1", "71", 1) + ); + let m = meldung(&falsche_farbe, &got); + assert!( + m.contains("Farbabweichung") && m.contains("Spalte 1"), + "{m}" + ); } #[test] diff --git a/crates/tb-cli/tests/foreign.rs b/crates/tb-cli/tests/foreign.rs index d8cc5b0..ecc84e8 100644 --- a/crates/tb-cli/tests/foreign.rs +++ b/crates/tb-cli/tests/foreign.rs @@ -1,5 +1,9 @@ //! Optionaler öffentlicher Bestand; kein Netzwerk und keine GUI. -use std::{path::PathBuf, process::Command, time::Duration}; +use std::{ + path::{Path, PathBuf}, + process::Command, + time::Duration, +}; use tb_runtime::host::{CaptureHost, Ereignis, Host}; use tb_vm::{ bytecode::CompiledModule, @@ -13,14 +17,17 @@ struct PruefHost { inner: CaptureHost, dialogantwort: Option, dialog_gesehen: bool, + dialogtext: Option<&'static str>, } 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.dialog_gesehen |= self + .dialogtext + .is_some_and(|text| tb_runtime::snapshot::text(screen).contains(text)); self.inner.present(screen); } fn next_event(&mut self, block: bool) -> Option { - if block { + if self.dialog_gesehen { if let Some(event) = self.dialogantwort.take() { return Some(event); } @@ -37,23 +44,78 @@ impl Host for PruefHost { const REVISION: &str = "1cdd2b32b829fe1721d0b6aecc433abc47a96fb6"; +fn pruefe_bestand(repo: Option<&Path>) -> Result<(), String> { + let repo = repo.ok_or_else(|| { + format!("TB_VBDOS_REPO fehlt: cout/vbdos bei Revision {REVISION} erforderlich") + })?; + let rev = Command::new("git") + .arg("-C") + .arg(repo) + .args(["rev-parse", "HEAD"]) + .output() + .map_err(|e| { + format!( + "Fremdprogrammbestand {}: git nicht ausführbar: {e}", + repo.display() + ) + })?; + if !rev.status.success() { + return Err(format!("Fremdprogrammbestand {} fehlt oder ist kein lesbares Git-Repository; benötigt {REVISION}", repo.display())); + } + let found = String::from_utf8_lossy(&rev.stdout); + if found.trim() != REVISION { + return Err(format!( + "Fremdprogrammbestand {}: falsche Revision, Soll {REVISION}, Ist {}", + repo.display(), + found.trim() + )); + } + Ok(()) +} + +#[test] +fn fehlender_oder_falscher_fremdbestand_wird_benannt() { + assert!(pruefe_bestand(None) + .unwrap_err() + .contains("TB_VBDOS_REPO fehlt")); + let missing = Path::new(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml"); + assert!(pruefe_bestand(Some(&missing)) + .unwrap_err() + .contains("kein lesbares Git-Repository")); + let error = pruefe_bestand(Some(Path::new(env!("CARGO_MANIFEST_DIR")))).unwrap_err(); + assert!( + error.contains("falsche Revision") && error.contains(REVISION), + "{error}" + ); +} + +struct TempBestand { + original: PathBuf, + base: PathBuf, +} + +impl Drop for TempBestand { + fn drop(&mut self) { + let _ = std::env::set_current_dir(&self.original); + let _ = std::fs::remove_dir_all(&self.base); + } +} + #[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 repo = std::env::var_os("TB_VBDOS_REPO").map(PathBuf::from); + pruefe_bestand(repo.as_deref()).unwrap_or_else(|e| panic!("{e}")); + let repo = repo.unwrap(); 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 _temp = TempBestand { + original: original.clone(), + base: base.clone(), + }; let archive = base.join("source.tar"); assert!(Command::new("git") .arg("-C") @@ -78,6 +140,7 @@ fn oeffentliche_formularprogramme_sind_reproduzierbar_bedienbar() { ] { let mut results = Vec::new(); for repeat in 0..2 { + eprintln!("{entry}: Lauf {} kompilieren", repeat + 1); let dir = base.join(format!("run-{repeat}")); std::fs::create_dir_all(&dir).unwrap(); assert!(Command::new("tar") @@ -105,7 +168,17 @@ fn oeffentliche_formularprogramme_sind_reproduzierbar_bedienbar() { 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(); + let mut host = PruefHost { + dialogtext: match entry { + "microsoft/check.mak" | "misc/mentors/mentors.frm" => { + Some("Save these records?") + } + "microsoft/notepad.frm" => Some("Save changes to"), + _ => None, + }, + ..Default::default() + }; + eprintln!("{entry}: Start und initiale Ereignisse"); assert_eq!(vm.run(&mut host), RunEvent::Ended, "{entry}: Start"); assert_eq!( vm.run_visible_forms(&mut host), @@ -124,8 +197,9 @@ fn oeffentliche_formularprogramme_sind_reproduzierbar_bedienbar() { let before = tb_runtime::snapshot::snapshot(&vm.rt.screen); let mut states = Vec::new(); for (key, shift) in &keys { + eprintln!("{entry}: Taste {key:?}, Modifikatoren {shift}"); vm.rt.ende = false; - if entry == "microsoft/notepad.frm" && *key == "x" { + if host.dialogtext.is_some() && *key == "x" { host.dialogantwort = Some(Ereignis::Taste("n".into(), 0)); } host.inner.ereignis(Ereignis::Taste((*key).into(), *shift)); @@ -167,10 +241,10 @@ fn oeffentliche_formularprogramme_sind_reproduzierbar_bedienbar() { "{entry}: Menü öffnen und Exit auswählen" ); } - if entry == "microsoft/notepad.frm" { + if host.dialogtext.is_some() { assert!( host.dialog_gesehen && host.dialogantwort.is_none(), - "Notepad: Save-Dialog mit N beantworten" + "{entry}: Save-Dialog mit N beantworten" ); } assert!( @@ -184,5 +258,9 @@ fn oeffentliche_formularprogramme_sind_reproduzierbar_bedienbar() { } assert_eq!(results[0], results[1], "{entry}: nicht reproduzierbar"); } - std::fs::remove_dir_all(base).unwrap(); + std::fs::remove_dir_all(&base).unwrap(); + assert!( + !base.exists(), + "temporärer Fremdprogrammbestand blieb zurück" + ); } diff --git a/crates/tb-cli/tests/inventar.rs b/crates/tb-cli/tests/inventar.rs index fa1676a..48ce018 100644 --- a/crates/tb-cli/tests/inventar.rs +++ b/crates/tb-cli/tests/inventar.rs @@ -439,6 +439,33 @@ fn abdeckungsstand_wird_ausgewiesen() { "Abdeckung: implementiert {i} · offen {o} · Non-Feature {n} · gesamt {}", inv.len() ); + for (name, forms) in [("Sprache", false), ("Forms", true)] { + let entries: Vec<_> = inv + .iter() + .filter(|e| matches!(e.art.as_str(), "Eigenschaft" | "Methode" | "Ereignis") == forms) + .collect(); + let count = |status: &str| entries.iter().filter(|e| e.status == status).count(); + println!( + "{name}: implementiert {} · offen {} · Non-Feature {} · gesamt {}", + count(IMPLEMENTIERT), + count(OFFEN), + count(NON_FEATURE), + entries.len() + ); + } + let offene: Vec<_> = inv + .iter() + .filter(|e| e.status == OFFEN) + .map(|e| e.name.as_str()) + .collect(); + println!( + "Offene Einträge: {}", + if offene.is_empty() { + "keine".into() + } else { + offene.join(", ") + } + ); // Der Kopf der Tabelle nennt dieselben Zahlen — sonst ist die Doku stale. let text = std::fs::read_to_string(wurzel().join("docs/inventar.md")).unwrap(); diff --git a/crates/tb-cli/tests/project.rs b/crates/tb-cli/tests/project.rs index 6771350..8ab5d78 100644 --- a/crates/tb-cli/tests/project.rs +++ b/crates/tb-cli/tests/project.rs @@ -129,6 +129,66 @@ fn cli_meldet_physische_quellorte_auch_nach_verschachtelten_includes() { std::fs::remove_dir_all(dir).unwrap(); } +#[test] +fn release_projekt_prueft_anfangsdateien_dateieffekte_und_include_fehler() { + struct Aufraeumen(std::path::PathBuf); + impl Drop for Aufraeumen { + fn drop(&mut self) { + let _ = std::fs::remove_dir_all(&self.0); + } + } + let dir = std::env::temp_dir().join(format!("tb-release-files-{}", std::process::id())); + { + std::fs::create_dir(&dir).unwrap(); + let _cleanup = Aufraeumen(dir.clone()); + std::fs::write(dir.join("main.bas"), "CALL Kopie\nEND\n").unwrap(); + std::fs::write(dir.join("lib.bas"), "'$INCLUDE: 'OUTER.BI'\n").unwrap(); + std::fs::write(dir.join("outer.bi"), "'$INCLUDE: 'INNER.BI'\n").unwrap(); + std::fs::write(dir.join("inner.bi"), "SUB Kopie\nOPEN \"seed.txt\" FOR INPUT AS #1\nLINE INPUT #1, s$\nCLOSE #1\nOPEN \"result.txt\" FOR APPEND AS #2\nPRINT #2, s$\nCLOSE #2\n200 ERROR 6\nEND SUB\n").unwrap(); + std::fs::write(dir.join("app.mak"), "MAIN.BAS\nLIB.BAS\n").unwrap(); + let build = tbc(&dir, "build", "app.mak"); + assert!(build.status.success(), "{build:?}"); + for entry in ["app.mak", "app.tbc"] { + if entry == "app.tbc" { + for source in ["main.bas", "lib.bas", "outer.bi", "inner.bi", "app.mak"] { + std::fs::remove_file(dir.join(source)).unwrap(); + } + } + for _ in 0..2 { + std::fs::write(dir.join("seed.txt"), "Grüße\n").unwrap(); + std::fs::write(dir.join("result.txt"), "Start\n").unwrap(); + let out = tbc(&dir, "run", entry); + assert_eq!(out.status.code(), Some(2), "{out:?}"); + assert!(out.stdout.is_empty(), "{out:?}"); + assert!( + String::from_utf8_lossy(&out.stderr) + .to_ascii_lowercase() + .contains("inner.bi:8:5:"), + "{out:?}" + ); + assert_eq!( + std::fs::read(dir.join("seed.txt")).unwrap(), + "Grüße\n".as_bytes() + ); + assert_eq!( + std::fs::read(dir.join("result.txt")).unwrap(), + "Start\nGrüße\n".as_bytes() + ); + } + } + let mut files: Vec<_> = std::fs::read_dir(&dir) + .unwrap() + .map(|f| f.unwrap().file_name()) + .collect(); + files.sort(); + assert_eq!(files, ["app.tbc", "result.txt", "seed.txt"]); + } + assert!( + !dir.exists(), + "Releaseprobe hat ihr Arbeitsverzeichnis nicht entfernt" + ); +} + #[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())); diff --git a/crates/tb-vm/src/interp.rs b/crates/tb-vm/src/interp.rs index 75459d7..139368d 100644 --- a/crates/tb-vm/src/interp.rs +++ b/crates/tb-vm/src/interp.rs @@ -1050,7 +1050,7 @@ impl Vm { /// Polls modeless forms after the module body has ended. pub fn poll_visible_forms(&mut self, host: &mut dyn Host, budget: usize) -> PollResult { - if self.terminated { + if self.terminated || (self.dialog.is_some() && self.rt.ende) { return PollResult::Event(RunEvent::Ended); } if self diff --git a/crates/tb-vm/tests/events.rs b/crates/tb-vm/tests/events.rs index 93ec712..016b2b6 100644 --- a/crates/tb-vm/tests/events.rs +++ b/crates/tb-vm/tests/events.rs @@ -392,6 +392,33 @@ fn eof_leert_angenommene_eingaben_bei_modalen_und_modellosen_forms() { } } +#[test] +fn eof_im_dialog_eines_formularhandlers_beendet_die_ereignispumpe() { + use tb_vm::interp::PollResult; + let mut vm = vm("DIM SHARED s$\nForm1.Show\nSUB Form_MouseDown(Button AS INTEGER, Shift AS INTEGER, X AS SINGLE, Y AS SINGLE)\nSHARED s$\ns$ = \"vorher\"\nr% = MSGBOX(\"Speichern?\", 4)\ns$ = \"nachher\"\nEND SUB"); + let mut host = CaptureHost::default(); + assert_eq!(vm.run(&mut host), RunEvent::Ended); + host.ereignis(mouse()); + for _ in 0..100 { + match vm.poll_visible_forms(&mut host, 32) { + PollResult::Event(RunEvent::Ended) => { + assert!(vm.rt.ende); + assert_eq!(string(&vm, "s"), "vorher"); + assert!(tb_runtime::snapshot::text(&vm.rt.screen).contains("Speichern?")); + return; + } + PollResult::Waiting { deadline } => { + if let Some(event) = host.warten(deadline) { + vm.rt.ereignis(event); + } + } + PollResult::Yield => {} + other => panic!("unerwartetes Ergebnis: {other:?}"), + } + } + panic!("Dialog-EOF wurde als Yield verschluckt"); +} + #[test] fn review_menu_schliessen_stellt_pending_trap_vor_erneutem_warten_zu() { struct MenuHost { diff --git a/openspec/changes/phase-6-01-kompatibilitaet-und-leistungsabnahme/.openspec.yaml b/openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/.openspec.yaml similarity index 100% rename from openspec/changes/phase-6-01-kompatibilitaet-und-leistungsabnahme/.openspec.yaml rename to openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/.openspec.yaml diff --git a/openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/abnahmematrix.md b/openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/abnahmematrix.md new file mode 100644 index 0000000..9039cae --- /dev/null +++ b/openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/abnahmematrix.md @@ -0,0 +1,87 @@ +# Release-Grundabnahme: Fälle und Voraussetzungen + +Basis: `54ee427c1c676170450068c5436e5875c84c9dce`, lokale Änderungen dieses +Changes. Alle folgenden Tests sind headless. Keine DOS-/GUI-Emulatoren. + +## Ausführbare Aufrufe + +Vom Repository aus; `--locked` bewahrt die Abhängigkeitsrevisionen: + +```sh +# K: gesamter Korpus einschließlich Vergleichs-Gegenproben +cargo test --locked -p tb-cli --test compat +# P: Projekte, Includes, TBC, Diagnosen und Dateieffekte +cargo test --locked -p tb-cli --test project +# I: Inventar, Sollquellen, Absenkungsziele und Statusgruppen +cargo test --locked -p tb-cli --test inventar -- --nocapture +# E: tatsächliche Ereignisauslöser bis BASIC +cargo test --locked -p tb-vm --test events +# A: IDE-Projektablauf, CLI-/TBC-Parität und Befehlsmatrix +cargo test --locked -p tb-cli --bin tbc ide_acceptance:: +# F: Pflichtnachweis des fixierten öffentlichen Bestands +TB_VBDOS_REPO=/absoluter/pfad/zu/vbdos cargo test --locked -p tb-cli --test foreign -- --include-ignored --nocapture +# G: zusammenhängende Grundabnahme, zuerst F, dann Workspace/Inventar/Gates/Benchmarks +python3 tests/support/release-abnahme.py --vbdos-repo /absoluter/pfad/zu/vbdos +``` + +Unter Windows wird TB_VBDOS_REPO als Prozessumgebungsvariable gesetzt; +G übernimmt dies plattformunabhängig. Alle Aufrufe liefern bei Fehler einen +von null verschiedenen Exitcode. Die reale Windows-/Linux-Prüfung folgt in +05/06, diese Matrix behauptet nur die ausgeführten lokalen Nachweise. + +## Verhaltensgruppen und unabhängiges Soll + +| Gruppe | Fälle | Aufruf | Soll und festgelegter Anfangszustand | +| --- | --- | --- | --- | +| Konsole/Zahlen/Steuerung | `hello`, `printzahlen`, `konvertierung`, `byref`, `kontrollfluss` | K: `korpus_laeuft_mit_korrekter_ausgabe` | Jeweilige `.out`, explizite tb-screen-Größe, keine Eingaben, Hostuhr 0; bestehende Sprach-/VM-Specs | +| Unicode/Attribute/Größe | `breitezeichen`, `bildschirm`, `groesse`, `groessenaenderung`, `release-formular` | K | COLOR/LOCATE-/Unicodevertrag; 80×25 bzw. 120×40, deklarierte Resize-Schritte; für release-formular 100×30 und Zeichen ab Spalte 90 | +| Forms/Fokus/Maus/Menü | `formular`, `formular-access-backtab`, `eingabe-maus-taste`, `eingabe-taste-maus`, `release-formular` | K | Geordnete Kopfereignisse; LostFocus vor GotFocus, genau ein Click, Menüauswahl; separate Zeichen-/Farberwartungen und zwei Läufe | +| Listen/Timer/modal | `listenauswahl`, `timer-aktivierung`, `formular` | K | Deklarierte Tasten/Maus/Zeit; bestehende `.out` mit Listenauswahl, Timertext und Dialogresultat | +| Zeit/Traps | `traps`, `trap-masken`, `datumzeit`, `timezoneknown`, `timezonefallback` | K, E | Virtuelle Uhr oder ausdrücklich gesetzte Zeit/Zone; KEY/UEVENT/TIMER-Masken und Reihenfolge. Zeitlose Invarianten bleiben als solche benannt | +| Dateien/ISAM | `dateien`, `eingabefortschritt`, alle `isam*.bas` | K | Frisches leeres Tempverzeichnis, deklarierte Bildschirmgröße, feste Dateiinhalte aus dem BASIC-Programm; `.out` prüft Lese-/Record-/Transaktionsresultate | +| Projekt-/Include-/Dateikette | `release_projekt_prueft_anfangsdateien_dateieffekte_und_include_fehler` | P | Zwei Module, verschachtelte Includes; seed.txt=`Grüße\n`, result.txt=`Start\n`; Ergebnis exakt `Start\nGrüße\n`, Quelle unverändert, Exit 2 und inner.bi Zeile 8 Spalte 5; Quell-/TBC-Lauf jeweils zweimal, Library-Quellen vor TBC-Lauf entfernt | +| Fehler/physische Orte | `fehlerbehandlung`; `cli_meldet_physische_quellorte_auch_nach_verschachtelten_includes`; `tbc_run_stop_exitcode`, `tbc_run_laufzeitfehler_exitcode` | K, P | ERR/ERL getrennt von Dateizeile; STOP=3, Runtimefehler=2; Sollstellen im jeweiligen Test, keine Systemzeitabhängigkeit | +| Quellfreie Projekte und Startdatei | `projekt_und_formular_laufen_nach_entfernen_saemtlicher_quellen`, `explicit_startup_order_and_form_selection_survive_tbc` | P | Festes Projekt/Form/Include, danach Quellen entfernt; identische Ausgabe und richtige Startformularwahl aus TBC | +| Vollständiges Inventar | `inventar_stimmt_mit_code_ueberein`, `jeder_inventareintrag_erreicht_hir_und_laufzeitziel`, `forms_inventar_stimmt_mit_der_klassentabelle_ueberein`, `abdeckungsstand_wird_ausgewiesen` | I | docs/inventar plus feste Inventar-Quellliste und unabhängige Absenkungsziele; Gegenproben für falsche Status-/Builtin-/Forms-Bindung | +| Ereignisvollständigkeit | `registrierte_form_ereignisse_erreichen_basic_aus_realen_quellen`, `registrierte_control_ereignisse_erreichen_basic_aus_realen_quellen`, `fehlender_ereignispfad_wird_namentlich_erkannt` | E | Jede registrierte Quelle löst ihren BASIC-Handler aus; künstlich fehlender Auslöser wird benannt | +| IDE/CLI/TBC | `create_save_debug_help_exports_and_reopen`, `saved_project_ide_source_cli_and_tbc_have_identical_sessions`, `reference_matrix_is_complete_and_points_to_runnable_tests` | A | Feste Tastensequenzen, Hostzeiten, Anfangsdateien und konkrete Ausgabe/Dateieffekte; bestehende 45-zeilige Phase-5-Befehlsmatrix. Native Exporterzeugung ist hier noch nicht implementiert | +| Dialog-EOF | `eof_im_dialog_eines_formularhandlers_beendet_die_ereignispumpe`, `eof_leert_angenommene_eingaben_bei_modalen_und_modellosen_forms` | E | EOF in einem Dialog beendet die Pumpe; bereits angenommene normale Formularereignisse bleiben vor EOF zustellbar | +| Vergleichsqualität | `release_formular_vergleicht_zeichen_und_attribute_unabhaengig`, `farbabweichung_nennt_zeile_spalte_soll_und_ist`, `fehlende_groessenangabe_wird_abgewiesen` | K | Vorab geschriebene Sollausgabe; gezielte Text-/Farbmutation, verkürzte Attributzeile und CJK-Spaltenposition müssen konkrete Diagnosen liefern | + +Jede Korpus-Sollausgabe muss bereits existieren. Der Harness erzeugt bei +fehlendem Soll keine Ersatzdatei. Neue Ausgabe `release-formular.out` wurde +aus Handlerfolge und COLOR/LOCATE-Vorgaben geschrieben, nicht vom Istwert +übernommen. Bestehende Golden-Dateien bleiben unverändert. + +## Öffentlicher Bestand + +`cout/vbdos`, Revision `1cdd2b32b829fe1721d0b6aecc433abc47a96fb6`. +Lokaler Prüfbestand: `/tmp/terminalbasic-vbdos-evidence`; die Bereitstellung +ist extern und wird nicht einvendort. `git archive` liefert für jeden Lauf +frische Dateien exakt dieser Revision. Größe 100×30, virtuelle Hostuhr 0, +keine echten Wartezeiten; je Einstieg zwei Läufe mit gleichen Eingaben. + +| Einstieg | Eingabe | Erwartete sichtbare Wirkung | +| --- | --- | --- | +| `graphics/graphics.mak` | Alt+X | Startformular sichtbar, danach entladen | +| `microsoft/check.mak` | Alt+F, X, N im Save-Dialog | Menü und Save-Dialog nachgewiesen, N konsumiert, Exit entlädt Formular | +| `microsoft/qlbview.mak` | Esc | Cancel entlädt Startformular | +| `microsoft/seek.mak` | Alt+X | Exit entlädt Startformular | +| `microsoft/spindemo.mak` | Tab, Tab, Pfeil hoch | Text1 wird ` 1`, sichtbarer Snapshot ändert sich | +| `microsoft/notepad.frm` | Alt+F, X, N im Save-Dialog | Menü und Save-Dialog nachgewiesen, N konsumiert, Formular entladen | +| `misc/mentors/mentors.frm` | Alt+F, X, N im Save-Dialog | Menü und Save-Dialog nachgewiesen, N konsumiert, Exit entlädt Formular | + +Snapshots/Zustände müssen zwischen beiden Läufen identisch sein; die +obigen konkreten Wirkungen werden zusätzlich geprüft. Ein bloßer Vergleich +zweier gleich falscher Starts genügt nicht. Fehlendes TB_VBDOS_REPO, ein +unlesbares Repository und eine falsche HEAD-Revision scheitern mit Sollrevision +und konkreter Ursache. Temporäre Dateien werden bei Erfolg und beim Entrollen +von Assertions aufgeräumt. + +## Übergabe an 02–07 + +G ist der wiederverwendbare lokale Abnahmeaufruf für spätere Actions; F +bleibt zwingend. 02–04 ergänzen EXE-/TBL-/IDE-Link-Parität mit diesem Sollsatz, +05 echte Terminalziele, 06 Zielpakete und Gitea-Läufe. Die finale Inventar- +und Leistungsprüfung nach deren Fachänderungen gehört in 07. Dieser Change +baut keine nativen Exportartefakte und nimmt keine fremden Plattformen ab. diff --git a/openspec/changes/phase-6-01-kompatibilitaet-und-leistungsabnahme/design.md b/openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/design.md similarity index 100% rename from openspec/changes/phase-6-01-kompatibilitaet-und-leistungsabnahme/design.md rename to openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/design.md diff --git a/openspec/changes/phase-6-01-kompatibilitaet-und-leistungsabnahme/phase-6-uebersicht.md b/openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/phase-6-uebersicht.md similarity index 100% rename from openspec/changes/phase-6-01-kompatibilitaet-und-leistungsabnahme/phase-6-uebersicht.md rename to openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/phase-6-uebersicht.md diff --git a/openspec/changes/phase-6-01-kompatibilitaet-und-leistungsabnahme/proposal.md b/openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/proposal.md similarity index 100% rename from openspec/changes/phase-6-01-kompatibilitaet-und-leistungsabnahme/proposal.md rename to openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/proposal.md diff --git a/openspec/changes/phase-6-01-kompatibilitaet-und-leistungsabnahme/specs/release-kompatibilitaet/spec.md b/openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/specs/release-kompatibilitaet/spec.md similarity index 100% rename from openspec/changes/phase-6-01-kompatibilitaet-und-leistungsabnahme/specs/release-kompatibilitaet/spec.md rename to openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/specs/release-kompatibilitaet/spec.md diff --git a/openspec/changes/phase-6-01-kompatibilitaet-und-leistungsabnahme/tasks.md b/openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/tasks.md similarity index 70% rename from openspec/changes/phase-6-01-kompatibilitaet-und-leistungsabnahme/tasks.md rename to openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/tasks.md index be5a6a9..812d704 100644 --- a/openspec/changes/phase-6-01-kompatibilitaet-und-leistungsabnahme/tasks.md +++ b/openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/tasks.md @@ -1,17 +1,17 @@ ## 1. Kompatibilitätsnachweise -- [ ] 1.1 Bestehende Fälle aus compat.rs, foreign.rs, inventar.rs, events.rs und IDE-Abnahme einer dokumentierten Release-Matrix zuordnen; jede vereinbarte Verhaltensgruppe muss konkrete Fallnamen und einen ausführbaren Aufruf besitzen. -- [ ] 1.2 Fehlende kombinierte Zeichen-/Attribut-/Resize- und Formularübergänge im vorhandenen Korpus ergänzen; wiederholte Läufe sowie absichtlich falsche Zeichen-/Attributerwartungen müssen die Vergleichsqualität belegen. -- [ ] 1.3 Projekt-/Include-, Fehler- und Dateifälle mit festgelegtem Anfangszustand und Dateieffekten vervollständigen; gezielte Tests müssen Diagnosen und saubere temporäre Verzeichnisse prüfen. -- [ ] 1.4 Öffentlichen Bestand in festgelegter Revision bereitstellen und den vorhandenen foreign-Test ausdrücklich ausführen; falsche/fehlende Revision muss den erforderlichen Abnahmepfad scheitern lassen. +- [x] 1.1 Bestehende Fälle aus compat.rs, foreign.rs, inventar.rs, events.rs und IDE-Abnahme einer dokumentierten Release-Matrix zuordnen; jede vereinbarte Verhaltensgruppe muss konkrete Fallnamen und einen ausführbaren Aufruf besitzen. +- [x] 1.2 Fehlende kombinierte Zeichen-/Attribut-/Resize- und Formularübergänge im vorhandenen Korpus ergänzen; wiederholte Läufe sowie absichtlich falsche Zeichen-/Attributerwartungen müssen die Vergleichsqualität belegen. +- [x] 1.3 Projekt-/Include-, Fehler- und Dateifälle mit festgelegtem Anfangszustand und Dateieffekten vervollständigen; gezielte Tests müssen Diagnosen und saubere temporäre Verzeichnisse prüfen. +- [x] 1.4 Öffentlichen Bestand in festgelegter Revision bereitstellen und den vorhandenen foreign-Test ausdrücklich ausführen; falsche/fehlende Revision muss den erforderlichen Abnahmepfad scheitern lassen. ## 2. Inventar und Leistung -- [ ] 2.1 Aktuelle Sprach- und Forms-Inventarsummen mit Quelllisten, Absenkungsproben und Ereignistests prüfen; Bericht muss alle Statusgruppen getrennt und offene Einträge namentlich ausweisen. -- [ ] 2.2 Vorhandene Compile-/VM-Benchmarks im Release-Profil messen und mit reproduzierbarem Vergleichsstand einordnen; Hardware, Revision, Verfahren und beide Compile-Budgets im Bericht festhalten. -- [ ] 2.3 Nur bei nachgewiesenem Engpass eine gezielte Fachkorrektur umsetzen und denselben Semantik-/Leistungsnachweis wiederholen; andernfalls „kein Optimierungsbedarf“ mit Messbegründung dokumentieren. +- [x] 2.1 Aktuelle Sprach- und Forms-Inventarsummen mit Quelllisten, Absenkungsproben und Ereignistests prüfen; Bericht muss alle Statusgruppen getrennt und offene Einträge namentlich ausweisen. +- [x] 2.2 Vorhandene Compile-/VM-Benchmarks im Release-Profil messen und mit reproduzierbarem Vergleichsstand einordnen; Hardware, Revision, Verfahren und beide Compile-Budgets im Bericht festhalten. +- [x] 2.3 Nur bei nachgewiesenem Engpass eine gezielte Fachkorrektur umsetzen und denselben Semantik-/Leistungsnachweis wiederholen; andernfalls „kein Optimierungsbedarf“ mit Messbegründung dokumentieren. ## 3. Übergabe -- [ ] 3.1 Gefundene Fachabweichungen vollständig beheben; betroffene Korpus-, Inventar- und Ereignistests müssen unveränderte Verträge bestätigen, ohne Golden-Dateien pauschal neu zu generieren. -- [ ] 3.2 Abnahmesatz und Ergebnisse für native Artefakte und CI dokumentieren; Workspace-Tests, Clippy, Format- und OpenSpec-Prüfung müssen bestehen, externe Voraussetzungen bleiben ausdrücklich sichtbar. +- [x] 3.1 Gefundene Fachabweichungen vollständig beheben; betroffene Korpus-, Inventar- und Ereignistests müssen unveränderte Verträge bestätigen, ohne Golden-Dateien pauschal neu zu generieren. +- [x] 3.2 Abnahmesatz und Ergebnisse für native Artefakte und CI dokumentieren; Workspace-Tests, Clippy, Format- und OpenSpec-Prüfung müssen bestehen, externe Voraussetzungen bleiben ausdrücklich sichtbar. diff --git a/openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/verification.md b/openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/verification.md new file mode 100644 index 0000000..3716e2c --- /dev/null +++ b/openspec/changes/archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/verification.md @@ -0,0 +1,183 @@ +# Verifizierung: phase-6-01-kompatibilitaet-und-leistungsabnahme + +Stand: 2026-09-07. Geprüft wurden Proposal, Design, Tasks, Delta-Spec und +Implementierung im Arbeitsbaum auf Basis von +`54ee427c1c676170450068c5436e5875c84c9dce`. + +## Ergebnis + +| Dimension | Ergebnis | +| --- | --- | +| Vollständigkeit | 9/9 Aufgaben umgesetzt; 4/4 Requirements nachgewiesen | +| Korrektheit | 4/4 Szenarien geprüft; alle gefundenen Abweichungen behoben | +| Kohärenz | Bestehende Harnesses, Inventare und Benchmarks weiterverwendet; keine neue Abhängigkeit | +| Offene Befunde | 0 CRITICAL, 0 WARNING, 0 SUGGESTION | + +Alle Prüfungen dieses Changes bestanden. Keine Prüfdimension übersprungen. +Die [Abnahmematrix](abnahmematrix.md) enthält konkrete Fallnamen, Eingaben, +Sollzustände, Aufrufe und Voraussetzungen für die weitere Phase 6. + +## Requirements und Szenarien + +| Requirement / Szenario | Implementierung und Nachweis | +| --- | --- | +| Deterministischer Abnahmesatz / Wiederholter Formularlauf | `tests/compat/release-formular.frm` verbindet Fokus, Maus, Menü und Resize mit Unicode und Farbe. `crates/tb-cli/tests/compat.rs`: `release_formular_vergleicht_zeichen_und_attribute_unabhaengig` prüft zwei Läufe und absichtlich falsche Text-/Farberwartungen. Das Soll wurde aus Handlerfolge, COLOR und LOCATE geschrieben. Der bestehende Korpus prüft fehlende Sollwerte als Fehler. | +| Deterministischer Abnahmesatz: Projekte, Includes, Fehler, Dateien | `crates/tb-cli/tests/project.rs`: `release_projekt_prueft_anfangsdateien_dateieffekte_und_include_fehler` prüft zwei Quell- und zwei quellfreie TBC-Läufe, Anfangsdateien, exakte Ergebnisbytes, Exitcode 2, physischen Include-Ort 8:5 und Aufräumen. Die Matrix ordnet die übrigen bestehenden Verhaltensgruppen zu. | +| Expliziter Fremdprogrammnachweis / Referenzbestand fehlt | `crates/tb-cli/tests/foreign.rs`: `pruefe_bestand` erzwingt Revision und lesbares Repository; Gegenprobe prüft fehlende Variable, ungültigen Pfad und falsche Revision. `tests/support/release-abnahme.py` aktiviert den Fremdtest ausdrücklich vor allen weiteren Gates. Der vollständige Lauf mit sieben Einstiegen, jeweils zweimal, bestand. | +| Begründete Leistungsentscheidung / Kein Optimierungsbedarf | Unveränderte Compile-/VM-Benchmarks auf identischer Hardware vor und nach der Änderung; Messwerte und Einordnung unten. Compile-Budgets bestanden. Keine Performance-Optimierung durchgeführt; die einzige VM-Änderung behebt einen funktionalen Dialog-EOF-Fehler. | +| Nachvollziehbare Befundbehandlung / Snapshot weicht ab | Vergleichs-Gegenproben nennen erste Text-/Farbabweichung mit Soll/Ist. Befunde und Korrekturen unten; vorhandene Golden-Dateien und Inventare unverändert. Regressionstests und vollständige erneute Abnahme bestanden. | + +## Behobene Befunde + +### 1. Fehlerhafte Attributdiagnose im Korpusvergleich + +Sollquelle: `kompat-testkorpus`, Requirement zum Sollvergleich mit erster +Abweichung; `textbildschirm`, Unicode-/Attributdarstellung. Zuständig: +`crates/tb-cli/tests/compat.rs`, `assert_output_matches`. + +Ist: Eine verkürzte Attributzeile mit gleichem Präfix führte zu Spalte 0 und +anschließendem Integer-Unterlauf. Nach einem breiten Unicode-Zeichen wurde +die logische Zeichenposition als Bildschirmspalte gemeldet. + +Korrektur: Der Vergleich berechnet zunächst den Attributindex, auch bei +unterschiedlicher Länge, und daraus die physische Spalte mit der bereits +verfügbaren Textbreitenfunktion. Die Regression prüft verkürzte Attribute +und `中a`: Eine Farbabweichung bei `a` muss Spalte 3 nennen. Die separate +Formularprobe weist sowohl falsche Zeichen als auch ausschließlich falsche +Farben zurück. Bestehende Sollausgaben wurden nicht angepasst. + +### 2. Unvollständige Dialogeingaben im öffentlichen Pflichtnachweis + +Sollquelle: `release-kompatibilitaet`, explizite Eingabefolge und sichtbares +Resultat des fixierten Bestands. Zuständig: `crates/tb-cli/tests/foreign.rs`. + +Ist: Der ausdrücklich aktivierte Lauf erreichte bei `check.mak` den +Save-Dialog ohne Antwort und überschritt seine Prozessfrist. Auch Mentors +benötigt eine Save-Antwort. Der Testhost lieferte vorbereitete Antworten +bisher nur bei blockierenden Hostaufrufen; kooperatives Polling verwendet +nichtblockierende Aufrufe. + +Korrektur: Die Eingabefolge für Check und Mentors enthält ausdrücklich N. +Der Host liefert die Antwort erst nach Darstellung des erwarteten Dialogs; +für Check/Mentors `Save these records?`, für Notepad `Save changes to`. +Dialoganzeige, Verbrauch der Antwort, sichtbare Menüwirkung und Entladen +werden weiter geprüft. Alle sieben bisherigen Einstiege bleiben erhalten; +beide Läufe jedes Einstiegs stimmen überein. + +### 3. Dialog-EOF wurde in der Formularpumpe verschluckt + +Sollquelle: `release-kompatibilitaet`, nachvollziehbare Fachkorrektur; +bestehender kooperativer Ausführungs-/EOF-Vertrag der VM-Ereignispumpe. +Zuständig: `crates/tb-vm/src/interp.rs`, `poll_visible_forms`. + +Ist: EOF während eines MSGBOX in einem modellosen Formularhandler ließ +`poll` enden. Die Formularpumpe wandelte dies wie ein normales Modulende +in Yield um und konnte endlos weiterlaufen. Dies wurde unabhängig vom +Fremdhost mit einem begrenzten Regressionstest reproduziert. + +Korrektur: Die gemeinsame Formularpumpe gibt bei aktivem Dialog und EOF +Ended zurück. CLI und IDE benutzen diesen Pfad. Der neue Test +`eof_im_dialog_eines_formularhandlers_beendet_die_ereignispumpe` scheiterte +vor der Korrektur mit „Dialog-EOF wurde als Yield verschluckt“ und besteht +danach. Er prüft auch, dass nach dem Dialog kein BASIC-Code ausgeführt wird. +Der bestehende Test für bereits angenommene Eingaben vor EOF bleibt grün; +33 Ereignis- und 7 kooperative Tests bestanden zusammen. + +## Ausgeführte Gates + +Gesamtabnahme: + +```sh +python3 tests/support/release-abnahme.py --vbdos-repo /tmp/terminalbasic-vbdos-evidence +openspec validate --all --strict +git diff --check +``` + +| Prüfung | Ergebnis | +| --- | --- | +| Expliziter Fremdtest mit `--include-ignored` | 3 Tests bestanden, darin 7 Einstiege × 2 Läufe; Revision `1cdd2b32b829fe1721d0b6aecc433abc47a96fb6` | +| `cargo test --locked --workspace` | 609 Tests sowie 1 Offline-Kindprozesstest bestanden; 0 fehlgeschlagen | +| Absichtlich ignorierte Entwicklertests | 2: Golden-Erzeugung und optionaler Fremdtest; Fremdtest separat verpflichtend bestanden, Golden-Erzeugung nicht ausgeführt | +| Korpus | 38 BAS + 7 FRM, 46 größenbezogene Sollausgaben, jeder Fall zweimal; zusätzliche Vergleichs-Gegenproben | +| Inventarbericht | 845 Einträge, siehe getrennte Statusgruppen unten | +| `cargo fmt --all -- --check` | Bestanden | +| `cargo clippy --locked --workspace --all-targets -- -D warnings` | Bestanden | +| Compile-/VM-Benchmarks | Bestanden, beide Compile-Budgets eingehalten | +| OpenSpec, alle Specs und Changes, strikt | 30 bestanden, 0 fehlgeschlagen; vorhandene INFO-Hinweise zu Textlängen sind keine Validierungsfehler | +| `git diff --check` | Bestanden | + +Zusätzliche Gegenproben des tatsächlichen Release-Aufrufs: Ohne +`--vbdos-repo` Exit 2 mit Benennung des Pflichtarguments; mit falschem +Repository Exit 101 und „falsche Revision“; mit nicht vorhandenem Pfad +Exit 101 und fehlendem/ungültigem Git-Bestand. Letzterer Aufruf erfolgte +außerhalb des Checkouts über den absoluten Skriptpfad. Kein Fehlfall wurde +als erfolgreicher Release-Nachweis gewertet. + +## Inventar + +| Gruppe | Implementiert | Offen | Non-Feature | Gesamt | +| --- | ---: | ---: | ---: | ---: | +| Sprache | 241 | 0 | 50 | 291 | +| Forms-Objektmodell | 554 | 0 | 0 | 554 | +| Gesamt | 795 | 0 | 50 | 845 | + +Offene Einträge: keine. Quelllisten, unabhängige Absenkungsziele, +Forms-Klassentabelle und tatsächliche BASIC-Ereignisauslöser wurden geprüft. +Es wurden keine Inventareinträge entfernt oder als Non-Feature umklassifiziert. + +## Leistung und Entscheidung + +Hardware: Apple M5 Max, 128 GiB RAM, macOS 26.6.2 (25G83), +`aarch64-apple-darwin`. Rust 1.97.1 (`8bab26f4f`), Cargo 1.97.1, +LLVM 22.1.6. Alle Messungen auf demselben Rechner mit +`cargo bench --locked -p tb-vm --bench compile --bench vm`, optimiertem +Bench-/Release-Profil, opt-level 3, LTO und codegen-units 1. + +Vergleich: sauberer Basiscommit `54ee427c1c676170450068c5436e5875c84c9dce` +vor Änderungen. Endstand: dieser Arbeitsbaum einschließlich Dialog-EOF-Fix. +Die erste Endmessung folgte dem vollständigen Abnahmelauf und Rebuild; +eine zusätzliche Messung ohne Rebuild klärte die beobachtete Streuung. + +| Last | Basis | Endstand, erster Lauf | Endstand, Bestätigung | +| --- | ---: | ---: | ---: | +| Modul, 508 Zeilen, Budget < 50 ms | 0,73 ms | 1,26 ms | 0,80 ms | +| Projekt, 49.760 Zeilen / 20 Module, Budget < 1.000 ms | 93 ms | 107 ms | 91 ms | +| Inkrementell, 1 Modul | 0,77 ms | 0,79 ms | 0,73 ms | +| Inkrementell, 20 Module | 22,46 ms | 22,38 ms | 21,19 ms | +| INTEGER, 10 Mio. Iterationen | 1.217 ms | 1.251 ms | 1.184 ms | +| DOUBLE, 5 Mio. Iterationen | 572 ms | 621 ms | 557 ms | +| SUB/BYREF, 1 Mio. Aufrufe | 139 ms | 141 ms | 140 ms | +| Stringfunktionen, 200.000 Runden | 109 ms | 118 ms | 110 ms | + +Verfahren unverändert: Modul als bester von zehn Läufen nach Aufwärmen; +Projekt als vollständige Übersetzung mit 335.421 Instruktionen; +inkrementell Median aus sieben Änderungen einschließlich Invalidierung +und Link, beim Projekt ein Modul neu und 19 wiederverwendet. VM-Zeiten +messen Ausführung ohne Compilation. Bestätigter VM-Durchsatz: 8,4 Mio. +INTEGER- und 9,0 Mio. DOUBLE-Iterationen/s, 7,1 Mio. Aufrufe/s sowie +1,8 Mio. Stringrunden/s. + +Entscheidung: **Kein Optimierungsbedarf im gemessenen Abnahmesatz.** Beide +Compile-Budgets bestehen mit deutlichem Abstand. Die anfänglichen +VM-Abstände von ungefähr 1–9 % bestätigten sich nicht: Schleifen waren +anschließend schneller als die Basis, Aufruf-/Stringzeiten lagen jeweils +nur 1 ms darüber. Das belegt keinen belastbaren Engpass. Es wurde kein +neues VM-Zeitlimit eingeführt und kein VM-Code zur Optimierung verändert. +Der funktionale EOF-Fix wurde mit denselben Semantik- und Leistungstests +abgenommen. Die Messung behauptet keine statistisch exakte Performanceparität. + +## Übergabe + +Der Abnahmeaufruf benötigt Rust/Cargo, Python 3, Git, tar und den extern +bereitgestellten fixierten Fremdbestand. Er lädt nichts herunter und +erzeugt keine Golden-Dateien. Fallmatrix und Zahlen stehen im Repository; +lokale Rohprotokolle liegen ergänzend in `/tmp/tb-phase6-01-evidence/` +(`baseline-environment.txt`, `baseline-bench.log`, `release-abnahme.log`, +`bench-confirmation.log`, `dialog-eof-before.log`, `vm-regression.log`). + +Changes 02–07 übernehmen diese Grundlage für tbrt, TBL/Linker, IDE-Export, +reale Zielplattformen, Gitea Actions und finale Abnahme. Windows/Linux, +native Exportartefakte und Release-Pakete sind hier nicht als abgenommen +ausgewiesen. Am 2026-09-07 wurden die vier Requirements in die Hauptspezifikation +`release-kompatibilitaet` synchronisiert, alle 24 Hauptspezifikationen strikt +validiert und der Change archiviert. diff --git a/openspec/changes/phase-6-02-native-executables/proposal.md b/openspec/changes/phase-6-02-native-executables/proposal.md index 31ebb61..73d4e21 100644 --- a/openspec/changes/phase-6-02-native-executables/proposal.md +++ b/openspec/changes/phase-6-02-native-executables/proposal.md @@ -22,4 +22,4 @@ Keine. TBC bleibt ein gesondertes Ausgabeformat mit unverändertem Sprachvertrag ## Impact -CLI-Build-/Runnerpfad in `crates/tb-cli/src/main.rs`, gemeinsame Projekt-/VM-Erzeugung, neuer wiederverwendbarer Export-/Runnerbereich, Release-Vorlagen und native Prozessprüfungen. Keine LLVM-/JIT-Umstellung. Referenzfälle aus [01](../phase-6-01-kompatibilitaet-und-leistungsabnahme/proposal.md); Auslieferung in 06. Der gleiche Exportvertrag wird von 03 und 04 verwendet. +CLI-Build-/Runnerpfad in `crates/tb-cli/src/main.rs`, gemeinsame Projekt-/VM-Erzeugung, neuer wiederverwendbarer Export-/Runnerbereich, Release-Vorlagen und native Prozessprüfungen. Keine LLVM-/JIT-Umstellung. Referenzfälle aus [01](../archive/2026-09-07-phase-6-01-kompatibilitaet-und-leistungsabnahme/proposal.md); Auslieferung in 06. Der gleiche Exportvertrag wird von 03 und 04 verwendet. diff --git a/openspec/specs/release-kompatibilitaet/spec.md b/openspec/specs/release-kompatibilitaet/spec.md new file mode 100644 index 0000000..4c1355d --- /dev/null +++ b/openspec/specs/release-kompatibilitaet/spec.md @@ -0,0 +1,35 @@ +# release-kompatibilitaet Specification + +## Purpose + +Sichert die nachvollziehbare Kompatibilitäts- und Leistungsgrundlage für auslieferbare Terminal-Basic-Artefakte auf dem bestehenden Sprachstandard. + +## Requirements + +### Requirement: Deterministischer Abnahmesatz +Die Release-Abnahme SHALL Konsolen-, Forms-, Mehrmodul-/Include-, Fehler- und Dateifälle mit ausdrücklich festgelegten Eingaben, Zeitverläufen, Bildschirmgrößen und Anfangsdateien prüfen. Sichtbare Zeichen, Attribute, Laufzeitresultat und relevante Dateieffekte SHALL mit unabhängig begründeten Sollwerten verglichen werden. Ein fehlender Sollwert MUST als fehlender Nachweis gelten und MUST NOT automatisch aus dem aktuellen Istwert entstehen. + +#### Scenario: Wiederholter Formularlauf +- **WHEN** ein Formularfall mit Fokuswechsel, Maus, Menü und Größenänderung zweimal ausgeführt wird +- **THEN** stimmen beide Läufe mit denselben Sollzuständen überein und eine Änderung nur am Farbattribut lässt die Prüfung fehlschlagen + +### Requirement: Expliziter Fremdprogrammnachweis +Die Release-Abnahme SHALL den verwendeten öffentlichen Fremdprogrammbestand mit unveränderlicher Revision, Einstieg, Eingabefolge und sichtbarem Resultat benennen und prüfen. Ein fehlender oder falscher Bestand MUST den erforderlichen Nachweis als fehlend melden; ein ignorierter optionaler Entwicklertest SHALL keinen erfolgreichen Release-Nachweis ersetzen. + +#### Scenario: Referenzbestand fehlt +- **WHEN** die Release-Abnahme ohne den vereinbarten Fremdprogrammbestand gestartet wird +- **THEN** scheitert der erforderliche Fremdprogrammnachweis mit Benennung der fehlenden Voraussetzung + +### Requirement: Begründete Leistungsentscheidung +Die bestehenden Compile-Budgets von unter 50 ms für das Referenzmodul und unter 1 s für das Referenzprojekt SHALL im Release-Profil eingehalten werden. Der VM-Durchsatz SHALL für die vorhandenen Schleifen-, Aufruf- und Stringlasten mit Hardware, Revision, Profil und Verfahren gemessen werden. Optimierung SHALL einen gemessenen Engpass oder eine reproduzierbare Regression voraussetzen und danach dieselben Semantikprüfungen bestehen. + +#### Scenario: Kein Optimierungsbedarf +- **WHEN** Compile-Budgets bestehen und auf demselben Rechner kein reproduzierbarer VM-Rückschritt gegenüber dem dokumentierten Vergleichsstand vorliegt +- **THEN** wird der Performance-Pass mit Messwerten und der begründeten Entscheidung abgeschlossen, den VM-Code unverändert zu lassen + +### Requirement: Nachvollziehbare Befundbehandlung +Abweichungen SHALL mit Sollquelle, Istverhalten und zuständigem Fachbereich dokumentiert und dort behoben werden. Geänderte Erwartungen SHALL durch den geltenden Vertrag begründet sein. Die Abnahme MUST NOT Anforderungen, Fälle oder Inventareinträge entfernen, um ein grünes Ergebnis zu erzielen. + +#### Scenario: Snapshot weicht ab +- **WHEN** ein bestehender Fall nach einer Fachänderung eine andere Ausgabe erzeugt +- **THEN** nennt der Bericht die erste Abweichung und begründet Korrektur beziehungsweise zulässige Solländerung vor erneutem Prüfen diff --git a/tests/compat/README.md b/tests/compat/README.md index c37a576..b0569bc 100644 --- a/tests/compat/README.md +++ b/tests/compat/README.md @@ -1,27 +1,74 @@ # Kompatibilitäts-Testkorpus -Kleine Referenzprogramme mit dokumentierter erwarteter Ausgabe. Jede -`name.bas` hat eine `name.out` mit der exakten Konsolenausgabe (UTF-8, -LF-Zeilenenden). **Achtung:** PRINT gibt Zahlen mit führendem Leerzeichen -(bzw. `-`) und nachgestelltem Leerzeichen aus — auch am Zeilenende. Die -`.out`-Dateien enthalten daher signifikante Leerzeichen am Zeilenende; -Editoren dürfen sie nicht wegtrimmen; `.gitattributes` schützt die Dateien -vor Zeilenenden-Konvertierung. +Kleine BAS- und FRM-Referenzprogramme mit unabhängig begründeter Sollausgabe. +`cargo test --locked -p tb-cli --test compat` übersetzt sie, führt sie ohne +Terminal durch die TBVM und vergleicht jeden Lauf mit seiner `.out`. +Jeder Fall läuft zweimal. Mehrere deklarierte Bildschirmgrößen besitzen +jeweils eine eigene `.x.out`. -Ab Phase 2 führt ein Test-Harness (`cargo test`) jede Datei per -`tbc run` aus und vergleicht die Ausgabe. Bis dahin dienen die -`.out`-Dateien als festgehaltene Verhaltensspezifikation — wo möglich am -Vorbild (Emulator) verifiziert, sonst nach Referenzlage rekonstruiert -und mit `TODO verify` im Programmkopf markiert. +Snapshots enthalten das getrimmte Textbild und bei abweichenden Farben eine +Attributebene. UTF-8 und LF sind verbindlich. PRINT-Leerzeichen sind auch am +Zeilenende signifikant; `.gitattributes` schützt `.out` vor Konvertierung. +Breite Zeichen erscheinen einmal, ihre Fortsetzungszellen werden nicht als +zusätzliches Leerzeichen ausgegeben. Die Farbdiagnose nennt Bildschirmspalten. -Konventionen: -- Nur Konsolen-E/A (keine Forms) bis Phase 4 -- Kein Zufall/Zeit, außer der Test fixiert die Saat (`RANDOMIZE` mit - Konstante) bzw. prüft nur Invarianten +Kopfzeilen stehen bei BAS am Anfang, bei FRM in den ersten acht Codezeilen: + +- `tb-screen: 80x25` ist Pflicht; mehrere Größen werden durch Kommas getrennt. +- `tb-resize: nach 1 auf 120x40` liefert eine einzelne Größenänderung nach + dem angegebenen Host-Zustellpunkt. +- `tb-keys:` beschreibt Tasten; ``, ``, ``, `` bis `` + und Pfeiltasten sind benannt, `+`/`^`/`%` stehen für Shift/Ctrl/Alt. +- `tb-events:` ordnet Tasten, Maus, Zeit und Resize ausdrücklich, etwa + `key: | resize:100x30 | mouse:down,1,0,4,25 | time:250ms`. + Mausfelder sind Art, Taste, Modifikatoren, Zeile, Spalte (einsbasiert). + `time:` rückt die virtuelle Uhr um den Wert vor; ohne Zeitschritt steht sie + bei 0. `mouse:up` und `mouse:move` ergänzen Loslassen und Bewegung. +- `tb-clock: 25ms/Zustellpunkt` ist die alternative Uhr für TIMER-Traps; + der Harness verbietet fehlende Trap-Uhren und die Mischung mit tb-events. +- `tb-tempdir` führt Dateiprogramme in einem frischen leeren Arbeitsverzeichnis + aus und entfernt es danach, auch beim Entrollen eines Testfehlers. +- `tb-tz: +00:00` setzt eine feste Zone, `tb-tz: unknown` prüft UTC-Rückfall. + Datums-/Zeitproben setzen ihre Werte oder prüfen ausdrücklich Invarianten; + Zufall verwendet eine feste Saat. + +`release-formular.frm` verbindet Fokuswechsel, Mausklick, Menü und Resize +mit farbiger Unicode-Ausgabe. Das Soll folgt den Handlern +`Lost;Got;Click;Menu;`, `COLOR 14,1` (e1) und `LOCATE 2,90` nach Resize auf +100×30. Der Test verändert gezielt ein Zeichen und ein Attribut in der +Erwartung und muss beides erkennen. Das Soll wird nicht aus dem VM-Istwert +gewonnen. Bestehende `formular.frm`-/Listen-/Timerfälle prüfen zusätzlich +die sichtbare Formularzeichnung und modale Übergänge. Die Programme `eingabefortschritt`, `timezoneknown` und `timezonefallback` -prüfen den TerminalBasic-Vertrag für Codepoints, gemeinsame Dateileseposition -und den sichtbaren Zonenzustand. `tb-tz: +00:00` setzt eine bekannte UTC-Zone, -`tb-tz: unknown` erzwingt den UTC-Rückfall. Späterer Ausfall und Erholung -werden zusätzlich mit einer ersetzbaren Zonenquelle in den Laufzeit- und -VM-Tests geprüft, ohne die Zeitzone des Rechners zu verändern. +prüfen Codepoints, gemeinsame Dateileseposition und Zonenzustand. Späterer +Zonenausfall und Erholung werden mit einer ersetzbaren Quelle geprüft, +ohne die Rechnerzeitzone zu ändern. + +## Erforderliche Release-Grundabnahme + +```sh +python3 tests/support/release-abnahme.py --vbdos-repo /absoluter/pfad/zu/vbdos +``` + +Der Aufruf funktioniert auch außerhalb des Checkouts mit absolutem Skriptpfad. +Er benötigt Python 3, Cargo/Rust sowie Git und tar für den Fremdbestand. +Er lädt nichts herunter. Der Git-Bestand muss HEAD +`1cdd2b32b829fe1721d0b6aecc433abc47a96fb6` enthalten; der Test exportiert exakt +diese Revision in temporäre Verzeichnisse, sodass lokale Änderungen am +Fremdcheckout nicht in die Prüfung eingehen. + +Zuerst läuft der erforderliche Fremdtest mit `--include-ignored`, danach +Workspace-Regressionen, Inventarbericht, Format, Clippy und die vorhandenen +Compile-/VM-Benchmarks. Jeder Fehler beendet den Aufruf erfolglos. Ohne +Bestand oder mit falscher Revision entsteht kein bestandener Release-Nachweis. +Der normale Entwicklertest darf den Fremdtest weiter ignorieren; er allein +ist keine Release-Abnahme. Das ignorierte Golden-Erzeugungswerkzeug wird vom +Release-Aufruf niemals gestartet. Bestehende Sollwerte werden nur nach +Prüfung ihrer Spec-/Referenzgrundlage geändert. + +Fallzuordnung, Eingabefolgen, Referenzrevision und Messwerte stehen im Change +`phase-6-01-kompatibilitaet-und-leistungsabnahme` in `abnahmematrix.md` und +`verification.md` (nach Archivierung unter `openspec/changes/archive/`). +Diese Grundabnahme ersetzt keine native EXE-/TBL-, reale Terminalmatrix- +oder Gitea-Releaseprüfung; diese folgen mit Phase-6-Changes 02–07. diff --git a/tests/compat/release-formular.frm b/tests/compat/release-formular.frm new file mode 100644 index 0000000..2afa091 --- /dev/null +++ b/tests/compat/release-formular.frm @@ -0,0 +1,60 @@ +VERSION 1.00 +Begin Form Formular + Height = 10 + Width = 40 + Begin Menu mnuDatei + Caption = "&Datei" + Begin Menu mnuOpen + Caption = "&Open" + End + End + Begin TextBox Text1 + Left = 2 + Top = 2 + Width = 18 + Height = 1 + TabIndex = 0 + End + Begin CommandButton Ok + Caption = "&OK" + Left = 23 + Top = 3 + Width = 10 + Height = 1 + TabIndex = 1 + End +End +' tb-screen: 80x25 +' tb-events: time:0ms | key: | resize:100x30 | mouse:down,1,0,4,25 | mouse:up,1,0,4,25 | key:%d | key:o +' Soll: LostFocus vor GotFocus, ein Click, dann Menue. COLOR e1; +' nach Resize passt die Ausgabe ab Spalte 90. Unicode belegt Doppelzellen. +' Quellen: Specs forms-objektmodell, textbildschirm, release-kompatibilitaet. +DIM SHARED trace$ +Formular.Show +Text1.SetFocus +FOR i% = 1 TO 20 +DOEVENTS +NEXT +Formular.Hide +COLOR 14, 1 +CLS +PRINT trace$; "中" +LOCATE 2, 90 +PRINT "界Z"; +END +SUB Text1_LostFocus() +SHARED trace$ +trace$ = trace$ + "Lost;" +END SUB +SUB Ok_GotFocus() +SHARED trace$ +trace$ = trace$ + "Got;" +END SUB +SUB Ok_Click() +SHARED trace$ +trace$ = trace$ + "Click;" +END SUB +SUB mnuOpen_Click() +SHARED trace$ +trace$ = trace$ + "Menu;" +END SUB diff --git a/tests/compat/release-formular.out b/tests/compat/release-formular.out new file mode 100644 index 0000000..82d5c9e --- /dev/null +++ b/tests/compat/release-formular.out @@ -0,0 +1,5 @@ +Lost;Got;Click;Menu;中 + 界Z +--- Attribute (Vordergrund/Hintergrund je Zelle, hex) --- +e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1 +e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1 diff --git a/tests/support/release-abnahme.py b/tests/support/release-abnahme.py new file mode 100644 index 0000000..58f217b --- /dev/null +++ b/tests/support/release-abnahme.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +"""Erforderlicher Phase-6-Abnahmesatz; keine Downloads oder Golden-Erzeugung.""" +import argparse +import os +from pathlib import Path +import subprocess + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--vbdos-repo", type=Path, required=True, + help="lokaler cout/vbdos-Git-Bestand in der in foreign.rs fixierten Revision") + args = parser.parse_args() + env = dict(os.environ, TB_VBDOS_REPO=str(args.vbdos_repo.resolve())) + root = Path(__file__).resolve().parents[2] + commands = [ + ["cargo", "test", "--locked", "-p", "tb-cli", "--test", "foreign", "--", + "--include-ignored", "--nocapture"], + ["cargo", "test", "--locked", "--workspace"], + ["cargo", "test", "--locked", "-p", "tb-cli", "--test", "inventar", "--", + "--exact", "abdeckungsstand_wird_ausgewiesen", "--nocapture"], + ["cargo", "fmt", "--all", "--", "--check"], + ["cargo", "clippy", "--locked", "--workspace", "--all-targets", "--", "-D", "warnings"], + ["cargo", "bench", "--locked", "-p", "tb-vm", "--bench", "compile", "--bench", "vm"], + ] + for command in commands: + print("+ " + " ".join(command), flush=True) + result = subprocess.run(command, cwd=root, env=env, check=False) + if result.returncode: + return result.returncode if result.returncode > 0 else 1 + print("Phase-6-Grundabnahme bestanden; native Ziel-/Releaseabnahme folgt in Changes 02–07.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())