Skip to main content

power_manager_integration_test_lib/
lib.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5pub mod client_connectors;
6mod mocks;
7
8use crate::mocks::activity_service::MockActivityService;
9use crate::mocks::admin::MockStateControlAdminService;
10use crate::mocks::input_settings_service::MockInputSettingsService;
11use crate::mocks::kernel_service::MockKernelService;
12use fidl::endpoints::{DiscoverableProtocolMarker, ProtocolMarker, ServiceMarker};
13use fidl_fuchsia_driver_test as fdt;
14use fidl_fuchsia_hardware_cpu_ctrl as fcpu_ctrl;
15use fidl_fuchsia_hardware_power_statecontrol as fpower;
16use fidl_fuchsia_io as fio;
17use fidl_fuchsia_kernel as fkernel;
18use fidl_fuchsia_powermanager_driver_temperaturecontrol as ftemperaturecontrol;
19use fidl_fuchsia_sys2 as fsys2;
20use fidl_fuchsia_testing as ftesting;
21use fuchsia_component::client::Service;
22use fuchsia_component_test::{
23    Capability, ChildOptions, RealmBuilder, RealmBuilderParams, RealmInstance, Ref, Route,
24};
25use fuchsia_driver_test::{DriverTestRealmBuilder, DriverTestRealmInstance};
26use log::*;
27use std::sync::Arc;
28use std::sync::atomic::{AtomicU64, Ordering};
29
30const POWER_MANAGER_URL: &str = "#meta/power-manager.cm";
31const CPU_MANAGER_URL: &str = "#meta/cpu-manager.cm";
32const POWER_BROKER_URL: &str = "#meta/power-broker.cm";
33const FAKE_COBALT_URL: &str = "#meta/fake_cobalt.cm";
34const FAKE_CLOCK_URL: &str = "#meta/fake_clock.cm";
35
36/// Increase the time scale so Power Manager's interval-based operation runs faster for testing.
37const FAKE_TIME_SCALE: u32 = 100;
38
39/// Unique number that is incremented for each TestEnv to avoid name clashes.
40static UNIQUE_REALM_NUMBER: AtomicU64 = AtomicU64::new(0);
41
42pub struct TestEnvBuilder {
43    power_manager_node_config_path: Option<String>,
44    cpu_manager_node_config_path: Option<String>,
45    cpu_boost_enabled: bool,
46}
47
48impl TestEnvBuilder {
49    pub fn new() -> Self {
50        Self {
51            power_manager_node_config_path: None,
52            cpu_manager_node_config_path: None,
53            cpu_boost_enabled: false,
54        }
55    }
56
57    /// Sets the node config path that Power Manager will be configured with.
58    pub fn power_manager_node_config_path(mut self, path: &str) -> Self {
59        self.power_manager_node_config_path = Some(path.into());
60        self
61    }
62
63    /// Sets the node config path that CPU Manager will be configured with.
64    pub fn cpu_manager_node_config_path(mut self, path: &str) -> Self {
65        self.cpu_manager_node_config_path = Some(path.into());
66        self
67    }
68
69    /// Sets whether CPU boost is enabled in CPU Manager's structured config.
70    pub fn cpu_boost_enabled(mut self, enabled: bool) -> Self {
71        self.cpu_boost_enabled = enabled;
72        self
73    }
74
75    pub async fn build(self) -> TestEnv {
76        // Generate a unique realm name based on the current process ID and unique realm number for
77        // the current process.
78        let realm_name = format!(
79            "{}-{}",
80            fuchsia_runtime::process_self().koid().unwrap().raw_koid(),
81            UNIQUE_REALM_NUMBER.fetch_add(1, Ordering::Relaxed)
82        );
83
84        let realm_builder =
85            RealmBuilder::with_params(RealmBuilderParams::new().realm_name(realm_name))
86                .await
87                .expect("Failed to create RealmBuilder");
88
89        realm_builder.driver_test_realm_setup().await.expect("Failed to setup driver test realm");
90
91        let expose =
92            fuchsia_component_test::Capability::service::<fcpu_ctrl::ServiceMarker>().into();
93        let dtr_exposes = vec![expose];
94
95        realm_builder.driver_test_realm_add_dtr_exposes(&dtr_exposes).await.unwrap();
96
97        let power_manager = realm_builder
98            .add_child("power_manager", POWER_MANAGER_URL, ChildOptions::new())
99            .await
100            .expect("Failed to add child: power_manager");
101
102        let cpu_manager = realm_builder
103            .add_child("cpu_manager", CPU_MANAGER_URL, ChildOptions::new())
104            .await
105            .expect("Failed to add child: cpu_manager");
106
107        let power_broker = realm_builder
108            .add_child("power_broker", POWER_BROKER_URL, ChildOptions::new())
109            .await
110            .expect("Failed to add child: power_broker");
111
112        let fake_cobalt = realm_builder
113            .add_child("fake_cobalt", FAKE_COBALT_URL, ChildOptions::new())
114            .await
115            .expect("Failed to add child: fake_cobalt");
116
117        let fake_clock = realm_builder
118            .add_child("fake_clock", FAKE_CLOCK_URL, ChildOptions::new())
119            .await
120            .expect("Failed to add child: fake_clock");
121
122        let activity_service = MockActivityService::new();
123        let activity_service_clone = activity_service.clone();
124        let activity_service_child = realm_builder
125            .add_local_child(
126                "activity_service",
127                move |handles| Box::pin(activity_service_clone.clone().run(handles)),
128                ChildOptions::new(),
129            )
130            .await
131            .expect("Failed to add child: activity_service");
132
133        let input_settings_service = MockInputSettingsService::new();
134        let input_settings_service_clone = input_settings_service.clone();
135        let input_settings_service_child = realm_builder
136            .add_local_child(
137                "input_settings_service",
138                move |handles| Box::pin(input_settings_service_clone.clone().run(handles)),
139                ChildOptions::new(),
140            )
141            .await
142            .expect("Failed to add child: input_settings_service");
143
144        let admin_service = MockStateControlAdminService::new();
145        let admin_service_clone = admin_service.clone();
146        let admin_service_child = realm_builder
147            .add_local_child(
148                "admin_service",
149                move |handles| Box::pin(admin_service_clone.clone().run(handles)),
150                ChildOptions::new(),
151            )
152            .await
153            .expect("Failed to add child: admin_service");
154
155        let kernel_service = MockKernelService::new();
156        let kernel_service_clone = kernel_service.clone();
157        let kernel_service_child = realm_builder
158            .add_local_child(
159                "kernel_service",
160                move |handles| Box::pin(kernel_service_clone.clone().run(handles)),
161                ChildOptions::new(),
162            )
163            .await
164            .expect("Failed to add child: kernel_service");
165
166        // Set up Power Manager's required routes
167        let parent_to_power_manager_routes = Route::new()
168            .capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
169            .capability(Capability::protocol_by_name("fuchsia.tracing.provider.Registry"))
170            .capability(Capability::protocol_by_name("fuchsia.boot.WriteOnlyLog"));
171        realm_builder
172            .add_route(parent_to_power_manager_routes.from(Ref::parent()).to(&power_manager))
173            .await
174            .unwrap();
175
176        let parent_to_cobalt_routes =
177            Route::new().capability(Capability::protocol_by_name("fuchsia.logger.LogSink"));
178        realm_builder
179            .add_route(parent_to_cobalt_routes.from(Ref::parent()).to(&fake_cobalt))
180            .await
181            .unwrap();
182
183        let parent_to_fake_clock_routes =
184            Route::new().capability(Capability::protocol_by_name("fuchsia.logger.LogSink"));
185        realm_builder
186            .add_route(parent_to_fake_clock_routes.from(Ref::parent()).to(&fake_clock))
187            .await
188            .unwrap();
189
190        let fake_clock_to_power_manager_routes =
191            Route::new().capability(Capability::protocol_by_name("fuchsia.testing.FakeClock"));
192        realm_builder
193            .add_route(fake_clock_to_power_manager_routes.from(&fake_clock).to(&power_manager))
194            .await
195            .unwrap();
196
197        let fake_clock_to_cpu_manager_routes =
198            Route::new().capability(Capability::protocol_by_name("fuchsia.testing.FakeClock"));
199        realm_builder
200            .add_route(fake_clock_to_cpu_manager_routes.from(&fake_clock).to(&cpu_manager))
201            .await
202            .unwrap();
203
204        let fake_clock_to_parent_routes = Route::new()
205            .capability(Capability::protocol_by_name("fuchsia.testing.FakeClockControl"));
206        realm_builder
207            .add_route(fake_clock_to_parent_routes.from(&fake_clock).to(Ref::parent()))
208            .await
209            .unwrap();
210
211        let cobalt_to_power_manager_routes = Route::new()
212            .capability(Capability::protocol_by_name("fuchsia.metrics.MetricEventLoggerFactory"));
213        realm_builder
214            .add_route(cobalt_to_power_manager_routes.from(&fake_cobalt).to(&power_manager))
215            .await
216            .unwrap();
217
218        let activity_service_to_power_manager_routes =
219            Route::new().capability(Capability::protocol_by_name("fuchsia.ui.activity.Provider"));
220        realm_builder
221            .add_route(
222                activity_service_to_power_manager_routes
223                    .from(&activity_service_child)
224                    .to(&power_manager),
225            )
226            .await
227            .unwrap();
228
229        let input_settings_service_to_power_manager_routes =
230            Route::new().capability(Capability::protocol_by_name("fuchsia.settings.Input"));
231        realm_builder
232            .add_route(
233                input_settings_service_to_power_manager_routes
234                    .from(&input_settings_service_child)
235                    .to(&power_manager),
236            )
237            .await
238            .unwrap();
239
240        let shutdown_shim_to_power_manager_routes = Route::new()
241            .capability(Capability::protocol_by_name("fuchsia.hardware.power.statecontrol.Admin"));
242
243        realm_builder
244            .add_route(
245                shutdown_shim_to_power_manager_routes.from(&admin_service_child).to(&power_manager),
246            )
247            .await
248            .unwrap();
249
250        let kernel_service_to_cpu_manager_routes =
251            Route::new().capability(Capability::protocol_by_name("fuchsia.kernel.Stats"));
252        realm_builder
253            .add_route(
254                kernel_service_to_cpu_manager_routes.from(&kernel_service_child).to(&cpu_manager),
255            )
256            .await
257            .unwrap();
258
259        realm_builder
260            .add_route(
261                Route::new()
262                    .capability(
263                        Capability::directory("pkg")
264                            .subdir("config/power_manager")
265                            .as_("config")
266                            .path("/config")
267                            .rights(fio::R_STAR_DIR),
268                    )
269                    .from(Ref::framework())
270                    .to(&power_manager),
271            )
272            .await
273            .unwrap();
274
275        realm_builder
276            .add_route(
277                Route::new()
278                    .capability(
279                        Capability::directory("pkg")
280                            .subdir("config/cpu_manager")
281                            .as_("config")
282                            .path("/config")
283                            .rights(fio::R_STAR_DIR),
284                    )
285                    .from(Ref::framework())
286                    .to(&cpu_manager),
287            )
288            .await
289            .unwrap();
290
291        realm_builder
292            .add_route(
293                Route::new()
294                    .capability(Capability::protocol::<fsys2::LifecycleControllerMarker>())
295                    .from(Ref::framework())
296                    .to(Ref::parent()),
297            )
298            .await
299            .unwrap();
300
301        let power_manager_to_parent_routes = Route::new()
302            .capability(Capability::protocol_by_name("fuchsia.power.clientlevel.Connector"))
303            .capability(Capability::protocol_by_name("fuchsia.power.profile.Watcher"))
304            .capability(Capability::protocol_by_name("fuchsia.thermal.ClientStateConnector"))
305            .capability(Capability::protocol_by_name("fuchsia.thermal.SensorManager"));
306
307        realm_builder
308            .add_route(power_manager_to_parent_routes.from(&power_manager).to(Ref::parent()))
309            .await
310            .unwrap();
311
312        // Set up CPU Manager's required routes
313        let parent_to_cpu_manager_routes = Route::new()
314            .capability(Capability::protocol_by_name("fuchsia.kernel.CpuResource"))
315            .capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
316            .capability(Capability::protocol_by_name("fuchsia.tracing.provider.Registry"));
317        realm_builder
318            .add_route(parent_to_cpu_manager_routes.from(Ref::parent()).to(&cpu_manager))
319            .await
320            .unwrap();
321
322        let parent_to_power_broker_routes = Route::new()
323            .capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
324            .capability(Capability::protocol_by_name("fuchsia.tracing.provider.Registry"));
325        realm_builder
326            .add_route(parent_to_power_broker_routes.from(Ref::parent()).to(&power_broker))
327            .await
328            .unwrap();
329
330        let power_broker_to_cpu_manager_routes =
331            Route::new().capability(Capability::protocol_by_name("fuchsia.power.broker.Topology"));
332        realm_builder
333            .add_route(power_broker_to_cpu_manager_routes.from(&power_broker).to(&cpu_manager))
334            .await
335            .unwrap();
336
337        let power_manager_to_cpu_manager_routes = Route::new()
338            .capability(Capability::protocol_by_name("fuchsia.thermal.ClientStateConnector"));
339        realm_builder
340            .add_route(power_manager_to_cpu_manager_routes.from(&power_manager).to(&cpu_manager))
341            .await
342            .unwrap();
343
344        let cpu_manager_to_parent_routes = Route::new()
345            .capability(Capability::protocol_by_name("fuchsia.power.cpu.DomainController"))
346            .capability(Capability::protocol_by_name("fuchsia.power.cpu.manager.Boost"));
347        realm_builder
348            .add_route(cpu_manager_to_parent_routes.from(&cpu_manager).to(Ref::parent()))
349            .await
350            .unwrap();
351
352        realm_builder
353            .add_route(
354                Route::new()
355                    .capability(Capability::directory("dev-topological"))
356                    .from(Ref::child("driver_test_realm"))
357                    .to(&power_manager),
358            )
359            .await
360            .unwrap();
361
362        realm_builder
363            .add_route(
364                Route::new()
365                    .capability(Capability::service::<fcpu_ctrl::ServiceMarker>())
366                    .from(Ref::child("driver_test_realm"))
367                    .to(&cpu_manager),
368            )
369            .await
370            .unwrap();
371
372        // Update Power Manager's capability config values
373        realm_builder
374            .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
375                name: "fuchsia.power.manager.EnableDebugService".parse().unwrap(),
376                value: false.into(),
377            }))
378            .await
379            .unwrap();
380        realm_builder
381            .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
382                name: "fuchsia.power.manager.NodeConfigPath".parse().unwrap(),
383                value: self
384                    .power_manager_node_config_path
385                    .expect("power_manager_node_config_path not set")
386                    .into(),
387            }))
388            .await
389            .unwrap();
390        realm_builder
391            .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
392                name: "fuchsia.power.manager.DisableTemperatureFilter".parse().unwrap(),
393                value: true.into(),
394            }))
395            .await
396            .unwrap();
397        realm_builder
398            .add_route(
399                Route::new()
400                    .capability(Capability::configuration(
401                        "fuchsia.power.manager.EnableDebugService",
402                    ))
403                    .capability(Capability::configuration("fuchsia.power.manager.NodeConfigPath"))
404                    .capability(Capability::configuration(
405                        "fuchsia.power.manager.DisableTemperatureFilter",
406                    ))
407                    .from(Ref::self_())
408                    .to(&power_manager),
409            )
410            .await
411            .unwrap();
412
413        // Update CPU Manager's capability config values
414        let cpu_node_config_path = self.cpu_manager_node_config_path.unwrap_or_default();
415        realm_builder
416            .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
417                name: "fuchsia.power.cpu.BoostEnabled".parse().unwrap(),
418                value: self.cpu_boost_enabled.into(),
419            }))
420            .await
421            .unwrap();
422        realm_builder
423            .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
424                name: "fuchsia.power.cpu.NodeConfigPath".parse().unwrap(),
425                value: cpu_node_config_path.into(),
426            }))
427            .await
428            .unwrap();
429        realm_builder
430            .add_route(
431                Route::new()
432                    .capability(Capability::configuration("fuchsia.power.cpu.BoostEnabled"))
433                    .capability(Capability::configuration("fuchsia.power.cpu.NodeConfigPath"))
434                    .from(Ref::self_())
435                    .to(&cpu_manager),
436            )
437            .await
438            .unwrap();
439
440        // Finally, build it
441        let realm_instance = realm_builder.build().await.expect("Failed to build RealmInstance");
442
443        // Start driver test realm
444        let args = fdt::RealmArgs {
445            root_driver: Some("#meta/root.cm".to_string()),
446            dtr_exposes: Some(dtr_exposes),
447            ..Default::default()
448        };
449
450        realm_instance
451            .driver_test_realm_start(args)
452            .await
453            .expect("Failed to start driver test realm");
454
455        // Increase the time scale so Power Manager's interval-based operation runs faster for
456        // testing
457        set_fake_time_scale(&realm_instance, FAKE_TIME_SCALE).await;
458
459        TestEnv {
460            realm_instance: Some(realm_instance),
461            mocks: Mocks {
462                activity_service,
463                input_settings_service,
464                admin_service,
465                kernel_service,
466            },
467        }
468    }
469}
470
471pub struct TestEnv {
472    realm_instance: Option<RealmInstance>,
473    pub mocks: Mocks,
474}
475
476impl TestEnv {
477    /// Connects to a protocol exposed by a component within the RealmInstance.
478    pub fn connect_to_protocol<P: DiscoverableProtocolMarker>(&self) -> P::Proxy {
479        self.realm_instance.as_ref().unwrap().root.connect_to_protocol_at_exposed_dir().unwrap()
480    }
481
482    pub fn connect_to_device<P: ProtocolMarker>(&self, driver_path: &str) -> P::Proxy {
483        let dev = self.realm_instance.as_ref().unwrap().driver_test_realm_connect_to_dev().unwrap();
484        let path = driver_path.strip_prefix("/dev/").unwrap();
485
486        fuchsia_component::client::connect_to_named_protocol_at_dir_root::<P>(&dev, path).unwrap()
487    }
488
489    /// Connects to a protocol exposed by a component within the RealmInstance.
490    pub async fn connect_to_first_service_instance<S: ServiceMarker>(&self, marker: S) -> S::Proxy {
491        Service::open_from_dir(self.realm_instance.as_ref().unwrap().root.get_exposed_dir(), marker)
492            .unwrap()
493            .watch_for_any()
494            .await
495            .unwrap()
496    }
497
498    /// Destroys the TestEnv and underlying RealmInstance.
499    ///
500    /// Every test that uses TestEnv must call this at the end of the test.
501    pub async fn destroy(&mut self) {
502        info!("Destroying TestEnv");
503        self.realm_instance
504            .take()
505            .expect("Missing realm instance")
506            .destroy()
507            .await
508            .expect("Failed to destroy realm instance");
509    }
510
511    /// Sets the temperature for a mock temperature device.
512    pub async fn set_temperature(&self, driver_path: &str, temperature: f32) {
513        let dev = self.realm_instance.as_ref().unwrap().driver_test_realm_connect_to_dev().unwrap();
514
515        let control_path = driver_path.strip_prefix("/dev").unwrap().to_owned() + "/control";
516
517        let fake_temperature_control =
518            fuchsia_component::client::connect_to_named_protocol_at_dir_root::<
519                ftemperaturecontrol::DeviceMarker,
520            >(&dev, &control_path)
521            .unwrap();
522
523        let _status = fake_temperature_control.set_temperature_celsius(temperature).await.unwrap();
524    }
525
526    pub async fn set_cpu_stats(&self, cpu_stats: fkernel::CpuStats) {
527        self.mocks.kernel_service.set_cpu_stats(cpu_stats).await;
528    }
529
530    pub async fn wait_for_shutdown_request(&self) {
531        self.mocks.admin_service.wait_for_shutdown_request().await;
532    }
533
534    // Wait for the device to finish enumerating.
535    pub async fn wait_for_device(&self, driver_path: &str) {
536        let dev = self.realm_instance.as_ref().unwrap().driver_test_realm_connect_to_dev().unwrap();
537
538        let path = driver_path.strip_prefix("/dev").unwrap().to_owned();
539
540        device_watcher::recursive_wait(&dev, &path).await.unwrap();
541    }
542}
543
544/// Ensures `destroy` was called on the TestEnv prior to it going out of scope. It would be nice to
545/// do the work of `destroy` right here in `drop`, but we can't since `destroy` requires async.
546impl Drop for TestEnv {
547    fn drop(&mut self) {
548        assert!(self.realm_instance.is_none(), "Must call destroy() to tear down test environment");
549    }
550}
551
552/// Increases the time scale so Power Manager's interval-based operation runs faster for testing.
553async fn set_fake_time_scale(realm_instance: &RealmInstance, scale: u32) {
554    let fake_clock_control: ftesting::FakeClockControlProxy =
555        realm_instance.root.connect_to_protocol_at_exposed_dir().unwrap();
556
557    fake_clock_control.pause().await.expect("failed to pause fake time: FIDL error");
558    fake_clock_control
559        .resume_with_increments(
560            zx::MonotonicDuration::from_millis(1).into_nanos(),
561            &ftesting::Increment::Determined(
562                zx::MonotonicDuration::from_millis(scale.into()).into_nanos(),
563            ),
564        )
565        .await
566        .expect("failed to set fake time scale: FIDL error")
567        .expect("failed to set fake time scale: protocol error");
568}
569
570/// Container to hold all of the mocks within the RealmInstance.
571pub struct Mocks {
572    pub activity_service: Arc<MockActivityService>,
573    pub input_settings_service: Arc<MockInputSettingsService>,
574    pub admin_service: Arc<MockStateControlAdminService>,
575    pub kernel_service: Arc<MockKernelService>,
576}
577
578/// Tests that Power Manager triggers a thermal reboot if the temperature sensor at the given path
579/// reaches the provided temperature. The provided TestEnv is consumed because Power Manager
580/// triggers a reboot.
581pub async fn test_thermal_reboot(mut env: TestEnv, sensor_path: &str, temperature: f32) {
582    // Start Power Manager by connecting to ClientStateConnector
583    let _client = client_connectors::ThermalClient::new(&env, "audio");
584
585    // 1) set the mock temperature to the provided temperature
586    // 2) verify the admin receives the reboot request for `HighTemperature`
587    env.set_temperature(sensor_path, temperature).await;
588    let result = env.mocks.admin_service.wait_for_shutdown_request().await;
589    assert_eq!(result.action.unwrap(), fpower::ShutdownAction::Reboot);
590    assert_eq!(result.reasons.unwrap(), vec![fpower::ShutdownReason::HighTemperature]);
591
592    env.destroy().await;
593}