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