mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-08 09:31:03 +00:00
Merge pull request #6075 from dotCooCoo/fix/more-tests-without-time-limits
Tests: the CSS, glow and .ai Zstandard guards count work instead of timing it
This commit is contained in:
@@ -140,7 +140,7 @@ fn pipes_and_folders_are_not_read() {
|
||||
let direct = s.execute("script.check", &json!({ "path": pipe })).map_err(|e| e.to_string());
|
||||
let _ = tx.send((included.map_err(|e| e.to_string()), direct));
|
||||
});
|
||||
let (included, direct) = rx.recv_timeout(std::time::Duration::from_secs(20)).expect("the checks return");
|
||||
let (included, direct) = rx.recv_timeout(std::time::Duration::from_secs(300)).expect("the checks return");
|
||||
let statuses: Vec<String> = included.unwrap().as_array().unwrap().iter().map(|i| i["status"].as_str().unwrap().to_string()).collect();
|
||||
assert_eq!(statuses, ["missing", "missing"]);
|
||||
assert!(direct.is_err_and(|e| e.contains("not a file")));
|
||||
|
||||
@@ -74,6 +74,19 @@ const MAX_ZSTD_WINDOW: u64 = 64 << 20;
|
||||
/// Most Zstandard frames read (the app writes one).
|
||||
const MAX_FRAMES: usize = 4096;
|
||||
|
||||
#[cfg(test)]
|
||||
thread_local! {
|
||||
/// How many bytes [`Codec::decode`] has looked at on this thread to find whether another
|
||||
/// Zstandard frame follows or only zero padding is left.
|
||||
static PADDING_LOOKS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
|
||||
}
|
||||
|
||||
/// [`PADDING_LOOKS`] of this thread.
|
||||
#[cfg(test)]
|
||||
fn padding_looks() -> u64 {
|
||||
PADDING_LOOKS.get()
|
||||
}
|
||||
|
||||
const EPS_BEGIN: &[u8] = b"%AI9_PrivateDataBegin";
|
||||
const EPS_END: &[u8] = b"%AI9_PrivateDataEnd";
|
||||
|
||||
@@ -171,7 +184,11 @@ impl Codec {
|
||||
// can itself end in a zero byte, so the padding is what is left after the last frame.
|
||||
let mut src = data;
|
||||
let mut frames = 0usize;
|
||||
while src.iter().any(|b| *b != 0) {
|
||||
while src.iter().any(|b| {
|
||||
#[cfg(test)]
|
||||
PADDING_LOOKS.set(PADDING_LOOKS.get().saturating_add(1));
|
||||
*b != 0
|
||||
}) {
|
||||
frames += 1;
|
||||
if frames > MAX_FRAMES {
|
||||
return Err(damaged("its Zstandard compression"));
|
||||
|
||||
@@ -5312,10 +5312,15 @@ fn editing_data_past_the_limit_is_refused() {
|
||||
fn many_zstandard_frames_are_read_in_one_pass_and_capped() {
|
||||
let frame = zstd(b" ");
|
||||
let frames = |n: usize| -> Vec<u8> { [&b"%AI24_ZStandard_Data"[..], &frame.repeat(n), &[0; 64]].concat() };
|
||||
let started = std::time::Instant::now();
|
||||
assert_eq!(decode_private(&frames(4000)).unwrap().len(), 4000);
|
||||
assert!(decode_private(&frames(50_000)).unwrap_err().contains("Zstandard"));
|
||||
assert!(started.elapsed() < std::time::Duration::from_secs(20), "{:?}", started.elapsed());
|
||||
let (few, many) = (frames(4000), frames(50_000));
|
||||
let before = padding_looks();
|
||||
assert_eq!(decode_private(&few).unwrap().len(), 4000);
|
||||
assert!(decode_private(&many).unwrap_err().contains("Zstandard"));
|
||||
// The first byte of each of the 4,000 frames and of the 4,097 read before the cap, and the
|
||||
// 64 zeros of padding once: looking past the frames to the padding before each frame would
|
||||
// look at 64 or more bytes per frame.
|
||||
let looked = padding_looks() - before;
|
||||
assert!(looked <= 2 * (4000 + 4097 + 64), "looked at {looked} bytes for padding");
|
||||
}
|
||||
|
||||
/// A Zstandard frame that ends in a zero byte keeps it: only the zeros after the last frame are
|
||||
|
||||
@@ -10,6 +10,26 @@ use super::{
|
||||
effects, fill_rule, peniko,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
thread_local! {
|
||||
/// How many silhouettes of drop shadows and outer glows renderers on this thread have drawn:
|
||||
/// one per shadow or glow raster made ([`Renderer::shadow_raster`]) and per one drawn through a
|
||||
/// filter layer ([`Renderer::draw_shadow_filter`]).
|
||||
static SILHOUETTES: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
|
||||
}
|
||||
|
||||
/// [`SILHOUETTES`] of this thread.
|
||||
#[cfg(test)]
|
||||
pub(super) fn silhouettes() -> u64 {
|
||||
SILHOUETTES.get()
|
||||
}
|
||||
|
||||
/// Adds one to [`SILHOUETTES`].
|
||||
#[cfg(test)]
|
||||
fn note_silhouette() {
|
||||
SILHOUETTES.set(SILHOUETTES.get().saturating_add(1));
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
/// Drop shadow / outer glow `fx` (shadow-cache slot `i` of `n`) as a cached raster: the
|
||||
/// silhouette `paint` draws is rendered once into a small crop, blurred and tinted on the CPU,
|
||||
@@ -125,6 +145,8 @@ impl Renderer {
|
||||
let decoded = crate::paint::decode_failures();
|
||||
let outer_image_left_out = self.image_left_out.replace(false);
|
||||
let pattern_images_left_out = crate::pattern::images_left_out();
|
||||
#[cfg(test)]
|
||||
note_silhouette();
|
||||
self.inside_layer(|r| paint(r, &mut off, &shifted(&crop, dx, dy)));
|
||||
let failed = crate::paint::decode_failures() - decoded;
|
||||
let image_left_out = self.image_left_out.unwrap_or(false) || crate::pattern::images_left_out() > pattern_images_left_out;
|
||||
@@ -230,6 +252,8 @@ impl Renderer {
|
||||
// The offset moves the content rather than the filter: vello_cpu drops layer content
|
||||
// that lies entirely outside the viewport before filtering.
|
||||
let reach = reach + Vec2::new(dx, dy);
|
||||
#[cfg(test)]
|
||||
note_silhouette();
|
||||
self.with_filters(ctx, f, reach, blur, Some((mode, opacity)), |r, c, fr, comp| {
|
||||
c.set_transform(fr.view);
|
||||
c.push_layer(None, comp.map(|m| blend_mode(m.0)), comp.map(|m| m.1), None, Some(filter));
|
||||
|
||||
@@ -467,9 +467,10 @@ fn a_glow_too_large_for_a_full_size_raster_keeps_its_solid_silhouette() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thirty_stacked_glows_draw_quickly_cached_or_not() {
|
||||
fn thirty_stacked_glows_draw_thirty_silhouettes_cached_or_not() {
|
||||
// GUARDS: each glow is of the content with the glows before it; drawing those again for every glow costs 2^n.
|
||||
// A shape draws through the shadow cache, a symbol instance without it (the filter layers).
|
||||
// Each glow's silhouette is drawn once.
|
||||
let glows: Vec<(&str, serde_json::Value)> =
|
||||
(0..30).map(|k| ("stylize.outerGlow", json!({"color": "#ff0000", "mode": "normal", "opacity": 20, "blur": 1 + k % 4}))).collect();
|
||||
let shape = doc_with(Rect::new(30.0, 30.0, 70.0, 70.0), Color::BLACK, glows.clone());
|
||||
@@ -486,9 +487,10 @@ fn thirty_stacked_glows_draw_quickly_cached_or_not() {
|
||||
let l = instanced.layers[0].id;
|
||||
instanced.insert(Some(l), 0, instance).unwrap();
|
||||
for d in [&shape, &instanced] {
|
||||
let start = std::time::Instant::now();
|
||||
let before = super::renderer::silhouettes();
|
||||
let p = render(d).pixel(25, 50);
|
||||
assert!(start.elapsed() < std::time::Duration::from_secs(20), "{:?}", start.elapsed());
|
||||
let drawn = super::renderer::silhouettes() - before;
|
||||
assert_eq!(drawn, 30, "silhouettes drawn for 30 glows");
|
||||
assert!(p[1] < 250, "the glows show: {p:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1333,7 +1333,7 @@ fn invalid_dash_pattern_strokes_solid() {
|
||||
let d = line(Some(pattern.clone()));
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
std::thread::spawn(move || tx.send(render(&d)).ok());
|
||||
let img = rx.recv_timeout(std::time::Duration::from_secs(20)).unwrap_or_else(|_| panic!("rendering dash {pattern:?} did not finish"));
|
||||
let img = rx.recv_timeout(std::time::Duration::from_secs(300)).unwrap_or_else(|_| panic!("rendering dash {pattern:?} did not finish"));
|
||||
assert_eq!(img.pixels, solid.pixels, "dash {pattern:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,18 @@ fn is_ident(c: char) -> bool {
|
||||
/// How deep `:not()`s may nest (real style sheets nest one or two).
|
||||
const MAX_NOT_NESTING: usize = 8;
|
||||
|
||||
#[cfg(test)]
|
||||
thread_local! {
|
||||
/// How many times [`Selector::matches`] has tested a selector against an element on this thread.
|
||||
static SELECTOR_TESTS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
|
||||
}
|
||||
|
||||
/// [`SELECTOR_TESTS`] of this thread.
|
||||
#[cfg(test)]
|
||||
fn selector_tests() -> u64 {
|
||||
SELECTOR_TESTS.get()
|
||||
}
|
||||
|
||||
impl Selector {
|
||||
/// `None` for selectors this cascade doesn't support (they never match).
|
||||
fn parse(s: &str) -> Option<Self> {
|
||||
@@ -273,6 +285,8 @@ impl Selector {
|
||||
memo.insert(key, hit);
|
||||
hit
|
||||
}
|
||||
#[cfg(test)]
|
||||
SELECTOR_TESTS.set(SELECTOR_TESTS.get().saturating_add(1));
|
||||
go(&self.parts, n, &mut HashMap::new())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,11 +249,11 @@ proptest! {
|
||||
}
|
||||
}
|
||||
|
||||
/// 50,000 class rules give 50,000 elements, each with one of the classes, their fill within
|
||||
/// 20 s.
|
||||
/// 5,000 class rules give 5,000 elements, each with one of the classes, their fill, testing
|
||||
/// about one rule per element.
|
||||
#[test]
|
||||
fn styling_50000_elements_with_50000_rules_finishes_within_20_s() {
|
||||
const N: usize = 50_000;
|
||||
fn styling_5000_elements_with_5000_rules_tests_about_one_rule_each() {
|
||||
const N: usize = 5_000;
|
||||
let rules: String = (0..N).map(|i| format!(".c{i} {{ fill: #{i:06x} }}\n")).collect();
|
||||
let rects: String = (0..N).map(|i| format!(r#"<rect class="c{i}"/>"#)).collect();
|
||||
let svg = format!(r#"<svg xmlns="http://www.w3.org/2000/svg"><style>{rules}</style>{rects}</svg>"#);
|
||||
@@ -262,9 +262,12 @@ fn styling_50000_elements_with_50000_rules_finishes_within_20_s() {
|
||||
let doc = roxmltree::Document::parse(&svg).unwrap();
|
||||
let st = Styles::new(&doc);
|
||||
let fills: Vec<Option<String>> = doc.descendants().filter(|n| n.tag_name().name() == "rect").map(|n| st.own(n, "fill")).collect();
|
||||
tx.send(fills).ok();
|
||||
tx.send((fills, selector_tests())).ok();
|
||||
});
|
||||
let fills = rx.recv_timeout(std::time::Duration::from_secs(20)).unwrap_or_else(|_| panic!("styling {N} elements did not finish within 20 s"));
|
||||
let (fills, tests) =
|
||||
rx.recv_timeout(std::time::Duration::from_secs(300)).unwrap_or_else(|_| panic!("styling {N} elements did not finish within 300 s"));
|
||||
// Testing every rule against every element would test N × N selectors.
|
||||
assert!(tests <= 2 * N as u64, "styling {N} elements tested {tests} selectors");
|
||||
assert_eq!(fills.len(), N);
|
||||
for (i, fill) in fills.iter().enumerate() {
|
||||
assert_eq!(fill.as_deref(), Some(format!("#{i:06x}").as_str()));
|
||||
|
||||
@@ -704,12 +704,12 @@ fn area_flow_moves_a_word_down_to_a_line_wide_enough_for_it() {
|
||||
}
|
||||
|
||||
/// Lays `t` out as [`layout`] does, on a thread of its own, and fails the test when that panics or
|
||||
/// takes more than 20 s.
|
||||
/// has not ended after 300 s.
|
||||
fn layout_soon(t: &TextObject) -> TextLayout {
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let (t, text) = (t.clone(), t.plain_text());
|
||||
std::thread::spawn(move || tx.send(layout(db(), &t)).ok());
|
||||
rx.recv_timeout(std::time::Duration::from_secs(20)).unwrap_or_else(|e| panic!("laying out {text:?}: {e}"))
|
||||
rx.recv_timeout(std::time::Duration::from_secs(300)).unwrap_or_else(|e| panic!("laying out {text:?}: {e}"))
|
||||
}
|
||||
|
||||
/// Each line's baseline, left and right edges, and byte range.
|
||||
|
||||
Reference in New Issue
Block a user