tenferro-cpu 0.4.0

CPU backend, kernels, provider selection, and CPU resource pools for tenferro.
use super::*;
use crate::{CpuBackendKind, CpuId, CpuSet, CpuTopology, NumaNodeId};

#[test]
fn faer_resolves_auto_and_explicit_managed_placements() {
    let topology = two_node_fixture();

    assert!(matches!(
        resolve_placement_with_affinity(CpuBackendKind::Faer, CpuPlacement::Auto, &topology, true,)
            .unwrap(),
        ResolvedCpuExecution::Managed(ResolvedCpuPlacement::AllAllowed { .. })
    ));
    assert!(matches!(
        resolve_placement_with_affinity(
            CpuBackendKind::Faer,
            CpuPlacement::NumaNode(NumaNodeId::new(7)),
            &topology,
            true,
        )
        .unwrap(),
        ResolvedCpuExecution::Managed(ResolvedCpuPlacement::NumaNode { id, .. })
            if id == NumaNodeId::new(7)
    ));
}

#[test]
fn explicit_external_provider_placement_never_falls_back() {
    let topology = two_node_fixture();
    assert!(matches!(
        resolve_placement(
            CpuBackendKind::Blas,
            CpuPlacement::NumaNode(NumaNodeId::new(2)),
            &topology,
        ),
        Err(CpuPlacementError::ExternalProviderAffinityUnmanaged { .. })
    ));
    assert!(matches!(
        resolve_placement(CpuBackendKind::Blas, CpuPlacement::AllAllowed, &topology),
        Err(CpuPlacementError::ExternalProviderAffinityUnmanaged { .. })
    ));
    assert_eq!(
        resolve_placement(CpuBackendKind::Blas, CpuPlacement::Auto, &topology).unwrap(),
        ResolvedCpuExecution::ProviderDefaultExclusive,
    );
}

#[test]
fn explicit_unknown_node_is_an_error_without_fallback() {
    let topology = two_node_fixture();
    assert!(matches!(
        resolve_placement_with_affinity(
            CpuBackendKind::Faer,
            CpuPlacement::NumaNode(NumaNodeId::new(9)),
            &topology,
            true,
        ),
        Err(CpuPlacementError::UnknownNumaNode { node, .. })
            if node == NumaNodeId::new(9)
    ));
}

#[test]
fn explicit_node_requires_discovered_numa_domains() {
    let topology = CpuTopology::from_discovered(cpu_set([4, 5]), []).unwrap();
    assert!(matches!(
        resolve_placement_with_affinity(
            CpuBackendKind::Faer,
            CpuPlacement::NumaNode(NumaNodeId::new(0)),
            &topology,
            true,
        ),
        Err(CpuPlacementError::NumaDiscoveryUnavailable { .. })
    ));
}

#[test]
fn unavailable_managed_affinity_keeps_auto_compatible_and_rejects_explicit_placement() {
    let topology = two_node_fixture();

    assert_eq!(
        resolve_placement_with_affinity(
            CpuBackendKind::Faer,
            CpuPlacement::Auto,
            &topology,
            false,
        )
        .unwrap(),
        ResolvedCpuExecution::Compatibility
    );
    assert!(matches!(
        resolve_placement_with_affinity(
            CpuBackendKind::Faer,
            CpuPlacement::AllAllowed,
            &topology,
            false,
        ),
        Err(CpuPlacementError::ManagedAffinityUnavailable { .. })
    ));
}

#[test]
fn unregistered_external_placement_error_preserves_the_request() {
    let requested = CpuPlacement::NumaNode(NumaNodeId::new(12));
    let error = CpuPlacementError::UnregisteredExternalPlacement { requested };

    assert!(matches!(
        &error,
        CpuPlacementError::UnregisteredExternalPlacement {
            requested: actual
        } if *actual == requested
    ));
    assert!(error.to_string().contains("12"));
}

fn two_node_fixture() -> CpuTopology {
    CpuTopology::from_discovered(
        cpu_set([8, 9, 12, 13]),
        [
            (NumaNodeId::new(2), cpu_set([8, 9])),
            (NumaNodeId::new(7), cpu_set([12, 13])),
        ],
    )
    .unwrap()
}

fn cpu_set<const N: usize>(cpus: [usize; N]) -> CpuSet {
    CpuSet::new(cpus.map(CpuId::new)).unwrap()
}