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