mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-25 11:07:01 -06:00
Merge #1292: Tests: UI tests that render with wgpu take turns, so the Windows test run no longer crashes
This commit is contained in:
@@ -192,6 +192,7 @@ fn preview_rejects_a_from_that_is_not_an_effect_index() {
|
||||
#[test]
|
||||
#[ignore = "writes a screenshot for review"]
|
||||
fn screenshot() {
|
||||
let _gpu = crate::gpu_lock::gpu_lock();
|
||||
let mut h = harness();
|
||||
h.run_steps(2);
|
||||
let img = h.render().unwrap();
|
||||
|
||||
@@ -65,6 +65,7 @@ fn reject_all_empties_the_pane_and_keeps_the_deck() {
|
||||
#[test]
|
||||
#[ignore = "writes a screenshot for review"]
|
||||
fn screenshot() {
|
||||
let _gpu = crate::gpu_lock::gpu_lock();
|
||||
let (mut h, _) = harness();
|
||||
// Review tab.
|
||||
h.state_mut().ui.tab = "Review".into();
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
//! Tests that render a harness run one at a time in each test process.
|
||||
//!
|
||||
//! `Harness::render` creates a wgpu device on its first call, and egui_kittest prefers a CPU
|
||||
//! adapter. When `cargo test` runs several of these tests on parallel threads, the test binary can
|
||||
//! exit with `0xc0000005` (`STATUS_ACCESS_VIOLATION`). Under cargo-nextest each test is its own
|
||||
//! process and the lock has no effect.
|
||||
//!
|
||||
//! Every test that calls `render()` on a harness takes [`gpu_lock`] on its first line
|
||||
//! (`let _gpu = crate::gpu_lock::gpu_lock();`) and holds the guard for its whole run.
|
||||
|
||||
use std::sync::{Mutex, MutexGuard, PoisonError};
|
||||
|
||||
static GPU_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
/// Returns a guard that the test holds for its whole run, so tests that render run one at a time.
|
||||
/// A test that failed while holding it does not block the tests after it.
|
||||
pub(crate) fn gpu_lock() -> MutexGuard<'static, ()> {
|
||||
GPU_LOCK.lock().unwrap_or_else(PoisonError::into_inner)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn a_panicked_holder_does_not_block_the_next_test() {
|
||||
let _ = std::thread::spawn(|| {
|
||||
let _gpu = gpu_lock();
|
||||
panic!("a GPU test failed");
|
||||
})
|
||||
.join();
|
||||
drop(gpu_lock());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn holders_take_turns() {
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
static INSIDE: AtomicUsize = AtomicUsize::new(0);
|
||||
static MAX: AtomicUsize = AtomicUsize::new(0);
|
||||
let threads: Vec<_> = (0..8)
|
||||
.map(|_| {
|
||||
std::thread::spawn(|| {
|
||||
let _gpu = gpu_lock();
|
||||
let now = INSIDE.fetch_add(1, Ordering::SeqCst) + 1;
|
||||
MAX.fetch_max(now, Ordering::SeqCst);
|
||||
std::thread::sleep(std::time::Duration::from_millis(2));
|
||||
INSIDE.fetch_sub(1, Ordering::SeqCst);
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
for t in threads {
|
||||
assert!(t.join().is_ok());
|
||||
}
|
||||
assert_eq!(MAX.load(Ordering::SeqCst), 1);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use crate::{Services, SlideApp};
|
||||
|
||||
#[test]
|
||||
fn turn_guides_on_drag_one_and_drop_it_off_the_slide() {
|
||||
let _gpu = crate::gpu_lock::gpu_lock();
|
||||
let mut s = Session::with_new();
|
||||
s.execute("slide.new", &json!({"layout": "blank"})).unwrap();
|
||||
let app = SlideApp::new(s, Services::default());
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::{Services, SlideApp};
|
||||
|
||||
#[test]
|
||||
fn pen_a_rectangle_then_ink_to_shape() {
|
||||
let _gpu = crate::gpu_lock::gpu_lock();
|
||||
let mut s = Session::with_new();
|
||||
s.execute("slide.new", &json!({"layout": "blank"})).unwrap();
|
||||
let mut app = SlideApp::new(s, Services::default());
|
||||
|
||||
@@ -2120,6 +2120,8 @@ mod field_frame_tests;
|
||||
#[cfg(test)]
|
||||
mod find_dialog_tests;
|
||||
#[cfg(test)]
|
||||
mod gpu_lock;
|
||||
#[cfg(test)]
|
||||
mod huge_deck_tests;
|
||||
#[cfg(test)]
|
||||
mod ime_tests;
|
||||
@@ -2867,6 +2869,7 @@ mod tests {
|
||||
/// they can be selected and moved.
|
||||
#[test]
|
||||
fn off_slide_objects_show_on_the_pasteboard_and_can_be_selected_and_moved() {
|
||||
let _gpu = crate::gpu_lock::gpu_lock();
|
||||
let mut harness = canvas_harness();
|
||||
let off =
|
||||
harness.state_mut().session.execute("shape.insert", &json!({"preset": "rect", "rect": [1000.0, 100.0, 120.0, 80.0]})).unwrap()["id"]
|
||||
|
||||
@@ -67,6 +67,7 @@ fn ribbon_button_opens_the_dialog_and_replaces() {
|
||||
#[test]
|
||||
#[ignore = "writes a screenshot for review"]
|
||||
fn screenshot() {
|
||||
let _gpu = crate::gpu_lock::gpu_lock();
|
||||
let mut h = harness();
|
||||
h.render().unwrap().save("/tmp/replacefonts0.png").unwrap();
|
||||
h.state_mut().dialog = Some(crate::dialogs::Dialog::new("replaceFonts"));
|
||||
|
||||
@@ -34,6 +34,7 @@ fn ruler(h: &mut Harness<'_, SlideApp>) -> serde_json::Value {
|
||||
|
||||
#[test]
|
||||
fn drag_indents_add_move_and_remove_tabs_on_the_ruler() {
|
||||
let _gpu = crate::gpu_lock::gpu_lock();
|
||||
let mut s = Session::with_new();
|
||||
s.execute("slide.new", &json!({"layout": "blank"})).unwrap();
|
||||
let id = s.execute("insert.textBox", &json!({"rect": [100, 150, 500, 40]})).unwrap()["id"].as_u64().unwrap();
|
||||
@@ -105,6 +106,7 @@ fn editing_harness() -> Harness<'static, SlideApp> {
|
||||
|
||||
#[test]
|
||||
fn the_tab_selector_picks_the_kind_of_stop_a_click_sets() {
|
||||
let _gpu = crate::gpu_lock::gpu_lock();
|
||||
use egui_kittest::kittest::Queryable;
|
||||
let mut h = editing_harness();
|
||||
let (slide, k) = (h.state().slide_rect.unwrap(), h.state().canvas_scale);
|
||||
@@ -127,6 +129,7 @@ fn the_tab_selector_picks_the_kind_of_stop_a_click_sets() {
|
||||
|
||||
#[test]
|
||||
fn tabs_dialog_sets_clears_and_applies_on_ok() {
|
||||
let _gpu = crate::gpu_lock::gpu_lock();
|
||||
use egui_kittest::kittest::Queryable;
|
||||
let mut h = editing_harness();
|
||||
h.state_mut().session.execute("format.tabs", &json!({"add": {"pos": 36}})).unwrap();
|
||||
|
||||
@@ -78,6 +78,7 @@ fn home_convert_to_smartart_gallery_converts_the_text() {
|
||||
|
||||
#[test]
|
||||
fn layout_gallery_shows_cached_pictures_and_previews_on_hover() {
|
||||
let _gpu = crate::gpu_lock::gpu_lock();
|
||||
let mut s = Session::with_new();
|
||||
s.execute("slide.new", &json!({"layout": "blank"})).unwrap();
|
||||
s.execute("insert.smartArt", &json!({"kind": "process", "items": ["Plan", "Build", "Ship"]})).unwrap();
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::{Services, SlideApp};
|
||||
|
||||
#[test]
|
||||
fn spelling_dialog_changes_and_ignores_words() {
|
||||
let _gpu = crate::gpu_lock::gpu_lock();
|
||||
let aff = "SET UTF-8\nTRY esianrtolcdugmphbyfvkwzxjq\nSFX S Y 1\nSFX S 0 s .\n";
|
||||
let dic = "6\nthe/S\nslide/S\nquarter/S\nplan/S\nour\nfor\n";
|
||||
deckcraft_engine::spell::install("en-US", deckcraft_engine::spell::Dictionary::parse(aff.as_bytes(), dic.as_bytes()));
|
||||
@@ -49,6 +50,7 @@ fn spelling_dialog_changes_and_ignores_words() {
|
||||
/// typed; the context menu's correction and Add to Dictionary clear them.
|
||||
#[test]
|
||||
fn squiggles_mark_misspelt_words_in_the_background() {
|
||||
let _gpu = crate::gpu_lock::gpu_lock();
|
||||
let aff = "SET UTF-8\nTRY esianrtolcdugmphbyfvkwzxjq\nSFX S Y 1\nSFX S 0 s .\n";
|
||||
let dic = "7\nthe/S\nslide/S\nquarter/S\nplan/S\nour\nfor\nreview\n";
|
||||
deckcraft_engine::spell::install("xx-SQ", deckcraft_engine::spell::Dictionary::parse(aff.as_bytes(), dic.as_bytes()));
|
||||
@@ -115,6 +117,7 @@ fn squiggles_mark_misspelt_words_in_the_background() {
|
||||
/// gets there a few milliseconds per frame.
|
||||
#[test]
|
||||
fn squiggles_in_table_cells_and_notes_and_on_the_ui_thread() {
|
||||
let _gpu = crate::gpu_lock::gpu_lock();
|
||||
let aff = "SET UTF-8\nTRY esianrtolcdugmphbyfvkwzxjq\nSFX S Y 1\nSFX S 0 s .\n";
|
||||
let dic = "10\nthe/S\nplan/S\nsales\nregion/S\ntotal/S\nnorth\nsouth\nremember\nto\nshow\n";
|
||||
deckcraft_engine::spell::install("xx-TB", deckcraft_engine::spell::Dictionary::parse(aff.as_bytes(), dic.as_bytes()));
|
||||
@@ -192,6 +195,7 @@ fn right_click(h: &mut Harness<'_, SlideApp>, pos: egui::Pos2) {
|
||||
/// dialog marks text and Do Not Check Spelling clears its squiggles.
|
||||
#[test]
|
||||
fn notes_menu_language_dialog_and_repeated_words() {
|
||||
let _gpu = crate::gpu_lock::gpu_lock();
|
||||
use egui::accesskit::Role;
|
||||
let aff = "SET UTF-8\nTRY esianrtolcdugmphbyfvkwzxjq\n";
|
||||
let dic = "6\nthe\nplan\nremember\nis\ngood\nwork\n";
|
||||
|
||||
@@ -57,6 +57,7 @@ fn a_very_tall_slide_in_the_editor_views_and_presenter_view() {
|
||||
/// notes and past the window.
|
||||
#[test]
|
||||
fn presenter_view_keeps_a_portrait_next_slide_in_its_column() {
|
||||
let _gpu = crate::gpu_lock::gpu_lock();
|
||||
let mut h = harness(tall_deck(), egui::vec2(1280.0, 800.0));
|
||||
h.run_steps(3);
|
||||
h.state_mut().run("show.presenter", json!({"on": true})).unwrap();
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::{Services, SlideApp};
|
||||
|
||||
#[test]
|
||||
fn thesaurus_pane_swaps_the_word_at_the_caret() {
|
||||
let _gpu = crate::gpu_lock::gpu_lock();
|
||||
let mut s = Session::with_new();
|
||||
s.execute("slide.new", &json!({"layout": "blank"})).unwrap();
|
||||
let id = s.execute("insert.textBox", &json!({"rect": [100, 100, 500, 50]})).unwrap()["id"].as_u64().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user