//! Интеграция `Power1` + dev-mock на session-шине. `#[ignore]` — запуск: `just test-integration`. //! Проверяет state по шине и fake-ACC: `dev.PowerMock1.SetAcc` → сигнал `Power1.AccChanged`. use futures_util::StreamExt; use shturman_ipc::{names, types::PowerState}; use shturman_power::coprocessor::MockCoprocessor; use shturman_power::thermal::MockTempSource; use shturman_power::PowerService; use shturman_sdk::PowerClient; use std::sync::Arc; #[tokio::test] #[ignore = "нужна session-шина: just test-integration"] async fn power_state_and_fake_acc() { let svc = PowerService::new(); let mock = svc.mock( Arc::new(MockTempSource::new(20)), Arc::new(MockCoprocessor::new()), ); // сервер: Power1 + dev.PowerMock1 на одном пути (владеет ru.shturman.Power) let server = zbus::Connection::session().await.unwrap(); server .object_server() .at(names::power::PATH, svc) .await .unwrap(); server .object_server() .at(names::power::PATH, mock) .await .unwrap(); server.request_name(names::power::NAME).await.unwrap(); let client_conn = zbus::Connection::session().await.unwrap(); let power = PowerClient::new(&client_conn).await.unwrap(); assert_eq!(power.power_state().await.unwrap(), PowerState::Running); // подписка на AccChanged let mut acc = power.proxy().receive_acc_changed().await.unwrap(); // dev.PowerMock1.SetAcc(false) сырым D-Bus-вызовом client_conn .call_method( Some(names::power::NAME), names::power::PATH, Some(names::power::MOCK_IFACE), "SetAcc", &(false,), ) .await .unwrap(); let sig = acc.next().await.unwrap(); assert!(!sig.args().unwrap().on()); } #[tokio::test] #[ignore = "нужна session-шина: just test-integration"] async fn shutdown_imminent_then_abort() { let svc = PowerService::new(); let mock = svc.mock( Arc::new(MockTempSource::new(20)), Arc::new(MockCoprocessor::new()), ); let server = zbus::Connection::session().await.unwrap(); server .object_server() .at(names::power::PATH, svc) .await .unwrap(); server .object_server() .at(names::power::PATH, mock) .await .unwrap(); server.request_name(names::power::NAME).await.unwrap(); let client = zbus::Connection::session().await.unwrap(); let power = PowerClient::new(&client).await.unwrap(); let mut imminent = power.proxy().receive_shutdown_imminent().await.unwrap(); let mut aborted = power.proxy().receive_shutdown_aborted().await.unwrap(); // ACC-off → ShutdownImminent(acc_off), состояние shutting_down client .call_method( Some(names::power::NAME), names::power::PATH, Some(names::power::MOCK_IFACE), "SetAcc", &(false,), ) .await .unwrap(); let sig = imminent.next().await.unwrap(); assert_eq!(sig.args().unwrap().reason(), "acc_off"); assert_eq!(power.power_state().await.unwrap(), PowerState::ShuttingDown); // re-power до grace → ShutdownAborted + running client .call_method( Some(names::power::NAME), names::power::PATH, Some(names::power::MOCK_IFACE), "SetAcc", &(true,), ) .await .unwrap(); aborted.next().await.unwrap(); assert_eq!(power.power_state().await.unwrap(), PowerState::Running); } #[tokio::test] #[ignore = "нужна session-шина: just test-integration"] async fn thermal_trip_then_clear() { let svc = PowerService::new(); let fsm = svc.fsm_handle(); let thermal_state = svc.thermal_state_handle(); let temp = Arc::new(MockTempSource::new(20)); let copro = Arc::new(MockCoprocessor::new()); let server = zbus::Connection::session().await.unwrap(); server .object_server() .at(names::power::PATH, svc) .await .unwrap(); server.request_name(names::power::NAME).await.unwrap(); let iface = server .object_server() .interface::<_, PowerService>(names::power::PATH) .await .unwrap(); let ctx = iface.signal_context().to_owned(); shturman_power::service::spawn_loops( fsm, thermal_state, temp.clone() as Arc, Arc::new(shturman_power::thermal::NoopThrottler::default()) as Arc, copro as Arc, ctx, ); let client = zbus::Connection::session().await.unwrap(); let power = PowerClient::new(&client).await.unwrap(); let mut imminent = power.proxy().receive_shutdown_imminent().await.unwrap(); let mut aborted = power.proxy().receive_shutdown_aborted().await.unwrap(); // перегрев → ShutdownImminent(thermal) temp.set(99); let sig = imminent.next().await.unwrap(); assert_eq!(sig.args().unwrap().reason(), "thermal"); assert_eq!(power.power_state().await.unwrap(), PowerState::ShuttingDown); // остыло до PONR → ShutdownAborted + running temp.set(20); aborted.next().await.unwrap(); assert_eq!(power.power_state().await.unwrap(), PowerState::Running); } #[tokio::test] #[ignore = "нужна session-шина: just test-integration"] async fn mcu_failsafe_cuts_on_hang() { let svc = PowerService::new(); let fsm = svc.fsm_handle(); let thermal_state = svc.thermal_state_handle(); let temp = Arc::new(MockTempSource::new(20)); let copro = Arc::new(MockCoprocessor::new()); let server = zbus::Connection::session().await.unwrap(); server .object_server() .at(names::power::PATH, svc) .await .unwrap(); server.request_name(names::power::NAME).await.unwrap(); let iface = server .object_server() .interface::<_, PowerService>(names::power::PATH) .await .unwrap(); let ctx = iface.signal_context().to_owned(); shturman_power::service::spawn_loops( fsm, thermal_state, temp as Arc, Arc::new(shturman_power::thermal::NoopThrottler::default()) as Arc, copro.clone() as Arc, ctx, ); let client = zbus::Connection::session().await.unwrap(); let power = PowerClient::new(&client).await.unwrap(); assert_eq!(power.power_state().await.unwrap(), PowerState::Running); // дать coproc-циклу послать ≥1 heartbeat (иначе last_heartbeat=0 и guard не даст cut) tokio::time::sleep(std::time::Duration::from_millis(1300)).await; copro.hang(); // SoC завис → heartbeat не освежает таймер // ждём, пока coproc-цикл (HEARTBEAT=1с) накопит > FAILSAFE_MISS окон и сделает FailsafeCut for _ in 0..10 { tokio::time::sleep(std::time::Duration::from_millis(700)).await; if power.power_state().await.unwrap() == PowerState::Off { break; } } assert_eq!(power.power_state().await.unwrap(), PowerState::Off); }