1use anyhow::{Context as _, Result, anyhow};
11use fidl_fuchsia_hardware_power_battery as fbattery;
12use fidl_fuchsia_hardware_power_charger as fcharger;
13use fuchsia_async as fasync;
14use fuchsia_component::client::connect_to_protocol;
15use futures::FutureExt;
16use log::{debug, info, warn};
17use std::convert::Infallible;
18
19const RECONNECT_DELAY: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(2);
20
21#[derive(Clone, Debug, Default, PartialEq)]
23pub struct Telemetry {
24 pub battery: fbattery::Status,
25 pub charger: Option<fcharger::Status>,
26}
27
28#[derive(Clone, Debug, Default, PartialEq)]
30pub struct Evaluation {
31 pub vote: fcharger::ControlOptions,
34 pub sample_interval: Option<zx::BootDuration>,
39 pub wake_for_samples: bool,
43}
44
45#[derive(Clone, Debug, Default, PartialEq)]
48pub struct WatchInterest {
49 pub battery: Option<fbattery::Status>,
53 pub charger: Option<fcharger::Status>,
55 pub battery_wake_on: Option<fbattery::Status>,
57 pub charger_wake_on: Option<fcharger::Status>,
59}
60
61pub trait Governor: Send + 'static {
63 fn evaluate(&mut self, telemetry: &Telemetry) -> Evaluation;
69
70 fn watch_interest(&self) -> WatchInterest {
73 WatchInterest::default()
74 }
75}
76
77macro_rules! for_each_control_field {
81 ($m:ident) => {
82 $m!(operating_mode);
83 $m!(input_current_limit_ua);
84 $m!(charge_current_limit_ua);
85 $m!(float_voltage_uv);
86 $m!(charge_term_current_ua);
87 $m!(input_voltage_limit_uv);
88 $m!(top_off_duration);
89 };
90}
91
92fn unforwarded_fields(vote: &fcharger::ControlOptions) -> fcharger::ControlOptions {
94 let mut rest = vote.clone();
95 macro_rules! strip {
96 ($f:ident) => {
97 rest.$f = None;
98 };
99 }
100 for_each_control_field!(strip);
101 rest
102}
103
104pub fn merge_vote(vote: &mut fcharger::ControlOptions, patch: &fcharger::ControlOptions) {
106 macro_rules! merge {
107 ($f:ident) => {
108 vote.$f = patch.$f.or(vote.$f);
109 };
110 }
111 for_each_control_field!(merge);
112}
113
114pub fn clear_vote_fields(vote: &mut fcharger::ControlOptions, mask: &fcharger::ControlOptions) {
116 macro_rules! clear {
117 ($f:ident) => {
118 if mask.$f.is_some() {
119 vote.$f = None;
120 }
121 };
122 }
123 for_each_control_field!(clear);
124}
125
126pub fn diff_controls(
128 applied: &fcharger::ControlOptions,
129 desired: &fcharger::ControlOptions,
130) -> (fcharger::ControlOptions, fcharger::ControlOptions) {
131 let mut to_set = fcharger::ControlOptions::default();
132 let mut to_clear = fcharger::ControlOptions::default();
133 macro_rules! diff {
135 ($f:ident) => {
136 to_set.$f = desired.$f.filter(|&v| Some(v) != applied.$f);
137 to_clear.$f = applied.$f.filter(|_| desired.$f.is_none());
138 };
139 }
140 for_each_control_field!(diff);
141 (to_set, to_clear)
142}
143
144async fn process_telemetry_event<T: Governor>(
153 governor: &mut T,
154 telemetry: &Telemetry,
155 last_accepted: &mut fcharger::ControlOptions,
156 controller: &fcharger::ControllerProxy,
157) -> Result<Evaluation> {
158 let evaluation = governor.evaluate(telemetry);
159 let unforwarded = unforwarded_fields(&evaluation.vote);
160 if unforwarded != fcharger::ControlOptions::default() {
161 static WARN_UNFORWARDED: std::sync::Once = std::sync::Once::new();
162 WARN_UNFORWARDED
163 .call_once(|| warn!("Ignoring vote fields the harness can't send: {unforwarded:?}"));
164 }
165 let (to_set, to_clear) = diff_controls(last_accepted, &evaluation.vote);
166
167 if to_set != fcharger::ControlOptions::default() {
168 match controller.set_control(&to_set).await.context("Controller.SetControl")? {
169 Ok(()) => merge_vote(last_accepted, &to_set),
170 Err(err) => {
171 warn!("Charger controller rejected SetControl ({to_set:?}): {err:?}");
172 return Ok(evaluation);
173 }
174 }
175 }
176
177 if to_clear != fcharger::ControlOptions::default() {
178 match controller.clear_control(&to_clear).await.context("Controller.ClearControl")? {
179 Ok(()) => clear_vote_fields(last_accepted, &to_clear),
180 Err(err) => {
181 warn!("Charger controller rejected ClearControl ({to_clear:?}): {err:?}");
182 }
183 }
184 }
185
186 Ok(evaluation)
187}
188
189#[derive(Clone, Copy, Debug, Default, PartialEq)]
191struct Sampling {
192 interval: Option<zx::BootDuration>,
194 wake: bool,
196}
197
198impl From<&Evaluation> for Sampling {
199 fn from(evaluation: &Evaluation) -> Self {
200 let interval = evaluation.sample_interval;
201 Self { interval, wake: interval.is_some() && evaluation.wake_for_samples }
202 }
203}
204
205async fn configure_battery_watch(
212 battery: &fbattery::BatteryProxy,
213 interest: &WatchInterest,
214 sampling: Sampling,
215) -> Result<()> {
216 let options = fbattery::WatchOptions {
217 interest: interest.battery.clone(),
218 wake_on: interest.battery_wake_on.clone(),
219 max_interval: sampling.interval.map(zx::BootDuration::into_nanos),
220 wake_on_max_interval: sampling.wake.then_some(true),
221 ..Default::default()
222 };
223 match battery.configure_watch(&options).await.context("Battery.ConfigureWatch")? {
224 Ok(effective) if effective == options => debug!("Battery watch options set to {options:?}"),
225 Ok(effective) => info!("Battery honors watch options {effective:?} instead of {options:?}"),
226 Err(e) => warn!("Battery.ConfigureWatch rejected {options:?}: {e:?}"),
227 }
228 Ok(())
229}
230
231async fn configure_charger_watch(
235 charger: &fcharger::ChargerProxy,
236 interest: &WatchInterest,
237) -> Result<()> {
238 let options = fcharger::WatchOptions {
239 interest: interest.charger.clone(),
240 wake_on: interest.charger_wake_on.clone(),
241 ..Default::default()
242 };
243 match charger.configure_watch(&options).await.context("Charger.ConfigureWatch")? {
244 Ok(effective) if effective == options => debug!("Charger watch options set to {options:?}"),
245 Ok(effective) => info!("Charger honors watch options {effective:?} instead of {options:?}"),
246 Err(e) => warn!("Charger.ConfigureWatch rejected {options:?}: {e:?}"),
247 }
248 Ok(())
249}
250
251struct Session {
253 battery: fbattery::BatteryProxy,
254 charger: fcharger::ChargerProxy,
255 controller: fcharger::ControllerProxy,
256}
257
258impl Session {
259 fn connect() -> Result<Self> {
261 Ok(Self {
262 battery: connect_to_protocol::<fbattery::BatteryMarker>()?,
263 charger: connect_to_protocol::<fcharger::ChargerMarker>()?,
264 controller: connect_to_protocol::<fcharger::ControllerMarker>()?,
265 })
266 }
267}
268
269fn battery_response(
272 result: Result<fbattery::BatteryWatchResult, fidl::Error>,
273 latest: &mut Option<fbattery::Status>,
274) -> Result<Option<zx::EventPair>> {
275 let (status, wake_lease) =
276 result.context("Battery.Watch")?.map_err(|e| anyhow!("Battery.Watch failed: {e:?}"))?;
277 *latest = Some(status);
278 Ok(wake_lease)
279}
280
281fn charger_response(
284 result: Result<fcharger::ChargerWatchResult, fidl::Error>,
285 latest: &mut Option<fcharger::Status>,
286) -> Result<Option<zx::EventPair>> {
287 let (status, wake_lease) =
288 result.context("Charger.Watch")?.map_err(|e| anyhow!("Charger.Watch failed: {e:?}"))?;
289 *latest = Some(status);
290 Ok(wake_lease)
291}
292
293async fn run_session<T: Governor>(governor: &mut T, session: &Session) -> Result<Infallible> {
315 let Session { battery, charger, controller } = session;
316 let mut last_accepted = fcharger::ControlOptions::default();
317 let mut sampling = Sampling::default();
319 let mut latest_battery = None;
320 let mut latest_charger = None;
321 let mut charger_unavailable = false;
323
324 let interest = governor.watch_interest();
325 if interest.battery.is_some() || interest.battery_wake_on.is_some() {
326 configure_battery_watch(battery, &interest, sampling).await?;
327 }
328 if interest.charger.is_some() || interest.charger_wake_on.is_some() {
329 configure_charger_watch(charger, &interest).await?;
330 }
331
332 let mut battery_watch = battery.watch(None).fuse();
333 let mut charger_watch = charger.watch(None).fuse();
334
335 loop {
336 let mut battery_updated = false;
337 let mut charger_updated = false;
338 let mut battery_lease = None;
339 let mut charger_lease = None;
340 futures::select_biased! {
341 result = battery_watch => {
342 battery_lease = battery_response(result, &mut latest_battery)?;
343 battery_updated = true;
344 if let Some(result) = (&mut charger_watch).now_or_never() {
345 charger_lease = charger_response(result, &mut latest_charger)?;
346 charger_updated = true;
347 }
348 }
349 result = charger_watch => {
350 charger_lease = charger_response(result, &mut latest_charger)?;
351 charger_updated = true;
352 if let Some(result) = (&mut battery_watch).now_or_never() {
353 battery_lease = battery_response(result, &mut latest_battery)?;
354 battery_updated = true;
355 }
356 }
357 }
358 if charger_updated {
359 charger_unavailable = false;
360 }
361
362 if !battery_updated {
368 match battery.get_status().await.context("Battery.GetStatus")? {
369 Ok(status) => latest_battery = Some(status),
370 Err(e) => warn!("Battery.GetStatus failed ({e:?}); using the last battery status"),
371 }
372 }
373 if !charger_updated && !charger_unavailable {
374 match charger.get_status().await.context("Charger.GetStatus")? {
375 Ok(status) => latest_charger = Some(status),
376 Err(e) if latest_charger.is_none() => {
377 debug!("No charger status ({e:?}); not reading it until Charger.Watch reports");
378 charger_unavailable = true;
379 }
380 Err(e) => warn!("Charger.GetStatus failed ({e:?}); using the last charger status"),
381 }
382 }
383
384 if let Some(battery_status) = &latest_battery {
385 let telemetry =
386 Telemetry { battery: battery_status.clone(), charger: latest_charger.clone() };
387 let evaluation =
388 process_telemetry_event(governor, &telemetry, &mut last_accepted, controller)
389 .await?;
390 let requested = Sampling::from(&evaluation);
391 if requested != sampling {
392 configure_battery_watch(battery, &interest, requested).await?;
393 sampling = requested;
394 }
395 }
396
397 if battery_updated {
400 battery_watch = battery.watch(battery_lease).fuse();
401 }
402 if charger_updated {
403 charger_watch = charger.watch(charger_lease).fuse();
404 }
405 }
406}
407
408async fn supervise<T: Governor>(
411 governor: &mut T,
412 mut connect: impl FnMut() -> Result<Session>,
413) -> Infallible {
414 loop {
415 match connect() {
416 Ok(session) => {
417 let Err(e) = run_session(governor, &session).await;
418 warn!("Governor session ended ({e:?}); reconnecting in {RECONNECT_DELAY:?}");
419 }
420 Err(e) => warn!("Failed to connect to battery-manager ({e:?}); retrying"),
421 }
422 fasync::Timer::new(fasync::MonotonicInstant::after(RECONNECT_DELAY)).await;
423 }
424}
425
426pub async fn run_governor<T: Governor>(mut governor: T) -> Result<()> {
433 let _inspect_server_task = inspect_runtime::publish(
434 fuchsia_inspect::component::inspector(),
435 inspect_runtime::PublishOptions::default(),
436 );
437 match supervise(&mut governor, Session::connect).await {}
438}
439
440#[cfg(test)]
441mod tests {
442 use super::*;
443 use fidl::endpoints::create_proxy_and_stream;
444 use futures::future::LocalBoxFuture;
445 use futures::{Stream, StreamExt, TryStreamExt};
446 use std::cell::RefCell;
447 use std::collections::VecDeque;
448 use std::rc::Rc;
449 use std::sync::{Arc, Mutex};
450 use std::task::Poll;
451
452 const TEST_FCC_HIGH_UA: u32 = 1_500_000;
453 const TEST_FCC_LOW_UA: u32 = 800_000;
454 const TEST_FV_UV: u32 = 4_200_000;
455 const TEST_ICL_UA: u32 = 2_000_000;
456 const TEST_ITERM_UA: u32 = 100_000;
457 const TEST_VINDPM_UV: u32 = 4_600_000;
458 const TEST_TOP_OFF_DURATION_NANOS: i64 = 30_000_000_000;
459 const TEST_VBAT_UV: u32 = 3_900_000;
460 const TEST_VBAT_UPDATED_UV: u32 = 3_950_000;
461 const TEST_SAMPLE_INTERVAL: zx::BootDuration = zx::BootDuration::from_seconds(5);
462 const TEST_LONGER_SAMPLE_INTERVAL: zx::BootDuration = zx::BootDuration::from_seconds(10);
463 const TEST_LONG_IDLE: zx::MonotonicDuration = zx::MonotonicDuration::from_hours(1);
464 const ONE_NANO: zx::MonotonicDuration = zx::MonotonicDuration::from_nanos(1);
465
466 #[derive(Clone, Debug, PartialEq)]
467 enum ControllerCall {
468 Set(fcharger::ControlOptions),
469 Clear(fcharger::ControlOptions),
470 }
471
472 struct SequenceGovernor {
473 votes: Vec<Evaluation>,
474 idx: usize,
475 interest: WatchInterest,
476 seen: Arc<Mutex<Vec<Telemetry>>>,
478 }
479
480 impl SequenceGovernor {
481 fn new(votes: Vec<Evaluation>) -> Self {
483 Self::with_interest(votes, WatchInterest::default())
484 }
485
486 fn with_interest(votes: Vec<Evaluation>, interest: WatchInterest) -> Self {
487 Self { votes, idx: 0, interest, seen: Arc::default() }
488 }
489 }
490
491 impl Governor for SequenceGovernor {
492 fn evaluate(&mut self, telemetry: &Telemetry) -> Evaluation {
493 self.seen.lock().unwrap().push(telemetry.clone());
494 let eval = self.votes[self.idx.min(self.votes.len() - 1)].clone();
495 self.idx += 1;
496 eval
497 }
498
499 fn watch_interest(&self) -> WatchInterest {
500 self.interest.clone()
501 }
502 }
503
504 fn battery_status(voltage_uv: u32) -> fbattery::Status {
505 fbattery::Status { voltage_uv: Some(voltage_uv), ..Default::default() }
506 }
507
508 fn fcc_vote(fcc_ua: u32) -> fcharger::ControlOptions {
509 fcharger::ControlOptions { charge_current_limit_ua: Some(fcc_ua), ..Default::default() }
510 }
511
512 fn full_vote() -> fcharger::ControlOptions {
514 fcharger::ControlOptions {
515 operating_mode: Some(fcharger::OperatingMode::Passthrough),
516 input_current_limit_ua: Some(TEST_ICL_UA),
517 charge_current_limit_ua: Some(TEST_FCC_HIGH_UA),
518 float_voltage_uv: Some(TEST_FV_UV),
519 charge_term_current_ua: Some(TEST_ITERM_UA),
520 input_voltage_limit_uv: Some(TEST_VINDPM_UV),
521 top_off_duration: Some(TEST_TOP_OFF_DURATION_NANOS),
522 ..Default::default()
523 }
524 }
525
526 fn serve_controller(
528 mut stream: fcharger::ControllerRequestStream,
529 set_result: Result<(), fcharger::Error>,
530 clear_result: Result<(), fcharger::Error>,
531 ) -> (fasync::Task<()>, Arc<Mutex<Vec<ControllerCall>>>) {
532 let calls = Arc::new(Mutex::new(Vec::new()));
533 let calls_clone = calls.clone();
534 let task = fasync::Task::local(async move {
535 while let Ok(Some(req)) = stream.try_next().await {
536 match req {
537 fcharger::ControllerRequest::SetControl { options, responder } => {
538 calls_clone.lock().unwrap().push(ControllerCall::Set(options));
539 let _ = responder.send(set_result);
540 }
541 fcharger::ControllerRequest::ClearControl { options, responder } => {
542 calls_clone.lock().unwrap().push(ControllerCall::Clear(options));
543 let _ = responder.send(clear_result);
544 }
545 fcharger::ControllerRequest::_UnknownMethod { .. } => {}
546 }
547 }
548 });
549 (task, calls)
550 }
551
552 #[fuchsia::test]
555 fn test_unforwarded_fields_is_empty_for_every_field() {
556 assert_eq!(unforwarded_fields(&full_vote()), fcharger::ControlOptions::default());
557 }
558
559 #[fuchsia::test]
562 fn test_merge_vote_table() {
563 struct Case {
564 name: &'static str,
565 vote: fcharger::ControlOptions,
566 patch: fcharger::ControlOptions,
567 expected: fcharger::ControlOptions,
568 }
569 let cases = [
570 Case {
571 name: "empty patch leaves vote unchanged",
572 vote: full_vote(),
573 patch: fcharger::ControlOptions::default(),
574 expected: full_vote(),
575 },
576 Case {
577 name: "patch onto empty vote copies patch",
578 vote: fcharger::ControlOptions::default(),
579 patch: full_vote(),
580 expected: full_vote(),
581 },
582 Case {
583 name: "partial patch overrides only its fields",
584 vote: full_vote(),
585 patch: fcharger::ControlOptions {
586 operating_mode: Some(fcharger::OperatingMode::Charging),
587 charge_current_limit_ua: Some(TEST_FCC_LOW_UA),
588 ..Default::default()
589 },
590 expected: fcharger::ControlOptions {
591 operating_mode: Some(fcharger::OperatingMode::Charging),
592 charge_current_limit_ua: Some(TEST_FCC_LOW_UA),
593 ..full_vote()
594 },
595 },
596 Case {
597 name: "patch adds new field alongside existing",
598 vote: fcc_vote(TEST_FCC_HIGH_UA),
599 patch: fcharger::ControlOptions {
600 float_voltage_uv: Some(TEST_FV_UV),
601 ..Default::default()
602 },
603 expected: fcharger::ControlOptions {
604 charge_current_limit_ua: Some(TEST_FCC_HIGH_UA),
605 float_voltage_uv: Some(TEST_FV_UV),
606 ..Default::default()
607 },
608 },
609 ];
610 for Case { name, mut vote, patch, expected } in cases {
611 merge_vote(&mut vote, &patch);
612 assert_eq!(vote, expected, "case: {name}");
613 }
614 }
615
616 #[fuchsia::test]
619 fn test_clear_vote_fields_table() {
620 struct Case {
621 name: &'static str,
622 vote: fcharger::ControlOptions,
623 mask: fcharger::ControlOptions,
624 expected: fcharger::ControlOptions,
625 }
626 let cases = [
627 Case {
628 name: "empty mask leaves vote unchanged",
629 vote: full_vote(),
630 mask: fcharger::ControlOptions::default(),
631 expected: full_vote(),
632 },
633 Case {
634 name: "full mask clears every field",
635 vote: full_vote(),
636 mask: full_vote(),
637 expected: fcharger::ControlOptions::default(),
638 },
639 Case {
640 name: "mask value is ignored, only presence matters",
641 vote: full_vote(),
642 mask: fcharger::ControlOptions {
643 operating_mode: Some(fcharger::OperatingMode::Otg),
644 charge_current_limit_ua: Some(TEST_FCC_LOW_UA),
645 ..Default::default()
646 },
647 expected: fcharger::ControlOptions {
648 operating_mode: None,
649 charge_current_limit_ua: None,
650 ..full_vote()
651 },
652 },
653 Case {
654 name: "clearing already-absent field is a no-op",
655 vote: fcc_vote(TEST_FCC_HIGH_UA),
656 mask: fcharger::ControlOptions { float_voltage_uv: Some(0), ..Default::default() },
657 expected: fcc_vote(TEST_FCC_HIGH_UA),
658 },
659 ];
660 for Case { name, mut vote, mask, expected } in cases {
661 clear_vote_fields(&mut vote, &mask);
662 assert_eq!(vote, expected, "case: {name}");
663 }
664 }
665
666 #[fuchsia::test]
669 fn test_diff_controls_table() {
670 struct Case {
671 name: &'static str,
672 applied: fcharger::ControlOptions,
673 desired: fcharger::ControlOptions,
674 to_set: fcharger::ControlOptions,
675 to_clear: fcharger::ControlOptions,
676 }
677 let cases = [
678 Case {
679 name: "both empty",
680 applied: fcharger::ControlOptions::default(),
681 desired: fcharger::ControlOptions::default(),
682 to_set: fcharger::ControlOptions::default(),
683 to_clear: fcharger::ControlOptions::default(),
684 },
685 Case {
686 name: "identical full vote",
687 applied: full_vote(),
688 desired: full_vote(),
689 to_set: fcharger::ControlOptions::default(),
690 to_clear: fcharger::ControlOptions::default(),
691 },
692 Case {
693 name: "all fields newly set",
694 applied: fcharger::ControlOptions::default(),
695 desired: full_vote(),
696 to_set: full_vote(),
697 to_clear: fcharger::ControlOptions::default(),
698 },
699 Case {
700 name: "all fields withdrawn",
701 applied: full_vote(),
702 desired: fcharger::ControlOptions::default(),
703 to_set: fcharger::ControlOptions::default(),
704 to_clear: full_vote(),
705 },
706 Case {
707 name: "single field changed",
708 applied: full_vote(),
709 desired: fcharger::ControlOptions {
710 charge_current_limit_ua: Some(TEST_FCC_LOW_UA),
711 ..full_vote()
712 },
713 to_set: fcc_vote(TEST_FCC_LOW_UA),
714 to_clear: fcharger::ControlOptions::default(),
715 },
716 Case {
717 name: "operating mode switch is a set",
718 applied: fcharger::ControlOptions {
719 operating_mode: Some(fcharger::OperatingMode::Passthrough),
720 ..Default::default()
721 },
722 desired: fcharger::ControlOptions {
723 operating_mode: Some(fcharger::OperatingMode::Charging),
724 ..Default::default()
725 },
726 to_set: fcharger::ControlOptions {
727 operating_mode: Some(fcharger::OperatingMode::Charging),
728 ..Default::default()
729 },
730 to_clear: fcharger::ControlOptions::default(),
731 },
732 Case {
733 name: "changing a value to zero is a set, not a clear",
734 applied: fcc_vote(TEST_FCC_HIGH_UA),
735 desired: fcc_vote(0),
736 to_set: fcc_vote(0),
737 to_clear: fcharger::ControlOptions::default(),
738 },
739 Case {
740 name: "mixed set and clear",
741 applied: fcharger::ControlOptions {
742 charge_current_limit_ua: Some(TEST_FCC_HIGH_UA),
743 float_voltage_uv: Some(TEST_FV_UV),
744 ..Default::default()
745 },
746 desired: fcharger::ControlOptions {
747 charge_current_limit_ua: Some(TEST_FCC_LOW_UA),
748 input_current_limit_ua: Some(TEST_ICL_UA),
749 ..Default::default()
750 },
751 to_set: fcharger::ControlOptions {
752 charge_current_limit_ua: Some(TEST_FCC_LOW_UA),
753 input_current_limit_ua: Some(TEST_ICL_UA),
754 ..Default::default()
755 },
756 to_clear: fcharger::ControlOptions {
757 float_voltage_uv: Some(TEST_FV_UV),
758 ..Default::default()
759 },
760 },
761 ];
762 for Case { name, applied, desired, to_set, to_clear } in cases {
763 assert_eq!(diff_controls(&applied, &desired), (to_set, to_clear), "case: {name}");
764 }
765 }
766
767 #[fuchsia::test]
770 async fn test_process_telemetry_event_sets_and_clears_diffs() {
771 let (controller_proxy, controller_stream) =
772 create_proxy_and_stream::<fcharger::ControllerMarker>();
773 let (_server, calls) = serve_controller(controller_stream, Ok(()), Ok(()));
774
775 let mut gov = SequenceGovernor::new(vec![
776 Evaluation {
778 vote: fcharger::ControlOptions {
779 operating_mode: Some(fcharger::OperatingMode::Passthrough),
780 charge_current_limit_ua: Some(TEST_FCC_HIGH_UA),
781 float_voltage_uv: Some(TEST_FV_UV),
782 ..Default::default()
783 },
784 sample_interval: Some(TEST_SAMPLE_INTERVAL),
785 ..Default::default()
786 },
787 Evaluation {
789 vote: fcharger::ControlOptions {
790 operating_mode: Some(fcharger::OperatingMode::Passthrough),
791 charge_current_limit_ua: Some(TEST_FCC_HIGH_UA),
792 float_voltage_uv: Some(TEST_FV_UV),
793 ..Default::default()
794 },
795 sample_interval: Some(TEST_SAMPLE_INTERVAL),
796 ..Default::default()
797 },
798 Evaluation {
800 vote: fcharger::ControlOptions {
801 operating_mode: None,
802 charge_current_limit_ua: Some(TEST_FCC_LOW_UA),
803 float_voltage_uv: Some(TEST_FV_UV),
804 ..Default::default()
805 },
806 sample_interval: None,
807 ..Default::default()
808 },
809 ]);
810
811 let telemetry = Telemetry::default();
812 let mut last_accepted = fcharger::ControlOptions::default();
813
814 let eval1 =
816 process_telemetry_event(&mut gov, &telemetry, &mut last_accepted, &controller_proxy)
817 .await
818 .unwrap();
819 assert_eq!(eval1.sample_interval, Some(TEST_SAMPLE_INTERVAL));
820 assert_eq!(
821 std::mem::take(&mut *calls.lock().unwrap()),
822 vec![ControllerCall::Set(fcharger::ControlOptions {
823 operating_mode: Some(fcharger::OperatingMode::Passthrough),
824 charge_current_limit_ua: Some(TEST_FCC_HIGH_UA),
825 float_voltage_uv: Some(TEST_FV_UV),
826 ..Default::default()
827 })]
828 );
829
830 let _ =
832 process_telemetry_event(&mut gov, &telemetry, &mut last_accepted, &controller_proxy)
833 .await
834 .unwrap();
835 assert!(calls.lock().unwrap().is_empty());
836
837 let eval3 =
839 process_telemetry_event(&mut gov, &telemetry, &mut last_accepted, &controller_proxy)
840 .await
841 .unwrap();
842 assert_eq!(eval3.sample_interval, None);
843 assert_eq!(
844 std::mem::take(&mut *calls.lock().unwrap()),
845 vec![
846 ControllerCall::Set(fcharger::ControlOptions {
847 charge_current_limit_ua: Some(TEST_FCC_LOW_UA),
848 ..Default::default()
849 }),
850 ControllerCall::Clear(fcharger::ControlOptions {
851 operating_mode: Some(fcharger::OperatingMode::Passthrough),
852 ..Default::default()
853 }),
854 ]
855 );
856 }
857
858 #[fuchsia::test]
861 async fn test_process_telemetry_event_rejection_is_not_an_error() {
862 let (controller_proxy, controller_stream) =
863 create_proxy_and_stream::<fcharger::ControllerMarker>();
864 let (_server, calls) =
865 serve_controller(controller_stream, Err(fcharger::Error::InvalidArgs), Ok(()));
866 let mut gov = SequenceGovernor::new(vec![Evaluation {
867 vote: fcc_vote(TEST_FCC_LOW_UA),
868 sample_interval: None,
869 ..Default::default()
870 }]);
871
872 let initial_accepted = fcharger::ControlOptions {
873 charge_current_limit_ua: Some(TEST_FCC_HIGH_UA),
874 float_voltage_uv: Some(TEST_FV_UV),
875 ..Default::default()
876 };
877 let mut last_accepted = initial_accepted.clone();
878 let res = process_telemetry_event(
879 &mut gov,
880 &Telemetry::default(),
881 &mut last_accepted,
882 &controller_proxy,
883 )
884 .await;
885 assert!(res.is_ok(), "rejection is not an error: {res:?}");
886 assert_eq!(
888 std::mem::take(&mut *calls.lock().unwrap()),
889 vec![ControllerCall::Set(fcc_vote(TEST_FCC_LOW_UA))]
890 );
891 assert_eq!(last_accepted, initial_accepted);
892 }
893
894 #[fuchsia::test]
896 async fn test_process_telemetry_event_empty_vote_initializes_without_calls() {
897 let (controller_proxy, controller_stream) =
898 create_proxy_and_stream::<fcharger::ControllerMarker>();
899 let (_server, calls) = serve_controller(controller_stream, Ok(()), Ok(()));
900 let mut gov = SequenceGovernor::new(vec![Evaluation::default()]);
901
902 let mut last_accepted = fcharger::ControlOptions::default();
903 process_telemetry_event(
904 &mut gov,
905 &Telemetry::default(),
906 &mut last_accepted,
907 &controller_proxy,
908 )
909 .await
910 .unwrap();
911
912 assert!(calls.lock().unwrap().is_empty());
913 assert_eq!(last_accepted, fcharger::ControlOptions::default());
914 }
915
916 #[fuchsia::test]
919 async fn test_process_telemetry_event_tracks_desired_vote_across_sequence() {
920 let (controller_proxy, controller_stream) =
921 create_proxy_and_stream::<fcharger::ControllerMarker>();
922 let (_server, _calls) = serve_controller(controller_stream, Ok(()), Ok(()));
923 let votes = vec![
924 full_vote(),
925 fcharger::ControlOptions::default(),
926 fcc_vote(TEST_FCC_LOW_UA),
927 full_vote(),
928 fcharger::ControlOptions {
929 operating_mode: Some(fcharger::OperatingMode::Charging),
930 float_voltage_uv: Some(TEST_FV_UV),
931 ..Default::default()
932 },
933 fcharger::ControlOptions::default(),
934 ];
935 let mut gov = SequenceGovernor::new(
936 votes
937 .iter()
938 .cloned()
939 .map(|vote| Evaluation { vote, sample_interval: None, ..Default::default() })
940 .collect(),
941 );
942
943 let mut last_accepted = fcharger::ControlOptions::default();
944 for (step, vote) in votes.into_iter().enumerate() {
945 process_telemetry_event(
946 &mut gov,
947 &Telemetry::default(),
948 &mut last_accepted,
949 &controller_proxy,
950 )
951 .await
952 .unwrap();
953 assert_eq!(last_accepted, vote, "step {step}");
954 }
955 }
956
957 #[fuchsia::test]
960 async fn test_process_telemetry_event_clear_rejection_keeps_accepted_set() {
961 let (controller_proxy, controller_stream) =
962 create_proxy_and_stream::<fcharger::ControllerMarker>();
963 let (_server, calls) =
964 serve_controller(controller_stream, Ok(()), Err(fcharger::Error::NotSupported));
965 let mut gov = SequenceGovernor::new(vec![Evaluation {
966 vote: fcc_vote(TEST_FCC_LOW_UA),
967 sample_interval: None,
968 ..Default::default()
969 }]);
970
971 let mut last_accepted = fcharger::ControlOptions {
972 charge_current_limit_ua: Some(TEST_FCC_HIGH_UA),
973 float_voltage_uv: Some(TEST_FV_UV),
974 ..Default::default()
975 };
976 let res = process_telemetry_event(
977 &mut gov,
978 &Telemetry::default(),
979 &mut last_accepted,
980 &controller_proxy,
981 )
982 .await;
983
984 assert!(res.is_ok(), "rejection is not an error: {res:?}");
985 assert_eq!(
986 std::mem::take(&mut *calls.lock().unwrap()),
987 vec![
988 ControllerCall::Set(fcc_vote(TEST_FCC_LOW_UA)),
989 ControllerCall::Clear(fcharger::ControlOptions {
990 float_voltage_uv: Some(TEST_FV_UV),
991 ..Default::default()
992 }),
993 ]
994 );
995 assert_eq!(
997 last_accepted,
998 fcharger::ControlOptions {
999 charge_current_limit_ua: Some(TEST_FCC_LOW_UA),
1000 float_voltage_uv: Some(TEST_FV_UV),
1001 ..Default::default()
1002 }
1003 );
1004 }
1005
1006 #[fuchsia::test]
1008 async fn test_process_telemetry_event_propagates_transport_error() {
1009 let (controller_proxy, controller_stream) =
1010 create_proxy_and_stream::<fcharger::ControllerMarker>();
1011 drop(controller_stream);
1012 let mut gov = SequenceGovernor::new(vec![Evaluation {
1013 vote: fcc_vote(TEST_FCC_HIGH_UA),
1014 sample_interval: None,
1015 ..Default::default()
1016 }]);
1017
1018 let mut last_accepted = fcharger::ControlOptions::default();
1019 let res = process_telemetry_event(
1020 &mut gov,
1021 &Telemetry::default(),
1022 &mut last_accepted,
1023 &controller_proxy,
1024 )
1025 .await;
1026
1027 assert!(res.is_err());
1028 assert_eq!(last_accepted, fcharger::ControlOptions::default());
1029 }
1030
1031 #[fuchsia::test]
1033 async fn test_process_telemetry_event_clear_propagates_transport_error() {
1034 let (controller_proxy, controller_stream) =
1035 create_proxy_and_stream::<fcharger::ControllerMarker>();
1036 drop(controller_stream);
1037 let mut gov = SequenceGovernor::new(vec![Evaluation::default()]);
1038
1039 let mut last_accepted = fcc_vote(TEST_FCC_HIGH_UA);
1040 let res = process_telemetry_event(
1041 &mut gov,
1042 &Telemetry::default(),
1043 &mut last_accepted,
1044 &controller_proxy,
1045 )
1046 .await;
1047
1048 assert!(res.is_err());
1049 assert_eq!(last_accepted, fcc_vote(TEST_FCC_HIGH_UA));
1050 }
1051
1052 fn poll_next<S: Stream + Unpin>(
1054 executor: &mut fasync::TestExecutor,
1055 stream: &mut S,
1056 ) -> Option<S::Item> {
1057 match executor.run_until_stalled(&mut stream.next()) {
1058 Poll::Ready(item) => item,
1059 Poll::Pending => None,
1060 }
1061 }
1062
1063 struct ServerEnds {
1065 battery: fbattery::BatteryRequestStream,
1066 charger: fcharger::ChargerRequestStream,
1067 controller: fcharger::ControllerRequestStream,
1068 }
1069
1070 fn new_session() -> (Session, ServerEnds) {
1071 let (battery, battery_stream) = create_proxy_and_stream::<fbattery::BatteryMarker>();
1072 let (charger, charger_stream) = create_proxy_and_stream::<fcharger::ChargerMarker>();
1073 let (controller, controller_stream) =
1074 create_proxy_and_stream::<fcharger::ControllerMarker>();
1075 let server = ServerEnds {
1076 battery: battery_stream,
1077 charger: charger_stream,
1078 controller: controller_stream,
1079 };
1080 (Session { battery, charger, controller }, server)
1081 }
1082
1083 struct LoopHarness {
1089 run: LocalBoxFuture<'static, Infallible>,
1090 battery: Option<fbattery::BatteryRequestStream>,
1091 charger: Option<fcharger::ChargerRequestStream>,
1092 controller: Option<fcharger::ControllerRequestStream>,
1093 connected: Rc<RefCell<VecDeque<ServerEnds>>>,
1095 seen: Arc<Mutex<Vec<Telemetry>>>,
1096 executor: fasync::TestExecutor,
1098 }
1099
1100 impl LoopHarness {
1101 fn new(votes: Vec<Evaluation>) -> Self {
1103 Self::with_interest(votes, WatchInterest::default())
1104 }
1105
1106 fn with_interest(votes: Vec<Evaluation>, interest: WatchInterest) -> Self {
1108 let executor = fasync::TestExecutor::new_with_fake_time();
1109 let connected: Rc<RefCell<VecDeque<ServerEnds>>> = Rc::default();
1110 let mut governor = SequenceGovernor::with_interest(votes, interest);
1111 let seen = governor.seen.clone();
1112 let sessions = connected.clone();
1113 let run = Box::pin(async move {
1114 let connect = || {
1115 let (session, server) = new_session();
1116 sessions.borrow_mut().push_back(server);
1117 Ok(session)
1118 };
1119 supervise(&mut governor, connect).await
1120 });
1121 let mut h = Self {
1122 run,
1123 battery: None,
1124 charger: None,
1125 controller: None,
1126 connected,
1127 seen,
1128 executor,
1129 };
1130 assert!(h.take_session(), "governor did not connect");
1131 h
1132 }
1133
1134 fn take_session(&mut self) -> bool {
1136 self.pump();
1137 let Some(server) = self.connected.borrow_mut().pop_front() else {
1138 return false;
1139 };
1140 self.battery = Some(server.battery);
1141 self.charger = Some(server.charger);
1142 self.controller = Some(server.controller);
1143 true
1144 }
1145
1146 fn pump(&mut self) {
1148 match self.executor.run_until_stalled(&mut self.run) {
1149 Poll::Pending => {}
1150 Poll::Ready(never) => match never {},
1151 }
1152 }
1153
1154 fn advance(&mut self, duration: zx::MonotonicDuration) {
1156 self.executor.set_fake_time(self.executor.now() + duration);
1157 let _ = self.executor.wake_expired_timers();
1158 }
1159
1160 fn expect_reconnect(&mut self) {
1163 self.pump();
1164 self.advance(RECONNECT_DELAY - ONE_NANO);
1165 assert!(!self.take_session(), "governor reconnected early");
1166 self.advance(ONE_NANO);
1167 assert!(self.take_session(), "governor did not reconnect");
1168 }
1169
1170 fn next_battery_request(&mut self) -> Option<fbattery::BatteryRequest> {
1171 self.pump();
1172 let battery = self.battery.as_mut().expect("battery closed");
1173 poll_next(&mut self.executor, battery).map(|r| r.expect("battery request"))
1174 }
1175
1176 fn next_charger_request(&mut self) -> Option<fcharger::ChargerRequest> {
1177 self.pump();
1178 let charger = self.charger.as_mut().expect("charger closed");
1179 poll_next(&mut self.executor, charger).map(|r| r.expect("charger request"))
1180 }
1181
1182 fn next_controller_request(&mut self) -> Option<fcharger::ControllerRequest> {
1183 self.pump();
1184 let controller = self.controller.as_mut().expect("controller closed");
1185 poll_next(&mut self.executor, controller).map(|r| r.expect("controller request"))
1186 }
1187
1188 fn expect_battery_watch(&mut self) -> fbattery::BatteryWatchResponder {
1190 let (lease, responder) = self.expect_battery_watch_with_lease();
1191 assert!(lease.is_none());
1192 responder
1193 }
1194
1195 fn expect_battery_watch_with_lease(
1196 &mut self,
1197 ) -> (Option<zx::EventPair>, fbattery::BatteryWatchResponder) {
1198 match self.next_battery_request() {
1199 Some(fbattery::BatteryRequest::Watch { lease, responder }) => (lease, responder),
1200 other => panic!("expected Battery.Watch, got {other:?}"),
1201 }
1202 }
1203
1204 fn expect_battery_configure_watch(
1205 &mut self,
1206 ) -> (fbattery::WatchOptions, fbattery::BatteryConfigureWatchResponder) {
1207 match self.next_battery_request() {
1208 Some(fbattery::BatteryRequest::ConfigureWatch { options, responder }) => {
1209 (options, responder)
1210 }
1211 other => panic!("expected Battery.ConfigureWatch, got {other:?}"),
1212 }
1213 }
1214
1215 fn accept_charger_configure_watch(&mut self) -> fcharger::WatchOptions {
1217 match self.next_charger_request() {
1218 Some(fcharger::ChargerRequest::ConfigureWatch { options, responder }) => {
1219 responder.send(Ok(&options)).unwrap();
1220 options
1221 }
1222 other => panic!("expected Charger.ConfigureWatch, got {other:?}"),
1223 }
1224 }
1225
1226 fn expect_battery_get_status(&mut self) -> fbattery::BatteryGetStatusResponder {
1227 match self.next_battery_request() {
1228 Some(fbattery::BatteryRequest::GetStatus { responder }) => responder,
1229 other => panic!("expected Battery.GetStatus, got {other:?}"),
1230 }
1231 }
1232
1233 fn expect_charger_get_status(&mut self) -> fcharger::ChargerGetStatusResponder {
1234 match self.next_charger_request() {
1235 Some(fcharger::ChargerRequest::GetStatus { responder }) => responder,
1236 other => panic!("expected Charger.GetStatus, got {other:?}"),
1237 }
1238 }
1239
1240 fn expect_charger_watch(&mut self) -> fcharger::ChargerWatchResponder {
1242 let (lease, responder) = self.expect_charger_watch_with_lease();
1243 assert!(lease.is_none());
1244 responder
1245 }
1246
1247 fn expect_charger_watch_with_lease(
1248 &mut self,
1249 ) -> (Option<zx::EventPair>, fcharger::ChargerWatchResponder) {
1250 match self.next_charger_request() {
1251 Some(fcharger::ChargerRequest::Watch { lease, responder }) => (lease, responder),
1252 other => panic!("expected Charger.Watch, got {other:?}"),
1253 }
1254 }
1255
1256 fn accept_sample_interval(&mut self, interval: Option<zx::BootDuration>) {
1258 let max_interval = interval.map(zx::BootDuration::into_nanos);
1259 let (options, responder) = self.expect_battery_configure_watch();
1260 assert_eq!(options, fbattery::WatchOptions { max_interval, ..Default::default() });
1261 responder.send(Ok(&options)).unwrap();
1262 }
1263
1264 fn accept_set_control(&mut self) -> fcharger::ControlOptions {
1266 let (options, responder) = self.expect_set_control();
1267 responder.send(Ok(())).unwrap();
1268 options
1269 }
1270
1271 fn expect_set_control(
1272 &mut self,
1273 ) -> (fcharger::ControlOptions, fcharger::ControllerSetControlResponder) {
1274 match self.next_controller_request() {
1275 Some(fcharger::ControllerRequest::SetControl { options, responder }) => {
1276 (options, responder)
1277 }
1278 other => panic!("expected Controller.SetControl, got {other:?}"),
1279 }
1280 }
1281
1282 fn expect_clear_control(
1283 &mut self,
1284 ) -> (fcharger::ControlOptions, fcharger::ControllerClearControlResponder) {
1285 match self.next_controller_request() {
1286 Some(fcharger::ControllerRequest::ClearControl { options, responder }) => {
1287 (options, responder)
1288 }
1289 other => panic!("expected Controller.ClearControl, got {other:?}"),
1290 }
1291 }
1292
1293 fn assert_no_battery_request(&mut self) {
1294 if let Some(req) = self.next_battery_request() {
1295 panic!("unexpected battery request: {req:?}");
1296 }
1297 }
1298
1299 fn assert_no_charger_request(&mut self) {
1300 if let Some(req) = self.next_charger_request() {
1301 panic!("unexpected charger request: {req:?}");
1302 }
1303 }
1304
1305 fn assert_no_controller_request(&mut self) {
1306 if let Some(req) = self.next_controller_request() {
1307 panic!("unexpected controller request: {req:?}");
1308 }
1309 }
1310
1311 fn evaluation_count(&self) -> usize {
1312 self.seen.lock().unwrap().len()
1313 }
1314
1315 fn last_telemetry(&self) -> Telemetry {
1316 self.seen.lock().unwrap().last().cloned().expect("governor never evaluated")
1317 }
1318
1319 fn answer_no_charger(&mut self) {
1321 self.expect_charger_get_status().send(Err(fcharger::Error::Io)).unwrap();
1322 }
1323
1324 fn complete_initial_battery_update(
1330 &mut self,
1331 voltage_uv: u32,
1332 ) -> fbattery::BatteryWatchResponder {
1333 let watch = self.expect_battery_watch();
1334 watch.send(Ok((&battery_status(voltage_uv), None))).unwrap();
1335 self.answer_no_charger();
1336 let _ = self.accept_set_control();
1337 self.expect_battery_watch()
1338 }
1339 }
1340
1341 #[fuchsia::test]
1344 fn test_loop_battery_watch_drives_votes() {
1345 let mut h = LoopHarness::new(vec![
1346 Evaluation { vote: full_vote(), sample_interval: None, ..Default::default() },
1347 Evaluation { vote: full_vote(), sample_interval: None, ..Default::default() },
1349 Evaluation {
1351 vote: fcharger::ControlOptions {
1352 charge_current_limit_ua: Some(TEST_FCC_LOW_UA),
1353 float_voltage_uv: None,
1354 ..full_vote()
1355 },
1356 sample_interval: None,
1357 ..Default::default()
1358 },
1359 ]);
1360
1361 let _pending_charger_watch = h.expect_charger_watch();
1362 let watch = h.expect_battery_watch();
1363 watch.send(Ok((&battery_status(TEST_VBAT_UV), None))).unwrap();
1364 h.answer_no_charger();
1365 assert_eq!(h.accept_set_control(), full_vote());
1366 assert_eq!(
1367 h.last_telemetry(),
1368 Telemetry { battery: battery_status(TEST_VBAT_UV), charger: None }
1369 );
1370
1371 let watch = h.expect_battery_watch();
1372 watch.send(Ok((&battery_status(TEST_VBAT_UPDATED_UV), None))).unwrap();
1373 h.assert_no_controller_request();
1374 assert_eq!(h.evaluation_count(), 2);
1375 assert_eq!(h.last_telemetry().battery, battery_status(TEST_VBAT_UPDATED_UV));
1376
1377 let watch = h.expect_battery_watch();
1378 watch.send(Ok((&battery_status(TEST_VBAT_UV), None))).unwrap();
1379 assert_eq!(h.accept_set_control(), fcc_vote(TEST_FCC_LOW_UA));
1380 let (clear, responder) = h.expect_clear_control();
1381 assert_eq!(
1382 clear,
1383 fcharger::ControlOptions { float_voltage_uv: Some(TEST_FV_UV), ..Default::default() }
1384 );
1385 responder.send(Ok(())).unwrap();
1386
1387 let _pending_watch = h.expect_battery_watch();
1388 assert_eq!(h.evaluation_count(), 3);
1389 }
1390
1391 fn is_released(lease_peer: &zx::EventPair) -> bool {
1393 matches!(
1394 lease_peer
1395 .wait_one(zx::Signals::EVENTPAIR_PEER_CLOSED, zx::MonotonicInstant::INFINITE_PAST),
1396 zx::WaitResult::Ok(_)
1397 )
1398 }
1399
1400 #[fuchsia::test]
1403 fn test_loop_passes_response_lease_to_next_watch() {
1404 let mut h = LoopHarness::new(vec![Evaluation {
1405 vote: fcc_vote(TEST_FCC_HIGH_UA),
1406 ..Default::default()
1407 }]);
1408 let _pending_charger_watch = h.expect_charger_watch();
1409
1410 let (lease, lease_peer) = zx::EventPair::create();
1411 let lease_koid = lease.koid().unwrap();
1412 let watch = h.expect_battery_watch();
1413 watch.send(Ok((&battery_status(TEST_VBAT_UV), Some(lease)))).unwrap();
1414 h.answer_no_charger();
1415
1416 let (_, set_responder) = h.expect_set_control();
1417 assert!(!is_released(&lease_peer));
1418 set_responder.send(Ok(())).unwrap();
1419
1420 let (setup_lease, _pending_watch) = h.expect_battery_watch_with_lease();
1421 let setup_lease = setup_lease.expect("the response's lease is passed to the next Watch");
1422 assert_eq!(setup_lease.koid().unwrap(), lease_koid);
1423 drop(setup_lease);
1424 assert!(is_released(&lease_peer));
1425 }
1426
1427 #[fuchsia::test]
1430 fn test_loop_votes_wake_for_samples() {
1431 let mut h = LoopHarness::new(vec![
1432 Evaluation {
1433 vote: fcc_vote(TEST_FCC_HIGH_UA),
1434 sample_interval: Some(TEST_SAMPLE_INTERVAL),
1435 wake_for_samples: true,
1436 },
1437 Evaluation {
1438 vote: fcc_vote(TEST_FCC_HIGH_UA),
1439 sample_interval: Some(TEST_SAMPLE_INTERVAL),
1440 wake_for_samples: false,
1441 },
1442 ]);
1443 let _pending_charger_watch = h.expect_charger_watch();
1444
1445 let watch = h.expect_battery_watch();
1446 watch.send(Ok((&battery_status(TEST_VBAT_UV), None))).unwrap();
1447 h.answer_no_charger();
1448 let _ = h.accept_set_control();
1449 let (options, responder) = h.expect_battery_configure_watch();
1450 assert_eq!(
1451 options,
1452 fbattery::WatchOptions {
1453 max_interval: Some(TEST_SAMPLE_INTERVAL.into_nanos()),
1454 wake_on_max_interval: Some(true),
1455 ..Default::default()
1456 }
1457 );
1458 responder.send(Ok(&options)).unwrap();
1459
1460 let watch = h.expect_battery_watch();
1461 watch.send(Ok((&battery_status(TEST_VBAT_UPDATED_UV), None))).unwrap();
1462 h.accept_sample_interval(Some(TEST_SAMPLE_INTERVAL));
1463 let _pending_watch = h.expect_battery_watch();
1464 }
1465
1466 #[fuchsia::test]
1469 fn test_loop_configures_charger_wake_on() {
1470 let wake_on = fcharger::Status { online: Some(true), ..Default::default() };
1471 let mut h = LoopHarness::with_interest(
1472 vec![Evaluation { vote: fcc_vote(TEST_FCC_HIGH_UA), ..Default::default() }],
1473 WatchInterest { charger_wake_on: Some(wake_on.clone()), ..Default::default() },
1474 );
1475 assert_eq!(
1476 h.accept_charger_configure_watch(),
1477 fcharger::WatchOptions { wake_on: Some(wake_on), ..Default::default() }
1478 );
1479 let _pending_charger_watch = h.expect_charger_watch();
1480 }
1481
1482 #[fuchsia::test]
1486 fn test_loop_rearms_watch_only_after_actuation() {
1487 let mut h = LoopHarness::new(vec![Evaluation {
1488 vote: fcc_vote(TEST_FCC_HIGH_UA),
1489 sample_interval: Some(TEST_SAMPLE_INTERVAL),
1490 ..Default::default()
1491 }]);
1492 let _pending_charger_watch = h.expect_charger_watch();
1493
1494 let watch = h.expect_battery_watch();
1495 watch.send(Ok((&battery_status(TEST_VBAT_UV), None))).unwrap();
1496 h.answer_no_charger();
1497
1498 let (_, set_responder) = h.expect_set_control();
1500 h.assert_no_battery_request();
1501 set_responder.send(Ok(())).unwrap();
1502 let (options, battery_responder) = h.expect_battery_configure_watch();
1503 h.assert_no_battery_request();
1504 battery_responder.send(Ok(&options)).unwrap();
1505
1506 let _pending_watch = h.expect_battery_watch();
1507 }
1508
1509 #[fuchsia::test]
1512 fn test_loop_configures_sample_interval_on_change() {
1513 let mut h = LoopHarness::new(vec![
1514 Evaluation {
1515 vote: fcc_vote(TEST_FCC_HIGH_UA),
1516 sample_interval: Some(TEST_SAMPLE_INTERVAL),
1517 ..Default::default()
1518 },
1519 Evaluation {
1521 vote: fcc_vote(TEST_FCC_HIGH_UA),
1522 sample_interval: Some(TEST_SAMPLE_INTERVAL),
1523 ..Default::default()
1524 },
1525 Evaluation {
1526 vote: fcc_vote(TEST_FCC_HIGH_UA),
1527 sample_interval: Some(TEST_LONGER_SAMPLE_INTERVAL),
1528 ..Default::default()
1529 },
1530 Evaluation {
1532 vote: fcc_vote(TEST_FCC_HIGH_UA),
1533 sample_interval: None,
1534 ..Default::default()
1535 },
1536 Evaluation {
1537 vote: fcc_vote(TEST_FCC_HIGH_UA),
1538 sample_interval: None,
1539 ..Default::default()
1540 },
1541 ]);
1542 let _pending_charger_watch = h.expect_charger_watch();
1543
1544 let watch = h.expect_battery_watch();
1545 watch.send(Ok((&battery_status(TEST_VBAT_UV), None))).unwrap();
1546 h.answer_no_charger();
1547 let _ = h.accept_set_control();
1548 h.accept_sample_interval(Some(TEST_SAMPLE_INTERVAL));
1549
1550 let watch = h.expect_battery_watch();
1552 h.advance(TEST_LONG_IDLE);
1553 h.assert_no_battery_request();
1554 assert_eq!(h.evaluation_count(), 1);
1555
1556 watch.send(Ok((&battery_status(TEST_VBAT_UPDATED_UV), None))).unwrap();
1557 let watch = h.expect_battery_watch();
1558 assert_eq!(h.evaluation_count(), 2);
1559
1560 watch.send(Ok((&battery_status(TEST_VBAT_UV), None))).unwrap();
1561 h.accept_sample_interval(Some(TEST_LONGER_SAMPLE_INTERVAL));
1562
1563 let watch = h.expect_battery_watch();
1564 watch.send(Ok((&battery_status(TEST_VBAT_UPDATED_UV), None))).unwrap();
1565 h.accept_sample_interval(None);
1566
1567 let watch = h.expect_battery_watch();
1568 watch.send(Ok((&battery_status(TEST_VBAT_UV), None))).unwrap();
1569 let _pending_watch = h.expect_battery_watch();
1570 assert_eq!(h.evaluation_count(), 5);
1571 h.assert_no_controller_request();
1572 h.assert_no_charger_request();
1573 }
1574
1575 #[fuchsia::test]
1578 fn test_loop_configures_watch_interest() {
1579 let battery_interest = fbattery::Status { present: Some(true), ..Default::default() };
1580 let charger_interest = fcharger::Status { online: Some(true), ..Default::default() };
1581 let mut h = LoopHarness::with_interest(
1582 vec![Evaluation {
1583 vote: fcc_vote(TEST_FCC_HIGH_UA),
1584 sample_interval: Some(TEST_SAMPLE_INTERVAL),
1585 ..Default::default()
1586 }],
1587 WatchInterest {
1588 battery: Some(battery_interest.clone()),
1589 charger: Some(charger_interest.clone()),
1590 ..Default::default()
1591 },
1592 );
1593 let interest_only = fbattery::WatchOptions {
1594 interest: Some(battery_interest.clone()),
1595 ..Default::default()
1596 };
1597 let with_interval = fbattery::WatchOptions {
1598 max_interval: Some(TEST_SAMPLE_INTERVAL.into_nanos()),
1599 ..interest_only.clone()
1600 };
1601 let charger_options =
1602 fcharger::WatchOptions { interest: Some(charger_interest), ..Default::default() };
1603
1604 for _ in 0..2 {
1605 let (options, responder) = h.expect_battery_configure_watch();
1606 assert_eq!(options, interest_only);
1607 responder.send(Ok(&options)).unwrap();
1608 assert_eq!(h.accept_charger_configure_watch(), charger_options);
1609
1610 let charger_watch = h.expect_charger_watch();
1611 let watch = h.expect_battery_watch();
1612 watch.send(Ok((&battery_status(TEST_VBAT_UV), None))).unwrap();
1613 h.answer_no_charger();
1614 let _ = h.accept_set_control();
1615 let (options, responder) = h.expect_battery_configure_watch();
1616 assert_eq!(options, with_interval);
1617 responder.send(Ok(&options)).unwrap();
1618 let _pending_watch = h.expect_battery_watch();
1619
1620 drop(charger_watch);
1621 h.charger = None;
1622 h.expect_reconnect();
1623 }
1624 }
1625
1626 #[fuchsia::test]
1628 fn test_loop_watch_interest_rejection_is_not_fatal() {
1629 let mut h = LoopHarness::with_interest(
1630 vec![Evaluation {
1631 vote: fcc_vote(TEST_FCC_HIGH_UA),
1632 sample_interval: None,
1633 ..Default::default()
1634 }],
1635 WatchInterest {
1636 charger: Some(fcharger::Status { online: Some(true), ..Default::default() }),
1637 ..Default::default()
1638 },
1639 );
1640 match h.next_charger_request() {
1641 Some(fcharger::ChargerRequest::ConfigureWatch { responder, .. }) => {
1642 responder.send(Err(fcharger::Error::NotSupported)).unwrap()
1643 }
1644 other => panic!("expected Charger.ConfigureWatch, got {other:?}"),
1645 }
1646
1647 let _pending_charger_watch = h.expect_charger_watch();
1648 let _pending_battery_watch = h.complete_initial_battery_update(TEST_VBAT_UV);
1649 assert_eq!(h.evaluation_count(), 1);
1650 }
1651
1652 #[fuchsia::test]
1655 fn test_loop_sample_interval_rejection_is_not_fatal_or_retried() {
1656 let mut h = LoopHarness::new(vec![Evaluation {
1657 vote: fcc_vote(TEST_FCC_HIGH_UA),
1658 sample_interval: Some(TEST_SAMPLE_INTERVAL),
1659 ..Default::default()
1660 }]);
1661 let _pending_charger_watch = h.expect_charger_watch();
1662
1663 let watch = h.expect_battery_watch();
1664 watch.send(Ok((&battery_status(TEST_VBAT_UV), None))).unwrap();
1665 h.answer_no_charger();
1666 let _ = h.accept_set_control();
1667 let (_, responder) = h.expect_battery_configure_watch();
1668 responder.send(Err(fbattery::Error::NotSupported)).unwrap();
1669
1670 let watch = h.expect_battery_watch();
1671 watch.send(Ok((&battery_status(TEST_VBAT_UPDATED_UV), None))).unwrap();
1672 let _pending_watch = h.expect_battery_watch();
1673 assert_eq!(h.evaluation_count(), 2);
1674 h.assert_no_charger_request();
1675 }
1676
1677 #[fuchsia::test]
1680 fn test_loop_charger_watch_refreshes_battery_status() {
1681 let mut h = LoopHarness::new(vec![Evaluation {
1682 vote: fcc_vote(TEST_FCC_HIGH_UA),
1683 sample_interval: None,
1684 ..Default::default()
1685 }]);
1686 let charger_watch = h.expect_charger_watch();
1687 let _pending_battery_watch = h.complete_initial_battery_update(TEST_VBAT_UV);
1688 assert_eq!(h.last_telemetry().charger, None);
1689 let online_charger = fcharger::Status { online: Some(true), ..Default::default() };
1690
1691 charger_watch.send(Ok((&online_charger, None))).unwrap();
1692 let get_status = h.expect_battery_get_status();
1694 h.assert_no_charger_request();
1695 get_status.send(Ok(&battery_status(TEST_VBAT_UPDATED_UV))).unwrap();
1696
1697 let _pending_charger_watch = h.expect_charger_watch();
1698 h.assert_no_battery_request();
1699 h.assert_no_controller_request();
1700 assert_eq!(h.evaluation_count(), 2);
1701 assert_eq!(
1702 h.last_telemetry(),
1703 Telemetry {
1704 battery: battery_status(TEST_VBAT_UPDATED_UV),
1705 charger: Some(online_charger)
1706 }
1707 );
1708 }
1709
1710 fn start_with_online_charger_interest(
1714 charger_status: &fcharger::Status,
1715 ) -> (LoopHarness, fbattery::BatteryWatchResponder, fcharger::ChargerWatchResponder) {
1716 let mut h = LoopHarness::with_interest(
1717 vec![Evaluation {
1718 vote: fcc_vote(TEST_FCC_HIGH_UA),
1719 sample_interval: None,
1720 ..Default::default()
1721 }],
1722 WatchInterest {
1723 charger: Some(fcharger::Status { online: Some(true), ..Default::default() }),
1724 ..Default::default()
1725 },
1726 );
1727 let _ = h.accept_charger_configure_watch();
1728 let charger_watch = h.expect_charger_watch();
1729 let battery_watch = h.complete_initial_battery_update(TEST_VBAT_UV);
1730 charger_watch.send(Ok((charger_status, None))).unwrap();
1731 h.expect_battery_get_status().send(Ok(&battery_status(TEST_VBAT_UV))).unwrap();
1732 let charger_watch = h.expect_charger_watch();
1733 (h, battery_watch, charger_watch)
1734 }
1735
1736 #[fuchsia::test]
1740 fn test_loop_battery_watch_refreshes_narrowed_charger_status() {
1741 let fast = fcharger::Status {
1742 online: Some(true),
1743 charge_phase: Some(fcharger::ChargePhase::Fast),
1744 ..Default::default()
1745 };
1746 let (mut h, battery_watch, _pending_charger_watch) =
1747 start_with_online_charger_interest(&fast);
1748
1749 battery_watch.send(Ok((&battery_status(TEST_VBAT_UPDATED_UV), None))).unwrap();
1750 let get_status = h.expect_charger_get_status();
1752 h.assert_no_battery_request();
1753 let taper = fcharger::Status { charge_phase: Some(fcharger::ChargePhase::Taper), ..fast };
1754 get_status.send(Ok(&taper)).unwrap();
1755
1756 let _pending_battery_watch = h.expect_battery_watch();
1757 assert_eq!(h.evaluation_count(), 3);
1758 assert_eq!(
1759 h.last_telemetry(),
1760 Telemetry { battery: battery_status(TEST_VBAT_UPDATED_UV), charger: Some(taper) }
1761 );
1762 }
1763
1764 #[fuchsia::test]
1767 fn test_loop_charger_refresh_error_uses_last_charger_status() {
1768 let online = fcharger::Status { online: Some(true), ..Default::default() };
1769 let (mut h, battery_watch, _pending_charger_watch) =
1770 start_with_online_charger_interest(&online);
1771
1772 battery_watch.send(Ok((&battery_status(TEST_VBAT_UPDATED_UV), None))).unwrap();
1773 h.expect_charger_get_status().send(Err(fcharger::Error::Io)).unwrap();
1774 let _pending_battery_watch = h.expect_battery_watch();
1775 assert_eq!(h.evaluation_count(), 3);
1776 assert_eq!(
1777 h.last_telemetry(),
1778 Telemetry { battery: battery_status(TEST_VBAT_UPDATED_UV), charger: Some(online) }
1779 );
1780 }
1781
1782 #[fuchsia::test]
1785 fn test_loop_battery_refresh_error_uses_last_battery_status() {
1786 let mut h = LoopHarness::new(vec![Evaluation {
1787 vote: fcc_vote(TEST_FCC_HIGH_UA),
1788 sample_interval: None,
1789 ..Default::default()
1790 }]);
1791 let charger_watch = h.expect_charger_watch();
1792 let _pending_battery_watch = h.complete_initial_battery_update(TEST_VBAT_UV);
1793 let online_charger = fcharger::Status { online: Some(true), ..Default::default() };
1794
1795 charger_watch.send(Ok((&online_charger, None))).unwrap();
1796 h.expect_battery_get_status().send(Err(fbattery::Error::Io)).unwrap();
1798 let _pending_charger_watch = h.expect_charger_watch();
1799 assert_eq!(h.evaluation_count(), 2);
1800 assert_eq!(
1801 h.last_telemetry(),
1802 Telemetry { battery: battery_status(TEST_VBAT_UV), charger: Some(online_charger) }
1803 );
1804 }
1805
1806 #[fuchsia::test]
1808 fn test_loop_battery_refresh_transport_error_reconnects() {
1809 let mut h = LoopHarness::new(vec![Evaluation {
1810 vote: fcc_vote(TEST_FCC_HIGH_UA),
1811 sample_interval: None,
1812 ..Default::default()
1813 }]);
1814 let charger_watch = h.expect_charger_watch();
1815 let battery_watch = h.complete_initial_battery_update(TEST_VBAT_UV);
1816
1817 charger_watch.send(Ok((&fcharger::Status::default(), None))).unwrap();
1818 let get_status = h.expect_battery_get_status();
1819 drop((battery_watch, get_status));
1820 h.battery = None;
1821 h.expect_reconnect();
1822 assert_eq!(h.evaluation_count(), 1);
1823 }
1824
1825 #[fuchsia::test]
1828 fn test_loop_charger_before_battery_waits_for_battery() {
1829 let mut h = LoopHarness::new(vec![Evaluation {
1830 vote: fcc_vote(TEST_FCC_HIGH_UA),
1831 sample_interval: None,
1832 ..Default::default()
1833 }]);
1834 let battery_watch = h.expect_battery_watch();
1835 let charger_watch = h.expect_charger_watch();
1836 let online_charger = fcharger::Status { online: Some(true), ..Default::default() };
1837
1838 charger_watch.send(Ok((&online_charger, None))).unwrap();
1840 h.expect_battery_get_status().send(Err(fbattery::Error::Io)).unwrap();
1841 let _pending_charger_watch = h.expect_charger_watch();
1842 h.assert_no_controller_request();
1843 assert_eq!(h.evaluation_count(), 0);
1844
1845 battery_watch.send(Ok((&battery_status(TEST_VBAT_UV), None))).unwrap();
1846 h.expect_charger_get_status().send(Ok(&online_charger)).unwrap();
1847 assert_eq!(h.accept_set_control(), fcc_vote(TEST_FCC_HIGH_UA));
1848 assert_eq!(
1849 h.last_telemetry(),
1850 Telemetry { battery: battery_status(TEST_VBAT_UV), charger: Some(online_charger) }
1851 );
1852 }
1853
1854 #[fuchsia::test]
1857 fn test_loop_pending_charger_watch_does_not_block_battery() {
1858 let mut h = LoopHarness::new(vec![
1859 Evaluation {
1860 vote: fcc_vote(TEST_FCC_HIGH_UA),
1861 sample_interval: None,
1862 ..Default::default()
1863 },
1864 Evaluation {
1865 vote: fcc_vote(TEST_FCC_LOW_UA),
1866 sample_interval: None,
1867 ..Default::default()
1868 },
1869 ]);
1870 let _pending_charger_watch = h.expect_charger_watch();
1872 let battery_watch = h.complete_initial_battery_update(TEST_VBAT_UV);
1873
1874 battery_watch.send(Ok((&battery_status(TEST_VBAT_UPDATED_UV), None))).unwrap();
1875 assert_eq!(h.accept_set_control(), fcc_vote(TEST_FCC_LOW_UA));
1876 let _pending_battery_watch = h.expect_battery_watch();
1877 assert_eq!(
1878 h.last_telemetry(),
1879 Telemetry { battery: battery_status(TEST_VBAT_UPDATED_UV), charger: None }
1880 );
1881 h.assert_no_charger_request();
1882 }
1883
1884 #[fuchsia::test]
1887 fn test_loop_battery_watch_error_reconnects() {
1888 let mut h = LoopHarness::new(vec![Evaluation {
1889 vote: fcc_vote(TEST_FCC_HIGH_UA),
1890 sample_interval: None,
1891 ..Default::default()
1892 }]);
1893 let watch = h.expect_battery_watch();
1894 watch.send(Err(fbattery::Error::AlreadyWatching)).unwrap();
1895 h.expect_reconnect();
1896 assert_eq!(h.evaluation_count(), 0);
1897 }
1898
1899 #[fuchsia::test]
1902 fn test_loop_charger_watch_error_reconnects() {
1903 let mut h = LoopHarness::new(vec![Evaluation {
1904 vote: fcc_vote(TEST_FCC_HIGH_UA),
1905 sample_interval: None,
1906 ..Default::default()
1907 }]);
1908 h.expect_charger_watch().send(Err(fcharger::Error::AlreadyWatching)).unwrap();
1909 h.expect_reconnect();
1910 }
1911
1912 #[fuchsia::test]
1915 fn test_loop_battery_channel_closed_reconnects() {
1916 let mut h = LoopHarness::new(vec![Evaluation {
1917 vote: fcc_vote(TEST_FCC_HIGH_UA),
1918 sample_interval: None,
1919 ..Default::default()
1920 }]);
1921 let watch = h.expect_battery_watch();
1922 drop(watch);
1923 h.battery = None;
1924 h.expect_reconnect();
1925 }
1926
1927 #[fuchsia::test]
1930 fn test_loop_charger_channel_closed_reconnects_and_votes_again() {
1931 let mut h = LoopHarness::new(vec![Evaluation {
1932 vote: full_vote(),
1933 sample_interval: Some(TEST_SAMPLE_INTERVAL),
1934 ..Default::default()
1935 }]);
1936 let charger_watch = h.expect_charger_watch();
1937 let watch = h.expect_battery_watch();
1938 watch.send(Ok((&battery_status(TEST_VBAT_UV), None))).unwrap();
1939 h.answer_no_charger();
1940 assert_eq!(h.accept_set_control(), full_vote());
1941 h.accept_sample_interval(Some(TEST_SAMPLE_INTERVAL));
1942 let _pending_watch = h.expect_battery_watch();
1943
1944 drop(charger_watch);
1945 h.charger = None;
1946 h.expect_reconnect();
1947
1948 let _pending_charger_watch = h.expect_charger_watch();
1950 let watch = h.expect_battery_watch();
1951 watch.send(Ok((&battery_status(TEST_VBAT_UPDATED_UV), None))).unwrap();
1952 h.answer_no_charger();
1953 assert_eq!(h.accept_set_control(), full_vote());
1954 h.accept_sample_interval(Some(TEST_SAMPLE_INTERVAL));
1955 let _pending_watch = h.expect_battery_watch();
1956 assert_eq!(h.evaluation_count(), 2);
1957 }
1958
1959 #[fuchsia::test]
1962 fn test_loop_controller_rejection_keeps_session_and_retries() {
1963 let mut h = LoopHarness::new(vec![Evaluation {
1964 vote: fcc_vote(TEST_FCC_HIGH_UA),
1965 sample_interval: None,
1966 ..Default::default()
1967 }]);
1968 let _pending_charger_watch = h.expect_charger_watch();
1969 let watch = h.expect_battery_watch();
1970 watch.send(Ok((&battery_status(TEST_VBAT_UV), None))).unwrap();
1971 h.answer_no_charger();
1972 let (_, responder) = h.expect_set_control();
1973 responder.send(Err(fcharger::Error::InvalidArgs)).unwrap();
1974
1975 let watch = h.expect_battery_watch();
1977 watch.send(Ok((&battery_status(TEST_VBAT_UPDATED_UV), None))).unwrap();
1978 assert_eq!(h.accept_set_control(), fcc_vote(TEST_FCC_HIGH_UA));
1979 let _pending_watch = h.expect_battery_watch();
1980 h.assert_no_controller_request();
1981 assert_eq!(h.evaluation_count(), 2);
1982 assert!(h.connected.borrow().is_empty(), "governor reconnected");
1983 }
1984
1985 #[fuchsia::test]
1988 fn test_loop_battery_and_charger_ready_together_evaluate_once() {
1989 let mut h = LoopHarness::new(vec![Evaluation {
1990 vote: fcc_vote(TEST_FCC_HIGH_UA),
1991 sample_interval: None,
1992 ..Default::default()
1993 }]);
1994 let charger_watch = h.expect_charger_watch();
1995 let battery_watch = h.complete_initial_battery_update(TEST_VBAT_UV);
1996 let online_charger = fcharger::Status { online: Some(true), ..Default::default() };
1997
1998 battery_watch.send(Ok((&battery_status(TEST_VBAT_UPDATED_UV), None))).unwrap();
2001 charger_watch.send(Ok((&online_charger, None))).unwrap();
2002
2003 let _pending_battery_watch = h.expect_battery_watch();
2005 let _pending_charger_watch = h.expect_charger_watch();
2006 h.assert_no_battery_request();
2007 h.assert_no_controller_request();
2008 assert_eq!(h.evaluation_count(), 2);
2009 assert_eq!(
2010 h.last_telemetry(),
2011 Telemetry {
2012 battery: battery_status(TEST_VBAT_UPDATED_UV),
2013 charger: Some(online_charger)
2014 }
2015 );
2016 }
2017
2018 #[fuchsia::test]
2023 fn test_loop_battery_update_reads_current_charger_status() {
2024 let mut h = LoopHarness::new(vec![Evaluation {
2025 vote: fcc_vote(TEST_FCC_HIGH_UA),
2026 sample_interval: None,
2027 ..Default::default()
2028 }]);
2029 let charger_watch = h.expect_charger_watch();
2030 let battery_watch = h.complete_initial_battery_update(TEST_VBAT_UV);
2032 battery_watch.send(Ok((&battery_status(TEST_VBAT_UV), None))).unwrap();
2033 let battery_watch = h.expect_battery_watch();
2034 h.assert_no_charger_request();
2035
2036 let offline = fcharger::Status { online: Some(false), ..Default::default() };
2038 charger_watch.send(Ok((&offline, None))).unwrap();
2039 h.expect_battery_get_status().send(Ok(&battery_status(TEST_VBAT_UV))).unwrap();
2040 let charger_watch = h.expect_charger_watch();
2041 assert_eq!(h.evaluation_count(), 3);
2042
2043 let online = fcharger::Status { online: Some(true), ..Default::default() };
2045 battery_watch.send(Ok((&battery_status(TEST_VBAT_UPDATED_UV), None))).unwrap();
2046 h.expect_charger_get_status().send(Ok(&online)).unwrap();
2047 let _pending_battery_watch = h.expect_battery_watch();
2048 assert_eq!(h.evaluation_count(), 4);
2049 assert_eq!(
2050 h.last_telemetry(),
2051 Telemetry {
2052 battery: battery_status(TEST_VBAT_UPDATED_UV),
2053 charger: Some(online.clone())
2054 }
2055 );
2056
2057 charger_watch.send(Ok((&online, None))).unwrap();
2059 h.expect_battery_get_status().send(Ok(&battery_status(TEST_VBAT_UPDATED_UV))).unwrap();
2060 let _pending_charger_watch = h.expect_charger_watch();
2061 h.assert_no_controller_request();
2062 assert_eq!(h.evaluation_count(), 5);
2063 assert_eq!(
2064 h.last_telemetry(),
2065 Telemetry { battery: battery_status(TEST_VBAT_UPDATED_UV), charger: Some(online) }
2066 );
2067 }
2068
2069 #[fuchsia::test]
2072 fn test_loop_controller_closed_reconnects() {
2073 let mut h = LoopHarness::new(vec![Evaluation {
2074 vote: fcc_vote(TEST_FCC_HIGH_UA),
2075 sample_interval: None,
2076 ..Default::default()
2077 }]);
2078 let _pending_charger_watch = h.expect_charger_watch();
2079 let watch = h.expect_battery_watch();
2080 watch.send(Ok((&battery_status(TEST_VBAT_UV), None))).unwrap();
2081 h.answer_no_charger();
2082 let (_, responder) = h.expect_set_control();
2083 drop(responder);
2084 h.controller = None;
2085 h.expect_reconnect();
2086 }
2087
2088 #[fuchsia::test]
2091 fn test_loop_sample_interval_clamped_by_server() {
2092 let mut h = LoopHarness::new(vec![Evaluation {
2093 vote: fcc_vote(TEST_FCC_HIGH_UA),
2094 sample_interval: Some(TEST_SAMPLE_INTERVAL),
2095 ..Default::default()
2096 }]);
2097 let _pending_charger_watch = h.expect_charger_watch();
2098
2099 let watch = h.expect_battery_watch();
2100 watch.send(Ok((&battery_status(TEST_VBAT_UV), None))).unwrap();
2101 h.answer_no_charger();
2102 let _ = h.accept_set_control();
2103 let (_, responder) = h.expect_battery_configure_watch();
2104 responder
2105 .send(Ok(&fbattery::WatchOptions {
2106 max_interval: Some(TEST_LONGER_SAMPLE_INTERVAL.into_nanos()),
2107 ..Default::default()
2108 }))
2109 .unwrap();
2110
2111 let watch = h.expect_battery_watch();
2112 watch.send(Ok((&battery_status(TEST_VBAT_UPDATED_UV), None))).unwrap();
2113 let _pending_watch = h.expect_battery_watch();
2114 h.assert_no_battery_request();
2115 assert_eq!(h.evaluation_count(), 2);
2116 }
2117
2118 #[fuchsia::test]
2120 fn test_loop_sample_interval_transport_error_reconnects() {
2121 let mut h = LoopHarness::new(vec![Evaluation {
2122 vote: fcc_vote(TEST_FCC_HIGH_UA),
2123 sample_interval: Some(TEST_SAMPLE_INTERVAL),
2124 ..Default::default()
2125 }]);
2126 let _pending_charger_watch = h.expect_charger_watch();
2127
2128 let watch = h.expect_battery_watch();
2129 watch.send(Ok((&battery_status(TEST_VBAT_UV), None))).unwrap();
2130 h.answer_no_charger();
2131 let _ = h.accept_set_control();
2132 let (_, configure_responder) = h.expect_battery_configure_watch();
2133 drop(configure_responder);
2134 h.battery = None;
2135 h.expect_reconnect();
2136 }
2137
2138 #[fuchsia::test]
2141 fn test_supervise_retries_failed_connect_after_delay() {
2142 let mut executor = fasync::TestExecutor::new_with_fake_time();
2143 let mut governor = SequenceGovernor::new(vec![Evaluation::default()]);
2144 let attempts = Rc::new(RefCell::new(0usize));
2145 let attempts_clone = attempts.clone();
2146 let connected: Rc<RefCell<Option<ServerEnds>>> = Rc::default();
2147 let connected_clone = connected.clone();
2148
2149 let mut run = Box::pin(async move {
2150 supervise(&mut governor, || {
2151 let mut count = attempts_clone.borrow_mut();
2152 *count += 1;
2153 if *count == 1 {
2154 return Err(anyhow!("connect failed"));
2155 }
2156 let (session, server) = new_session();
2157 *connected_clone.borrow_mut() = Some(server);
2158 Ok(session)
2159 })
2160 .await
2161 });
2162
2163 assert!(executor.run_until_stalled(&mut run).is_pending());
2164 assert_eq!(*attempts.borrow(), 1);
2165 assert!(connected.borrow().is_none());
2166
2167 executor.set_fake_time(executor.now() + RECONNECT_DELAY - ONE_NANO);
2168 let _ = executor.wake_expired_timers();
2169 assert!(executor.run_until_stalled(&mut run).is_pending());
2170 assert_eq!(*attempts.borrow(), 1);
2171
2172 executor.set_fake_time(executor.now() + ONE_NANO);
2173 let _ = executor.wake_expired_timers();
2174 assert!(executor.run_until_stalled(&mut run).is_pending());
2175 assert_eq!(*attempts.borrow(), 2);
2176 assert!(connected.borrow().is_some());
2177 }
2178}