Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add egui_kittest, a test harness for egui #5166

Merged
merged 47 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
c50394a
Add egui testing library
lucasmerlin Sep 24, 2024
d5dedba
Improved usability
lucasmerlin Sep 25, 2024
608e4df
Rename and move kittest to external crate
lucasmerlin Sep 25, 2024
6f2691f
Enable git lfs for snapshots files
lucasmerlin Sep 25, 2024
e791a74
Add widget_gallery.png
lucasmerlin Sep 25, 2024
0853a52
Fix Cargo.toml formatting
lucasmerlin Sep 25, 2024
661bc3e
Add snapshot tests for all demos
lucasmerlin Sep 25, 2024
87cdb17
Move demo snapshots
lucasmerlin Sep 26, 2024
ee3e766
Update kittest
lucasmerlin Sep 26, 2024
e63ffa3
Add text_edit.rs test and fix ime input
lucasmerlin Sep 28, 2024
bba834b
Typo
lucasmerlin Sep 28, 2024
b2db13d
Run tests with gpu
lucasmerlin Sep 28, 2024
94d0b84
Improve snapshot error
lucasmerlin Sep 28, 2024
f26e98d
Test if snapshots fail in ci
lucasmerlin Sep 28, 2024
02f734c
Update dify
lucasmerlin Oct 4, 2024
80381ab
Fix lints
lucasmerlin Oct 4, 2024
7cbf557
Try if tests fail on style change
lucasmerlin Oct 4, 2024
d478162
Upload snapshots as artifacts
lucasmerlin Oct 4, 2024
10c0767
Fail tests on missing snapshot
lucasmerlin Oct 4, 2024
b4f7129
Checkout with lfs
lucasmerlin Oct 4, 2024
b00315b
Always upload artifacts
lucasmerlin Oct 4, 2024
2ac2d85
Update dify
lucasmerlin Oct 4, 2024
d7b874e
"un-break" tests
lucasmerlin Oct 4, 2024
66a6c00
Fix lint
lucasmerlin Oct 4, 2024
e0d8263
Always run egui_demo_lib tests with chrono feature
lucasmerlin Oct 5, 2024
e1a8196
Add Harness::builder and add README.md
lucasmerlin Oct 5, 2024
a5994d8
Add documentation
lucasmerlin Oct 5, 2024
dcdc601
Fixes after rebase
lucasmerlin Oct 5, 2024
76aa7ad
Fixes tests after rebase (where do those dots come from?)
lucasmerlin Oct 5, 2024
da32e8c
Checkout lfs
lucasmerlin Oct 5, 2024
d0a307d
Only run tests on macos
lucasmerlin Oct 9, 2024
0d4cebf
Set fixed date in widget_gallery test
lucasmerlin Oct 9, 2024
84977aa
Fix doc
lucasmerlin Oct 9, 2024
2d411a8
Fix deny
lucasmerlin Oct 9, 2024
cac6db8
Fix doc tests
lucasmerlin Oct 9, 2024
311d447
Refactor events and impl debug for Harness
lucasmerlin Oct 10, 2024
ddf79ce
Use kittest from main
lucasmerlin Oct 11, 2024
148e79a
Review changes
lucasmerlin Oct 12, 2024
24f29d6
Remember mouse position
lucasmerlin Oct 12, 2024
27414ea
Add comment about threshold and for the SnapshotError variants
lucasmerlin Oct 12, 2024
b2fe731
Improved snapshot error handling
lucasmerlin Oct 12, 2024
554fc57
Rename texture_to_bytes
lucasmerlin Oct 12, 2024
97ecb25
Round screen size
lucasmerlin Oct 12, 2024
c9d1bea
Add step function
lucasmerlin Oct 12, 2024
2b1fafa
Add more convenient snapshot api, allow reusing the TestRenderer and …
lucasmerlin Oct 12, 2024
df24f74
lints
lucasmerlin Oct 12, 2024
e824e01
Update example snapshot
lucasmerlin Oct 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
* text=auto eol=lf
Cargo.lock linguist-generated=false
**/tests/snapshots/**/*.png filter=lfs diff=lfs merge=lfs -text
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we just apply lfs to all png:s everywhere?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so, but I think we should do that in a separate PR. And it should also include gifs, which seem to be the biggest files in the repo. And maybe also the entirety of the media folder.

41 changes: 35 additions & 6 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
lfs: true

- uses: dtolnay/rust-toolchain@master
with:
Expand Down Expand Up @@ -60,18 +62,12 @@ jobs:
- name: cargo check -p test_egui_extras_compilation
run: cargo check -p test_egui_extras_compilation

- name: Test doc-tests
run: cargo test --doc --all-features

- name: cargo doc --lib
run: cargo doc --lib --no-deps --all-features

- name: cargo doc --document-private-items
run: cargo doc --document-private-items --no-deps --all-features

- name: Test
run: cargo test --all-features

- name: clippy
run: cargo clippy --all-targets --all-features -- -D warnings

Expand Down Expand Up @@ -222,3 +218,36 @@ jobs:

- name: Check hello_world
run: cargo check -p hello_world

# ---------------------------------------------------------------------------

tests:
name: Run tests
# We run the tests on macOS because it will run with a actual GPU
runs-on: macos-latest

steps:
- uses: actions/checkout@v4
with:
lfs: true
- uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.76.0

- name: Set up cargo cache
uses: Swatinem/rust-cache@v2

- name: Run tests
# TODO(lucasmerlin): Enable --all-features (currently this breaks the rendering in the tests because of the `unity` feature)
run: cargo test

- name: Run doc-tests
# TODO(lucasmerlin): Enable --all-features (currently this breaks the rendering in the tests because of the `unity` feature)
run: cargo test --doc
lucasmerlin marked this conversation as resolved.
Show resolved Hide resolved

- name: Upload artifacts
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: "**/tests/snapshots"
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
/.vscode
/media/*
.idea/
**/tests/snapshots/**/*.new.png
**/tests/snapshots/**/*.diff.png
lucasmerlin marked this conversation as resolved.
Show resolved Hide resolved
77 changes: 74 additions & 3 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,12 @@ version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"

[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"

[[package]]
name = "bytes"
version = "1.5.0"
Expand Down Expand Up @@ -818,6 +824,16 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"

[[package]]
name = "colored"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
dependencies = [
"lazy_static",
"windows-sys 0.48.0",
]

[[package]]
name = "com"
version = "0.6.0"
Expand Down Expand Up @@ -1097,6 +1113,19 @@ dependencies = [
"syn 1.0.109",
]

[[package]]
name = "dify"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11217d469eafa3b809ad84651eb9797ccbb440b4a916d5d85cb1b994e89787f6"
dependencies = [
"anyhow",
"colored",
"getopts",
"image",
"rayon",
]

[[package]]
name = "digest"
version = "0.10.7"
Expand Down Expand Up @@ -1304,9 +1333,12 @@ dependencies = [
"criterion",
"document-features",
"egui",
"egui_demo_lib",
"egui_extras",
"egui_kittest",
"serde",
"unicode_names2",
"wgpu",
]

[[package]]
Expand Down Expand Up @@ -1348,6 +1380,20 @@ dependencies = [
"winit",
]

[[package]]
name = "egui_kittest"
version = "0.29.1"
dependencies = [
"accesskit_consumer",
"dify",
"egui",
"egui-wgpu",
"image",
"kittest",
"pollster",
"wgpu",
]

[[package]]
name = "ehttp"
version = "0.5.0"
Expand Down Expand Up @@ -1767,6 +1813,15 @@ dependencies = [
"windows-targets 0.48.5",
]

[[package]]
name = "getopts"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
dependencies = [
"unicode-width",
]

[[package]]
name = "getrandom"
version = "0.2.10"
Expand Down Expand Up @@ -2130,12 +2185,12 @@ dependencies = [

[[package]]
name = "image"
version = "0.25.0"
version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9b4f005360d32e9325029b38ba47ebd7a56f3316df09249368939562d518645"
checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
dependencies = [
"bytemuck",
"byteorder",
"byteorder-lite",
"color_quant",
"gif",
"num-traits",
Expand Down Expand Up @@ -2290,6 +2345,16 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"

[[package]]
name = "kittest"
version = "0.1.0"
source = "git+https://github.com/rerun-io/kittest?branch=impl-debug#240832d5d086ae0a9cf358af9450c0cef16fbeab"
dependencies = [
"accesskit",
"accesskit_consumer",
"parking_lot",
]

[[package]]
name = "kurbo"
version = "0.9.5"
Expand All @@ -2299,6 +2364,12 @@ dependencies = [
"arrayvec",
]

[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"

[[package]]
name = "libc"
version = "0.2.155"
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"crates/egui_demo_lib",
"crates/egui_extras",
"crates/egui_glow",
"crates/egui_kittest",
"crates/egui-wgpu",
"crates/egui-winit",
"crates/egui",
Expand Down Expand Up @@ -64,6 +65,7 @@ egui_extras = { version = "0.29.1", path = "crates/egui_extras", default-feature
egui-wgpu = { version = "0.29.1", path = "crates/egui-wgpu", default-features = false }
egui_demo_lib = { version = "0.29.1", path = "crates/egui_demo_lib", default-features = false }
egui_glow = { version = "0.29.1", path = "crates/egui_glow", default-features = false }
egui_kittest = { version = "0.29.1", path = "crates/egui_kittest", default-features = false }
eframe = { version = "0.29.1", path = "crates/eframe", default-features = false }

ahash = { version = "0.8.11", default-features = false, features = [
Expand Down
7 changes: 6 additions & 1 deletion crates/egui_demo_lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,13 @@ serde = { workspace = true, optional = true }


[dev-dependencies]
criterion.workspace = true
# when running tests we always want to use the `chrono` feature
egui_demo_lib = { features = ["chrono"], workspace = true }

criterion.workspace = true
egui_kittest = { workspace = true, features = ["default", "wgpu", "snapshot"] }
wgpu = { workspace = true, features = ["metal"] }
egui = { workspace = true, features = ["default_fonts"] }

[[bench]]
name = "benchmark"
Expand Down
57 changes: 57 additions & 0 deletions crates/egui_demo_lib/src/demo/demo_app_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,3 +377,60 @@ fn file_menu_button(ui: &mut Ui) {
}
});
}

#[cfg(test)]
mod tests {
use crate::demo::demo_app_windows::Demos;
use egui::Vec2;
use egui_kittest::kittest::Queryable;
use egui_kittest::try_image_snapshot;
use egui_kittest::wgpu::TestRenderer;
use egui_kittest::Harness;

#[test]
fn demos_should_match_snapshot() {
let demos = Demos::default();

let mut errors = Vec::new();

for mut demo in demos.demos {
// Remove the emoji from the demo name
let name = demo
.name()
.split_once(' ')
.map_or(demo.name(), |(_, name)| name);

// Widget Gallery needs to be customized (to set a specific date) and has its own test
if name == "Widget Gallery" {
continue;
}

let mut harness = Harness::new(|ctx| {
demo.show(ctx, &mut true);
});

// We need to run the app for multiple frames before all windows have the right size
harness.run();
harness.run();

let window = harness.node().children().next().unwrap();
// TODO(lucasmerlin): Windows should probably have a label?
//let window = harness.get_by_name(name);

let size = window.raw_bounds().expect("window bounds").size();
harness.set_size(Vec2::new(size.width as f32, size.height as f32));

// We need to run the app for some more frames...
harness.run();
harness.run();

let image = TestRenderer::new().render(&harness);
let result = try_image_snapshot(&image, &format!("demos/{name}"));
if let Err(err) = result {
errors.push(err);
}
}

assert!(errors.is_empty(), "Errors: {errors:#?}");
}
}
34 changes: 34 additions & 0 deletions crates/egui_demo_lib/src/demo/text_edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,37 @@ impl crate::View for TextEditDemo {
});
}
}

#[cfg(test)]
mod tests {
use egui::{accesskit, CentralPanel};
use egui_kittest::kittest::{Key, Queryable};
use egui_kittest::Harness;

#[test]
pub fn should_type() {
let mut text = "Hello, world!".to_owned();
let mut harness = Harness::new(move |ctx| {
CentralPanel::default().show(ctx, |ui| {
ui.text_edit_singleline(&mut text);
});
});

harness.run();

let text_edit = harness.get_by_role(accesskit::Role::TextInput);
assert_eq!(text_edit.value().as_deref(), Some("Hello, world!"));

text_edit.key_combination(&[Key::Command, Key::A]);
text_edit.type_text("Hi ");

harness.run();
harness
.get_by_role(accesskit::Role::TextInput)
.type_text("there!");

harness.run();
let text_edit = harness.get_by_role(accesskit::Role::TextInput);
assert_eq!(text_edit.value().as_deref(), Some("Hi there!"));
}
}
35 changes: 35 additions & 0 deletions crates/egui_demo_lib/src/demo/widget_gallery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,38 @@ fn doc_link_label_with_crate<'a>(
})
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::View;
use egui::{CentralPanel, Context, Vec2};
use egui_kittest::image_snapshot;
use egui_kittest::wgpu::TestRenderer;
use egui_kittest::Harness;

#[test]
pub fn should_match_screenshot() {
let mut demo = WidgetGallery {
// If we don't set a fixed date, the snapshot test will fail.
date: Some(chrono::NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()),
..Default::default()
};
let app = |ctx: &Context| {
CentralPanel::default().show(ctx, |ui| {
demo.ui(ui);
});
};
let mut harness = Harness::builder()
.with_size(Vec2::new(380.0, 550.0))
.with_dpi(2.0)
.build(app);

// The first and second frames are slightly different, so we take the second frame
harness.run();
lucasmerlin marked this conversation as resolved.
Show resolved Hide resolved

let image = TestRenderer::new().render(&harness);

image_snapshot(&image, "widget_gallery");
}
}
3 changes: 3 additions & 0 deletions crates/egui_demo_lib/tests/snapshots/demos/Bézier Curve.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading