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