use std::fmt;
use std::sync::Arc;
use harn_clock::{Clock, RealClock};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HarnessKind {
Root,
Stdio,
Clock,
Fs,
Env,
Random,
Net,
}
impl HarnessKind {
pub const fn type_name(self) -> &'static str {
match self {
HarnessKind::Root => "Harness",
HarnessKind::Stdio => "HarnessStdio",
HarnessKind::Clock => "HarnessClock",
HarnessKind::Fs => "HarnessFs",
HarnessKind::Env => "HarnessEnv",
HarnessKind::Random => "HarnessRandom",
HarnessKind::Net => "HarnessNet",
}
}
pub const fn field_name(self) -> Option<&'static str> {
match self {
HarnessKind::Root => None,
HarnessKind::Stdio => Some("stdio"),
HarnessKind::Clock => Some("clock"),
HarnessKind::Fs => Some("fs"),
HarnessKind::Env => Some("env"),
HarnessKind::Random => Some("random"),
HarnessKind::Net => Some("net"),
}
}
pub fn from_field_name(name: &str) -> Option<Self> {
match name {
"stdio" => Some(HarnessKind::Stdio),
"clock" => Some(HarnessKind::Clock),
"fs" => Some(HarnessKind::Fs),
"env" => Some(HarnessKind::Env),
"random" => Some(HarnessKind::Random),
"net" => Some(HarnessKind::Net),
_ => None,
}
}
pub const SUB_HANDLES: &'static [HarnessKind] = &[
HarnessKind::Stdio,
HarnessKind::Clock,
HarnessKind::Fs,
HarnessKind::Env,
HarnessKind::Random,
HarnessKind::Net,
];
pub const ALL: &'static [HarnessKind] = &[
HarnessKind::Root,
HarnessKind::Stdio,
HarnessKind::Clock,
HarnessKind::Fs,
HarnessKind::Env,
HarnessKind::Random,
HarnessKind::Net,
];
}
#[derive(Debug)]
pub struct HarnessInner {
clock: Arc<dyn Clock>,
}
impl HarnessInner {
pub fn clock(&self) -> &Arc<dyn Clock> {
&self.clock
}
}
#[derive(Debug, Clone)]
pub struct Harness {
inner: Arc<HarnessInner>,
}
impl Harness {
pub fn real() -> Self {
Self::with_clock(RealClock::arc())
}
pub fn with_clock(clock: Arc<dyn Clock>) -> Self {
Self {
inner: Arc::new(HarnessInner { clock }),
}
}
pub fn stdio(&self) -> HarnessStdio {
HarnessStdio {
inner: Arc::clone(&self.inner),
}
}
pub fn clock(&self) -> HarnessClock {
HarnessClock {
inner: Arc::clone(&self.inner),
}
}
pub fn fs(&self) -> HarnessFs {
HarnessFs {
inner: Arc::clone(&self.inner),
}
}
pub fn env(&self) -> HarnessEnv {
HarnessEnv {
inner: Arc::clone(&self.inner),
}
}
pub fn random(&self) -> HarnessRandom {
HarnessRandom {
inner: Arc::clone(&self.inner),
}
}
pub fn net(&self) -> HarnessNet {
HarnessNet {
inner: Arc::clone(&self.inner),
}
}
pub fn into_vm_value(self) -> crate::value::VmValue {
crate::value::VmValue::Harness(VmHarness {
inner: self.inner,
kind: HarnessKind::Root,
})
}
}
impl Default for Harness {
fn default() -> Self {
Self::real()
}
}
#[derive(Debug, Clone)]
pub struct HarnessStdio {
inner: Arc<HarnessInner>,
}
#[derive(Debug, Clone)]
pub struct HarnessClock {
inner: Arc<HarnessInner>,
}
impl HarnessClock {
pub fn clock(&self) -> &Arc<dyn Clock> {
self.inner.clock()
}
}
#[derive(Debug, Clone)]
pub struct HarnessFs {
inner: Arc<HarnessInner>,
}
#[derive(Debug, Clone)]
pub struct HarnessEnv {
inner: Arc<HarnessInner>,
}
#[derive(Debug, Clone)]
pub struct HarnessRandom {
inner: Arc<HarnessInner>,
}
#[derive(Debug, Clone)]
pub struct HarnessNet {
inner: Arc<HarnessInner>,
}
macro_rules! sub_handle_inner {
($($ty:ty),* $(,)?) => {
$(
impl $ty {
#[allow(dead_code)]
pub(crate) fn inner(&self) -> &Arc<HarnessInner> {
&self.inner
}
}
)*
};
}
sub_handle_inner!(
HarnessStdio,
HarnessFs,
HarnessEnv,
HarnessRandom,
HarnessNet,
);
impl HarnessClock {
#[allow(dead_code)]
pub(crate) fn inner(&self) -> &Arc<HarnessInner> {
&self.inner
}
}
#[derive(Clone)]
pub struct VmHarness {
inner: Arc<HarnessInner>,
kind: HarnessKind,
}
impl VmHarness {
pub fn kind(&self) -> HarnessKind {
self.kind
}
pub fn type_name(&self) -> &'static str {
self.kind.type_name()
}
pub fn inner(&self) -> &Arc<HarnessInner> {
&self.inner
}
pub fn sub_handle(&self, field: &str) -> Option<VmHarness> {
if self.kind != HarnessKind::Root {
return None;
}
let kind = HarnessKind::from_field_name(field)?;
Some(VmHarness {
inner: Arc::clone(&self.inner),
kind,
})
}
}
impl fmt::Debug for VmHarness {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("VmHarness")
.field("kind", &self.kind)
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn real_constructs_without_panic() {
let _harness = Harness::real();
}
#[test]
fn sub_handles_share_inner_state() {
let harness = Harness::real();
let stdio_inner = Arc::as_ptr(harness.stdio().inner());
let clock_inner = Arc::as_ptr(harness.clock().inner());
assert_eq!(stdio_inner, clock_inner, "sub-handles share Arc<Inner>");
}
#[test]
fn kinds_round_trip_through_field_names() {
for kind in HarnessKind::SUB_HANDLES {
let field = kind.field_name().unwrap();
assert_eq!(HarnessKind::from_field_name(field), Some(*kind));
}
assert!(HarnessKind::from_field_name("nope").is_none());
assert!(HarnessKind::Root.field_name().is_none());
}
#[test]
fn vm_harness_property_access_returns_sub_handle() {
let root = match Harness::real().into_vm_value() {
crate::value::VmValue::Harness(h) => h,
other => panic!("expected Harness variant, got {}", other.type_name()),
};
let stdio = root.sub_handle("stdio").expect("stdio sub-handle");
assert_eq!(stdio.kind(), HarnessKind::Stdio);
assert!(stdio.sub_handle("clock").is_none(), "nested access denied");
assert!(root.sub_handle("not_a_field").is_none());
}
}