tenferro-runtime 0.7.0

Concrete tensor helpers, traced tensors, graph execution, and extension runtime support for tenferro.
Documentation
use std::error::Error as StdError;
use std::sync::Arc;

use crate::runtime::{
    CacheOwnerError, CacheOwnerFailure, CacheOwnerId, CacheStats, IdentityKind, RuntimeCacheError,
    RuntimeCacheOwner, RuntimeStateError,
};

#[derive(Debug, thiserror::Error)]
#[error("source a")]
struct SourceA;

#[derive(Debug, thiserror::Error)]
#[error("source b")]
struct SourceB;

#[derive(Debug)]
struct Owner;

impl RuntimeCacheOwner for Owner {
    fn cache_stats(&self) -> Result<CacheStats, CacheOwnerError> {
        Ok(CacheStats {
            entries: 2,
            retained_bytes: 128,
            hits: 3,
            misses: 4,
            evictions: 5,
            clears: 6,
        })
    }

    fn clear_caches(&self) -> Result<(), CacheOwnerError> {
        Ok(())
    }
}

fn owner_id(value: &str) -> CacheOwnerId {
    CacheOwnerId::new(value).unwrap_or_else(|error| panic!("{error}"))
}

fn owner_error_a() -> CacheOwnerError {
    CacheOwnerError::new(Arc::new(SourceA))
}

#[test]
fn cache_owner_ids_validate_namespaced_ascii_with_cache_owner_kind() {
    let owner = CacheOwnerId::new("tenferro.cache.owner").unwrap_or_else(|error| panic!("{error}"));
    assert_eq!(owner.as_str(), "tenferro.cache.owner");

    let error = CacheOwnerId::new("owner").unwrap_err();
    assert_eq!(error.kind(), IdentityKind::CacheOwner);
}

#[test]
fn cache_owner_ids_sort_stably_by_validated_text() {
    let mut owners = [
        owner_id("tenferro.cache.z"),
        owner_id("tenferro.cache.a"),
        owner_id("tenferro.cache.m"),
    ];

    owners.sort();

    assert_eq!(owners[0].as_str(), "tenferro.cache.a");
    assert_eq!(owners[1].as_str(), "tenferro.cache.m");
    assert_eq!(owners[2].as_str(), "tenferro.cache.z");
}

#[test]
fn cache_owner_error_preserves_arc_identity_and_error_source() {
    let source: Arc<dyn StdError + Send + Sync> = Arc::new(SourceA);
    let error = CacheOwnerError::new(source.clone());

    assert!(Arc::ptr_eq(error.source_arc(), &source));
    assert_eq!(
        StdError::source(&error)
            .expect("source should be preserved")
            .to_string(),
        "source a"
    );
    assert!(format!("{error:?}").contains("source a"));
}

#[test]
fn runtime_cache_owner_trait_is_object_safe_and_returns_stats() {
    let owner: Arc<dyn RuntimeCacheOwner> = Arc::new(Owner);
    let stats = owner
        .cache_stats()
        .unwrap_or_else(|error| panic!("{error}"));

    assert_eq!(
        stats,
        CacheStats {
            entries: 2,
            retained_bytes: 128,
            hits: 3,
            misses: 4,
            evictions: 5,
            clears: 6,
        }
    );
    owner
        .clear_caches()
        .unwrap_or_else(|error| panic!("{error}"));
}

#[test]
fn runtime_cache_error_aggregate_preserves_runtime_and_owner_failures_in_order() {
    let error = RuntimeCacheError::Aggregate {
        runtime: Some(RuntimeStateError::Poisoned {
            lock: "runtime.cache",
        }),
        owners: Box::new([
            CacheOwnerFailure {
                owner: owner_id("tenferro.cache.a"),
                source: owner_error_a(),
            },
            CacheOwnerFailure {
                owner: owner_id("tenferro.cache.b"),
                source: CacheOwnerError::new(Arc::new(SourceB)),
            },
        ]),
    };

    let RuntimeCacheError::Aggregate { runtime, owners } = error;
    assert!(matches!(
        runtime,
        Some(RuntimeStateError::Poisoned {
            lock: "runtime.cache"
        })
    ));
    assert_eq!(owners[0].owner.as_str(), "tenferro.cache.a");
    assert_eq!(owners[0].source.to_string(), "source a");
    assert_eq!(owners[1].owner.as_str(), "tenferro.cache.b");
    assert_eq!(owners[1].source.to_string(), "source b");
}