190 lines
8.1 KiB
Rust
190 lines
8.1 KiB
Rust
//! RPC reset coverage for server main USB recovery replies.
|
|
//!
|
|
//! Scope: include `server/src/main.rs` and exercise reset RPC edge replies.
|
|
//! Targets: `server/src/main.rs`.
|
|
//! Why: USB reset is an operator recovery path, so failed HID reopen behavior
|
|
//! needs deterministic coverage without requiring real gadget hardware.
|
|
|
|
#[allow(warnings)]
|
|
mod server_main_rpc_reset {
|
|
include!(env!("LESAVKA_SERVER_MAIN_SRC"));
|
|
|
|
use serial_test::serial;
|
|
use temp_env::with_var;
|
|
use tempfile::tempdir;
|
|
|
|
fn build_handler_for_tests() -> (tempfile::TempDir, Handler) {
|
|
let dir = tempdir().expect("tempdir");
|
|
let kb_path = dir.path().join("hidg0.bin");
|
|
let ms_path = dir.path().join("hidg1.bin");
|
|
std::fs::write(&kb_path, []).expect("create kb file");
|
|
std::fs::write(&ms_path, []).expect("create ms file");
|
|
|
|
let kb = tokio::fs::File::from_std(
|
|
std::fs::OpenOptions::new()
|
|
.read(true)
|
|
.write(true)
|
|
.create(true)
|
|
.truncate(true)
|
|
.open(&kb_path)
|
|
.expect("open kb"),
|
|
);
|
|
let ms = tokio::fs::File::from_std(
|
|
std::fs::OpenOptions::new()
|
|
.read(true)
|
|
.write(true)
|
|
.create(true)
|
|
.truncate(true)
|
|
.open(&ms_path)
|
|
.expect("open ms"),
|
|
);
|
|
|
|
let handler = with_var("LESAVKA_CAPTURE_POWER_UNIT", Some("none"), || Handler {
|
|
kb: std::sync::Arc::new(tokio::sync::Mutex::new(Some(kb))),
|
|
ms: std::sync::Arc::new(tokio::sync::Mutex::new(Some(ms))),
|
|
gadget: UsbGadget::new("lesavka"),
|
|
did_cycle: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
|
|
camera_rt: std::sync::Arc::new(CameraRuntime::new()),
|
|
upstream_media_rt: std::sync::Arc::new(UpstreamMediaRuntime::new()),
|
|
calibration: std::sync::Arc::new(CalibrationStore::load(std::sync::Arc::new(
|
|
UpstreamMediaRuntime::new(),
|
|
))),
|
|
capture_power: CapturePowerManager::new(),
|
|
eye_hubs: std::sync::Arc::new(
|
|
tokio::sync::Mutex::new(std::collections::HashMap::new()),
|
|
),
|
|
});
|
|
|
|
(dir, handler)
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(coverage)]
|
|
#[serial]
|
|
fn reset_usb_tolerates_missing_hid_after_successful_cycle() {
|
|
let dir = tempdir().expect("tempdir");
|
|
std::fs::write(dir.path().join("hidg0.bin"), "").expect("create kb file");
|
|
std::fs::write(dir.path().join("hidg1.bin"), "").expect("create ms file");
|
|
std::fs::create_dir_all(dir.path().join("sys/class/udc/fake-ctrl.usb"))
|
|
.expect("create udc dir");
|
|
std::fs::create_dir_all(dir.path().join("cfg/lesavka")).expect("create cfg dir");
|
|
std::fs::write(
|
|
dir.path().join("sys/class/udc/fake-ctrl.usb/state"),
|
|
"configured\n",
|
|
)
|
|
.expect("write state");
|
|
std::fs::write(dir.path().join("cfg/lesavka/UDC"), "fake-ctrl.usb\n").expect("write udc");
|
|
|
|
let kb = tokio::fs::File::from_std(
|
|
std::fs::OpenOptions::new()
|
|
.read(true)
|
|
.write(true)
|
|
.open(dir.path().join("hidg0.bin"))
|
|
.expect("open kb"),
|
|
);
|
|
let ms = tokio::fs::File::from_std(
|
|
std::fs::OpenOptions::new()
|
|
.read(true)
|
|
.write(true)
|
|
.open(dir.path().join("hidg1.bin"))
|
|
.expect("open ms"),
|
|
);
|
|
|
|
with_var(
|
|
"LESAVKA_GADGET_SYSFS_ROOT",
|
|
Some(dir.path().join("sys").to_string_lossy().to_string()),
|
|
|| {
|
|
with_var(
|
|
"LESAVKA_GADGET_CONFIGFS_ROOT",
|
|
Some(dir.path().join("cfg").to_string_lossy().to_string()),
|
|
|| {
|
|
let handler = Handler {
|
|
kb: std::sync::Arc::new(tokio::sync::Mutex::new(Some(kb))),
|
|
ms: std::sync::Arc::new(tokio::sync::Mutex::new(Some(ms))),
|
|
gadget: UsbGadget::new("lesavka"),
|
|
did_cycle: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(
|
|
false,
|
|
)),
|
|
camera_rt: std::sync::Arc::new(CameraRuntime::new()),
|
|
upstream_media_rt: std::sync::Arc::new(UpstreamMediaRuntime::new()),
|
|
calibration: std::sync::Arc::new(CalibrationStore::load(
|
|
std::sync::Arc::new(UpstreamMediaRuntime::new()),
|
|
)),
|
|
capture_power: CapturePowerManager::new(),
|
|
eye_hubs: std::sync::Arc::new(tokio::sync::Mutex::new(
|
|
std::collections::HashMap::new(),
|
|
)),
|
|
};
|
|
|
|
with_var(
|
|
"LESAVKA_HID_DIR",
|
|
Some(dir.path().join("missing").to_string_lossy().to_string()),
|
|
|| {
|
|
let rt = tokio::runtime::Runtime::new().expect("runtime");
|
|
let reply = rt
|
|
.block_on(async {
|
|
handler.reset_usb(tonic::Request::new(Empty {})).await
|
|
})
|
|
.expect("missing HID should not fail USB reset")
|
|
.into_inner();
|
|
assert!(reply.ok);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(coverage)]
|
|
#[serial]
|
|
fn reset_usb_reports_reopen_hid_failure_after_successful_cycle() {
|
|
let dir = tempdir().expect("tempdir");
|
|
std::fs::create_dir_all(dir.path().join("bad-hid/hidg0")).expect("create bad hidg0 dir");
|
|
std::fs::write(dir.path().join("bad-hid/hidg1"), "").expect("create hidg1 file");
|
|
std::fs::create_dir_all(dir.path().join("sys/class/udc/fake-ctrl.usb"))
|
|
.expect("create udc dir");
|
|
std::fs::create_dir_all(dir.path().join("cfg/lesavka")).expect("create cfg dir");
|
|
std::fs::write(
|
|
dir.path().join("sys/class/udc/fake-ctrl.usb/state"),
|
|
"configured\n",
|
|
)
|
|
.expect("write state");
|
|
std::fs::write(dir.path().join("cfg/lesavka/UDC"), "fake-ctrl.usb\n").expect("write udc");
|
|
|
|
let (_hid_dir, handler) = build_handler_for_tests();
|
|
with_var(
|
|
"LESAVKA_GADGET_SYSFS_ROOT",
|
|
Some(dir.path().join("sys").to_string_lossy().to_string()),
|
|
|| {
|
|
with_var(
|
|
"LESAVKA_GADGET_CONFIGFS_ROOT",
|
|
Some(dir.path().join("cfg").to_string_lossy().to_string()),
|
|
|| {
|
|
with_var(
|
|
"LESAVKA_HID_DIR",
|
|
Some(dir.path().join("bad-hid").to_string_lossy().to_string()),
|
|
|| {
|
|
let rt = tokio::runtime::Runtime::new().expect("runtime");
|
|
let err = rt
|
|
.block_on(async {
|
|
handler.reset_usb(tonic::Request::new(Empty {})).await
|
|
})
|
|
.expect_err("bad HID path should fail reopen");
|
|
assert_eq!(err.code(), tonic::Code::Internal);
|
|
assert!(
|
|
err.message().contains("opening")
|
|
&& err.message().contains("bad-hid/hidg0"),
|
|
"unexpected reopen error: {}",
|
|
err.message()
|
|
);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|