blob: 70ab0b7ceb9714876710a2eb0f1cc55313936c21 [file] [log] [blame]
// Copyright 2021 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::{anyhow, Context as _, Result};
use async_lock::Mutex;
use async_trait::async_trait;
use ffx_config::{self, api::ConfigError, ConfigLevel};
use serde_json::{json, Map, Value};
use std::sync::atomic::{AtomicUsize, Ordering};
#[cfg(test)]
pub(crate) const MANUAL_TARGETS: &'static str = "targets.manual";
#[cfg(not(test))]
const MANUAL_TARGETS: &'static str = "targets.manual";
#[async_trait(?Send)]
pub trait ManualTargets: Sync {
async fn storage_set(&self, targets: Value) -> Result<()>;
async fn storage_get(&self) -> Result<Value>;
async fn get(&self) -> Result<Value> {
self.storage_get().await
}
async fn get_or_default(&self) -> Map<String, Value> {
self.get().await.unwrap_or(Value::default()).as_object().unwrap_or(&Map::new()).clone()
}
async fn add(&self, target: String, expiry: Option<u64>) -> Result<()> {
let mut targets = self.get_or_default().await;
targets.insert(target, json!(expiry));
self.storage_set(targets.into()).await
}
async fn remove(&self, target: String) -> Result<()> {
let mut targets = self.get_or_default().await;
targets.remove(&target);
self.storage_set(targets.into()).await
}
}
#[derive(Default)]
pub struct Config();
#[async_trait(?Send)]
impl ManualTargets for Config {
async fn storage_get(&self) -> Result<Value> {
ffx_config::get((MANUAL_TARGETS, ConfigLevel::User)).await.context("manual_targets::get")
}
async fn storage_set(&self, targets: Value) -> Result<()> {
ffx_config::set((MANUAL_TARGETS, ConfigLevel::User), targets.into()).await
}
}
#[derive(Default)]
pub struct Mock {
targets: Mutex<Option<Value>>,
set_count: AtomicUsize,
}
#[async_trait(?Send)]
impl ManualTargets for Mock {
async fn storage_get(&self) -> Result<Value> {
self.targets
.lock()
.await
.clone()
.map(|t| Ok(t))
.unwrap_or(Err(anyhow::Error::new(ConfigError::new(anyhow!("value missing")))))
}
async fn storage_set(&self, targets: Value) -> Result<()> {
let _ = self
.set_count
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |_| {
Some(targets.as_object().unwrap().len())
})
.expect("Couldn't update target count for Mock.");
self.targets.lock().await.replace(targets);
Ok(())
}
}
impl Mock {
#[cfg(test)]
pub fn new(targets: Map<String, Value>) -> Self {
Self { targets: Mutex::new(Some(Value::from(targets))), ..Self::default() }
}
}
#[cfg(test)]
mod test {
use super::*;
use serial_test::serial;
mod real_impl {
use super::*;
use serde_json::json;
use serial_test::serial;
#[fuchsia_async::run_singlethreaded(test)]
#[serial]
async fn test_get_manual_targets() {
ffx_config::test_init().unwrap();
ffx_config::set(
(MANUAL_TARGETS, ConfigLevel::User),
json!({"127.0.0.1:8022": 0, "127.0.0.1:8023": 12345}),
)
.await
.unwrap();
let mt = Config::default();
let value = mt.get().await.unwrap();
let targets = value.as_object().unwrap();
assert!(targets.contains_key("127.0.0.1:8022"));
assert!(targets.contains_key("127.0.0.1:8023"));
}
#[fuchsia_async::run_singlethreaded(test)]
#[serial]
async fn test_add_manual_target() {
ffx_config::test_init().unwrap();
let mt = Config::default();
mt.add("127.0.0.1:8022".to_string(), None).await.unwrap();
mt.add("127.0.0.1:8023".to_string(), Some(12345)).await.unwrap();
// duplicate additions are ignored
mt.add("127.0.0.1:8022".to_string(), None).await.unwrap();
mt.add("127.0.0.1:8023".to_string(), Some(12345)).await.unwrap();
let value = mt.get().await.unwrap();
let targets = value.as_object().unwrap();
assert!(targets.contains_key("127.0.0.1:8022"));
assert!(targets.contains_key("127.0.0.1:8023"));
}
#[fuchsia_async::run_singlethreaded(test)]
#[serial]
async fn test_remove_manual_target() {
ffx_config::test_init().unwrap();
ffx_config::set(
(MANUAL_TARGETS, ConfigLevel::User),
json!({"127.0.0.1:8022": 0, "127.0.0.1:8023": 12345}),
)
.await
.unwrap();
let mt = Config::default();
let value = mt.get().await.unwrap();
let targets = value.as_object().unwrap();
assert!(targets.contains_key("127.0.0.1:8022"));
assert!(targets.contains_key("127.0.0.1:8023"));
mt.remove("127.0.0.1:8022".to_string()).await.unwrap();
mt.remove("127.0.0.1:8023".to_string()).await.unwrap();
let targets = mt.get_or_default().await;
assert_eq!(targets, Map::<String, Value>::new());
}
}
mod mock_impl {
use super::*;
#[fuchsia_async::run_singlethreaded(test)]
async fn test_new() {
let mut map = Map::new();
map.insert("127.0.0.1:8022".to_string(), json!(0));
let mt = Mock::new(map);
let value = mt.get().await.unwrap();
let targets = value.as_object().unwrap();
assert!(targets.contains_key("127.0.0.1:8022"));
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_default() {
let mt = Mock::default();
assert_eq!(mt.get_or_default().await, Map::<String, Value>::new());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_get_manual_targets() {
let mut map = Map::new();
map.insert("127.0.0.1:8022".to_string(), json!(0));
map.insert("127.0.0.1:8023".to_string(), json!(0));
let mt = Mock::new(map);
let value = mt.get().await.unwrap();
let targets = value.as_object().unwrap();
assert!(targets.contains_key("127.0.0.1:8022"));
assert!(targets.contains_key("127.0.0.1:8023"));
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_add_manual_target() {
let mt = Mock::default();
mt.add("127.0.0.1:8022".to_string(), None).await.unwrap();
mt.add("127.0.0.1:8023".to_string(), Some(12345)).await.unwrap();
// duplicate additions are ignored
mt.add("127.0.0.1:8022".to_string(), None).await.unwrap();
mt.add("127.0.0.1:8023".to_string(), Some(12345)).await.unwrap();
let value = mt.get().await.unwrap();
let targets = value.as_object().unwrap();
assert!(targets.contains_key("127.0.0.1:8022"));
assert!(targets.contains_key("127.0.0.1:8023"));
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_remove_manual_target() {
let mut map = Map::new();
map.insert("127.0.0.1:8022".to_string(), json!(0));
map.insert("127.0.0.1:8023".to_string(), json!(12345));
let mt = Mock::new(map);
let value = mt.get().await.unwrap();
let targets = value.as_object().unwrap();
assert!(targets.contains_key("127.0.0.1:8022"));
assert!(targets.contains_key("127.0.0.1:8023"));
mt.remove("127.0.0.1:8022".to_string()).await.unwrap();
mt.remove("127.0.0.1:8023".to_string()).await.unwrap();
let targets = mt.get_or_default().await;
assert!(targets.is_empty());
}
}
#[fuchsia_async::run_singlethreaded(test)]
#[serial]
async fn test_repeated_adds_do_not_rewrite_storage() {
let mt = Mock::new(Map::new());
mt.add("127.0.0.1:8022".to_string(), None).await.unwrap();
assert_eq!(mt.set_count.load(Ordering::SeqCst), 1);
mt.add("127.0.0.1:8022".to_string(), None).await.unwrap();
assert_eq!(mt.set_count.load(Ordering::SeqCst), 1);
mt.add("127.0.0.1:8023".to_string(), Some(12345)).await.unwrap();
assert_eq!(mt.set_count.load(Ordering::SeqCst), 2);
mt.add("127.0.0.1:8023".to_string(), Some(12345)).await.unwrap();
assert_eq!(mt.set_count.load(Ordering::SeqCst), 2);
}
}