| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| anyhow::{format_err, Context, Error, Result}, |
| fidl_fuchsia_device as fdevice, fidl_fuchsia_hardware_temperature as ftemperature, |
| fidl_fuchsia_thermal_test::{self as fthermal, TemperatureLoggerRequest}, |
| fuchsia_async as fasync, |
| fuchsia_component::server::ServiceFs, |
| fuchsia_inspect::{self as inspect, Property}, |
| fuchsia_syslog::{fx_log_err, fx_log_info}, |
| fuchsia_zircon as zx, |
| futures::{ |
| stream::{FuturesUnordered, StreamExt, TryStreamExt}, |
| TryFutureExt, |
| }, |
| serde_derive::Deserialize, |
| serde_json as json, |
| std::{cell::RefCell, collections::HashMap, rc::Rc, task::Poll}, |
| }; |
| |
| const CONFIG_PATH: &'static str = "/config/data/config.json"; |
| |
| // The fuchsia.hardware.temperature.Device is composed into fuchsia.hardware.thermal.Device, so |
| // drivers are found in two directories. |
| const SERVICE_DIRS: [&str; 2] = ["/dev/class/temperature", "/dev/class/thermal"]; |
| |
| // Configuration for a temperature driver, used in the building process. |
| #[derive(Deserialize)] |
| struct DriverConfig { |
| /// Human-readable name. |
| name: String, |
| |
| /// Topological path. |
| topological_path: String, |
| } |
| |
| // Representation of an actively-used temperature driver. |
| struct Driver { |
| /// Human-readable name. |
| name: String, |
| |
| /// Topological path. |
| topological_path: String, |
| |
| proxy: ftemperature::DeviceProxy, |
| } |
| |
| pub fn connect_proxy<T: fidl::endpoints::ProtocolMarker>(path: &str) -> Result<T::Proxy> { |
| let (proxy, server) = fidl::endpoints::create_proxy::<T>() |
| .map_err(|e| format_err!("Failed to create proxy: {}", e))?; |
| |
| fdio::service_connect(path, server.into_channel()) |
| .map_err(|s| format_err!("Failed to connect to service at {}: {}", path, s))?; |
| Ok(proxy) |
| } |
| |
| /// Maps from devices' topological paths to their class paths in the provided directory. |
| async fn map_topo_paths_to_class_paths( |
| dir_path: &str, |
| path_map: &mut HashMap<String, String>, |
| ) -> Result<()> { |
| let drivers = list_drivers(dir_path).await?; |
| for driver in drivers.iter() { |
| let class_path = format!("{}/{}", dir_path, driver); |
| let topo_path = get_driver_topological_path(&class_path).await?; |
| path_map.insert(topo_path, class_path); |
| } |
| Ok(()) |
| } |
| |
| async fn get_driver_topological_path(path: &str) -> Result<String> { |
| let proxy = connect_proxy::<fdevice::ControllerMarker>(path)?; |
| proxy |
| .get_topological_path() |
| .await? |
| .map_err(|raw| format_err!("zx error: {}", zx::Status::from_raw(raw))) |
| } |
| |
| async fn list_drivers(path: &str) -> Result<Vec<String>> { |
| let dir = fuchsia_fs::open_directory_in_namespace(path, fuchsia_fs::OpenFlags::RIGHT_READABLE)?; |
| Ok(fuchsia_fs::directory::readdir(&dir) |
| .await? |
| .iter() |
| .map(|dir_entry| dir_entry.name.clone()) |
| .collect()) |
| } |
| |
| /// Builds a TemperatureLoggerServer. |
| pub struct ServerBuilder<'a> { |
| /// Paths to temperature sensor drivers. |
| driver_configs: Vec<DriverConfig>, |
| |
| /// Optional proxies for test usage. |
| driver_proxies: Option<Vec<ftemperature::DeviceProxy>>, |
| |
| /// Optional inspect root for test usage. |
| inspect_root: Option<&'a inspect::Node>, |
| } |
| |
| impl<'a> ServerBuilder<'a> { |
| /// Constructs a new ServerBuilder from a JSON configuration. |
| fn new_from_json(json_data: json::Value) -> Self { |
| #[derive(Deserialize)] |
| struct Config { |
| drivers: Vec<DriverConfig>, |
| } |
| |
| let config: Config = json::from_value(json_data).unwrap(); |
| |
| ServerBuilder { driver_configs: config.drivers, driver_proxies: None, inspect_root: None } |
| } |
| |
| /// For testing purposes, proxies may be provided directly to the Server builder. Each element |
| /// of `proxies` will be paired with the corresponding element of `self.driver_configs`. |
| #[cfg(test)] |
| fn with_proxies(mut self, proxies: Vec<ftemperature::DeviceProxy>) -> Self { |
| assert_eq!(proxies.len(), self.driver_configs.len()); |
| self.driver_proxies = Some(proxies); |
| self |
| } |
| |
| /// Injects an Inspect root for use in tests. |
| #[cfg(test)] |
| fn with_inspect_root(mut self, root: &'a inspect::Node) -> Self { |
| self.inspect_root = Some(root); |
| self |
| } |
| |
| /// Builds a TemperatureLoggerServer. |
| async fn build(self) -> Result<Rc<TemperatureLoggerServer>> { |
| // Clone driver names for InspectData before consuming self.driver_configs. |
| let driver_names = self.driver_configs.iter().map(|c| c.name.clone()).collect(); |
| |
| let drivers = match self.driver_proxies { |
| // If no proxies are provided, create proxies based on driver paths. |
| None => { |
| // Determine topological paths for devices in SERVICE_DIRS. |
| let mut topo_to_class = HashMap::new(); |
| for dir in &SERVICE_DIRS { |
| map_topo_paths_to_class_paths(dir, &mut topo_to_class).await?; |
| } |
| |
| // For each driver path, create a proxy for the temperature service. |
| let mut drivers = Vec::new(); |
| for config in self.driver_configs.into_iter() { |
| let class_path = |
| topo_to_class.get(&config.topological_path).ok_or(format_err!( |
| "Topological path {} missing from topo-to-class mapping: {:?}", |
| &config.topological_path, |
| topo_to_class |
| ))?; |
| let proxy = connect_proxy::<ftemperature::DeviceMarker>(class_path)?; |
| drivers.push(Driver { |
| name: config.name, |
| topological_path: config.topological_path, |
| proxy, |
| }); |
| } |
| drivers |
| } |
| // If proxies were provided, match them to the driver configs. |
| Some(proxies) => self |
| .driver_configs |
| .into_iter() |
| .zip(proxies.into_iter()) |
| .map(|(config, proxy)| Driver { |
| name: config.name, |
| topological_path: config.topological_path, |
| proxy, |
| }) |
| .collect(), |
| }; |
| |
| // Optionally use the default inspect root node |
| let inspect_root = self.inspect_root.unwrap_or(inspect::component::inspector().root()); |
| |
| Ok(TemperatureLoggerServer::new( |
| Rc::new(drivers), |
| Rc::new(InspectData::new(inspect_root, driver_names)), |
| )) |
| } |
| } |
| |
| struct TemperatureLogger { |
| /// List of temperature sensor drivers. |
| drivers: Rc<Vec<Driver>>, |
| |
| /// Logging interval. |
| interval: zx::Duration, |
| |
| /// Start time for the logger; used to calculate elapsed time. |
| start_time: fasync::Time, |
| |
| /// Time at which the logger will stop. |
| end_time: fasync::Time, |
| |
| inspect: Rc<InspectData>, |
| } |
| |
| impl TemperatureLogger { |
| fn new( |
| drivers: Rc<Vec<Driver>>, |
| interval: zx::Duration, |
| duration: Option<zx::Duration>, |
| inspect: Rc<InspectData>, |
| ) -> Self { |
| let start_time = fasync::Time::now(); |
| let end_time = duration.map_or(fasync::Time::INFINITE, |d| start_time + d); |
| TemperatureLogger { drivers, interval, start_time, end_time, inspect } |
| } |
| |
| /// Constructs a Task that will log temperatures from all provided sensors. |
| fn spawn_logging_task(self) -> fasync::Task<()> { |
| let mut interval = fasync::Interval::new(self.interval); |
| |
| fasync::Task::local(async move { |
| while let Some(()) = interval.next().await { |
| // If we're interested in very high-rate polling in the future, it might be worth |
| // comparing the elapsed time to the intended polling interval and logging any |
| // anomalies. |
| let now = fasync::Time::now(); |
| if now >= self.end_time { |
| break; |
| } |
| self.log_temperatures(now - self.start_time).await; |
| } |
| }) |
| } |
| |
| /// Polls temperature sensors and logs the resulting data to Inspect and trace. |
| async fn log_temperatures(&self, elapsed: zx::Duration) { |
| // Execute a query to each temperature sensor driver. |
| let queries = FuturesUnordered::new(); |
| for (index, driver) in self.drivers.iter().enumerate() { |
| let query = async move { |
| let result = match driver.proxy.get_temperature_celsius().await { |
| Ok((zx_status, temperature)) => match zx::Status::ok(zx_status) { |
| Ok(()) => Ok(temperature), |
| Err(e) => Err(format_err!( |
| "{}: get_temperature_celsius returned an error: {}", |
| driver.topological_path, |
| e |
| )), |
| }, |
| Err(e) => Err(format_err!( |
| "{}: get_temperature_celsius IPC failed: {}", |
| driver.topological_path, |
| e |
| )), |
| }; |
| (index, result) |
| }; |
| queries.push(query); |
| } |
| let results = queries.collect::<Vec<(usize, Result<f32, Error>)>>().await; |
| |
| // Log the temperatures to Inspect and to a trace counter. |
| let mut trace_args = Vec::new(); |
| for (index, result) in results.into_iter() { |
| match result { |
| Ok(temperature) => { |
| self.inspect.log_temperature(index, temperature, elapsed.into_millis()); |
| |
| let name = &self.drivers[index].name; |
| trace_args.push(fuchsia_trace::ArgValue::of(name, temperature as f64)); |
| } |
| // In case of a polling error, the previous value will from this sensor will not be |
| // updated. We could do something fancier like exposing an error count, but this |
| // sample will be missing from the trace counter as is, and any serious analysis |
| // should be performed on the trace. |
| Err(e) => fx_log_err!("Error reading temperature: {:?}", e), |
| }; |
| } |
| |
| fuchsia_trace::counter( |
| fuchsia_trace::cstr!("temperature_logger"), |
| fuchsia_trace::cstr!("temperature"), |
| 0, |
| &trace_args, |
| ); |
| } |
| } |
| |
| /// Server for the TemperatureLogger protocol. |
| struct TemperatureLoggerServer { |
| /// List of temperature sensor drivers to provie the logged temperatures. |
| drivers: Rc<Vec<Driver>>, |
| |
| /// Task that asynchronously executes the polling and logging. |
| logging_task: RefCell<Option<fasync::Task<()>>>, |
| |
| inspect: Rc<InspectData>, |
| } |
| |
| impl TemperatureLoggerServer { |
| fn new(drivers: Rc<Vec<Driver>>, inspect: Rc<InspectData>) -> Rc<Self> { |
| Rc::new(Self { drivers, inspect, logging_task: RefCell::new(None) }) |
| } |
| |
| // Creates a Task to handle the request stream from a new client. |
| fn handle_new_service_connection( |
| self: Rc<Self>, |
| mut stream: fthermal::TemperatureLoggerRequestStream, |
| ) -> fasync::Task<()> { |
| fasync::Task::local( |
| async move { |
| while let Some(request) = stream.try_next().await? { |
| self.handle_temperature_logger_request(request).await?; |
| } |
| Ok(()) |
| } |
| .unwrap_or_else(|e: Error| fx_log_err!("{:?}", e)), |
| ) |
| } |
| |
| // Handles a single TemperatureLoggerRequest. |
| async fn handle_temperature_logger_request( |
| self: &Rc<Self>, |
| request: TemperatureLoggerRequest, |
| ) -> Result<()> { |
| match request { |
| TemperatureLoggerRequest::StartLogging { interval_ms, duration_ms, responder } => { |
| let mut result = self.start_logging(interval_ms, Some(duration_ms)).await; |
| responder.send(&mut result)?; |
| } |
| TemperatureLoggerRequest::StartLoggingForever { interval_ms, responder } => { |
| let mut result = self.start_logging(interval_ms, None).await; |
| responder.send(&mut result)?; |
| } |
| fthermal::TemperatureLoggerRequest::StopLogging { responder } => { |
| *self.logging_task.borrow_mut() = None; |
| responder.send()?; |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| async fn start_logging( |
| &self, |
| interval_ms: u32, |
| duration_ms: Option<u32>, |
| ) -> fthermal::TemperatureLoggerStartLoggingResult { |
| // If self.logging_task is None, then the server has never logged. If the task exists and |
| // is Pending then logging is already active, and an error is returned. If the task is |
| // Ready, then a previous logging session has ended naturally, and we proceed to create a |
| // new task. |
| if let Some(task) = self.logging_task.borrow_mut().as_mut() { |
| if let Poll::Pending = futures::poll!(task) { |
| return Err(fthermal::TemperatureLoggerError::AlreadyLogging); |
| } |
| } |
| |
| if interval_ms == 0 || duration_ms.map_or(false, |d| d <= interval_ms) { |
| return Err(fthermal::TemperatureLoggerError::InvalidArgument); |
| } |
| |
| let logger = TemperatureLogger::new( |
| self.drivers.clone(), |
| zx::Duration::from_millis(interval_ms as i64), |
| duration_ms.map(|ms| zx::Duration::from_millis(ms as i64)), |
| self.inspect.clone(), |
| ); |
| self.logging_task.borrow_mut().replace(logger.spawn_logging_task()); |
| |
| Ok(()) |
| } |
| } |
| |
| struct InspectData { |
| temperatures: Vec<inspect::DoubleProperty>, |
| elapsed_millis: inspect::IntProperty, |
| } |
| |
| impl InspectData { |
| fn new(parent: &inspect::Node, sensor_names: Vec<String>) -> Self { |
| let root = parent.create_child("TemperatureLogger"); |
| let temperatures = sensor_names |
| .into_iter() |
| .map(|name| root.create_double(name + " (°C)", f64::MIN)) |
| .collect(); |
| let elapsed_millis = root.create_int("elapsed time (ms)", std::i64::MIN); |
| parent.record(root); |
| Self { temperatures, elapsed_millis: elapsed_millis } |
| } |
| |
| fn log_temperature(&self, index: usize, value: f32, elapsed_millis: i64) { |
| self.temperatures[index].set(value as f64); |
| self.elapsed_millis.set(elapsed_millis); |
| } |
| } |
| |
| #[fasync::run_singlethreaded] |
| async fn main() { |
| // v2 components can't surface stderr yet, so we need to explicitly log errors. |
| match inner_main().await { |
| Err(e) => fx_log_err!("Terminated with error: {}", e), |
| Ok(()) => fx_log_info!("Terminated with Ok(())"), |
| } |
| } |
| |
| async fn inner_main() -> Result<()> { |
| fuchsia_syslog::init_with_tags(&["temperature-logger"]).expect("failed to initialize logger"); |
| |
| // Set up tracing |
| fuchsia_trace_provider::trace_provider_create_with_fdio(); |
| |
| let mut fs = ServiceFs::new_local(); |
| |
| // Allow our services to be discovered. |
| fs.take_and_serve_directory_handle()?; |
| |
| // Required call to serve the inspect tree |
| let inspector = inspect::component::inspector(); |
| inspect_runtime::serve(inspector, &mut fs)?; |
| |
| // Construct the server, and begin serving. |
| let config: json::Value = json::from_reader(std::io::BufReader::new( |
| std::fs::File::open(CONFIG_PATH) |
| .context(format!("Failed to open config file at path {}", CONFIG_PATH))?, |
| )) |
| .context("Failed to parse config file JSON")?; |
| let server = ServerBuilder::new_from_json(config).build().await?; |
| fs.dir("svc").add_fidl_service(move |stream: fthermal::TemperatureLoggerRequestStream| { |
| TemperatureLoggerServer::handle_new_service_connection(server.clone(), stream).detach(); |
| }); |
| |
| // This future never completes. |
| fs.collect::<()>().await; |
| |
| Ok(()) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| assert_matches::assert_matches, |
| futures::{FutureExt, TryStreamExt}, |
| inspect::assert_data_tree, |
| std::cell::Cell, |
| }; |
| |
| fn setup_fake_driver<'a>( |
| mut get_temperature: impl FnMut() -> f32 + 'static, |
| ) -> (ftemperature::DeviceProxy, fasync::Task<()>) { |
| let (proxy, mut stream) = |
| fidl::endpoints::create_proxy_and_stream::<ftemperature::DeviceMarker>().unwrap(); |
| let task = fasync::Task::local(async move { |
| while let Ok(req) = stream.try_next().await { |
| match req { |
| Some(ftemperature::DeviceRequest::GetTemperatureCelsius { responder }) => { |
| let _ = responder.send(zx::Status::OK.into_raw(), get_temperature()); |
| } |
| _ => assert!(false), |
| } |
| } |
| }); |
| |
| (proxy, task) |
| } |
| |
| // Helper struct for managing test state. |
| struct Runner { |
| server_task: fasync::Task<()>, |
| proxy: fthermal::TemperatureLoggerProxy, |
| |
| cpu_temperature: Rc<Cell<f32>>, |
| gpu_temperature: Rc<Cell<f32>>, |
| |
| inspector: inspect::Inspector, |
| |
| // Driver tasks are never used but must remain in scope. |
| _driver_tasks: Vec<fasync::Task<()>>, |
| |
| // Fields are dropped in declaration order. Always drop executor last because we hold other |
| // zircon objects tied to the executor in this struct, and those can't outlive the executor. |
| // |
| // See |
| // - https://fuchsia-docs.firebaseapp.com/rust/fuchsia_async/struct.TestExecutor.html |
| // - https://doc.rust-lang.org/reference/destructors.html. |
| executor: fasync::TestExecutor, |
| } |
| |
| impl Runner { |
| fn new() -> Self { |
| let mut executor = fasync::TestExecutor::new_with_fake_time().unwrap(); |
| executor.set_fake_time(fasync::Time::from_nanos(0)); |
| |
| let inspector = inspect::Inspector::new(); |
| |
| let config = json::json!({ |
| "drivers": [ |
| { |
| "name": "cpu", |
| "topological_path": "/dev/fake/cpu_temperature" |
| }, |
| { |
| "name": "gpu", |
| "topological_path": "/dev/fake/gpu_temperature" |
| } |
| ] |
| }); |
| |
| // Create two fake temperature drivers. |
| let mut driver_tasks = Vec::new(); |
| let cpu_temperature = Rc::new(Cell::new(0.0)); |
| let cpu_temperature_clone = cpu_temperature.clone(); |
| let (cpu_temperature_proxy, task) = |
| setup_fake_driver(move || cpu_temperature_clone.get()); |
| driver_tasks.push(task); |
| let gpu_temperature = Rc::new(Cell::new(0.0)); |
| let gpu_temperature_clone = gpu_temperature.clone(); |
| let (gpu_temperature_proxy, task) = |
| setup_fake_driver(move || gpu_temperature_clone.get()); |
| driver_tasks.push(task); |
| |
| // Build the server. |
| let builder = ServerBuilder::new_from_json(config) |
| .with_proxies(vec![cpu_temperature_proxy, gpu_temperature_proxy]) |
| .with_inspect_root(inspector.root()); |
| let poll = executor.run_until_stalled(&mut builder.build().boxed_local()); |
| let server = match poll { |
| Poll::Ready(Ok(server)) => server, |
| _ => panic!("Failed to build TemperatureLoggerServer"), |
| }; |
| |
| // Construct the server task. |
| let (proxy, stream) = |
| fidl::endpoints::create_proxy_and_stream::<fthermal::TemperatureLoggerMarker>() |
| .unwrap(); |
| let server_task = server.handle_new_service_connection(stream); |
| |
| Self { |
| executor, |
| server_task, |
| proxy, |
| cpu_temperature, |
| gpu_temperature, |
| inspector, |
| _driver_tasks: driver_tasks, |
| } |
| } |
| |
| // Runs the next iteration of the logging task. |
| fn iterate_logging_task(&mut self) { |
| let wakeup_time = self.executor.wake_next_timer().unwrap(); |
| self.executor.set_fake_time(wakeup_time); |
| assert_eq!( |
| futures::task::Poll::Pending, |
| self.executor.run_until_stalled(&mut self.server_task) |
| ); |
| } |
| } |
| #[test] |
| fn test_logging_duration() { |
| let mut runner = Runner::new(); |
| |
| // Starting logging for 1 second at 100ms intervals. When the query stalls, the logging task |
| // will be waiting on its timer. |
| let mut query = runner.proxy.start_logging(100, 1_000); |
| assert_matches!(runner.executor.run_until_stalled(&mut query), Poll::Ready(Ok(Ok(())))); |
| |
| // Check default values before first temperature poll. |
| assert_data_tree!( |
| runner.inspector, |
| root: { |
| TemperatureLogger: { |
| "cpu (°C)": f64::MIN, |
| "gpu (°C)": f64::MIN, |
| "elapsed time (ms)": std::i64::MIN |
| } |
| } |
| ); |
| |
| // For the first 9 steps, CPU and GPU temperature are logged to Insepct. |
| for i in 0..9 { |
| runner.cpu_temperature.set(30.0 + i as f32); |
| runner.gpu_temperature.set(40.0 + i as f32); |
| runner.iterate_logging_task(); |
| assert_data_tree!( |
| runner.inspector, |
| root: { |
| TemperatureLogger: { |
| "cpu (°C)": runner.cpu_temperature.get() as f64, |
| "gpu (°C)": runner.gpu_temperature.get() as f64, |
| "elapsed time (ms)": 100 * (1 + i as i64) |
| } |
| } |
| ); |
| } |
| |
| // With one more time step, the end time has been reached, and Inspect data is not updated. |
| runner.cpu_temperature.set(77.0); |
| runner.gpu_temperature.set(77.0); |
| runner.iterate_logging_task(); |
| assert_data_tree!( |
| runner.inspector, |
| root: { |
| TemperatureLogger: { |
| "cpu (°C)": 38.0, |
| "gpu (°C)": 48.0, |
| "elapsed time (ms)": 900i64 |
| } |
| } |
| ); |
| } |
| |
| #[test] |
| fn test_log_forever() { |
| let mut runner = Runner::new(); |
| |
| // We can't actually that the logger never stops, but we'll run it through a decent number |
| // of iterations to exercise the code path. |
| let mut query = runner.proxy.start_logging_forever(1_000); |
| assert_matches!(runner.executor.run_until_stalled(&mut query), Poll::Ready(Ok(Ok(())))); |
| for i in 0..1000 { |
| runner.cpu_temperature.set(i as f32); |
| runner.gpu_temperature.set(i as f32); |
| runner.iterate_logging_task(); |
| assert_data_tree!( |
| runner.inspector, |
| root: { |
| TemperatureLogger: { |
| "cpu (°C)": runner.cpu_temperature.get() as f64, |
| "gpu (°C)": runner.gpu_temperature.get() as f64, |
| "elapsed time (ms)": 1000 * (1 + i as i64) |
| } |
| } |
| ); |
| } |
| } |
| |
| #[test] |
| fn test_already_logging_error() { |
| let mut runner = Runner::new(); |
| |
| // Start logging. |
| let mut query = runner.proxy.start_logging(100, 200); |
| assert_matches!(runner.executor.run_until_stalled(&mut query), Poll::Ready(Ok(Ok(())))); |
| |
| // Attempting to start logging a second time should fail. |
| let mut query = runner.proxy.start_logging(100, 500); |
| assert_matches!( |
| runner.executor.run_until_stalled(&mut query), |
| Poll::Ready(Ok(Err(fthermal::TemperatureLoggerError::AlreadyLogging))) |
| ); |
| |
| // Stop logging. |
| let mut query = runner.proxy.stop_logging(); |
| assert_matches!(runner.executor.run_until_stalled(&mut query), Poll::Ready(Ok(()))); |
| |
| // Now starting logging will succeed. |
| let mut query = runner.proxy.start_logging(100, 200); |
| assert_matches!(runner.executor.run_until_stalled(&mut query), Poll::Ready(Ok(Ok(())))); |
| |
| // Logging stops on the second wakeup. |
| runner.iterate_logging_task(); |
| runner.iterate_logging_task(); |
| |
| // Starting logging again succeeds. |
| let mut query = runner.proxy.start_logging(100, 200); |
| assert_matches!(runner.executor.run_until_stalled(&mut query), Poll::Ready(Ok(Ok(())))); |
| } |
| |
| #[test] |
| fn test_invalid_arguments() { |
| let mut runner = Runner::new(); |
| |
| // Interval must be positive. |
| let mut query = runner.proxy.start_logging(0, 200); |
| assert_matches!( |
| runner.executor.run_until_stalled(&mut query), |
| Poll::Ready(Ok(Err(fthermal::TemperatureLoggerError::InvalidArgument))) |
| ); |
| |
| // Duration must be greater than the interval length. |
| let mut query = runner.proxy.start_logging(100, 100); |
| assert_matches!( |
| runner.executor.run_until_stalled(&mut query), |
| Poll::Ready(Ok(Err(fthermal::TemperatureLoggerError::InvalidArgument))) |
| ); |
| } |
| |
| #[test] |
| fn test_multiple_stops_ok() { |
| let mut runner = Runner::new(); |
| |
| // Start logging. |
| let mut query = runner.proxy.start_logging(100, 200); |
| assert_matches!(runner.executor.run_until_stalled(&mut query), Poll::Ready(Ok(Ok(())))); |
| |
| // Stop logging multiple times. |
| let mut query = runner.proxy.stop_logging(); |
| assert_matches!(runner.executor.run_until_stalled(&mut query), Poll::Ready(Ok(()))); |
| let mut query = runner.proxy.stop_logging(); |
| assert_matches!(runner.executor.run_until_stalled(&mut query), Poll::Ready(Ok(()))); |
| } |
| } |