Skip to content

Commit

Permalink
WIP: Plumbing for system testing
Browse files Browse the repository at this point in the history
  • Loading branch information
tronical committed May 23, 2024
1 parent 6058260 commit 4d41a6f
Show file tree
Hide file tree
Showing 16 changed files with 221 additions and 4 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ softbuffer = { version = "0.3.3", default-features = false }
strum = { version = "0.26.1", default-features = false, features = ["derive"] }
toml_edit = { version = "0.22.7" }
cfg_aliases = { version = "0.2.0" }

slab = { version = "0.4.3", default-features = false }
raw-window-handle-06 = { package = "raw-window-handle", version = "0.6", features = ["alloc"] }

[profile.release]
Expand Down
1 change: 1 addition & 0 deletions api/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ define_cargo_dependent_feature(backend-linuxkms-noseat "Enable support for the b
define_cargo_dependent_feature(gettext "Enable support of translations using gettext" OFF "NOT SLINT_FEATURE_FREESTANDING")
define_cargo_dependent_feature(accessibility "Enable integration with operating system provided accessibility APIs" ON "NOT SLINT_FEATURE_FREESTANDING")
define_cargo_dependent_feature(testing "Enable support for testing API (experimental)" ON "NOT SLINT_FEATURE_FREESTANDING")
define_cargo_dependent_feature(system-testing "Enable system testing support" OFF "NOT SLINT_FEATURE_FREESTANDING")
define_cargo_feature(experimental "Enable experimental features. (No backward compatibility guarantees)" OFF)

if (SLINT_BUILD_RUNTIME)
Expand Down
1 change: 1 addition & 0 deletions api/cpp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ renderer-skia-vulkan = ["i-slint-backend-selector/renderer-skia-vulkan", "render
renderer-software = ["i-slint-backend-selector/renderer-software"]
gettext = ["i-slint-core/gettext-rs"]
accessibility = ["i-slint-backend-selector/accessibility"]
system-testing = ["i-slint-backend-selector/system-testing"]

std = ["image", "i-slint-core/default", "i-slint-backend-selector"]
freestanding = ["i-slint-core/libm", "i-slint-core/unsafe-single-threaded"]
Expand Down
3 changes: 3 additions & 0 deletions api/rs/slint/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ accessibility = ["i-slint-backend-selector/accessibility"]
## [HasDisplayHandle](raw_window_handle_06::HasDisplayHandle) implementation.
raw-window-handle-06 = ["dep:raw-window-handle-06", "i-slint-backend-selector/raw-window-handle-06"]

## Enable support for system testing via out of process orchestration.
system-testing = ["i-slint-backend-selector/system-testing"]

#! ### Backends

#! Slint needs a backend that will act as liaison between Slint and the OS.
Expand Down
3 changes: 3 additions & 0 deletions internal/backends/selector/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,15 @@ accessibility = ["i-slint-backend-winit?/accessibility"]

raw-window-handle-06 = ["i-slint-core/raw-window-handle-06", "i-slint-backend-winit?/raw-window-handle-06"]

system-testing = ["i-slint-backend-testing/system-testing"]

# note that default enable the i-slint-backend-qt, but not its enable feature
default = ["i-slint-backend-qt", "backend-winit"]

[dependencies]
cfg-if = "1"
i-slint-core = { workspace = true }
i-slint-backend-testing = { workspace = true, optional = true }

[target.'cfg(not(target_os = "android"))'.dependencies]
i-slint-backend-winit = { workspace = true, features = ["default"], optional = true }
Expand Down
17 changes: 16 additions & 1 deletion internal/backends/selector/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,5 +133,20 @@ cfg_if::cfg_if! {
pub fn with_platform<R>(
f: impl FnOnce(&dyn Platform) -> Result<R, PlatformError>,
) -> Result<R, PlatformError> {
i_slint_core::with_platform(create_backend, f)
let mut platform_created = false;
let result = i_slint_core::with_platform(
|| {
let backend = create_backend();
platform_created = backend.is_ok();
backend
},
f,
);

#[cfg(feature = "system-testing")]
if result.is_ok() && platform_created {
i_slint_backend_testing::systest::init();
}

result
}
9 changes: 9 additions & 0 deletions internal/backends/testing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ repository.workspace = true
rust-version.workspace = true
version.workspace = true
publish = false
build = "build.rs"

[lib]
path = "lib.rs"
Expand All @@ -21,10 +22,18 @@ path = "lib.rs"
internal = []
# ffi for C++ bindings
ffi = []
system-testing = ["quick-protobuf", "pb-rs", "slab", "smol", "byteorder"]

[dependencies]
i-slint-core = { workspace = true }
vtable = { workspace = true }
quick-protobuf = { version = "0.8.1", optional = true }
slab = { workspace = true, optional = true }
smol = { version = "2.0.0", optional = true }
byteorder = { version = "1.5.0", optional = true }

[build-dependencies]
pb-rs = { version = "0.10.0", optional = true, default-features = false }

[dev-dependencies]
slint = { workspace = true, default-features = false, features = ["std", "compat-1-2"] }
Expand Down
15 changes: 15 additions & 0 deletions internal/backends/testing/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.2 OR LicenseRef-Slint-commercial

fn main() {
#[cfg(feature = "system-testing")]
{
let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
let proto_file = std::path::PathBuf::from(::std::env::var("CARGO_MANIFEST_DIR").unwrap())
.join("slint_systest.proto");
let config_builder = pb_rs::ConfigBuilder::new(&[proto_file], None, Some(&out_dir), &[])
.unwrap()
.headers(false);
pb_rs::types::FileDescriptor::run(&config_builder.build()).unwrap();
}
}
4 changes: 3 additions & 1 deletion internal/backends/testing/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ pub use internal_tests::*;
mod testing_backend;
#[cfg(feature = "internal")]
pub use testing_backend::*;
#[cfg(feature = "ffi")]
#[cfg(all(feature = "ffi", not(test)))]
mod ffi;
#[cfg(feature = "system-testing")]
pub mod systest;

/// Initialize the testing backend without support for event loop.
/// This means that each test thread can use its own backend, but global functions that needs
Expand Down
27 changes: 27 additions & 0 deletions internal/backends/testing/slint_systest.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.2 OR LicenseRef-Slint-commercial

syntax = "proto3";

message RequestWindowListMessage {
}

message RequestToAUT {
oneof msg {
RequestWindowListMessage request_window_list = 1;
}
}

message WindowDetails {
// Can add information such as size
}

message WindowListResponse {
map<uint64, WindowDetails> windows = 1;
}

message AUTResponse {
oneof msg {
WindowListResponse window_list = 1;
}
}
122 changes: 122 additions & 0 deletions internal/backends/testing/systest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.2 OR LicenseRef-Slint-commercial

use i_slint_core::debug_log;
//use i_slint_core::api::invoke_from_event_loop;
use i_slint_core::api::EventLoopError;
//use i_slint_core::platform::WindowAdapter;
//use std::cell::RefCell;
//use std::rc::Rc;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use quick_protobuf::{MessageRead, MessageWrite};
use smol::io::AsyncReadExt;
use smol::io::AsyncWriteExt;
use std::io::Cursor;

#[allow(non_snake_case, unused_imports, non_camel_case_types)]
mod proto {
include!(concat!(env!("OUT_DIR"), "/slint_systest.rs"));
}

/*
struct TestingClient {
windows: RefCell<slab::Slab<Rc<dyn WindowAdapter>>>,
}
impl TestingClient {
fn new() -> Self {
todo!()
}
fn window_shown(self: Rc<Self>, adapter: Rc<dyn WindowAdapter>) {
let id = self.windows.borrow_mut().insert(adapter);
let _ = invoke_from_event_loop(move || eprintln!("{id}"));
}
}
*/

async fn message_loop(
server_addr: String,
mut message_callback: impl FnMut(
proto::mod_RequestToAUT::OneOfmsg,
) -> Option<proto::mod_AUTResponse::OneOfmsg>,
) {
let mut stream = match smol::net::TcpStream::connect(server_addr.clone()).await {
Ok(stream) => stream,
Err(err) => {
eprintln!("Error connecting to Slint test server at {server_addr}: {}", err);
return;
}
};
debug_log!("Connected to test server");

loop {
let mut message_size_buf = vec![0; 4];
let mut n = stream
.read(&mut message_size_buf)
.await
.expect("Unable to read request header from AUT connection");
if n != 4 {
panic!("fatal: unexpected AUT message header: received {n} expected 4")
}
let message_size: usize =
Cursor::new(message_size_buf).read_u32::<BigEndian>().unwrap() as usize;
let mut message_buf = Vec::with_capacity(message_size);
message_buf.resize(message_size, 0);
n = stream
.read(&mut message_buf)
.await
.expect("Unable to read request data from AUT connection");
if n != message_size {
panic!("fatal: unexpected AUT message body: received {n} expected {message_size}");
}

let message = proto::RequestToAUT::from_reader(
&mut quick_protobuf::reader::BytesReader::from_bytes(&message_buf),
&mut message_buf,
)
.expect("Unable to de-serialize AUT request message");
let Some(response) = message_callback(message.msg) else { continue };
let response = proto::AUTResponse { msg: response };
let mut size_header = Vec::new();
size_header.write_u32::<BigEndian>(response.get_size() as u32).unwrap();
stream.write_all(&size_header).await.expect("Unable to write AUT response header");
let mut message_body = Vec::new();
response.write_message(&mut quick_protobuf::Writer::new(&mut message_body)).unwrap();
stream.write_all(&message_body).await.expect("Unable to write AUT response body");
}
}

pub fn init() -> Result<(), EventLoopError> {
let Ok(server_addr) = std::env::var("SLINT_TEST_SERVER") else {
return Ok(());
};
debug_log!("Attempting to connect to testing server at {server_addr}");

i_slint_core::future::spawn_local(async move {
message_loop(server_addr, |request| {
match request {
proto::mod_RequestToAUT::OneOfmsg::request_window_list(..) => {
proto::mod_AUTResponse::OneOfmsg::window_list(proto::WindowListResponse {
windows: [(0, proto::WindowDetails::default())].into_iter().collect(),
})
}
proto::mod_RequestToAUT::OneOfmsg::None => return None,
}
.into()
})
.await;
})?;

/*
let Some(client) = TestingClient::connect() else {
return Ok(());
};
i_slint_core::context::set_window_shown_hook(Some(Box::new(move |adapter| {
client.window_shown(adapter);
})))?;
*/
Ok(())
}
2 changes: 1 addition & 1 deletion internal/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ pin-weak = { version = "1.1", default-features = false }
rgb = "0.8.27"
scoped-tls-hkt = { version = "0.1", optional = true }
scopeguard = { version = "1.1.0", default-features = false }
slab = { version = "0.4.3", default-features = false }
slab = { workspace = true }
static_assertions = "1.1"
strum = { workspace = true }
unicode-segmentation = "1.8.0"
Expand Down
12 changes: 12 additions & 0 deletions internal/core/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ pub(crate) struct SlintContextInner {
/// This property is read by all translations, and marked dirty when the language change
/// so that every translated string gets re-translated
pub(crate) translations_dirty: core::pin::Pin<Box<Property<()>>>,
pub(crate) window_shown_hook:
core::cell::RefCell<Option<Box<dyn FnMut(Rc<dyn crate::platform::WindowAdapter>)>>>,
}

/// This context is meant to hold the state and the backend.
Expand All @@ -36,6 +38,7 @@ impl SlintContext {
platform,
window_count: 0.into(),
translations_dirty: Box::pin(Property::new_named((), "SlintContext::translations")),
window_shown_hook: Default::default(),
}))
}

Expand Down Expand Up @@ -74,3 +77,12 @@ pub fn with_platform<R>(
}
})
}

pub fn set_window_shown_hook(
hook: Option<Box<dyn FnMut(Rc<dyn crate::platform::WindowAdapter>)>>,
) -> Result<Option<Box<dyn FnMut(Rc<dyn crate::platform::WindowAdapter>)>>, PlatformError> {
GLOBAL_CONTEXT.with(|p| match p.get() {
Some(ctx) => Ok(ctx.0.window_shown_hook.replace(hook)),
None => Err(PlatformError::NoPlatform),
})
}
3 changes: 3 additions & 0 deletions internal/core/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,9 @@ impl WindowInner {
let size = self.window_adapter().size();
self.set_window_item_geometry(size.to_logical(self.scale_factor()).to_euclid());
self.window_adapter().renderer().resize(size).unwrap();
if let Some(hook) = self.ctx.0.window_shown_hook.borrow_mut().as_mut() {
hook(self.window_adapter());
}
Ok(())
}

Expand Down
3 changes: 3 additions & 0 deletions internal/interpreter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ accessibility = ["i-slint-backend-selector/accessibility"]
## [HasDisplayHandle](raw_window_handle_06::HasDisplayHandle) implementation.
raw-window-handle-06 = ["dep:raw-window-handle-06", "i-slint-backend-selector/raw-window-handle-06"]

## Enable support for system testing via out of process orchestration.
system-testing = ["i-slint-backend-selector/system-testing"]

## Features used internally by Slint tooling that are not stable and come without
## any stability guarantees whatsoever.
internal = []
Expand Down
1 change: 1 addition & 0 deletions xtask/src/license_headers_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,7 @@ lazy_static! {
("\\.yaml$", LicenseLocation::Tag(LicenseTagStyle::shell_comment_style())),
("\\.yml$", LicenseLocation::Tag(LicenseTagStyle::shell_comment_style())),
("\\.py$", LicenseLocation::Tag(LicenseTagStyle::shell_comment_style())),
("\\.proto$", LicenseLocation::Tag(LicenseTagStyle::c_style_comment_style())),
]
.iter()
.map(|(re, ty)| (regex::Regex::new(re).unwrap(), *ty))
Expand Down

0 comments on commit 4d41a6f

Please sign in to comment.