unmanaged_element/
unmanaged_element.rs

1// Copyright 2024 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
5use anyhow::{anyhow, Result};
6use {fidl_fuchsia_power_broker as fbroker, power_broker_client as pbclient};
7
8pub struct UnmanagedElement {
9    pub context: pbclient::PowerElementContext,
10}
11
12impl UnmanagedElement {
13    pub async fn add(
14        topology: &fbroker::TopologyProxy,
15        name: &str,
16        valid_levels: &[fbroker::PowerLevel],
17        initial_current_level: &fbroker::PowerLevel,
18    ) -> Result<Self> {
19        // Build a PowerElementContext with no dependencies (by default) and no dependent elements.
20        let context = pbclient::PowerElementContext::builder(topology, name, valid_levels)
21            .initial_current_level(*initial_current_level)
22            .register_dependency_tokens(false) // Prevent other elements from depending in this one.
23            .build()
24            .await?;
25
26        Ok(Self { context })
27    }
28
29    pub async fn set_level(&self, new_current_level: &fbroker::PowerLevel) -> Result<()> {
30        self.context.current_level.update(*new_current_level).await?.map_err(|e| anyhow!("{e:?}"))
31    }
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37    use anyhow::Context;
38    use fidl::endpoints::create_proxy_and_stream;
39    use fuchsia_async as fasync;
40    use futures::channel::mpsc;
41    use futures::prelude::*;
42
43    struct FakePowerBroker {
44        // Forwards ElementSchema received via AddElement requests.
45        element_schema_sender: mpsc::UnboundedSender<fbroker::ElementSchema>,
46    }
47
48    impl FakePowerBroker {
49        async fn run(&self, stream: fbroker::TopologyRequestStream) -> Result<()> {
50            stream
51                .map(|request| request.context("failed request"))
52                .try_for_each(|request| async {
53                    match request {
54                        fbroker::TopologyRequest::AddElement { payload, responder } => {
55                            self.element_schema_sender
56                                .unbounded_send(payload)
57                                .expect("forwarding element schema");
58                            responder.send(Ok(())).context("send failed")
59                        }
60                        _ => unreachable!(),
61                    }
62                })
63                .await
64        }
65    }
66
67    struct FakeCurrentLevel {
68        // Forwards PowerLevel received via Update requests.
69        power_level_sender: mpsc::UnboundedSender<fbroker::PowerLevel>,
70    }
71
72    impl FakeCurrentLevel {
73        async fn run(&self, stream: fbroker::CurrentLevelRequestStream) -> Result<()> {
74            stream
75                .map(|request| request.context("failed request"))
76                .try_for_each(|request| async {
77                    match request {
78                        fbroker::CurrentLevelRequest::Update { current_level, responder } => {
79                            self.power_level_sender
80                                .unbounded_send(current_level)
81                                .expect("intercepting power level update");
82                            responder.send(Ok(())).context("send failed")
83                        }
84                        _ => unreachable!(),
85                    }
86                })
87                .await
88        }
89    }
90
91    #[fasync::run_until_stalled(test)]
92    async fn test_add_to_power_topology_sends_correct_schema() -> Result<()> {
93        // Create and run the Topology server in the background.
94        let (topology, topology_stream) = create_proxy_and_stream::<fbroker::TopologyMarker>();
95        let (element_schema_sender, mut element_schema_receiver) =
96            mpsc::unbounded::<fbroker::ElementSchema>();
97        fasync::Task::local(async move {
98            let power_broker = FakePowerBroker { element_schema_sender };
99            power_broker.run(topology_stream).await.expect("topology server teardown");
100        })
101        .detach();
102
103        // Create an unmanaged element and add it to the power topology.
104        let element_name = "unmanaged-element";
105        let valid_levels = &pbclient::BINARY_POWER_LEVELS;
106        let initial_level = &pbclient::BINARY_POWER_LEVELS[0]; // Initial state is "off".
107        let _ = UnmanagedElement::add(&topology, element_name, valid_levels, initial_level).await?;
108
109        // Check the ElementSchema received by the Topology server.
110        let schema = element_schema_receiver.next().await.expect("server AddElement call");
111        assert_eq!(schema.element_name, Some(element_name.to_string()));
112        assert_eq!(schema.valid_levels, Some(valid_levels.to_vec()));
113        assert_eq!(schema.initial_current_level, Some(*initial_level));
114        assert_eq!(schema.dependencies, Some(vec![]));
115        Ok(())
116    }
117
118    #[fasync::run_until_stalled(test)]
119    async fn test_set_level_changes_topology_current_level() -> Result<()> {
120        // Create and run the Topology server in the background.
121        let (topology, topology_stream) = create_proxy_and_stream::<fbroker::TopologyMarker>();
122        let (element_schema_sender, mut element_schema_receiver) =
123            mpsc::unbounded::<fbroker::ElementSchema>();
124        fasync::Task::local(async move {
125            let power_broker = FakePowerBroker { element_schema_sender };
126            power_broker.run(topology_stream).await.expect("topology server teardown");
127        })
128        .detach();
129
130        // Create an unmanaged element and add it to the power topology.
131        let element_name = "unmanaged-element";
132        let valid_levels = &pbclient::BINARY_POWER_LEVELS;
133        let initial_level = &pbclient::BINARY_POWER_LEVELS[0]; // Initial state is "off".
134        let element =
135            UnmanagedElement::add(&topology, element_name, valid_levels, initial_level).await?;
136        let schema = element_schema_receiver.next().await.expect("server AddElement call");
137
138        // Create a CurrentLevel server to handle Update requests in the background.
139        let (power_level_sender, mut power_level_receiver) =
140            mpsc::unbounded::<fbroker::PowerLevel>();
141        fasync::Task::local(async move {
142            let current_level = FakeCurrentLevel { power_level_sender };
143            assert!(schema.level_control_channels.is_some());
144            let stream = schema.level_control_channels.unwrap().current.into_stream();
145            current_level.run(stream).await.expect("current level server teardown");
146        })
147        .detach();
148
149        element.set_level(&pbclient::BINARY_POWER_LEVELS[1]).await?;
150        assert_eq!(power_level_receiver.next().await, Some(pbclient::BINARY_POWER_LEVELS[1]));
151
152        element.set_level(&pbclient::BINARY_POWER_LEVELS[0]).await?;
153        assert_eq!(power_level_receiver.next().await, Some(pbclient::BINARY_POWER_LEVELS[0]));
154
155        Ok(())
156    }
157}