mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
@@ -275,5 +275,7 @@ license = "Apache-2.0"
|
||||
edition = "2021"
|
||||
|
||||
[workspace.dependencies]
|
||||
serde = { version = "1", features = ["serde_derive"] }
|
||||
async-io = "2.3.1"
|
||||
gui-test = { path = "it/gui-test" }
|
||||
mint = "0.5.6"
|
||||
serde = { version = "1", features = ["serde_derive"] }
|
||||
|
||||
@@ -8,8 +8,10 @@ edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-executor = "1.8.0"
|
||||
async-io = "2.3.1"
|
||||
async-io.workspace = true
|
||||
async-lock = "3.3.0"
|
||||
async-process = "2.1.0"
|
||||
inventory = "0.3.15"
|
||||
owo-colors = "4.0.0"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = "1.0.114"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
//! A testing framework that can be run remotely.
|
||||
|
||||
pub mod stream;
|
||||
pub mod remote;
|
||||
pub mod user;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::env;
|
||||
@@ -17,7 +21,7 @@ pub use inventory as __inventory;
|
||||
/// Replacement for the `main` function.
|
||||
#[macro_export]
|
||||
macro_rules! main {
|
||||
($harness:expr) => {
|
||||
($handler:expr) => {
|
||||
fn main() {
|
||||
$crate::__entry($harness)
|
||||
}
|
||||
@@ -122,10 +126,7 @@ impl Harness {
|
||||
}
|
||||
|
||||
// Send the "test started" event to the handler.
|
||||
self.handler.handle_test(TestEvent {
|
||||
runner: self.name.clone(),
|
||||
ty: TestEventType::TestStarted { name: name.into() },
|
||||
});
|
||||
self.send_event(TestEventType::TestStarted { name: name.into() });
|
||||
|
||||
// Return the handle.
|
||||
Testing {
|
||||
@@ -156,10 +157,7 @@ impl Harness {
|
||||
}
|
||||
|
||||
// Send the "group started" event to the handler.
|
||||
self.handler.handle_test(TestEvent {
|
||||
ty: TestEventType::GroupStarted { name: name.into() },
|
||||
runner: self.name.clone(),
|
||||
});
|
||||
self.send_event(TestEventType::GroupStarted { name: name.into() });
|
||||
|
||||
// Return the handle.
|
||||
Grouping { harness: self }
|
||||
@@ -180,10 +178,7 @@ impl Harness {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.handler.handle_test(TestEvent {
|
||||
runner: self.name.clone(),
|
||||
ty: TestEventType::TestEnded { result: reason },
|
||||
});
|
||||
self.send_event(TestEventType::TestEnded { result: reason });
|
||||
|
||||
let count = match mem::replace(&mut self.state, State::Default) {
|
||||
State::InTest { past_groups } => past_groups,
|
||||
@@ -198,10 +193,7 @@ impl Harness {
|
||||
|
||||
/// End the current group.
|
||||
fn end_group(&mut self) {
|
||||
self.handler.handle_test(TestEvent {
|
||||
runner: self.name.clone(),
|
||||
ty: TestEventType::GroupEnded,
|
||||
});
|
||||
self.send_event(TestEventType::GroupEnded);
|
||||
|
||||
let count = match mem::replace(&mut self.state, State::Default) {
|
||||
State::InGroups(groups) => groups,
|
||||
@@ -213,6 +205,26 @@ impl Harness {
|
||||
Some(groups) => State::InGroups(groups),
|
||||
};
|
||||
}
|
||||
|
||||
/// Send a test event of the provided type.
|
||||
fn send_event(&mut self, ty: TestEventType) {
|
||||
let event = TestEvent {
|
||||
runner: self.name.clone(),
|
||||
ty,
|
||||
};
|
||||
|
||||
self.handler.handle_test(event);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Harness {
|
||||
fn drop(&mut self) {
|
||||
self.send_event(TestEventType::Complete {
|
||||
total: self.test_count,
|
||||
fail: self.test_fails,
|
||||
pass: self.test_passed,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// An in-progress test.
|
||||
@@ -279,14 +291,13 @@ pub trait TestHandler {
|
||||
}
|
||||
|
||||
/// An event produced by the test harness.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct TestEvent {
|
||||
/// The name of the runner associated with the event.
|
||||
runner: String,
|
||||
pub runner: String,
|
||||
|
||||
/// The type of the event.
|
||||
ty: TestEventType,
|
||||
pub ty: TestEventType,
|
||||
}
|
||||
|
||||
/// The type of the event.
|
||||
@@ -294,7 +305,16 @@ pub struct TestEvent {
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum TestEventType {
|
||||
/// The tests are complete and the harness can be disconnected.
|
||||
Complete,
|
||||
Complete {
|
||||
/// Total number of tests.
|
||||
total: usize,
|
||||
|
||||
/// Total number of passing tests.
|
||||
pass: usize,
|
||||
|
||||
/// Total number of failed tests.
|
||||
fail: usize,
|
||||
},
|
||||
|
||||
/// A test has started.
|
||||
TestStarted { name: String },
|
||||
|
||||
30
it/gui-test/src/remote.rs
Normal file
30
it/gui-test/src/remote.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
//! Create a test handler that can be run remotely.
|
||||
|
||||
use crate::TestHandler;
|
||||
use crate::stream::WriteHandler;
|
||||
use crate::user::UserHandler;
|
||||
|
||||
use std::env;
|
||||
use std::net::TcpStream;
|
||||
|
||||
/// Create a test handler adjusted for the current environment.
|
||||
pub fn handler() -> Box<dyn TestHandler + Send + 'static> {
|
||||
// If GUI_TEST_UNIX_STREAM is enabled, use that as a Unix stream.
|
||||
#[cfg(unix)]
|
||||
if let Some(stream_path) = env::var_os("GUI_TEST_UNIX_STREAM")
|
||||
.filter(|s| !s.is_empty()) {
|
||||
let stream = std::os::unix::net::UnixStream::connect(stream_path).expect("unable to connect to gui-test handler");
|
||||
return Box::new(WriteHandler::new(stream));
|
||||
}
|
||||
|
||||
// If GUI_TEST_TCP_STREAM is enabled, use that as a TCP stream.
|
||||
if let Some(tcp_ip) = env::var("GUI_TEST_TCP_STREAM")
|
||||
.ok()
|
||||
.filter(|s| !s.is_empty()) {
|
||||
let stream = TcpStream::connect(tcp_ip).unwrap();
|
||||
return Box::new(WriteHandler::new(stream));
|
||||
}
|
||||
|
||||
// By default, use the user handler.
|
||||
Box::new(UserHandler::new())
|
||||
}
|
||||
34
it/gui-test/src/stream.rs
Normal file
34
it/gui-test/src/stream.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
//! Write events to an output stream.
|
||||
//!
|
||||
//! The format is as follows:
|
||||
//! - First 8 bytes: big-endian length of payload.
|
||||
//! - Next {len} bytes: JSON payload to deserialize from.
|
||||
|
||||
use crate::{TestEvent, TestHandler};
|
||||
use std::io::Write;
|
||||
|
||||
/// A wrapper around a writer that sends data down a stream.
|
||||
pub struct WriteHandler<W> {
|
||||
/// The inner writer.
|
||||
writer: W,
|
||||
}
|
||||
|
||||
impl<W: Write> WriteHandler<W> {
|
||||
/// Create a new write handler.
|
||||
pub fn new(writer: W) -> Self {
|
||||
Self {
|
||||
writer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> TestHandler for WriteHandler<W> {
|
||||
fn handle_test(&mut self, event: TestEvent) {
|
||||
let payload = serde_json::to_vec(&event).unwrap();
|
||||
let length = u64::to_be_bytes(payload.len() as u64);
|
||||
|
||||
// Write the payload to the stream.
|
||||
self.writer.write_all(&length).unwrap();
|
||||
self.writer.write_all(&payload).unwrap();
|
||||
}
|
||||
}
|
||||
164
it/gui-test/src/user.rs
Normal file
164
it/gui-test/src/user.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
//! User-facing reporter.
|
||||
|
||||
use crate::{TestEvent, TestEventType, TestHandler, TestResult};
|
||||
use owo_colors::OwoColorize;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::{self, prelude::*};
|
||||
|
||||
const TABSIZE: usize = 2;
|
||||
|
||||
/// User-facing reporter.
|
||||
///
|
||||
/// This reporter dumps events to the console in a user-readable format.
|
||||
pub struct UserHandler {
|
||||
/// Current indent.
|
||||
indent: usize,
|
||||
|
||||
/// The test set we're currently displaying.
|
||||
current_start: Option<String>,
|
||||
|
||||
/// Test name we are running, if any.
|
||||
test_name: Option<String>,
|
||||
|
||||
/// Cached events.
|
||||
cache: BTreeMap<String, Vec<TestEventType>>,
|
||||
|
||||
/// Failures we had.
|
||||
failures: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
impl UserHandler {
|
||||
/// Create a new handler.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
indent: 0,
|
||||
current_start: None,
|
||||
test_name: None,
|
||||
cache: BTreeMap::new(),
|
||||
failures: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Process the provided events.
|
||||
fn process_events(&mut self, events: impl IntoIterator<Item = TestEvent>) {
|
||||
for event in events {
|
||||
// Tell if this is an end event.
|
||||
let mut ender = matches!(event.ty, TestEventType::Complete { .. });
|
||||
|
||||
// If there is no test name set, run the current one.
|
||||
match self.current_start.as_ref() {
|
||||
None => {
|
||||
let TestEvent { runner, ty } = event;
|
||||
self.current_start = Some(runner);
|
||||
self.dump_events(Some(ty));
|
||||
}
|
||||
|
||||
Some(test_name) => {
|
||||
// If there is a test name set and it's ours, post it immediately.
|
||||
if test_name == &event.runner {
|
||||
self.dump_events(Some(event.ty));
|
||||
} else {
|
||||
// Add it to the back of another one of the events.
|
||||
self.cache
|
||||
.entry(test_name.clone())
|
||||
.or_default()
|
||||
.push(event.ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the end, dump other events.
|
||||
while ender {
|
||||
assert!(self.current_start.take().is_some());
|
||||
|
||||
// Pick one set.
|
||||
if let Some(entry) = self.cache.first_entry() {
|
||||
let (test_name, entries) = entry.remove_entry();
|
||||
self.current_start = Some(test_name);
|
||||
|
||||
// Dump events and look for a conclusion.
|
||||
ender = false;
|
||||
self.dump_events(entries.into_iter().inspect(|ty| {
|
||||
ender |= matches!(ty, TestEventType::Complete { .. });
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dump the provided events to the console.
|
||||
fn dump_events(&mut self, events: impl IntoIterator<Item = TestEventType>) {
|
||||
let mut stdout = io::stdout().lock();
|
||||
|
||||
for event in events {
|
||||
// Write the indent.
|
||||
for _ in 0..(self.indent * TABSIZE) {
|
||||
stdout.write_all(b" ").unwrap();
|
||||
}
|
||||
|
||||
match event {
|
||||
TestEventType::GroupStarted { name } => {
|
||||
assert!(self.test_name.is_none());
|
||||
|
||||
// Write the group name and bump the indent.
|
||||
writeln!(stdout, "{}", name.yellow().italic()).unwrap();
|
||||
|
||||
// Add to the indent.
|
||||
self.indent += 1;
|
||||
}
|
||||
|
||||
TestEventType::GroupEnded => {
|
||||
assert!(self.test_name.is_none());
|
||||
|
||||
// Drop the indent.
|
||||
self.indent = self.indent.checked_sub(1).unwrap();
|
||||
}
|
||||
|
||||
TestEventType::TestStarted { name } => {
|
||||
assert!(self.test_name.is_none());
|
||||
|
||||
// Write the line.
|
||||
write!(stdout, "{} ", name.white().italic()).unwrap();
|
||||
self.test_name = Some(name);
|
||||
}
|
||||
|
||||
TestEventType::TestEnded { result } => {
|
||||
let test_name = self.test_name.take().unwrap();
|
||||
|
||||
// Write the result.
|
||||
match result {
|
||||
TestResult::Passed => {
|
||||
writeln!(stdout, "{}", "ok".green().bold()).unwrap();
|
||||
}
|
||||
|
||||
TestResult::Failed(failure) => {
|
||||
self.failures.push((test_name, failure));
|
||||
writeln!(stdout, "{}", "FAIL".red().bold()).unwrap();
|
||||
}
|
||||
|
||||
TestResult::Skipped => {
|
||||
writeln!(stdout, "{}", "skipped".yellow().bold()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
// Completion.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TestHandler for UserHandler {
|
||||
fn handle_test(&mut self, event: TestEvent) {
|
||||
self.process_events(Some(event));
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for UserHandler {
|
||||
fn drop(&mut self) {
|
||||
assert!(self.cache.is_empty());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user