use crabcamera::commands::advanced::{
capture_burst_sequence, capture_focus_stack_legacy, capture_hdr_sequence,
get_camera_controls, get_camera_performance, set_camera_controls, set_manual_exposure,
set_manual_focus, set_white_balance, test_camera_capabilities as test_capabilities,
};
use crabcamera::types::{BurstConfig, CameraControls, WhiteBalance};
use std::time::{Duration, Instant};
use tokio::sync::Mutex as AsyncMutex;
use tokio;
static TEST_LOCK: AsyncMutex<()> = AsyncMutex::const_new(());
const TEST_DEVICE_ID: &str = "test_camera_advanced";
fn create_test_controls() -> CameraControls {
CameraControls {
auto_focus: Some(false),
focus_distance: Some(0.5),
auto_exposure: Some(false),
exposure_time: Some(1.0 / 125.0), iso_sensitivity: Some(400),
white_balance: Some(WhiteBalance::Auto),
aperture: Some(5.6),
zoom: Some(1.0),
brightness: Some(0.0),
contrast: Some(0.0),
saturation: Some(0.0),
sharpness: Some(0.0),
noise_reduction: Some(true),
image_stabilization: Some(true),
}
}
#[tokio::test]
async fn test_set_get_camera_controls() {
let _lock = TEST_LOCK.lock().await;
let controls = create_test_controls();
let device_id = TEST_DEVICE_ID.to_string();
let set_result = set_camera_controls(device_id.clone(), controls.clone()).await;
match set_result {
Ok(message) => {
assert!(message.contains("Controls applied"));
}
Err(e) => {
println!("Warning: Camera control test failed (expected in CI): {}", e);
return;
}
}
tokio::time::sleep(Duration::from_millis(100)).await;
let get_result = get_camera_controls(device_id.clone()).await;
match get_result {
Ok(retrieved_controls) => {
assert!(retrieved_controls.auto_focus.is_some(), "auto_focus should be set");
}
Err(e) => {
println!("Warning: Get controls test failed (expected in CI): {}", e);
}
}
let default_controls = CameraControls::default();
let _ = set_camera_controls(device_id, default_controls).await;
}
#[tokio::test]
async fn test_manual_focus_control() {
let device_id = TEST_DEVICE_ID.to_string();
let valid_distances = [0.0, 0.25, 0.5, 0.75, 1.0];
for distance in valid_distances.iter() {
let result = set_manual_focus(device_id.clone(), *distance).await;
match result {
Ok(_) => {
}
Err(e) if e.contains("mutex") || e.contains("camera") => {
println!("Warning: Focus test failed (expected in CI): {}", e);
}
Err(e) => {
assert!(!e.contains("Focus distance must be"));
}
}
}
let invalid_distances = [-0.1, 1.1, 2.0, -1.0];
for distance in invalid_distances.iter() {
let result = set_manual_focus(device_id.clone(), *distance).await;
assert!(result.is_err());
if let Err(e) = result {
assert!(e.contains("Focus distance must be between 0.0"));
}
}
}
#[tokio::test]
async fn test_manual_exposure_settings() {
let device_id = TEST_DEVICE_ID.to_string();
let valid_combinations = [
(1.0 / 1000.0, 100), (1.0 / 125.0, 400), (1.0 / 30.0, 800), (1.0 / 4.0, 3200), ];
for (exposure_time, iso) in valid_combinations.iter() {
let result = set_manual_exposure(device_id.clone(), *exposure_time, *iso).await;
match result {
Ok(_) => {
}
Err(e) if e.contains("mutex") || e.contains("camera") => {
println!("Warning: Exposure test failed (expected in CI): {}", e);
}
Err(e) => {
assert!(!e.contains("Exposure time must be"));
assert!(!e.contains("ISO sensitivity must be"));
}
}
}
let invalid_exposures = [0.0, -0.1, 11.0, 20.0];
for exposure in invalid_exposures.iter() {
let result = set_manual_exposure(device_id.clone(), *exposure, 400).await;
assert!(result.is_err());
if let Err(e) = result {
assert!(e.contains("Exposure time must be"));
}
}
let invalid_isos = [25, 49, 25600, 100000];
for iso in invalid_isos.iter() {
let result = set_manual_exposure(device_id.clone(), 1.0 / 125.0, *iso).await;
assert!(result.is_err());
if let Err(e) = result {
assert!(e.contains("ISO sensitivity must be"));
}
}
}
#[tokio::test]
async fn test_white_balance_modes() {
let device_id = TEST_DEVICE_ID.to_string();
let wb_modes = [
WhiteBalance::Auto,
WhiteBalance::Daylight,
WhiteBalance::Fluorescent,
WhiteBalance::Incandescent,
WhiteBalance::Flash,
WhiteBalance::Cloudy,
WhiteBalance::Shade,
WhiteBalance::Custom(5500), ];
for wb_mode in wb_modes.iter() {
let result = set_white_balance(device_id.clone(), wb_mode.clone()).await;
match result {
Ok(_) => {
}
Err(e) if e.contains("mutex") || e.contains("camera") => {
println!("Warning: WB test failed (expected in CI): {}", e);
}
Err(e) => {
println!("Unexpected white balance error: {}", e);
}
}
}
}
#[tokio::test]
async fn test_burst_sequence_capture() {
let device_id = TEST_DEVICE_ID.to_string();
let basic_config = BurstConfig {
count: 3,
interval_ms: 100,
bracketing: None,
focus_stacking: false,
auto_save: false,
save_directory: None,
};
let result = capture_burst_sequence(device_id.clone(), basic_config).await;
match result {
Ok(frames) => {
assert_eq!(frames.len(), 3);
for frame in frames {
assert!(frame.is_valid());
assert!(!frame.data.is_empty());
}
}
Err(e) if e.contains("mutex") || e.contains("camera") => {
println!("Warning: Burst test failed (expected in CI): {}", e);
}
Err(e) => {
println!("Unexpected burst error: {}", e);
}
}
}
#[tokio::test]
async fn test_exposure_bracketing() {
let device_id = TEST_DEVICE_ID.to_string();
let bracketing_config = crabcamera::types::ExposureBracketing {
base_exposure: 1.0 / 125.0,
stops: vec![-1.0, 0.0, 1.0], };
let burst_config = BurstConfig {
count: 3,
interval_ms: 200,
bracketing: Some(bracketing_config),
focus_stacking: false,
auto_save: false,
save_directory: None,
};
let result = capture_burst_sequence(device_id, burst_config).await;
match result {
Ok(frames) => {
assert_eq!(frames.len(), 3);
for (i, frame) in frames.iter().enumerate() {
assert!(frame.is_valid());
if let Some(ref settings) = frame.metadata.capture_settings {
assert!(settings.exposure_time.is_some() || settings.auto_exposure == Some(false));
}
println!("Bracketed frame {}: {} bytes", i + 1, frame.size_bytes);
}
}
Err(e) if e.contains("mutex") || e.contains("camera") => {
println!("Warning: Bracketing test failed (expected in CI): {}", e);
}
Err(e) => {
println!("Unexpected bracketing error: {}", e);
}
}
}
#[tokio::test]
async fn test_focus_stacking_legacy() {
let device_id = TEST_DEVICE_ID.to_string();
let valid_counts = [3, 5, 10, 20];
for count in valid_counts.iter() {
let result = capture_focus_stack_legacy(device_id.clone(), *count).await;
match result {
Ok(frames) => {
assert_eq!(frames.len() as u32, *count);
for frame in frames {
assert!(frame.is_valid());
}
}
Err(e) if e.contains("mutex") || e.contains("camera") => {
println!("Warning: Focus stack test failed (expected in CI): {}", e);
}
Err(e) => {
assert!(!e.contains("Focus stack count must be"));
println!("Unexpected focus stack error: {}", e);
}
}
}
let invalid_counts = [1, 2, 21, 50];
for count in invalid_counts.iter() {
let result = capture_focus_stack_legacy(device_id.clone(), *count).await;
assert!(result.is_err());
if let Err(e) = result {
assert!(e.contains("Focus stack count must be between 3 and 20"));
}
}
}
#[tokio::test]
async fn test_hdr_capture() {
let device_id = TEST_DEVICE_ID.to_string();
let result = capture_hdr_sequence(device_id).await;
match result {
Ok(frames) => {
assert!(frames.len() >= 3);
assert!(frames.len() <= 7);
for frame in frames {
assert!(frame.is_valid());
assert!(frame.size_bytes > 0);
}
}
Err(e) if e.contains("mutex") || e.contains("camera") => {
println!("Warning: HDR test failed (expected in CI): {}", e);
}
Err(e) => {
println!("Unexpected HDR error: {}", e);
}
}
}
#[tokio::test]
async fn test_camera_capabilities() {
let device_id = TEST_DEVICE_ID.to_string();
let result = test_capabilities(device_id).await;
match result {
Ok(capabilities) => {
assert!(capabilities.max_resolution.0 > 0);
assert!(capabilities.max_resolution.1 > 0);
println!("Camera capabilities:");
println!(" Manual focus: {}", capabilities.supports_manual_focus);
println!(" Manual exposure: {}", capabilities.supports_manual_exposure);
println!(" White balance: {}", capabilities.supports_white_balance);
println!(" Max resolution: {}x{}",
capabilities.max_resolution.0, capabilities.max_resolution.1);
println!(" Manual focus: {}", capabilities.supports_manual_focus);
}
Err(e) if e.contains("mutex") || e.contains("camera") => {
println!("Warning: Capabilities test failed (expected in CI): {}", e);
}
Err(e) => {
println!("Unexpected capabilities error: {}", e);
}
}
}
#[tokio::test]
async fn test_performance_metrics() {
let device_id = TEST_DEVICE_ID.to_string();
let result = get_camera_performance(device_id).await;
match result {
Ok(metrics) => {
assert!(metrics.capture_latency_ms >= 0.0);
assert!(metrics.fps_actual >= 0.0);
assert!(metrics.fps_actual >= 0.0);
println!("Performance metrics:");
println!(" Capture latency: {:.2}ms", metrics.capture_latency_ms);
println!(" Actual FPS: {:.2}", metrics.fps_actual);
println!(" Processing time: {:.2}ms", metrics.processing_time_ms);
println!(" Frame drops: {}", metrics.dropped_frames);
}
Err(e) if e.contains("mutex") || e.contains("camera") => {
println!("Warning: Performance test failed (expected in CI): {}", e);
}
Err(e) => {
println!("Unexpected performance error: {}", e);
}
}
}
#[tokio::test]
async fn test_burst_config_validation() {
let device_id = TEST_DEVICE_ID.to_string();
let invalid_config_zero = BurstConfig {
count: 0,
interval_ms: 100,
bracketing: None,
focus_stacking: false,
auto_save: false,
save_directory: None,
};
let result = capture_burst_sequence(device_id.clone(), invalid_config_zero).await;
assert!(result.is_err());
if let Err(e) = result {
assert!(e.contains("Invalid burst count"));
}
let invalid_config_high = BurstConfig {
count: 100,
interval_ms: 100,
bracketing: None,
focus_stacking: false,
auto_save: false,
save_directory: None,
};
let result = capture_burst_sequence(device_id, invalid_config_high).await;
assert!(result.is_err());
if let Err(e) = result {
assert!(e.contains("Invalid burst count"));
}
}
#[tokio::test]
async fn test_advanced_operations_performance() {
let device_id = TEST_DEVICE_ID.to_string();
let start = Instant::now();
let controls = create_test_controls();
match set_camera_controls(device_id.clone(), controls).await {
Ok(_) => {
let controls_time = start.elapsed();
println!("Camera controls setting took: {:?}", controls_time);
assert!(controls_time < Duration::from_secs(1));
}
Err(e) if e.contains("mutex") || e.contains("camera") => {
println!("Warning: Performance test skipped (no camera): {}", e);
return;
}
Err(e) => {
println!("Unexpected performance test error: {}", e);
}
}
let start = Instant::now();
let burst_config = BurstConfig {
count: 5,
interval_ms: 50, bracketing: None,
focus_stacking: false,
auto_save: false,
save_directory: None,
};
match capture_burst_sequence(device_id, burst_config).await {
Ok(frames) => {
let burst_time = start.elapsed();
println!("Burst capture ({} frames) took: {:?}", frames.len(), burst_time);
let fps = frames.len() as f32 / burst_time.as_secs_f32();
println!("Effective burst FPS: {:.2}", fps);
assert!(fps >= 1.0); }
Err(e) if e.contains("mutex") || e.contains("camera") => {
println!("Warning: Burst performance test skipped (no camera): {}", e);
}
Err(e) => {
println!("Unexpected burst performance error: {}", e);
}
}
}
#[tokio::test]
async fn test_advanced_edge_cases() {
let device_id = TEST_DEVICE_ID.to_string();
let fast_config = BurstConfig {
count: 2,
interval_ms: 1, bracketing: None,
focus_stacking: false,
auto_save: false,
save_directory: None,
};
let result = capture_burst_sequence(device_id.clone(), fast_config).await;
match result {
Ok(frames) => {
assert_eq!(frames.len(), 2);
}
Err(e) if e.contains("mutex") || e.contains("camera") => {
println!("Warning: Fast interval test skipped: {}", e);
}
Err(e) => {
println!("Fast interval error: {}", e);
}
}
let extreme_controls = CameraControls {
exposure_time: Some(0.001), iso_sensitivity: Some(50), ..CameraControls::default()
};
let result = set_camera_controls(device_id, extreme_controls).await;
match result {
Ok(_) => {
}
Err(e) if e.contains("mutex") || e.contains("camera") => {
println!("Warning: Extreme values test skipped: {}", e);
}
Err(e) => {
println!("Extreme values error: {}", e);
}
}
}
#[tokio::test]
async fn test_concurrent_advanced_operations() {
let device_id = TEST_DEVICE_ID.to_string();
let handles = (0..3).map(|i| {
let device_id = device_id.clone();
tokio::spawn(async move {
let controls = CameraControls {
focus_distance: Some(i as f32 * 0.3),
..CameraControls::default()
};
set_camera_controls(device_id, controls).await
})
}).collect::<Vec<_>>();
let results = futures::future::join_all(handles).await;
let mut success_count = 0;
let mut expected_failures = 0;
for result in results {
match result {
Ok(Ok(_)) => success_count += 1,
Ok(Err(e)) if e.contains("mutex") || e.contains("camera") => {
expected_failures += 1;
}
Ok(Err(e)) => {
println!("Unexpected concurrent error: {}", e);
}
Err(e) => {
println!("Task join error: {}", e);
}
}
}
if success_count > 0 {
println!("Concurrent operations succeeded: {}", success_count);
} else {
println!("Concurrent operations failed (expected in CI): {}", expected_failures);
}
}
#[tokio::test]
async fn test_resource_management() {
let device_id = TEST_DEVICE_ID.to_string();
for i in 0..3 {
let config = BurstConfig {
count: 2,
interval_ms: 100,
bracketing: None,
focus_stacking: false,
auto_save: false,
save_directory: None,
};
match capture_burst_sequence(device_id.clone(), config).await {
Ok(frames) => {
println!("Resource test iteration {}: {} frames captured", i + 1, frames.len());
for frame in frames {
assert!(!frame.data.is_empty());
assert!(frame.size_bytes > 0);
}
}
Err(e) if e.contains("mutex") || e.contains("camera") => {
println!("Warning: Resource test {} skipped: {}", i + 1, e);
}
Err(e) => {
println!("Resource test {} error: {}", i + 1, e);
}
}
}
}