Phase 6: IDE-Exportanbindung abschließen und Change archivieren
This commit is contained in:
@@ -8,7 +8,7 @@ use std::{
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
use tb_ide::{
|
||||
app::{App, DialogKind, Execution, Mode, WindowKind, WindowState},
|
||||
app::{App, DialogKind, Execution, Hit, Mode, WindowKind, WindowState},
|
||||
commands::{self, Command},
|
||||
export::{ExportStatus, ProjectStamp},
|
||||
options::Options,
|
||||
@@ -347,23 +347,33 @@ fn status_and_project_buttons_use_the_keyboard_command_path() {
|
||||
fn export_dialogs_validate_and_render_all_results_without_creating_files() {
|
||||
let t = Temp::new();
|
||||
let mut app = t.app();
|
||||
app.export_backend = false;
|
||||
let stamp = ProjectStamp::capture(&app.project);
|
||||
for command in [Command::MakeExe, Command::MakeLibrary] {
|
||||
menu(&mut app, command);
|
||||
while app.dialog.as_ref().unwrap().focus != 1 {
|
||||
plain(&mut app, K::Tab);
|
||||
}
|
||||
plain(&mut app, K::Right);
|
||||
let system = app.dialog.as_ref().unwrap().fields[1].string();
|
||||
plain(&mut app, K::Tab);
|
||||
plain(&mut app, K::Right);
|
||||
let architecture = app.dialog.as_ref().unwrap().fields[2].string();
|
||||
let output = if command == Command::MakeLibrary {
|
||||
1
|
||||
} else {
|
||||
3
|
||||
};
|
||||
let (system, architecture) = if command == Command::MakeExe {
|
||||
(
|
||||
app.dialog.as_ref().unwrap().fields[1].string(),
|
||||
app.dialog.as_ref().unwrap().fields[2].string(),
|
||||
)
|
||||
} else {
|
||||
(String::new(), String::new())
|
||||
};
|
||||
let (text, _) = draw(&mut app);
|
||||
assert!(text.contains("Erzeugen: Phase 6"));
|
||||
field(&mut app, 3, "");
|
||||
field(&mut app, output, "");
|
||||
plain(&mut app, K::Enter);
|
||||
assert!(app.dialog.as_ref().unwrap().error.contains("Ausgabepfad"));
|
||||
field(&mut app, 3, &t.0.join("notthere/out").display().to_string());
|
||||
field(
|
||||
&mut app,
|
||||
output,
|
||||
&t.0.join("notthere/out").display().to_string(),
|
||||
);
|
||||
plain(&mut app, K::Enter);
|
||||
assert!(app
|
||||
.dialog
|
||||
@@ -371,12 +381,12 @@ fn export_dialogs_validate_and_render_all_results_without_creating_files() {
|
||||
.unwrap()
|
||||
.error
|
||||
.contains("Zielverzeichnis"));
|
||||
field(&mut app, 3, &t.0.join("bad.tbc").display().to_string());
|
||||
field(&mut app, output, &t.0.join("bad.tbc").display().to_string());
|
||||
plain(&mut app, K::Enter);
|
||||
assert!(app.dialog.as_ref().unwrap().error.contains("TBC"));
|
||||
let existing = t.0.join("existing");
|
||||
let existing = t.0.join("existing.tbl");
|
||||
fs::write(&existing, b"keep").unwrap();
|
||||
field(&mut app, 3, &existing.display().to_string());
|
||||
field(&mut app, output, &existing.display().to_string());
|
||||
plain(&mut app, K::Enter);
|
||||
assert!(app
|
||||
.dialog
|
||||
@@ -389,8 +399,8 @@ fn export_dialogs_validate_and_render_all_results_without_creating_files() {
|
||||
plain(&mut app, K::Enter);
|
||||
assert_eq!(fs::read(&existing).unwrap(), b"keep");
|
||||
assert!(app.dialog.as_ref().unwrap().request.is_some());
|
||||
let target = t.0.join("result");
|
||||
field(&mut app, 3, &target.display().to_string());
|
||||
let target = t.0.join("result.tbl");
|
||||
field(&mut app, output, &target.display().to_string());
|
||||
plain(&mut app, K::Enter);
|
||||
let request = app.dialog.as_ref().unwrap().request.clone().unwrap();
|
||||
assert_eq!(request.system, system);
|
||||
@@ -702,3 +712,215 @@ fn options_apply_persist_across_bases_and_preserve_unsaved_values_on_error() {
|
||||
assert_eq!(defaults, Options::default());
|
||||
assert_eq!(errors.len(), 4);
|
||||
}
|
||||
|
||||
fn await_export(app: &mut App) -> ExportStatus {
|
||||
let start = std::time::Instant::now();
|
||||
loop {
|
||||
app.tick_export();
|
||||
let status = app.dialog.as_ref().unwrap().export_status.clone();
|
||||
if status != ExportStatus::Running {
|
||||
return status;
|
||||
}
|
||||
assert!(start.elapsed().as_secs() < 10, "Export hängt");
|
||||
std::thread::sleep(std::time::Duration::from_millis(2));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn real_library_export_uses_dirty_include_and_mouse_without_runtime_template() {
|
||||
let t = Temp::new();
|
||||
fs::write(
|
||||
t.0.join("lib.bas"),
|
||||
"SUB Answer\n'$INCLUDE: 'body.bi'\nEND SUB\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(t.0.join("body.bi"), "PRINT 1\n").unwrap();
|
||||
let mut app = t.app();
|
||||
app.load_initial_project(t.0.join("lib.bas")).unwrap();
|
||||
let include = app.project.find_document(&t.0.join("body.bi")).unwrap();
|
||||
app.project
|
||||
.replace_text(include, 0..8, "PRINT 42\n")
|
||||
.unwrap();
|
||||
let stamp = ProjectStamp::capture(&app.project);
|
||||
menu(&mut app, Command::MakeLibrary);
|
||||
assert_eq!(app.dialog.as_ref().unwrap().fields.len(), 3);
|
||||
let out = t.0.join("out.tbl");
|
||||
field(&mut app, 1, &out.display().to_string());
|
||||
draw(&mut app);
|
||||
let r = app
|
||||
.hits
|
||||
.iter()
|
||||
.find(|(_, h)| matches!(h, Hit::DialogSubmit))
|
||||
.unwrap()
|
||||
.0;
|
||||
app.handle(Event::Mouse(MouseEvent {
|
||||
kind: MouseEventKind::Down(MouseButton::Left),
|
||||
column: r.x,
|
||||
row: r.y,
|
||||
modifiers: M::NONE,
|
||||
}));
|
||||
assert_eq!(await_export(&mut app), ExportStatus::Success);
|
||||
assert_eq!(ProjectStamp::capture(&app.project), stamp);
|
||||
assert!(app.project.document(include).unwrap().is_dirty());
|
||||
assert_eq!(
|
||||
fs::read_to_string(t.0.join("body.bi")).unwrap(),
|
||||
"PRINT 1\n"
|
||||
);
|
||||
plain(&mut app, K::Esc);
|
||||
assert_eq!(app.last_export.as_ref().unwrap().1, ExportStatus::Success);
|
||||
fs::remove_file(t.0.join("lib.bas")).unwrap();
|
||||
fs::remove_file(t.0.join("body.bi")).unwrap();
|
||||
fs::write(t.0.join("consumer.bas"), "CALL Answer\nEND\n").unwrap();
|
||||
fs::write(t.0.join("consumer.mak"), "consumer.bas\nout.tbl\n").unwrap();
|
||||
app.load_initial_project(t.0.join("consumer.mak")).unwrap();
|
||||
app.start_execution().unwrap();
|
||||
for n in 0..100 {
|
||||
app.tick_execution(n);
|
||||
if app.execution == Execution::Ended {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_eq!(tb_runtime::snapshot::text(app.session.screen()), " 42 \n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn executable_target_validation_and_missing_template_are_honest() {
|
||||
use tb_ide::{
|
||||
documents::Destination,
|
||||
export::{Artifact, ExportRequest},
|
||||
};
|
||||
let t = Temp::new();
|
||||
let mut app = t.app();
|
||||
for (system, arch) in [
|
||||
("windows", "aarch64"),
|
||||
("macos", "x86_64"),
|
||||
("unknown", "x86_64"),
|
||||
] {
|
||||
assert!(ExportRequest::validate(
|
||||
&app.project,
|
||||
Artifact::Executable,
|
||||
system,
|
||||
arch,
|
||||
&Destination::new(t.0.join("out"))
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
app.runtime_dir = t.0.join("missing-runtimes");
|
||||
menu(&mut app, Command::MakeExe);
|
||||
let out = t.0.join("out");
|
||||
field(&mut app, 3, &out.display().to_string());
|
||||
plain(&mut app, K::Enter);
|
||||
let status = await_export(&mut app);
|
||||
assert!(
|
||||
matches!(&status, ExportStatus::Failed(e) if e.contains("Runtime-Vorlage fehlt")),
|
||||
"{status:?}"
|
||||
);
|
||||
assert!(!out.exists());
|
||||
assert!(app.dialog.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debugger_reports_absent_library_source_and_can_step_back_to_caller() {
|
||||
let t = Temp::new();
|
||||
let path = t.0.join("lib.bas");
|
||||
fs::write(&path, "SUB Answer\nPRINT 42\nEND SUB\n").unwrap();
|
||||
let mut compiler = tb_vm::project::ProjectCompiler::default();
|
||||
compiler.debug_symbols = true;
|
||||
let bytes = tb_vm::project_io::SourceLoader::default()
|
||||
.load(&path)
|
||||
.unwrap()
|
||||
.compile_library(&mut compiler)
|
||||
.unwrap()
|
||||
.to_tbl()
|
||||
.unwrap();
|
||||
fs::write(t.0.join("lib.tbl"), bytes).unwrap();
|
||||
fs::remove_file(&path).unwrap();
|
||||
fs::write(t.0.join("main.bas"), "CALL Answer\nPRINT 9\nEND\n").unwrap();
|
||||
fs::write(t.0.join("main.mak"), "main.bas\nlib.tbl\n").unwrap();
|
||||
let mut app = t.app();
|
||||
app.load_initial_project(t.0.join("main.mak")).unwrap();
|
||||
let documents = app.project.documents().count();
|
||||
let mut missing = false;
|
||||
for _ in 0..10 {
|
||||
app.execute(Command::Step);
|
||||
for n in 0..100 {
|
||||
app.tick_execution(n);
|
||||
if app.execution != Execution::Running {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if app.message.contains("Bibliotheksquelle nicht verfügbar") {
|
||||
missing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert!(missing, "{}", app.message);
|
||||
assert_eq!(app.project.documents().count(), documents);
|
||||
assert!(!path.exists());
|
||||
app.execute(Command::ProcedureStep);
|
||||
for n in 0..100 {
|
||||
app.tick_execution(n);
|
||||
if app.execution != Execution::Running {
|
||||
break;
|
||||
}
|
||||
}
|
||||
app.execute(Command::Continue);
|
||||
for n in 0..100 {
|
||||
app.tick_execution(n);
|
||||
if app.execution == Execution::Ended {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_eq!(app.execution, Execution::Ended, "{}", app.message);
|
||||
assert_eq!(
|
||||
tb_runtime::snapshot::text(app.session.screen()),
|
||||
" 42 \n 9 \n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finalization_failure_is_reported_and_preserves_existing_output() {
|
||||
let t = Temp::new();
|
||||
let mut app = t.app();
|
||||
let target = tb_export::Target::MacosArm64;
|
||||
app.runtime_dir = t.0.join("runtimes");
|
||||
let template = app
|
||||
.runtime_dir
|
||||
.join(target.triple())
|
||||
.join(target.runtime_name());
|
||||
fs::create_dir_all(template.parent().unwrap()).unwrap();
|
||||
// Header für den negativen Finalisierungstest, kein ausführbarer Zielnachweis.
|
||||
let mut header = vec![0; 256];
|
||||
header[..4].copy_from_slice(&[0xcf, 0xfa, 0xed, 0xfe]);
|
||||
header[4..8].copy_from_slice(&0x0100000cu32.to_le_bytes());
|
||||
header[12..16].copy_from_slice(&2u32.to_le_bytes());
|
||||
header.extend_from_slice(&tb_export::runtime_marker(target));
|
||||
fs::write(&template, header).unwrap();
|
||||
tb_export::prepare_template(&template, target).unwrap();
|
||||
menu(&mut app, Command::MakeExe);
|
||||
for (n, value) in [(1, "macos"), (2, "aarch64")] {
|
||||
while app.dialog.as_ref().unwrap().focus != n {
|
||||
plain(&mut app, K::Tab);
|
||||
}
|
||||
while app.dialog.as_ref().unwrap().fields[n].string() != value {
|
||||
plain(&mut app, K::Right);
|
||||
}
|
||||
}
|
||||
let out = t.0.join("existing");
|
||||
fs::write(&out, b"old").unwrap();
|
||||
field(&mut app, 3, &out.display().to_string());
|
||||
plain(&mut app, K::Tab);
|
||||
plain(&mut app, K::Char(' '));
|
||||
plain(&mut app, K::Enter);
|
||||
let status = await_export(&mut app);
|
||||
assert!(
|
||||
matches!(&status,ExportStatus::Failed(e) if e.contains("Finalisierung")),
|
||||
"{status:?}"
|
||||
);
|
||||
assert_eq!(fs::read(&out).unwrap(), b"old");
|
||||
assert!(fs::read_dir(&t.0).unwrap().all(|p| !p
|
||||
.unwrap()
|
||||
.file_name()
|
||||
.to_string_lossy()
|
||||
.starts_with(".tb-export-")));
|
||||
}
|
||||
|
||||
@@ -488,3 +488,51 @@ fn cursor_after_shortening_uses_original_position_and_open_after_save_uses_fresh
|
||||
.unwrap());
|
||||
assert_eq!(p.document(p.members()[0]).unwrap().code(), "PRINT 7\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn libraries_are_binary_members_with_relative_save_as_and_no_source_views() {
|
||||
let t = Temp::new();
|
||||
let lib = t.write("lib.bas", "SUB Answer\nPRINT 42\nEND SUB\n");
|
||||
let mut compiler = tb_vm::project::ProjectCompiler::default();
|
||||
let bytes = SourceLoader::default()
|
||||
.load(&lib)
|
||||
.unwrap()
|
||||
.compile_library(&mut compiler)
|
||||
.unwrap()
|
||||
.to_tbl()
|
||||
.unwrap();
|
||||
let tbl = t.0.join("lib.tbl");
|
||||
fs::write(&tbl, &bytes).unwrap();
|
||||
fs::remove_file(lib).unwrap();
|
||||
let main = t.write("main.bas", "CALL Answer\nEND\n");
|
||||
let mak = t.write("p.mak", "lib.tbl\nmain.bas\n'$STARTUP: \"main.bas\"\n");
|
||||
let mut p = Project::open(&mak, vec![]).unwrap();
|
||||
let id = p.members()[0];
|
||||
assert!(p.document(id).unwrap().is_library());
|
||||
assert!(p.open_view(id).is_err());
|
||||
assert!(p.replace_text(id, 0..0, "bad").is_err());
|
||||
assert!(p.save_file(id, None).is_err());
|
||||
assert!(p.set_startup(Some(id)).is_err());
|
||||
assert_eq!(p.source_members().len(), 1);
|
||||
p.sources()
|
||||
.unwrap()
|
||||
.compile(&mut compiler, "consumer")
|
||||
.unwrap();
|
||||
fs::create_dir(t.0.join("sub")).unwrap();
|
||||
p.save_project(&t.plan("sub/p.mak")).unwrap();
|
||||
let text = fs::read_to_string(t.0.join("sub/p.mak")).unwrap();
|
||||
assert!(text.contains("../lib.tbl"), "{text}");
|
||||
assert_eq!(fs::read(&tbl).unwrap(), bytes);
|
||||
let mut reopened = Project::open(&t.0.join("sub/p.mak"), vec![]).unwrap();
|
||||
let id = reopened.members()[0];
|
||||
reopened.remove_file(id, None).unwrap();
|
||||
assert!(tbl.exists());
|
||||
reopened.add_file(&tbl).unwrap();
|
||||
assert_eq!(reopened.manifest().members().last().unwrap(), &tbl);
|
||||
assert!(reopened.find_document(&main).is_some());
|
||||
reopened
|
||||
.sources()
|
||||
.unwrap()
|
||||
.compile(&mut compiler, "consumer")
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -305,7 +305,8 @@ fn twenty_back_steps_end_of_contents_and_all_entries() {
|
||||
h.back();
|
||||
assert_eq!(h.position, p);
|
||||
assert!(h.notice.contains("20"));
|
||||
h.visit(11, 0, None);
|
||||
let (last, _) = Catalog::embedded().resolve("", "PLAN.md").unwrap();
|
||||
h.visit(last, 0, None);
|
||||
let p = h.position.clone();
|
||||
h.next();
|
||||
assert_eq!(h.position, p);
|
||||
|
||||
Reference in New Issue
Block a user