lenso-runtime-conformance 0.1.11

Kernel-owned conformance fixtures for Lenso Runtime Drivers and Execution Adapters.
Documentation
use lenso_app_plan::{
    AppComposition, CapabilityBinding, CapabilityEndpointPlan, CapabilityRequirementPlan,
    ModuleInstancePlan,
};
use lenso_kernel::{
    DeterministicDriver, EventAdmission, Kernel, NativeApp, NativeEventHandle, NativeStreamHandle,
    NoopModuleLifecycle, RuntimeDriver, RuntimeFailure, StreamEvent,
};
use lenso_runtime_conformance::{
    ConformanceExecutionAdapter, ConformanceModule, ConformanceModuleFactory,
    EVENT_PROBE_CAPABILITY_ID, EVENT_PROBE_CONSUMER_PACKAGE_ID, EVENT_PROBE_DESCRIPTOR_VERSION,
    EVENT_PROBE_OPERATION, EVENT_PROBE_PROVIDER_PACKAGE_ID, EventProbe, EventProbeProviderFactory,
    EventProbeRecorder, EventProbeValue, STREAM_PROBE_CAPABILITY_ID,
    STREAM_PROBE_CONSUMER_PACKAGE_ID, STREAM_PROBE_DESCRIPTOR_VERSION, STREAM_PROBE_OPERATION,
    STREAM_PROBE_PROVIDER_PACKAGE_ID, StreamProbe, StreamProbeError, StreamProbeMessage,
    StreamProbeOpen, StreamProbeProviderFactory,
};

#[derive(Debug)]
struct EmptyFactory {
    package_id: &'static str,
}

impl ConformanceModuleFactory for EmptyFactory {
    fn package_id(&self) -> &'static str {
        self.package_id
    }

    fn instantiate(
        &self,
        _instance: &ModuleInstancePlan,
    ) -> Result<ConformanceModule, RuntimeFailure> {
        Ok(ConformanceModule::with_lifecycle(
            Vec::new(),
            NoopModuleLifecycle,
        ))
    }
}

fn interaction_plan() -> lenso_app_plan::ResolvedAppPlan {
    AppComposition::new(
        vec![
            ModuleInstancePlan::new("stream-consumer", STREAM_PROBE_CONSUMER_PACKAGE_ID)
                .with_requirement(CapabilityRequirementPlan::one(
                    STREAM_PROBE_CAPABILITY_ID,
                    STREAM_PROBE_DESCRIPTOR_VERSION,
                )),
            ModuleInstancePlan::new("stream-provider", STREAM_PROBE_PROVIDER_PACKAGE_ID)
                .with_capability(
                    CapabilityEndpointPlan::new(
                        STREAM_PROBE_CAPABILITY_ID,
                        STREAM_PROBE_DESCRIPTOR_VERSION,
                        [STREAM_PROBE_OPERATION],
                    )
                    .with_stream_operation(STREAM_PROBE_OPERATION)
                    .with_limits(0, 1),
                ),
            ModuleInstancePlan::new("event-consumer", EVENT_PROBE_CONSUMER_PACKAGE_ID)
                .with_requirement(CapabilityRequirementPlan::many(
                    EVENT_PROBE_CAPABILITY_ID,
                    EVENT_PROBE_DESCRIPTOR_VERSION,
                )),
            ModuleInstancePlan::new("event-provider-a", EVENT_PROBE_PROVIDER_PACKAGE_ID)
                .with_capability(
                    CapabilityEndpointPlan::new(
                        EVENT_PROBE_CAPABILITY_ID,
                        EVENT_PROBE_DESCRIPTOR_VERSION,
                        [EVENT_PROBE_OPERATION],
                    )
                    .with_event_operation(EVENT_PROBE_OPERATION)
                    .with_event_capacity(2),
                ),
            ModuleInstancePlan::new("event-provider-b", EVENT_PROBE_PROVIDER_PACKAGE_ID)
                .with_capability(
                    CapabilityEndpointPlan::new(
                        EVENT_PROBE_CAPABILITY_ID,
                        EVENT_PROBE_DESCRIPTOR_VERSION,
                        [EVENT_PROBE_OPERATION],
                    )
                    .with_event_operation(EVENT_PROBE_OPERATION)
                    .with_event_capacity(2),
                ),
        ],
        vec![
            CapabilityBinding::new(
                "stream-consumer",
                STREAM_PROBE_CAPABILITY_ID,
                STREAM_PROBE_DESCRIPTOR_VERSION,
                "stream-provider",
            ),
            CapabilityBinding::new(
                "event-consumer",
                EVENT_PROBE_CAPABILITY_ID,
                EVENT_PROBE_DESCRIPTOR_VERSION,
                "event-provider-a",
            ),
            CapabilityBinding::new(
                "event-consumer",
                EVENT_PROBE_CAPABILITY_ID,
                EVENT_PROBE_DESCRIPTOR_VERSION,
                "event-provider-b",
            ),
        ],
    )
    .resolve()
    .expect("the interaction-complete conformance Plan should resolve")
}

fn exercise_stream(
    driver: &DeterministicDriver,
    app: &NativeApp,
) -> NativeStreamHandle<StreamProbe> {
    let stream_handle = app
        .stream_handle::<StreamProbe>("stream-consumer")
        .expect("the stream conformance binding should resolve");
    let stream = driver
        .run(stream_handle.open(
            STREAM_PROBE_OPERATION,
            StreamProbeOpen {
                value: "session".to_owned(),
            },
        ))
        .expect("stream open should not fail")
        .expect("stream open should not return a Domain Error");
    driver
        .run(stream.send(StreamProbeMessage {
            sequence: 1,
            value: "value".to_owned(),
        }))
        .expect("stream send should succeed");
    assert_eq!(
        driver.run(stream.receive()),
        Ok(StreamEvent::Message(StreamProbeMessage {
            sequence: 1,
            value: "session: value".to_owned(),
        }))
    );
    driver
        .run(stream.close_send())
        .expect("stream half-close should succeed");
    assert_eq!(
        driver.run(stream.receive()),
        Ok(StreamEvent::PeerHalfClosed)
    );
    assert_eq!(
        driver.run(stream.receive()),
        Ok(StreamEvent::Terminal(Ok(())))
    );
    drop(stream);
    assert!(matches!(
        driver
            .run(stream_handle.open(
                STREAM_PROBE_OPERATION,
                StreamProbeOpen {
                    value: "reject".to_owned(),
                },
            ))
            .expect("a Domain Error is not a Runtime Failure"),
        Err(StreamProbeError::Rejected)
    ));
    stream_handle
}

fn exercise_event(
    driver: &DeterministicDriver,
    app: &NativeApp,
    recorder: &EventProbeRecorder,
) -> NativeEventHandle<EventProbe> {
    let event_handle = app
        .many_event_handle::<EventProbe>("event-consumer")
        .expect("the Event conformance bindings should resolve");
    let admissions = driver.run(event_handle.publish(
        EVENT_PROBE_OPERATION,
        EventProbeValue {
            sequence: 1,
            value: "value".to_owned(),
        },
    ));
    assert_eq!(
        admissions
            .iter()
            .map(|result| (result.subscriber_instance(), result.admission()))
            .collect::<Vec<_>>(),
        vec![
            ("event-provider-a", EventAdmission::Accepted),
            ("event-provider-b", EventAdmission::Accepted),
        ]
    );
    driver.run(async { driver.yield_now().await });
    assert_eq!(
        recorder.values(),
        vec![
            EventProbeValue {
                sequence: 1,
                value: "value".to_owned(),
            },
            EventProbeValue {
                sequence: 1,
                value: "value".to_owned(),
            },
        ]
    );
    event_handle
}

#[test]
fn conformance_adapter_exercises_stream_event_and_shutdown_through_one_interface() {
    let driver = DeterministicDriver::new();
    let recorder = EventProbeRecorder::default();
    let adapter = ConformanceExecutionAdapter::new()
        .with_factory(EmptyFactory {
            package_id: STREAM_PROBE_CONSUMER_PACKAGE_ID,
        })
        .with_factory(StreamProbeProviderFactory)
        .with_factory(EmptyFactory {
            package_id: EVENT_PROBE_CONSUMER_PACKAGE_ID,
        })
        .with_factory(EventProbeProviderFactory::new(recorder.clone()));
    let app = driver
        .run(Kernel::start_native(
            interaction_plan(),
            driver.clone(),
            adapter,
        ))
        .expect("the interaction-complete conformance App should start");

    let stream_handle = exercise_stream(&driver, &app);
    let event_handle = exercise_event(&driver, &app, &recorder);

    assert_eq!(
        driver.run(app.shutdown(std::time::Duration::from_secs(1))),
        lenso_kernel::ShutdownOutcome::Clean
    );
    assert!(matches!(
        driver.run(stream_handle.open(
            STREAM_PROBE_OPERATION,
            StreamProbeOpen {
                value: "late".to_owned(),
            },
        )),
        Err(RuntimeFailure::AdmissionClosed)
    ));
    let late_admissions = driver.run(event_handle.publish(
        EVENT_PROBE_OPERATION,
        EventProbeValue {
            sequence: 2,
            value: "late".to_owned(),
        },
    ));
    assert!(
        late_admissions
            .iter()
            .all(|result| result.admission() == EventAdmission::Unavailable)
    );
}