use super::{Administered, Elevation, Host, Process, Scheduler};
use crate::context::Context;
use crate::service::Permit;
use crate::store::memory::MemoryStore;
use crate::store::{Backend, Cost, Error, RawStore};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
struct Vault {
store: Arc<MemoryStore>,
argument_line: bool,
}
impl RawStore for Vault {
fn kind(&self) -> Backend {
RawStore::kind(&self.store)
}
fn contains(&self, service: &str) -> Result<bool, Error> {
RawStore::contains(&self.store, service)
}
fn read(&self, service: &str) -> Result<Option<String>, Error> {
RawStore::read(&self.store, service)
}
fn write(&self, permit: Permit, service: &str, contents: &str) -> Result<(), Error> {
if let Some(cost) = self.cost(service, contents)
&& cost.refused()
{
return Err(Error::Write(format!(
"this credential is {} bytes, past the {}-byte command limit",
cost.needs, cost.limit
)));
}
RawStore::write(&self.store, permit, service, contents)
}
fn delete(&self, permit: Permit, service: &str) -> Result<(), Error> {
RawStore::delete(&self.store, permit, service)
}
fn list(&self) -> Result<Option<Vec<String>>, Error> {
RawStore::list(&self.store)
}
fn cost(&self, service: &str, contents: &str) -> Option<Cost> {
RawStore::cost(&self.store, service, contents).map(|cost| Cost {
second_route: self.argument_line,
..cost
})
}
}
#[derive(Debug)]
pub struct MemoryHost {
keychain: Arc<MemoryStore>,
vault: Arc<MemoryStore>,
files: Mutex<HashMap<PathBuf, Arc<MemoryStore>>>,
running: Mutex<HashMap<String, Vec<Process>>>,
listless: AtomicBool,
shared_vault: AtomicBool,
scheduler: Option<Box<dyn Scheduler>>,
refuse_start: Arc<AtomicBool>,
unscheduled: AtomicBool,
elevation: Mutex<Elevation>,
administered: Mutex<HashMap<PathBuf, Administered>>,
forced: Mutex<HashMap<(String, String), Administered>>,
}
impl Default for MemoryHost {
fn default() -> MemoryHost {
let refuse_start = Arc::new(AtomicBool::new(false));
MemoryHost {
keychain: MemoryStore::of(Backend::Keychain),
vault: MemoryStore::of(Backend::Keychain),
files: Mutex::new(HashMap::new()),
running: Mutex::new(HashMap::new()),
listless: AtomicBool::new(false),
shared_vault: AtomicBool::new(true),
scheduler: super::os::pretend_scheduler(Arc::clone(&refuse_start)),
refuse_start,
unscheduled: AtomicBool::new(false),
elevation: Mutex::new(Elevation::Normal),
administered: Mutex::new(HashMap::new()),
forced: Mutex::new(HashMap::new()),
}
}
}
impl MemoryHost {
pub fn new() -> Arc<MemoryHost> {
Arc::new(MemoryHost::default())
}
pub fn live(&self) -> &Arc<MemoryStore> {
&self.keychain
}
pub fn vault(&self) -> &Arc<MemoryStore> {
&self.vault
}
pub fn file_at(&self, path: PathBuf) -> Arc<MemoryStore> {
Arc::clone(
self.files
.lock()
.expect("a poisoned test host is a failed test")
.entry(path)
.or_insert_with(|| MemoryStore::of(Backend::File)),
)
}
pub fn vault_of_its_own(&self) {
self.shared_vault.store(false, Ordering::SeqCst);
}
pub fn runs(&self, program: &str, count: usize) {
self.runs_at(program, &vec![program; count]);
}
pub fn runs_at(&self, program: &str, paths: &[&str]) {
let processes = (1..)
.zip(paths)
.map(|(pid, path)| Process {
pid,
path: PathBuf::from(path),
})
.collect();
self.running
.lock()
.expect("a poisoned test host is a failed test")
.insert(program.to_string(), processes);
}
pub fn without_a_process_list(&self) {
self.listless.store(true, Ordering::SeqCst);
}
pub fn refuse_next_start(&self) {
self.refuse_start.store(true, Ordering::SeqCst);
}
pub fn without_a_scheduler(&self) {
self.unscheduled.store(true, Ordering::SeqCst);
}
pub fn runs_with(&self, elevation: Elevation) {
*self
.elevation
.lock()
.expect("a poisoned test host is a failed test") = elevation;
}
pub fn administers(&self, path: impl Into<PathBuf>, contents: &str) {
self.administer(path.into(), Administered::Set(contents.into()));
}
pub fn administers_unreadably(&self, path: impl Into<PathBuf>, why: &str) {
self.administer(path.into(), Administered::Unreadable(why.into()));
}
fn administer(&self, path: PathBuf, holds: Administered) {
self.administered
.lock()
.expect("a poisoned test host is a failed test")
.insert(path, holds);
}
pub fn forces(&self, domain: &str, key: &str, value: &str) {
self.forced
.lock()
.expect("a poisoned test host is a failed test")
.insert(
(domain.to_string(), key.to_string()),
Administered::Set(value.into()),
);
}
}
impl Host for MemoryHost {
fn foreign_secrets(&self, _ctx: &Context, _account: &str) -> Option<Box<dyn RawStore>> {
Some(Box::new(Arc::clone(&self.keychain)))
}
fn file(&self, path: PathBuf) -> Box<dyn RawStore> {
Box::new(self.file_at(path))
}
fn vault(&self, ctx: &Context) -> Box<dyn RawStore> {
Box::new(Vault {
store: Arc::clone(&self.vault),
argument_line: ctx.argv_fallback(),
})
}
fn vault_is_shared(&self) -> bool {
self.shared_vault.load(Ordering::SeqCst)
}
fn processes(&self, program: &str) -> Option<Vec<Process>> {
if self.listless.load(Ordering::SeqCst) {
return None;
}
Some(
self.running
.lock()
.expect("a poisoned test host is a failed test")
.get(program)
.cloned()
.unwrap_or_default(),
)
}
fn scheduler(&self) -> Option<&dyn Scheduler> {
self.scheduler
.as_deref()
.filter(|_| !self.unscheduled.load(Ordering::SeqCst))
}
fn elevation(&self, _ctx: &Context) -> Elevation {
*self
.elevation
.lock()
.expect("a poisoned test host is a failed test")
}
fn administered_file(&self, path: &Path) -> Administered {
self.administered
.lock()
.expect("a poisoned test host is a failed test")
.get(path)
.cloned()
.unwrap_or(Administered::Unset)
}
fn managed_preference(&self, domain: &str, key: &str) -> Administered {
self.forced
.lock()
.expect("a poisoned test host is a failed test")
.get(&(domain.to_string(), key.to_string()))
.cloned()
.unwrap_or(Administered::Unset)
}
}