Replaces the single-crate Cargo.toml with a workspace containing ahfail-gtklock (migrated from root src/) and three stub crates (ahfail-ui, ahfail-pam, ahfail-display). Updates meson.build to build with -p ahfail-gtklock. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
299 lines
9.1 KiB
Rust
299 lines
9.1 KiB
Rust
use ahfail_module::*;
|
|
use gtk::prelude::*;
|
|
use gtk::{glib, gdk_pixbuf, gdk};
|
|
use gstreamer as gst;
|
|
use std::ptr;
|
|
use std::ffi::c_void;
|
|
use gtk::glib::translate::{ToGlibPtr, Stash};
|
|
|
|
// Mock implementation of the resource getter for tests
|
|
#[no_mangle]
|
|
extern "C" fn ahfail_get_resource() -> *mut gtk::gio::ffi::GResource {
|
|
ptr::null_mut()
|
|
}
|
|
|
|
// Helper struct to simulate the C flexible array member allocation
|
|
#[repr(C)]
|
|
struct WindowWithStorage {
|
|
window: Window,
|
|
// Reserve space for the 'module_data' flexible array (1 pointer)
|
|
storage: *mut c_void,
|
|
}
|
|
|
|
fn setup_test_environment() {
|
|
static ONCE: std::sync::Once = std::sync::Once::new();
|
|
ONCE.call_once(|| {
|
|
let _ = gtk::init();
|
|
let _ = gst::init();
|
|
});
|
|
}
|
|
|
|
fn flush_events() {
|
|
while gtk::events_pending() {
|
|
gtk::main_iteration();
|
|
}
|
|
}
|
|
|
|
fn create_mock_context() -> (WindowWithStorage, gtk::Window, gtk::Label, gtk::Overlay, gdk::Display) {
|
|
setup_test_environment();
|
|
|
|
let error_label = gtk::Label::new(Some(""));
|
|
let overlay = gtk::Overlay::new();
|
|
let window_widget = gtk::Window::new(gtk::WindowType::Toplevel);
|
|
let display = gdk::Display::default().expect("No display available for testing");
|
|
let monitor = display.monitor(0).expect("No monitor available");
|
|
|
|
// Explicitly type the Stash to avoid ambiguity
|
|
let monitor_stash: Stash<*mut gdk::ffi::GdkMonitor, _> = monitor.to_glib_none();
|
|
let window_stash: Stash<*mut gtk::ffi::GtkWindow, _> = window_widget.to_glib_none();
|
|
let overlay_stash: Stash<*mut gtk::ffi::GtkOverlay, _> = overlay.to_glib_none();
|
|
let label_stash: Stash<*mut gtk::ffi::GtkLabel, _> = error_label.to_glib_none();
|
|
|
|
let win = Window {
|
|
monitor: monitor_stash.0,
|
|
window: window_stash.0 as *mut gtk::ffi::GtkWidget,
|
|
overlay: overlay_stash.0,
|
|
window_box: ptr::null_mut(),
|
|
body_revealer: ptr::null_mut(),
|
|
body_grid: ptr::null_mut(),
|
|
input_label: ptr::null_mut(),
|
|
input_field: ptr::null_mut(),
|
|
message_revealer: ptr::null_mut(),
|
|
message_scrolled_window: ptr::null_mut(),
|
|
message_box: ptr::null_mut(),
|
|
unlock_button: ptr::null_mut(),
|
|
error_label: label_stash.0 as *mut gtk::ffi::GtkWidget,
|
|
warning_label: ptr::null_mut(),
|
|
info_box: ptr::null_mut(),
|
|
time_box: ptr::null_mut(),
|
|
clock_label: ptr::null_mut(),
|
|
date_label: ptr::null_mut(),
|
|
module_data: __IncompleteArrayField::new(),
|
|
};
|
|
|
|
let storage = WindowWithStorage {
|
|
window: win,
|
|
storage: ptr::null_mut(),
|
|
};
|
|
|
|
(storage, window_widget, error_label, overlay, display)
|
|
}
|
|
|
|
fn inject_test_state() {
|
|
let pixbuf = gdk_pixbuf::Pixbuf::new(gdk_pixbuf::Colorspace::Rgb, false, 8, 1, 1).unwrap();
|
|
let anim = gdk_pixbuf::PixbufSimpleAnim::new(1, 1, 1.0);
|
|
anim.add_frame(&pixbuf);
|
|
|
|
// Use a minimal valid WAV (silent) to avoid GStreamer errors
|
|
let wav_base64 = "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAIA+AAACABAAZGF0YQEAAAAA";
|
|
let data_uri = format!("data:audio/wav;base64,{}", wav_base64);
|
|
|
|
MODULE_STATE.with(|state| {
|
|
let mut state = state.borrow_mut();
|
|
state.animation = Some(anim.upcast());
|
|
state.audio_uri = Some(data_uri);
|
|
});
|
|
}
|
|
|
|
fn run_test_01_initialization() {
|
|
unsafe {
|
|
on_activation(ptr::null_mut(), 0);
|
|
}
|
|
MODULE_STATE.with(|state| {
|
|
let state = state.borrow();
|
|
// It should have set the audio URI to the default
|
|
assert_eq!(state.audio_uri.as_deref(), Some("resource:///ahfail/audio/magic-word.mp3"));
|
|
});
|
|
}
|
|
|
|
fn run_test_02_window_create_null() {
|
|
unsafe {
|
|
on_window_create(ptr::null_mut(), ptr::null_mut());
|
|
}
|
|
}
|
|
|
|
fn run_test_03_window_create_valid() {
|
|
let (mut win_storage, _w, _l, _o, _) = create_mock_context();
|
|
let ctx_ptr = &mut win_storage.window as *mut Window;
|
|
|
|
unsafe {
|
|
on_window_create(ptr::null_mut(), ctx_ptr);
|
|
let data = get_window_data_ref(ctx_ptr);
|
|
assert!(data.is_some(), "WindowData should be initialized");
|
|
|
|
on_window_destroy(ptr::null_mut(), ctx_ptr);
|
|
flush_events();
|
|
}
|
|
}
|
|
|
|
fn run_test_04_trigger_effect_sprite() {
|
|
let (mut win_storage, _w, label, _o, _) = create_mock_context();
|
|
let ctx_ptr = &mut win_storage.window as *mut Window;
|
|
inject_test_state();
|
|
|
|
unsafe {
|
|
on_window_create(ptr::null_mut(), ctx_ptr);
|
|
label.set_text("Error");
|
|
flush_events();
|
|
|
|
let data = get_window_data_ref(ctx_ptr).unwrap();
|
|
assert_eq!(data.sprites.len(), 1);
|
|
|
|
on_window_destroy(ptr::null_mut(), ctx_ptr);
|
|
flush_events();
|
|
}
|
|
}
|
|
|
|
fn run_test_05_trigger_effect_audio() {
|
|
let (mut win_storage, _w, label, _o, _) = create_mock_context();
|
|
let ctx_ptr = &mut win_storage.window as *mut Window;
|
|
inject_test_state();
|
|
|
|
unsafe {
|
|
on_window_create(ptr::null_mut(), ctx_ptr);
|
|
label.set_text("Error");
|
|
flush_events();
|
|
|
|
let data = get_window_data_ref(ctx_ptr).unwrap();
|
|
assert_eq!(data.active_players.len(), 1);
|
|
|
|
on_window_destroy(ptr::null_mut(), ctx_ptr);
|
|
flush_events();
|
|
}
|
|
}
|
|
|
|
fn run_test_06_multiple_triggers() {
|
|
let (mut win_storage, _w, label, _o, _) = create_mock_context();
|
|
let ctx_ptr = &mut win_storage.window as *mut Window;
|
|
inject_test_state();
|
|
|
|
unsafe {
|
|
on_window_create(ptr::null_mut(), ctx_ptr);
|
|
for _ in 0..5 {
|
|
label.set_text("Error");
|
|
flush_events();
|
|
}
|
|
|
|
let data = get_window_data_ref(ctx_ptr).unwrap();
|
|
assert_eq!(data.sprites.len(), 5);
|
|
assert_eq!(data.active_players.len(), 5);
|
|
|
|
on_window_destroy(ptr::null_mut(), ctx_ptr);
|
|
flush_events();
|
|
}
|
|
}
|
|
|
|
fn run_test_07_window_cleanup() {
|
|
let (mut win_storage, _w, label, _o, _) = create_mock_context();
|
|
let ctx_ptr = &mut win_storage.window as *mut Window;
|
|
inject_test_state();
|
|
|
|
unsafe {
|
|
on_window_create(ptr::null_mut(), ctx_ptr);
|
|
label.set_text("Error");
|
|
flush_events();
|
|
|
|
assert!(get_window_data_ref(ctx_ptr).is_some());
|
|
on_window_destroy(ptr::null_mut(), ctx_ptr);
|
|
assert!(get_window_data_ref(ctx_ptr).is_none());
|
|
flush_events();
|
|
}
|
|
}
|
|
|
|
fn run_test_08_multiple_windows() {
|
|
let (mut win1, _w1, label1, _o1, _) = create_mock_context();
|
|
let (mut win2, _w2, label2, _o2, _) = create_mock_context();
|
|
let ctx1 = &mut win1.window as *mut Window;
|
|
let ctx2 = &mut win2.window as *mut Window;
|
|
inject_test_state();
|
|
|
|
unsafe {
|
|
on_window_create(ptr::null_mut(), ctx1);
|
|
on_window_create(ptr::null_mut(), ctx2);
|
|
|
|
label1.set_text("E1");
|
|
flush_events();
|
|
|
|
label2.set_text("E2");
|
|
label2.set_text("E2 again");
|
|
flush_events();
|
|
|
|
let data1 = get_window_data_ref(ctx1).unwrap();
|
|
let data2 = get_window_data_ref(ctx2).unwrap();
|
|
|
|
assert_eq!(data1.sprites.len(), 1);
|
|
assert_eq!(data2.sprites.len(), 2);
|
|
|
|
on_window_destroy(ptr::null_mut(), ctx1);
|
|
on_window_destroy(ptr::null_mut(), ctx2);
|
|
flush_events();
|
|
}
|
|
}
|
|
|
|
fn run_test_09_idle_hide_cleanup() {
|
|
let (mut win_storage, _w, label, _o, _) = create_mock_context();
|
|
let ctx_ptr = &mut win_storage.window as *mut Window;
|
|
inject_test_state();
|
|
|
|
unsafe {
|
|
on_window_create(ptr::null_mut(), ctx_ptr);
|
|
label.set_text("Error");
|
|
flush_events();
|
|
|
|
// Mock GtkLock struct
|
|
let mut windows_array = glib::ffi::g_array_new(0, 0, std::mem::size_of::<*mut Window>() as u32);
|
|
glib::ffi::g_array_append_vals(windows_array, &ctx_ptr as *const _ as *const c_void, 1);
|
|
|
|
let mut lock = GtkLock {
|
|
windows: windows_array,
|
|
};
|
|
|
|
on_idle_hide(&mut lock);
|
|
|
|
assert!(get_window_data_ref(ctx_ptr).is_none());
|
|
glib::ffi::g_array_free(windows_array, 1);
|
|
flush_events();
|
|
}
|
|
}
|
|
|
|
fn run_test_10_module_unload() {
|
|
inject_test_state();
|
|
MODULE_STATE.with(|state| {
|
|
assert!(state.borrow().audio_uri.is_some());
|
|
});
|
|
unsafe {
|
|
g_module_unload(ptr::null_mut());
|
|
}
|
|
MODULE_STATE.with(|state| {
|
|
let state = state.borrow();
|
|
assert!(state.animation.is_none());
|
|
assert!(state.audio_uri.is_none());
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn run_all_tests_sequentially() {
|
|
println!("Running test 01...");
|
|
run_test_01_initialization();
|
|
println!("Running test 02...");
|
|
run_test_02_window_create_null();
|
|
println!("Running test 03...");
|
|
run_test_03_window_create_valid();
|
|
println!("Running test 04...");
|
|
run_test_04_trigger_effect_sprite();
|
|
println!("Running test 05...");
|
|
run_test_05_trigger_effect_audio();
|
|
println!("Running test 06...");
|
|
run_test_06_multiple_triggers();
|
|
println!("Running test 07...");
|
|
run_test_07_window_cleanup();
|
|
println!("Running test 08...");
|
|
run_test_08_multiple_windows();
|
|
println!("Running test 09...");
|
|
run_test_09_idle_hide_cleanup();
|
|
println!("Running test 10...");
|
|
run_test_10_module_unload();
|
|
|
|
// Final flush
|
|
flush_events();
|
|
} |