testing: broaden keyboard and mouse include branch contracts
This commit is contained in:
parent
ed60b3e0ba
commit
e1c7b9e7d8
@ -269,4 +269,113 @@ mod keyboard_contract {
|
|||||||
let pkt = rx.try_recv().expect("empty report after reset");
|
let pkt = rx.try_recv().expect("empty report after reset");
|
||||||
assert_eq!(pkt.data, vec![0; 8]);
|
assert_eq!(pkt.data, vec![0; 8]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn set_send_false_blocks_manual_empty_report() {
|
||||||
|
let Some(dev) = open_any_keyboard_device()
|
||||||
|
.or_else(|| build_keyboard("lesavka-include-kbd-nosend").map(|(_, dev)| dev))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let (mut agg, mut rx) = new_aggregator(dev);
|
||||||
|
agg.set_send(false);
|
||||||
|
agg.send_empty_report();
|
||||||
|
assert!(rx.try_recv().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn process_events_respects_send_toggle() {
|
||||||
|
let Some((mut vdev, dev)) = build_keyboard("lesavka-include-kbd-send-toggle") else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let (mut agg, mut rx) = new_aggregator(dev);
|
||||||
|
agg.set_send(false);
|
||||||
|
vdev.emit(&[evdev::InputEvent::new(
|
||||||
|
evdev::EventType::KEY.0,
|
||||||
|
evdev::KeyCode::KEY_B.0,
|
||||||
|
1,
|
||||||
|
)])
|
||||||
|
.expect("emit key");
|
||||||
|
thread::sleep(std::time::Duration::from_millis(20));
|
||||||
|
agg.process_events();
|
||||||
|
assert!(rx.try_recv().is_err(), "send-disabled aggregator should not publish reports");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn paste_chord_active_supports_ctrl_v_variant() {
|
||||||
|
let Some(dev) = open_any_keyboard_device()
|
||||||
|
.or_else(|| build_keyboard("lesavka-include-kbd-ctrl-v").map(|(_, dev)| dev))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let (mut agg, _) = new_aggregator(dev);
|
||||||
|
agg.pressed_keys.insert(evdev::KeyCode::KEY_LEFTCTRL);
|
||||||
|
agg.pressed_keys.insert(evdev::KeyCode::KEY_V);
|
||||||
|
with_var("LESAVKA_CLIPBOARD_CHORD", Some("ctrl+v"), || {
|
||||||
|
assert!(agg.paste_chord_active());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn paste_debounced_rejects_rapid_repeat_with_positive_window() {
|
||||||
|
let Some(dev) = open_any_keyboard_device()
|
||||||
|
.or_else(|| build_keyboard("lesavka-include-kbd-debounce").map(|(_, dev)| dev))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let (agg, _) = new_aggregator(dev);
|
||||||
|
let now_ms = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.expect("time")
|
||||||
|
.as_millis() as u64;
|
||||||
|
LAST_PASTE_MS.store(now_ms, Ordering::Relaxed);
|
||||||
|
with_var("LESAVKA_CLIPBOARD_DEBOUNCE_MS", Some("9999"), || {
|
||||||
|
assert!(!agg.paste_debounced());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn paste_via_rpc_returns_false_without_sender() {
|
||||||
|
let Some(dev) = open_any_keyboard_device()
|
||||||
|
.or_else(|| build_keyboard("lesavka-include-kbd-rpc-none").map(|(_, dev)| dev))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let (tx, _rx) = tokio::sync::broadcast::channel(8);
|
||||||
|
let agg = KeyboardAggregator::new(dev, false, tx, None);
|
||||||
|
assert!(!agg.paste_via_rpc());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn read_clipboard_text_returns_none_when_custom_and_fallback_tools_fail() {
|
||||||
|
with_var("LESAVKA_CLIPBOARD_CMD", Some("nonexistent-clipboard-command"), || {
|
||||||
|
with_var("PATH", Some("/tmp/definitely-missing-path"), || {
|
||||||
|
assert!(read_clipboard_text().is_none());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn try_handle_paste_event_swallows_incomplete_chord_sequences() {
|
||||||
|
let Some(dev) = open_any_keyboard_device()
|
||||||
|
.or_else(|| build_keyboard("lesavka-include-kbd-incomplete").map(|(_, dev)| dev))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let (mut agg, mut rx) = new_aggregator(dev);
|
||||||
|
agg.paste_enabled = true;
|
||||||
|
agg.paste_chord_armed = true;
|
||||||
|
agg.pressed_keys.insert(evdev::KeyCode::KEY_LEFTCTRL);
|
||||||
|
|
||||||
|
assert!(agg.try_handle_paste_event(evdev::KeyCode::KEY_LEFTCTRL, 1));
|
||||||
|
let pkt = rx.try_recv().expect("swallow report");
|
||||||
|
assert_eq!(pkt.data, vec![0; 8]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -122,6 +122,27 @@ mod mouse_contract {
|
|||||||
Some((vdev, dev))
|
Some((vdev, dev))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_absolute_mouse_without_touch(
|
||||||
|
name: &str,
|
||||||
|
) -> Option<(evdev::uinput::VirtualDevice, evdev::Device)> {
|
||||||
|
let mut keys = evdev::AttributeSet::<evdev::KeyCode>::new();
|
||||||
|
keys.insert(evdev::KeyCode::BTN_LEFT);
|
||||||
|
let abs = evdev::AbsInfo::new(0, 0, 1000, 0, 0, 0);
|
||||||
|
let mut vdev = evdev::uinput::VirtualDevice::builder()
|
||||||
|
.ok()?
|
||||||
|
.name(name)
|
||||||
|
.with_keys(&keys)
|
||||||
|
.ok()?
|
||||||
|
.with_absolute_axis(&evdev::UinputAbsSetup::new(evdev::AbsoluteAxisCode::ABS_X, abs))
|
||||||
|
.ok()?
|
||||||
|
.with_absolute_axis(&evdev::UinputAbsSetup::new(evdev::AbsoluteAxisCode::ABS_Y, abs))
|
||||||
|
.ok()?
|
||||||
|
.build()
|
||||||
|
.ok()?;
|
||||||
|
let dev = open_virtual_device(&mut vdev)?;
|
||||||
|
Some((vdev, dev))
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn relative_events_emit_button_motion_and_wheel_packets() {
|
fn relative_events_emit_button_motion_and_wheel_packets() {
|
||||||
@ -293,4 +314,144 @@ mod mouse_contract {
|
|||||||
agg.set_btn(0, 0);
|
agg.set_btn(0, 0);
|
||||||
assert_eq!(agg.buttons & 0b11, 0b10);
|
assert_eq!(agg.buttons & 0b11, 0b10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn absolute_motion_ignores_large_jumps_without_touch_state() {
|
||||||
|
let Some((mut vdev, dev)) = build_absolute_mouse_without_touch("lesavka-include-abs-jump")
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let (tx, _rx) = tokio::sync::broadcast::channel(8);
|
||||||
|
let mut agg = MouseAggregator::new(dev, false, tx);
|
||||||
|
|
||||||
|
agg.last_abs_x = Some(0);
|
||||||
|
agg.last_abs_y = Some(0);
|
||||||
|
assert!(!agg.has_touch_state);
|
||||||
|
|
||||||
|
vdev.emit(&[
|
||||||
|
evdev::InputEvent::new(
|
||||||
|
evdev::EventType::ABSOLUTE.0,
|
||||||
|
evdev::AbsoluteAxisCode::ABS_X.0,
|
||||||
|
900,
|
||||||
|
),
|
||||||
|
evdev::InputEvent::new(
|
||||||
|
evdev::EventType::ABSOLUTE.0,
|
||||||
|
evdev::AbsoluteAxisCode::ABS_Y.0,
|
||||||
|
900,
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.expect("emit large abs jump");
|
||||||
|
|
||||||
|
thread::sleep(std::time::Duration::from_millis(20));
|
||||||
|
agg.process_events();
|
||||||
|
|
||||||
|
assert_eq!(agg.dx, 0);
|
||||||
|
assert_eq!(agg.dy, 0);
|
||||||
|
assert_eq!(agg.last_abs_x, Some(900));
|
||||||
|
assert_eq!(agg.last_abs_y, Some(900));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn absolute_motion_applies_scaled_delta_within_threshold() {
|
||||||
|
let Some((mut vdev, dev)) = build_absolute_mouse_without_touch("lesavka-include-abs-delta")
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let (tx, _rx) = tokio::sync::broadcast::channel(8);
|
||||||
|
let mut agg = MouseAggregator::new(dev, false, tx);
|
||||||
|
agg.last_abs_x = Some(100);
|
||||||
|
agg.last_abs_y = Some(100);
|
||||||
|
|
||||||
|
vdev.emit(&[
|
||||||
|
evdev::InputEvent::new(
|
||||||
|
evdev::EventType::ABSOLUTE.0,
|
||||||
|
evdev::AbsoluteAxisCode::ABS_X.0,
|
||||||
|
140,
|
||||||
|
),
|
||||||
|
evdev::InputEvent::new(
|
||||||
|
evdev::EventType::ABSOLUTE.0,
|
||||||
|
evdev::AbsoluteAxisCode::ABS_Y.0,
|
||||||
|
180,
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.expect("emit small abs delta");
|
||||||
|
|
||||||
|
thread::sleep(std::time::Duration::from_millis(20));
|
||||||
|
agg.process_events();
|
||||||
|
|
||||||
|
assert_ne!(agg.dx, 0);
|
||||||
|
assert_ne!(agg.dy, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn touch_guarded_inactive_abs_events_only_update_origins() {
|
||||||
|
let Some((mut vdev, dev)) = build_touch_device("lesavka-include-touch-guarded") else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let (tx, _rx) = tokio::sync::broadcast::channel(8);
|
||||||
|
let mut agg = MouseAggregator::new(dev, false, tx);
|
||||||
|
agg.touch_guarded = true;
|
||||||
|
agg.touch_active = false;
|
||||||
|
agg.last_abs_x = Some(10);
|
||||||
|
agg.last_abs_y = Some(10);
|
||||||
|
|
||||||
|
vdev.emit(&[
|
||||||
|
evdev::InputEvent::new(
|
||||||
|
evdev::EventType::ABSOLUTE.0,
|
||||||
|
evdev::AbsoluteAxisCode::ABS_X.0,
|
||||||
|
50,
|
||||||
|
),
|
||||||
|
evdev::InputEvent::new(
|
||||||
|
evdev::EventType::ABSOLUTE.0,
|
||||||
|
evdev::AbsoluteAxisCode::ABS_Y.0,
|
||||||
|
70,
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.expect("emit guarded abs updates");
|
||||||
|
|
||||||
|
thread::sleep(std::time::Duration::from_millis(20));
|
||||||
|
agg.process_events();
|
||||||
|
|
||||||
|
assert_eq!(agg.dx, 0);
|
||||||
|
assert_eq!(agg.dy, 0);
|
||||||
|
assert_eq!(agg.last_abs_x, Some(50));
|
||||||
|
assert_eq!(agg.last_abs_y, Some(70));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn flush_skips_when_state_unchanged_before_interval_deadline() {
|
||||||
|
let Some(dev) = open_any_mouse_device()
|
||||||
|
.or_else(|| build_relative_mouse("lesavka-include-mouse-skip").map(|(_, dev)| dev))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let (tx, mut rx) = tokio::sync::broadcast::channel(8);
|
||||||
|
let mut agg = MouseAggregator::new(dev, false, tx);
|
||||||
|
agg.buttons = 1;
|
||||||
|
agg.last_buttons = 1;
|
||||||
|
agg.next_send = std::time::Instant::now() + std::time::Duration::from_millis(50);
|
||||||
|
|
||||||
|
agg.flush();
|
||||||
|
assert!(rx.try_recv().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn drop_emits_shutdown_packet() {
|
||||||
|
let Some(dev) = open_any_mouse_device()
|
||||||
|
.or_else(|| build_relative_mouse("lesavka-include-mouse-drop").map(|(_, dev)| dev))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let (tx, mut rx) = tokio::sync::broadcast::channel(8);
|
||||||
|
let agg = MouseAggregator::new(dev, false, tx);
|
||||||
|
drop(agg);
|
||||||
|
let pkt = rx.try_recv().expect("drop packet");
|
||||||
|
assert_eq!(pkt.data, vec![0; 8]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user