openpencil/crates/op-host-native/tests/resize_smoke.rs

126 lines
4.7 KiB
Rust
Raw Normal View History

2026-05-05 13:15:00 +00:00
//! Spec v19 acceptance #5 (resize / DPI no-flicker) / Phase A Gate
//! round 2 CONCERN 2.
//!
//! Round 1 only exercised the `resize` tracing span on an inert
//! context — that proves the span fires but doesn't pin the
//! invariant the acceptance is actually about (after a resize, a
//! draw issued through `NativeBackend` must land on a surface of
//! the new dimensions without panic). This test fills that gap
//! using a raster Skia surface as a stand-in for the GL FBO that
//! `SharedSkiaContext::resize` rebuilds — `cargo test` cannot drive
//! a real GL surface from a worker thread, but the
//! `NativeBackend::fill_rect` translation layer is identical
//! whether the canvas is raster- or GPU-backed.
//!
//! Two passes:
//! 1. Grow 400×300 → 800×600, paint at centre, assert red.
//! 2. Shrink 800×600 → 400×300, paint at centre, assert red.
//!
//! Plus: a tracing-span pin on `SharedSkiaContext::resize` itself
//! (already covered structurally by `tracing_spans.rs`, repeated
//! here at the new size cadence to lock the resize-grow / resize-
//! shrink invariant on round-2's stricter spec).
//!
//! Windows is `#[ignore]`d at the test level — CI runners ship
//! without a usable GL stack, the raster path is identical across
//! macOS / Linux, and CONCERN 2 only requires non-Windows coverage.
use op_editor_ui::{Color, Point2D, Rect};
use op_host_native::{NativeBackend, SharedSkiaContext};
2026-05-05 13:15:00 +00:00
use tracing_test::traced_test;
/// Helper: build a raster Skia surface of `(w, h)`, paint a
/// `NativeBackend::fill_rect` covering most of it, return the
/// resulting pixel at the surface centre. Mirrors the chain
/// `SharedSkiaContext::resize` → `with_frame` →
/// `NativeBackend::fill_rect` minus the GL stack.
fn paint_at_size(backend: &mut NativeBackend, w: i32, h: i32) -> [u8; 4] {
let mut surface = skia_safe::surfaces::raster_n32_premul((w, h))
.expect("raster_n32_premul allocated for resize test");
surface.canvas().clear(skia_safe::Color::WHITE);
backend.fill_rect(
surface.canvas(),
Rect {
origin: Point2D::new(10.0, 10.0),
size: Point2D::new((w - 20) as f32, (h - 20) as f32),
},
Color::RED,
);
let stride = (w * 4) as usize;
let mut pixels = vec![0u8; stride * (h as usize)];
let info = skia_safe::ImageInfo::new(
(w, h),
skia_safe::ColorType::RGBA8888,
skia_safe::AlphaType::Premul,
None,
);
let read_ok = surface.read_pixels(&info, &mut pixels, stride, (0, 0));
assert!(
read_ok,
"raster Surface::read_pixels must succeed at {}×{}",
w, h
);
let cx = (w / 2) as usize;
let cy = (h / 2) as usize;
let offset = cy * stride + cx * 4;
[
pixels[offset],
pixels[offset + 1],
pixels[offset + 2],
pixels[offset + 3],
]
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
fn resize_grow_then_shrink_paints_through() {
let mut backend = NativeBackend::with_dpi(1.0);
// Initial 400×300.
let small_pixel = paint_at_size(&mut backend, 400, 300);
assert!(
small_pixel[0] > 200 && small_pixel[1] < 60 && small_pixel[2] < 60,
"400×300 centre expected red, got {:?}",
small_pixel
);
// Grow to 800×600 — same `NativeBackend` instance must paint
// through to a fresh-larger surface (matches the realistic
// resize path: window expanded, `SharedSkiaContext::resize`
// rebuilt the Skia surface, next frame's `fill_rect` lands on
// it).
let grown_pixel = paint_at_size(&mut backend, 800, 600);
assert!(
grown_pixel[0] > 200 && grown_pixel[1] < 60 && grown_pixel[2] < 60,
"800×600 centre expected red, got {:?}",
grown_pixel
);
// Shrink back to 400×300 — same invariant, opposite direction.
let shrunk_pixel = paint_at_size(&mut backend, 400, 300);
assert!(
shrunk_pixel[0] > 200 && shrunk_pixel[1] < 60 && shrunk_pixel[2] < 60,
"shrunk 400×300 centre expected red, got {:?}",
shrunk_pixel
);
}
/// Pin the `resize` span emission on a `SharedSkiaContext` whose
/// fields are at the post-teardown shape (surface = None, dc =
/// None). Complements `tracing_spans.rs` by exercising both grow
/// (800×600) and shrink (400×300) paths plus the 0×0 no-op clamp.
#[cfg_attr(target_os = "windows", ignore)]
#[traced_test]
#[test]
fn resize_emits_span_grow_and_shrink() {
let mut ctx = SharedSkiaContext::inert_for_lifecycle_test();
ctx.resize(800, 600).expect("resize 800x600");
ctx.resize(400, 300).expect("resize 400x300");
ctx.resize(0, 0).expect("resize 0x0 no-op clamp");
assert!(logs_contain("resize"), "resize span missing");
}