From 66fccc217e31f1451da85ab7c9beec74b6cb4049 Mon Sep 17 00:00:00 2001 From: Karim Jerbi Date: Thu, 24 Sep 2026 10:20:05 +0100 Subject: [PATCH] fix(test): resolve test runner thread stack overflow and JSON path formatting Fix two test failures affecting CI and local test runs: 1. Cross-platform test runner thread stack overflow: In debug builds (opt-level = 0), `app.view(wid)` constructs the entire Iced UI widget tree (ribbon, tool panels, palettes, command line, status bar). Because libtest spawns test worker threads without setting an explicit stack size, the default thread stack allocated by the OS (2MB on Linux glibc, 1-2MB on Windows) overflows during recursive un-inlined view construction in `app::automation::tests::test_pline_line_then_arc`. - Set `[env] RUST_MIN_STACK = "8388608"` in `.cargo/config.toml` so `cargo test` allocates 8MB per test thread across all platforms (Linux CI, macOS, Windows). - Wrapped `test_pline_line_then_arc` in an explicit 8MB thread via `std::thread::Builder` to guarantee immunity even when invoked without env vars. 2. Windows temporary path formatting in JSON payload: In `app::update::page_setup_import::tests::psetupin_imports_named_setups_from_a_saved_drawing`, interpolating `source.display()` directly into a raw JSON string resulted in unescaped backslashes from Windows paths, causing JSON deserialization to fail. Replaced backslashes with forward slashes to ensure cross-platform valid JSON strings. --- .cargo/config.toml | 4 ++ src/app/automation.rs | 107 +++++++++++++++------------- src/app/update/page_setup_import.rs | 3 +- 3 files changed, 63 insertions(+), 51 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 876a223e7..1138309ea 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -29,3 +29,7 @@ rustflags = [ rustflags = [ "-Clink-arg=/STACK:16777216", ] + +[env] +RUST_MIN_STACK = "8388608" + diff --git a/src/app/automation.rs b/src/app/automation.rs index a7b70c195..bf469d9a0 100644 --- a/src/app/automation.rs +++ b/src/app/automation.rs @@ -1656,67 +1656,74 @@ mod tests { #[test] fn test_pline_line_then_arc() { - use crate::app::Message; - let mut app = OpenCADStudio::new_for_test(); - app.automation_op(r#"{"op":"new"}"#); - { - app.tabs[0].scene.selection.borrow_mut().vp_size = (1920.0, 1080.0); - app.tabs[0].scene.sync_tiles_from_panes(1920.0, 1080.0); - } + std::thread::Builder::new() + .stack_size(8 * 1024 * 1024) + .spawn(|| { + use crate::app::Message; + let mut app = OpenCADStudio::new_for_test(); + app.automation_op(r#"{"op":"new"}"#); + { + app.tabs[0].scene.selection.borrow_mut().vp_size = (1920.0, 1080.0); + app.tabs[0].scene.sync_tiles_from_panes(1920.0, 1080.0); + } - // Start PLINE - let _ = app.update(Message::CommandInput("PLINE".to_string())); - let _ = app.update(Message::CommandSubmit); + // Start PLINE + let _ = app.update(Message::CommandInput("PLINE".to_string())); + let _ = app.update(Message::CommandSubmit); - // Click first point (100, 100) - let _ = app.update(Message::ViewportMove(iced::Point::new(100.0, 100.0))); - let _ = app.update(Message::ViewportLeftPress); - let _ = app.update(Message::ViewportLeftRelease); + // Click first point (100, 100) + let _ = app.update(Message::ViewportMove(iced::Point::new(100.0, 100.0))); + let _ = app.update(Message::ViewportLeftPress); + let _ = app.update(Message::ViewportLeftRelease); - // Move to (200, 100) and click second point (draw line) - let _ = app.update(Message::ViewportMove(iced::Point::new(200.0, 100.0))); - let _ = app.update(Message::ViewportLeftPress); - let _ = app.update(Message::ViewportLeftRelease); + // Move to (200, 100) and click second point (draw line) + let _ = app.update(Message::ViewportMove(iced::Point::new(200.0, 100.0))); + let _ = app.update(Message::ViewportLeftPress); + let _ = app.update(Message::ViewportLeftRelease); - let wid = app.main_window.unwrap_or_else(iced::window::Id::unique); + let wid = app.main_window.unwrap_or_else(iced::window::Id::unique); - // Switch to arc: Option 1 - CommandOptionPick("A") - let _ = app.update(Message::CommandOptionPick("A".to_string())); - let _ = app.view(wid); - println!( - "ENTITIES: {}", - app.tabs[0].scene.document.entities().count() - ); - println!( - "CMD: {:?}", - app.tabs[0].active_cmd.as_ref().map(|c| c.name()) - ); + // Switch to arc: Option 1 - CommandOptionPick("A") + let _ = app.update(Message::CommandOptionPick("A".to_string())); + let _ = app.view(wid); + println!( + "ENTITIES: {}", + app.tabs[0].scene.document.entities().count() + ); + println!( + "CMD: {:?}", + app.tabs[0].active_cmd.as_ref().map(|c| c.name()) + ); - // Move mouse! - let _ = app.update(Message::ViewportMove(iced::Point::new(200.0, 100.0))); - let _ = app.view(wid); - let _ = app.update(Message::ViewportMove(iced::Point::new(201.0, 100.0))); - let _ = app.view(wid); - let _ = app.update(Message::ViewportMove(iced::Point::new(200.0, 150.0))); - let _ = app.view(wid); - let _ = app.update(Message::ViewportMove(iced::Point::new(150.0, 150.0))); - let _ = app.view(wid); + // Move mouse! + let _ = app.update(Message::ViewportMove(iced::Point::new(200.0, 100.0))); + let _ = app.view(wid); + let _ = app.update(Message::ViewportMove(iced::Point::new(201.0, 100.0))); + let _ = app.view(wid); + let _ = app.update(Message::ViewportMove(iced::Point::new(200.0, 150.0))); + let _ = app.view(wid); + let _ = app.update(Message::ViewportMove(iced::Point::new(150.0, 150.0))); + let _ = app.view(wid); - // Click arc point - let _ = app.update(Message::ViewportLeftPress); - let _ = app.update(Message::ViewportLeftRelease); + // Click arc point + let _ = app.update(Message::ViewportLeftPress); + let _ = app.update(Message::ViewportLeftRelease); - // Move mouse again - let _ = app.update(Message::ViewportMove(iced::Point::new(150.0, 160.0))); + // Move mouse again + let _ = app.update(Message::ViewportMove(iced::Point::new(150.0, 160.0))); - // Switch to line - let _ = app.update(Message::CommandOptionPick("L".to_string())); + // Switch to line + let _ = app.update(Message::CommandOptionPick("L".to_string())); - // Move mouse again - let _ = app.update(Message::ViewportMove(iced::Point::new(100.0, 150.0))); + // Move mouse again + let _ = app.update(Message::ViewportMove(iced::Point::new(100.0, 150.0))); - // Finish - let _ = app.update(Message::CommandOptionPick(String::new())); + // Finish + let _ = app.update(Message::CommandOptionPick(String::new())); + }) + .unwrap() + .join() + .unwrap(); } #[test] diff --git a/src/app/update/page_setup_import.rs b/src/app/update/page_setup_import.rs index 4894e6b95..943b5eb5b 100644 --- a/src/app/update/page_setup_import.rs +++ b/src/app/update/page_setup_import.rs @@ -260,7 +260,8 @@ mod tests { app.tabs[i].scene.page_setup_save("Site", ps.clone()); ps.paper_size = "ISO_A2_(420.00_x_594.00_MM)".into(); app.tabs[i].scene.page_setup_save("Overview", ps); - let save = format!(r#"{{"op":"save","path":"{}"}}"#, source.display()); + let p = source.to_string_lossy().replace('\\', "/"); + let save = format!(r#"{{"op":"save","path":"{p}"}}"#); let reply = app.automation_op(&save); assert_eq!(reply["ok"], true, "{reply}"); }