#![cfg(feature = "mock")]
use std::time::{Duration, Instant};
use crate::{
Button, Device, DeviceInfo, DeviceKind, Error, FrameType, Health, LogLevel, MockBox,
RebootTarget, Version,
};
#[test]
fn device_info_and_version_mac_round_trip_through_the_mock() {
let mock = MockBox::new()
.with_version(Version {
proto_ver: 2,
fw_major: 2,
fw_minor: 3,
fw_patch: 0,
mac: [0x5A, 0x4E, 0x00, 0x11, 0x1e, 0x28],
name: "Left PC".to_string(),
})
.with_device_info(DeviceInfo {
vid: 0x1532,
pid: 0x0072,
bcd_device: 0x0200,
bcd_usb: 0x0200,
has_serial: true,
has_bos: false,
kind: DeviceKind::Mouse,
product: "Razer Mamba Elite".to_string(),
});
let device = Device::with_mock(mock);
let v = device.query_version().unwrap();
assert_eq!(v.mac_hex(), "5a4e00111e28");
assert_eq!(v.name, "Left PC");
let info = device.device_info().unwrap();
assert_eq!((info.vid, info.pid), (0x1532, 0x0072));
assert_eq!(info.kind, DeviceKind::Mouse);
assert_eq!(info.product, "Razer Mamba Elite");
assert!(info.has_serial && !info.has_bos);
}
#[test]
fn pushed_logs_reach_the_logs_channel_in_order() {
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
let rx = device.logs();
mock.push_log(LogLevel::Warn, "overheating");
mock.push_log(LogLevel::Info, "recovered");
let a = rx.recv_timeout(Duration::from_secs(1)).unwrap();
let b = rx.recv_timeout(Duration::from_secs(1)).unwrap();
assert_eq!((a.level, a.text.as_str()), (LogLevel::Warn, "overheating"));
assert_eq!((b.level, b.text.as_str()), (LogLevel::Info, "recovered"));
}
#[test]
fn set_health_updates_subsequent_queries() {
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
assert!(!device.query_health().unwrap().mouse_attached);
mock.set_health(Health::from_flags(0x02));
assert!(device.query_health().unwrap().mouse_attached);
}
#[test]
fn handshake_rejects_wrong_proto_ver() {
let mock = MockBox::new().with_version(Version {
proto_ver: 11,
fw_major: 0,
fw_minor: 0,
fw_patch: 0,
mac: [0; 6],
name: String::new(),
});
let err = Device::open_mock(mock).unwrap_err();
assert!(matches!(err, Error::BadProtoVer { got: 11 }), "got {err:?}");
}
#[test]
fn handshake_refuses_a_v3_4_0_box() {
let mock = MockBox::new().with_version(Version {
proto_ver: 7,
fw_major: 3,
fw_minor: 4,
fw_patch: 0,
mac: [0; 6],
name: String::new(),
});
let err = Device::open_mock(mock).unwrap_err();
assert!(matches!(err, Error::BadProtoVer { got: 7 }), "got {err:?}");
assert_eq!(
err.to_string(),
"unsupported protocol version 7 (expected 9)"
);
}
#[test]
fn handshake_on_silent_box_is_no_reply() {
let err = Device::open_mock(MockBox::new().silent()).unwrap_err();
assert!(matches!(err, Error::NoReply), "got {err:?}");
}
#[test]
fn dropping_the_last_clone_joins_threads_without_hanging() {
let device = Device::with_mock(MockBox::new());
device.move_rel(1, 1).unwrap();
let start = Instant::now();
drop(device);
assert!(
start.elapsed() < Duration::from_millis(500),
"drop took too long: {:?}",
start.elapsed()
);
}
#[test]
fn a_clone_keeps_the_reader_alive_until_the_last_drop() {
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
let clone = device.clone();
drop(device);
let rx = clone.logs();
mock.push_log(LogLevel::Info, "still here");
assert_eq!(
rx.recv_timeout(Duration::from_secs(1)).unwrap().text,
"still here"
);
}
#[test]
fn device_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Device>();
}
#[test]
fn reapply_re_emits_only_held_overrides() {
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
device.press(Button::LEFT).unwrap();
device.force_release(Button::SIDE1).unwrap();
device.press(Button::MIDDLE).unwrap();
device.release(Button::MIDDLE).unwrap();
mock.clear_recorded();
device.reapply().unwrap();
let buttons: Vec<Vec<u8>> = mock
.recorded_frames()
.iter()
.filter(|f| f.ty == FrameType::Inject)
.map(|f| f.payload.clone())
.collect();
assert_eq!(buttons, vec![vec![0, 0, 0, 1], vec![0, 3, 0, 2]]);
drop(device);
}
#[test]
fn reapply_re_emits_held_locks_but_not_released_ones() {
use crate::{Axis, Blanket, Direction, Key};
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
device.lock(Axis::X, Direction::Positive).unwrap();
device.lock(Key::A, Direction::Both).unwrap();
device.lock_all(Blanket::Keys, Direction::Both).unwrap();
device.unlock(Key::A, Direction::Both).unwrap();
mock.clear_recorded();
device.reapply().unwrap();
let locks: Vec<Vec<u8>> = mock
.recorded_frames()
.iter()
.filter(|f| f.ty == FrameType::Lock)
.map(|f| f.payload.clone())
.collect();
assert_eq!(
locks,
vec![vec![1, 0xFF, 0xFF, 0, 0, 0], vec![3, 0, 0, 1, 0, 0]]
);
drop(device);
}
#[test]
fn reapply_re_emits_a_scale_at_its_own_value() {
use crate::{Axis, Direction};
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
device.scale(Axis::X, Direction::Against, 40).unwrap();
device.scale(Axis::Y, Direction::With, 130).unwrap();
mock.clear_recorded();
device.reapply().unwrap();
let locks: Vec<Vec<u8>> = mock
.recorded_frames()
.iter()
.filter(|f| f.ty == FrameType::Lock)
.map(|f| f.payload.clone())
.collect();
assert_eq!(
locks,
vec![vec![3, 0, 0, 4, 40, 0], vec![3, 1, 0, 3, 130, 0]]
);
drop(device);
}
#[test]
fn reapply_re_emits_a_reversal_at_its_own_sign() {
use crate::{Axis, Direction};
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
device.scale(Axis::X, Direction::Both, -100).unwrap();
device.scale(Axis::Y, Direction::Against, -255).unwrap();
mock.clear_recorded();
device.reapply().unwrap();
let locks: Vec<Vec<u8>> = mock
.recorded_frames()
.iter()
.filter(|f| f.ty == FrameType::Lock)
.map(|f| f.payload.clone())
.collect();
assert_eq!(
locks,
vec![vec![3, 0, 0, 0, 0x9C, 0xFF], vec![3, 1, 0, 4, 0x01, 0xFF]]
);
drop(device);
}
#[test]
fn unlocking_both_forgets_the_relative_scales_too() {
use crate::{Axis, Direction};
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
device.scale(Axis::X, Direction::Against, 40).unwrap();
device.scale(Axis::X, Direction::Positive, 60).unwrap();
device.unlock(Axis::X, Direction::Both).unwrap();
mock.clear_recorded();
device.reapply().unwrap();
let locks: Vec<Vec<u8>> = mock
.recorded_frames()
.iter()
.filter(|f| f.ty == FrameType::Lock)
.map(|f| f.payload.clone())
.collect();
assert!(
locks.is_empty(),
"expected nothing re-asserted, got {locks:?}"
);
drop(device);
}
#[test]
fn releasing_one_sign_of_a_both_lock_is_not_undone_by_a_reapply() {
use crate::{Axis, Direction};
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
device.lock(Axis::X, Direction::Both).unwrap();
device.unlock(Axis::X, Direction::Negative).unwrap();
mock.clear_recorded();
device.reapply().unwrap();
let locks: Vec<Vec<u8>> = mock
.recorded_frames()
.iter()
.filter(|f| f.ty == FrameType::Lock)
.map(|f| f.payload.clone())
.collect();
assert_eq!(locks, vec![vec![3, 0, 0, 1, 0, 0]]);
drop(device);
}
#[test]
fn releasing_one_button_of_a_blanket_is_not_undone_by_a_reapply() {
use crate::{Blanket, Button, Direction};
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
device.lock_all(Blanket::Buttons, Direction::Both).unwrap();
device.unlock(Button::LEFT, Direction::Both).unwrap();
mock.clear_recorded();
device.reapply().unwrap();
let ids: Vec<u16> = mock
.recorded_frames()
.iter()
.filter(|f| f.ty == FrameType::Lock)
.map(|f| u16::from_le_bytes([f.payload[1], f.payload[2]]))
.collect();
assert_eq!(ids, vec![1, 2, 3, 4]);
drop(device);
}
#[test]
fn a_button_blanket_over_a_wide_mouse_reasserts_the_wide_buttons_on_reconnect() {
use crate::types::MouseCaps;
use crate::{Blanket, Direction};
let mock = MockBox::new().with_mouse_caps(MouseCaps {
n_buttons: 16,
has_x: true,
has_y: true,
has_wheel: true,
pan: false,
has_report_id: false,
n_hid: 1,
});
let device = Device::with_mock(mock.clone());
assert_eq!(device.caps().unwrap().mouse.n_buttons, 16);
device.lock_all(Blanket::Buttons, Direction::Both).unwrap();
mock.clear_recorded();
device.reapply().unwrap();
let ids: Vec<u16> = mock
.recorded_frames()
.iter()
.filter(|f| f.ty == FrameType::Lock)
.map(|f| u16::from_le_bytes([f.payload[1], f.payload[2]]))
.collect();
assert_eq!(ids, (0..16).collect::<Vec<u16>>());
drop(device);
}
#[test]
fn a_button_blanket_set_before_any_caps_call_reasserts_the_wide_buttons() {
use crate::types::MouseCaps;
use crate::{Blanket, Direction};
let mock = MockBox::new().with_mouse_caps(MouseCaps {
n_buttons: 8,
has_x: true,
has_y: true,
has_wheel: true,
pan: false,
has_report_id: false,
n_hid: 1,
});
let device = Device::open_mock(mock.clone()).unwrap();
device.lock_all(Blanket::Buttons, Direction::Both).unwrap();
mock.clear_recorded();
device.reapply().unwrap();
let ids: Vec<u16> = mock
.recorded_frames()
.iter()
.filter(|f| f.ty == FrameType::Lock)
.map(|f| u16::from_le_bytes([f.payload[1], f.payload[2]]))
.collect();
assert_eq!(ids, (0..8).collect::<Vec<u16>>());
drop(device);
}
#[test]
fn a_scale_a_one_bit_class_cannot_hold_is_not_held_here_either() {
use crate::{Button, Direction};
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
device
.scale(Button::LEFT, Direction::Positive, 150)
.unwrap();
mock.clear_recorded();
device.reapply().unwrap();
assert!(!mock.saw(FrameType::Lock));
drop(device);
}
#[test]
fn reboot_emits_the_target_byte() {
let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
for target in [
RebootTarget::DeviceRun,
RebootTarget::HostRun,
RebootTarget::DeviceDownload,
RebootTarget::HostDownload,
] {
device.reboot(target).unwrap();
}
let reboots: Vec<u8> = mock
.recorded_frames()
.iter()
.filter(|f| f.ty == FrameType::RebootDl)
.map(|f| f.payload[0])
.collect();
assert_eq!(reboots, vec![2, 3, 0, 1]);
drop(device);
}