From 04b90d88d070cdf82611e712ce56beee708933cc Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 14 Nov 2026 02:29:18 -0800 Subject: [PATCH] Tests: raster effects draw the same twice while another test assigns a document profile tests::newdoc::a_profile_brings_the_color_profile_its_file_carries and tests::saveoptions::an_embedded_profile_is_restored give their front document a non-sRGB RGB profile. That profile becomes the process-wide active color settings and stays active until a document without one comes to the front. Every color the engine test process renders goes through those settings, so the test tests::rasterfilters::each_effect_changes_the_canvas_and_draws_the_same_twice could draw the same art in two colors and fail with " is deterministic". The two tests now run again in a child process of the test binary through a shared helper, tests::in_own_process. The SVG Drop Shadow test for a non-sRGB working space uses the helper in place of its own copy, and the GLOBAL doc comment in tests/colormgmt.rs names this route. The rasterfilters test now checks that the color settings are the defaults before it draws and that nothing replaced them while it drew. A test that changes them in the same process makes it fail with "another test changed the color settings while this one drew". With the three tests run together (--test-threads=3) on Linux, the rasterfilters test failed 10 of 10 runs without the child processes, each time with that message, and passed 10 of 10 with them. --- crates/engine/src/cmd/fileio/tests_svg.rs | 16 ++-------------- crates/engine/src/tests.rs | 19 +++++++++++++++++++ crates/engine/src/tests/colormgmt.rs | 5 ++++- crates/engine/src/tests/newdoc.rs | 3 +++ crates/engine/src/tests/rasterfilters.rs | 11 +++++++++-- crates/engine/src/tests/saveoptions.rs | 3 +++ 6 files changed, 40 insertions(+), 17 deletions(-) diff --git a/crates/engine/src/cmd/fileio/tests_svg.rs b/crates/engine/src/cmd/fileio/tests_svg.rs index eb3989597f..121cc0b279 100644 --- a/crates/engine/src/cmd/fileio/tests_svg.rs +++ b/crates/engine/src/cmd/fileio/tests_svg.rs @@ -352,20 +352,8 @@ fn svg_darkness_preserves_source_model_and_gradient_appearance() { #[test] fn svg_darkness_uses_rendered_shadow_in_non_srgb_working_space() { - const CHILD: &str = "VECTORCRAFT_TEST_SHADOW_RGB_PROFILE"; - if std::env::var_os(CHILD).is_none() { - // Color settings are process-wide: exercise this profile in a separate test process. - let output = std::process::Command::new(std::env::current_exe().unwrap()) - .args(["--exact", "cmd::fileio::tests_svg::svg_darkness_uses_rendered_shadow_in_non_srgb_working_space", "--nocapture"]) - .env(CHILD, "1") - .output() - .unwrap(); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!( - output.status.success() && stdout.contains("test result: ok. 1 passed; 0 failed;"), - "isolated color profile test failed:\n{stdout}\n{}", - String::from_utf8_lossy(&output.stderr), - ); + // Color settings are process-wide: exercise this profile in a separate test process. + if !crate::tests::in_own_process("cmd::fileio::tests_svg::svg_darkness_uses_rendered_shadow_in_non_srgb_working_space") { return; } use vectorcraft_color::{Color, Paint, cms}; diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 2f849901a4..57445a36be 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -241,6 +241,25 @@ pub(crate) fn key_guide(s: &Session) -> Option { st.selection.key_guide.and_then(|k| crate::cmd::guide_indexes(&st.doc, &[k]).first().copied()) } +/// Runs the unit test `path` (`tests::newdoc::...`) again in a child process of the test binary +/// → whether this is that child. For a test that changes process-wide state the tests running +/// beside it read, such as the active color settings. +pub(crate) fn in_own_process(path: &str) -> bool { + const CHILD: &str = "VECTORCRAFT_TEST_OWN_PROCESS"; + if std::env::var_os(CHILD).is_some() { + return true; + } + let output = + std::process::Command::new(std::env::current_exe().unwrap()).args(["--exact", path, "--nocapture"]).env(CHILD, "1").output().unwrap(); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!( + output.status.success() && stdout.contains("test result: ok. 1 passed; 0 failed;"), + "{path} failed in its own process:\n{stdout}\n{}", + String::from_utf8_lossy(&output.stderr), + ); + false +} + #[test] fn create_and_undo_redo() { let mut s = session(); diff --git a/crates/engine/src/tests/colormgmt.rs b/crates/engine/src/tests/colormgmt.rs index 493f569805..6ab0569e82 100644 --- a/crates/engine/src/tests/colormgmt.rs +++ b/crates/engine/src/tests/colormgmt.rs @@ -16,7 +16,10 @@ use crate::*; /// Every test here reads or changes the process-wide proof view or colour settings: one at a time. /// Tests that switch the Color Settings away from the defaults live in `tests/color_settings.rs` /// (their own process): every colour shown or rendered goes through them, so no lock could cover -/// all the tests that would see the change. +/// all the tests that would see the change. A unit test that assigns its front document a profile +/// other than the working space changes the active settings the same way until another document +/// comes to the front: it starts with `crate::tests::in_own_process`, which runs it again in a +/// child process. pub(crate) static GLOBAL: Mutex<()> = Mutex::new(()); fn session() -> Session { diff --git a/crates/engine/src/tests/newdoc.rs b/crates/engine/src/tests/newdoc.rs index 209d22a42a..094f634799 100644 --- a/crates/engine/src/tests/newdoc.rs +++ b/crates/engine/src/tests/newdoc.rs @@ -683,6 +683,9 @@ fn the_new_document_links_from_the_profile_file() { /// is a warning. #[test] fn a_profile_brings_the_color_profile_its_file_carries() { + if !super::in_own_process("tests::newdoc::a_profile_brings_the_color_profile_its_file_carries") { + return; + } use vectorcraft_color::cms; let (mut s, dir) = with_profiles("icc"); let builtin = cms::profiles().into_iter().find(|p| p.kind == cms::ProfileKind::Rgb && p.builtin && p.name != cms::SRGB).unwrap(); diff --git a/crates/engine/src/tests/rasterfilters.rs b/crates/engine/src/tests/rasterfilters.rs index 236580d5b7..534f79b1bc 100644 --- a/crates/engine/src/tests/rasterfilters.rs +++ b/crates/engine/src/tests/rasterfilters.rs @@ -5,6 +5,7 @@ //! as images of the effected object. use serde_json::{Value, json}; +use vectorcraft_color::cms; use vectorcraft_doc::NodeKind; use vectorcraft_geom::Rect; use vectorcraft_render::{Rendered, Renderer}; @@ -173,16 +174,22 @@ fn each_effect_changes_the_canvas_and_draws_the_same_twice() { EFFECTS.into_iter().chain(ARTISTIC).chain(BRUSH_STROKES).chain(DISTORT).chain(PIXELATE).chain(SKETCH).chain(TEXTURE).chain(VIDEO) { let (mut s, g) = striped(); + // Every color drawn goes through the process-wide color settings. A test that changes them + // runs in a process of its own (`crate::tests::in_own_process`). + let colors = cms::active(); + assert_eq!(cms::active_settings(), cms::ColorSettings::default(), "{id}: another test left the color settings changed"); let plain = render(&s); let params: Value = serde_json::from_str(params).unwrap(); s.execute("effect.apply", &json!({"effect": id, "ids": [g], "params": params})).unwrap(); let a = render(&s); - assert!(mean_diff(&plain, &a) > 0.0005, "{id} shows: {}", mean_diff(&plain, &a)); - assert_eq!(a.pixels, render(&s).pixels, "{id} is deterministic"); + let again = render(&s); // The multithreaded pipeline draws the same. let mut mt = Renderer::new(); mt.threads = 4; let b = mt.render_region(&s.doc().unwrap().doc, Rect::new(0.0, 0.0, 200.0, 200.0), 1.0, true); + assert!(std::sync::Arc::ptr_eq(&colors, &cms::active()), "{id}: another test changed the color settings while this one drew"); + assert!(mean_diff(&plain, &a) > 0.0005, "{id} shows: {}", mean_diff(&plain, &a)); + assert_eq!(a.pixels, again.pixels, "{id} is deterministic"); assert!(mean_diff(&a, &b) < 0.002, "{id}: multithreaded differs by {}", mean_diff(&a, &b)); } } diff --git a/crates/engine/src/tests/saveoptions.rs b/crates/engine/src/tests/saveoptions.rs index a5b19563a9..0f7205eaae 100644 --- a/crates/engine/src/tests/saveoptions.rs +++ b/crates/engine/src/tests/saveoptions.rs @@ -112,6 +112,9 @@ fn a_linked_pngs_bytes_are_embedded() { #[test] fn an_embedded_profile_is_restored() { + if !super::in_own_process("tests::saveoptions::an_embedded_profile_is_restored") { + return; + } let builtin = cms::profiles().into_iter().find(|p| p.kind == cms::ProfileKind::Rgb && p.builtin && p.name != cms::SRGB).unwrap(); let icc = cms::icc_bytes(&builtin.name).unwrap(); let name = format!("Studio RGB {}", std::process::id());