testing: add gadget include coverage contracts

This commit is contained in:
Brad Stein 2026-04-12 21:12:22 -03:00
parent 30490aaa93
commit aaf7230178
2 changed files with 110 additions and 0 deletions

View File

@ -16,6 +16,10 @@ fn main() {
.join("server/src/video.rs")
.canonicalize()
.expect("canonical server video path");
let server_gadget = workspace_dir
.join("server/src/gadget.rs")
.canonicalize()
.expect("canonical server gadget path");
let client_main = workspace_dir
.join("client/src/main.rs")
.canonicalize()
@ -50,6 +54,10 @@ fn main() {
"cargo:rustc-env=LESAVKA_SERVER_VIDEO_SRC={}",
server_video.display()
);
println!(
"cargo:rustc-env=LESAVKA_SERVER_GADGET_SRC={}",
server_gadget.display()
);
println!(
"cargo:rustc-env=LESAVKA_CLIENT_MAIN_SRC={}",
client_main.display()

View File

@ -0,0 +1,102 @@
//! Include-based coverage for USB gadget orchestration helpers.
//!
//! Scope: include `server/src/gadget.rs` and exercise deterministic helper
//! logic and error branches without touching live gadget sysfs state.
//! Targets: `server/src/gadget.rs`.
//! Why: most gadget logic is file-system and errno handling that should remain
//! stable regardless of host environment.
#[allow(warnings)]
mod gadget_include_contract {
include!(env!("LESAVKA_SERVER_GADGET_SRC"));
use serial_test::serial;
use temp_env::with_var;
use tempfile::{NamedTempFile, tempdir};
#[test]
fn new_builds_expected_udc_path() {
let gadget = UsbGadget::new("lesavka-test");
assert!(gadget.udc_file.ends_with("/lesavka-test/UDC"));
}
#[test]
fn state_errors_for_missing_controller() {
let result = UsbGadget::state("definitely-missing-udc");
assert!(result.is_err());
}
#[test]
fn wait_state_any_times_out_for_missing_controller() {
let result = UsbGadget::wait_state_any("definitely-missing-udc", 0);
assert!(result.is_err());
}
#[test]
fn write_attr_writes_value_with_trailing_newline() {
let file = NamedTempFile::new().expect("temp file");
UsbGadget::write_attr(file.path(), "configured").expect("write attr");
let content = std::fs::read_to_string(file.path()).expect("read back");
assert_eq!(content, "configured\n");
}
#[test]
fn wait_udc_present_times_out_for_missing_path() {
let result = UsbGadget::wait_udc_present("definitely-missing-udc", 0);
assert!(result.is_err());
}
#[test]
fn probe_platform_udc_is_non_panicking() {
let result = UsbGadget::probe_platform_udc();
assert!(result.is_ok());
}
#[test]
fn find_controller_returns_name_or_error_without_panicking() {
let result = UsbGadget::find_controller();
if let Ok(name) = result {
assert!(!name.is_empty());
}
}
#[test]
fn is_still_detaching_matches_expected_errno_set() {
let busy = anyhow::Error::from(std::io::Error::from_raw_os_error(libc::EBUSY));
let missing = anyhow::Error::from(std::io::Error::from_raw_os_error(libc::ENOENT));
let no_dev = anyhow::Error::from(std::io::Error::from_raw_os_error(libc::ENODEV));
let other = anyhow::Error::from(std::io::Error::from_raw_os_error(libc::EACCES));
assert!(UsbGadget::is_still_detaching(&busy));
assert!(UsbGadget::is_still_detaching(&missing));
assert!(UsbGadget::is_still_detaching(&no_dev));
assert!(!UsbGadget::is_still_detaching(&other));
}
#[test]
#[serial]
fn cycle_handles_missing_gadget_sysfs_gracefully() {
let gadget = UsbGadget::new("lesavka-test");
with_var("LESAVKA_GADGET_FORCE_CYCLE", None::<&str>, || {
let result = gadget.cycle();
assert!(result.is_err() || result.is_ok());
});
}
#[test]
#[serial]
fn cycle_force_mode_still_returns_without_panicking() {
let dir = tempdir().expect("tempdir");
let fake_udc = dir.path().join("UDC");
std::fs::write(&fake_udc, "").expect("create fake udc file");
let gadget = UsbGadget {
udc_file: Box::leak(fake_udc.to_string_lossy().to_string().into_boxed_str()),
};
with_var("LESAVKA_GADGET_FORCE_CYCLE", Some("1"), || {
let result = gadget.cycle();
assert!(result.is_err() || result.is_ok());
});
}
}