mod support;
use ridl_loopback::Loopback;
use ridl_rt::contract::{CatalogHash, CatalogRef, InterfaceNo, Ordinal};
use ridl_rt::port::Caller;
use ridl_rt::sample::Provenance;
const CATALOG: CatalogRef = CatalogRef {
name: "face.demo",
hash: CatalogHash([0u8; 32]),
};
fn loopback() -> Loopback {
Loopback::new(CATALOG)
}
#[allow(
dead_code,
reason = "not every generated item is named from this file's own tests; \
the byte-equality guard is what pins the emitter's output"
)]
#[allow(
clippy::derivable_impls,
reason = "the domain-type Default emission (crate::defaults, predating M3) writes a manual \
impl rather than #[derive(Default)]; this is the first place that output is \
compiled in-tree, so it is the first place this lint sees it. Fixing the emitter \
is outside Lane M stage M3's scope: it is baseline domain-type emission every \
backend consumer shares, not face- or descriptor-specific."
)]
#[allow(
clippy::upper_case_acronyms,
reason = "an enum variant keeps its typl SCREAMING_SNAKE spelling by design \
(crate::emit_enum's own doc comment), predating M3; this file is the first place \
that spelling is compiled in-tree"
)]
mod generated {
include!("generated/interaction_face.rs");
}
struct TestProvider {
set_level_calls: Vec<i64>,
next_average: i64,
}
impl TestProvider {
fn new(next_average: i64) -> Self {
TestProvider {
set_level_calls: Vec::new(),
next_average,
}
}
}
impl generated::cabin::Provider for TestProvider {
fn set_level(&mut self, level: &generated::Level) {
self.set_level_calls.push(level.get());
}
fn average(&mut self, _window: &generated::Window) -> generated::Average {
generated::Average::new_unchecked(self.next_average)
}
}
#[test]
fn generated_interaction_face_matches_the_emitter() {
let package = support::ir::compile_fixture("interaction_face.ridl");
let face = ridl_backend_rust::generate_face(&package)
.expect("generate_face")
.rust_source;
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/generated/interaction_face.rs");
if std::env::var_os("RIDL_UPDATE_GENERATED").is_some() {
std::fs::write(&path, &face)
.unwrap_or_else(|error| panic!("write {}: {error}", path.display()));
return;
}
let checked_in = std::fs::read_to_string(&path)
.unwrap_or_else(|error| panic!("read {}: {error}", path.display()));
assert_eq!(
face, checked_in,
"generated interaction_face.rs is stale; regenerate it with \
RIDL_UPDATE_GENERATED=1 cargo test -p ridl-backend-rust --test interaction_face"
);
}
#[test]
fn round_trip_signal_publish_and_read() {
let mut port = loopback();
{
let mut publisher = generated::cabin::Publisher::new(&mut port);
publisher
.temperature(generated::Temperature::new_unchecked(21))
.expect("set");
publisher.commit();
}
let client = generated::cabin::Client::new(&mut port);
let sample = client.temperature().expect("read");
assert_eq!(sample.value.get(), 21);
assert_eq!(sample.provenance, Provenance::Live);
}
#[test]
fn round_trip_event_raise_and_receive() {
let mut port = loopback();
{
let mut client = generated::cabin::Client::new(&mut port);
client.subscribe_warning().expect("subscribe");
}
{
let mut publisher = generated::cabin::Publisher::new(&mut port);
publisher
.warning(generated::Warning {
code: generated::Level::new_unchecked(5),
health: generated::Health::WARN,
})
.expect("raise");
}
let mut client = generated::cabin::Client::new(&mut port);
let event = client
.next_event()
.expect("next_event")
.expect("an occurrence is waiting");
match event {
generated::cabin::Event::Warning(occurrence) => {
let warning = occurrence.payload.expect("payload verifies");
assert_eq!(warning.code.get(), 5);
assert!(matches!(warning.health, generated::Health::WARN));
}
}
}
#[test]
fn round_trip_command_is_acknowledged() {
let mut port = loopback();
let correlation = {
let mut client = generated::cabin::Client::new(&mut port);
client
.set_level(generated::Level::new_unchecked(42))
.expect("send")
};
let mut provider = TestProvider::new(0);
let mut buf = [0u8; generated::Cabin::MAX_BUFFER_SIZE];
let settled = generated::cabin::dispatch(&mut port, &mut provider, &mut buf);
assert_eq!(settled, 1);
assert_eq!(provider.set_level_calls, vec![42]);
let mut client = generated::cabin::Client::new(&mut port);
assert_eq!(client.set_level_ack(correlation), Some(Ok(())));
}
#[test]
fn round_trip_query_reply_is_delivered() {
let mut port = loopback();
let correlation = {
let mut client = generated::cabin::Client::new(&mut port);
client
.average(generated::Window::new_unchecked(10))
.expect("send")
};
let mut provider = TestProvider::new(7);
let mut buf = [0u8; generated::Cabin::MAX_BUFFER_SIZE];
let settled = generated::cabin::dispatch(&mut port, &mut provider, &mut buf);
assert_eq!(settled, 1);
let mut client = generated::cabin::Client::new(&mut port);
let reply = client
.average_reply(correlation)
.expect("reply read")
.expect("reply is known")
.expect("no call error");
assert_eq!(reply.get(), 7);
}
#[test]
fn round_trip_failing_require_settles_precondition_failed() {
use ridl_rt::contract::Interaction;
use ridl_rt::payload::Ref;
let mut port = loopback();
let level = generated::Level::new_unchecked(100);
let mut encode_buf = [0u8; generated::Cabin::MAX_BUFFER_SIZE];
let bytes = Ref::<generated::Level, generated::Wire>::encode(&level, &mut encode_buf)
.expect("encode")
.bytes();
let ordinal = <generated::CabinSetLevel as Interaction>::MEMBER.ordinal;
let correlation = port
.command(
<generated::Cabin as ridl_rt::contract::Interface>::NUMBER,
ordinal,
bytes,
)
.expect("send");
let mut provider = TestProvider::new(0);
let mut buf = [0u8; generated::Cabin::MAX_BUFFER_SIZE];
let settled = generated::cabin::dispatch(&mut port, &mut provider, &mut buf);
assert_eq!(settled, 1, "an outcome was still settled");
assert!(
provider.set_level_calls.is_empty(),
"the provider must not be called when require fails"
);
let outcome = port.ack(correlation).expect("settled");
assert_eq!(
outcome,
Err(ridl_rt::error::CallError::Contract(
ridl_rt::error::Contract::PreconditionFailed
)),
);
}
#[test]
fn round_trip_client_set_level_short_circuits_on_failing_require() {
let mut port = loopback();
let result = {
let mut client = generated::cabin::Client::new(&mut port);
client.set_level(generated::Level::new_unchecked(100))
};
assert_eq!(
result,
Err(ridl_rt::port::SendError::Contract(
ridl_rt::error::Contract::PreconditionFailed
)),
"a failing require is reported before anything is sent",
);
let mut provider = TestProvider::new(0);
let mut buf = [0u8; generated::Cabin::MAX_BUFFER_SIZE];
let settled = generated::cabin::dispatch(&mut port, &mut provider, &mut buf);
assert_eq!(
settled, 0,
"nothing was sent to the port, so dispatch settles nothing"
);
assert!(provider.set_level_calls.is_empty());
}
#[test]
fn round_trip_client_average_short_circuits_on_failing_require() {
let mut port = loopback();
let result = {
let mut client = generated::cabin::Client::new(&mut port);
client.average(generated::Window::new_unchecked(0))
};
assert_eq!(
result,
Err(ridl_rt::port::SendError::Contract(
ridl_rt::error::Contract::PreconditionFailed
)),
"a failing require is reported before anything is sent",
);
let mut provider = TestProvider::new(0);
let mut buf = [0u8; generated::Cabin::MAX_BUFFER_SIZE];
let settled = generated::cabin::dispatch(&mut port, &mut provider, &mut buf);
assert_eq!(
settled, 0,
"nothing was sent to the port, so dispatch settles nothing"
);
}
#[test]
fn round_trip_failing_ensure_settles_contract_broken() {
let mut port = loopback();
let correlation = {
let mut client = generated::cabin::Client::new(&mut port);
client
.average(generated::Window::new_unchecked(1))
.expect("send")
};
let mut provider = TestProvider::new(-1);
let mut buf = [0u8; generated::Cabin::MAX_BUFFER_SIZE];
let settled = generated::cabin::dispatch(&mut port, &mut provider, &mut buf);
assert_eq!(settled, 1, "an outcome was still settled");
let mut client = generated::cabin::Client::new(&mut port);
let reply = client
.average_reply(correlation)
.expect("reply read")
.expect("reply is known");
assert!(matches!(
reply,
Err(ridl_rt::error::CallError::Contract(
ridl_rt::error::Contract::ContractBroken
))
));
}
#[test]
fn round_trip_dispatch_counts_only_accepted_settlements() {
let mut port = loopback();
let first = {
let mut client = generated::cabin::Client::new(&mut port);
client
.set_level(generated::Level::new_unchecked(1))
.expect("send first")
};
port.fail_next_settle();
let mut provider = TestProvider::new(0);
let mut buf = [0u8; generated::Cabin::MAX_BUFFER_SIZE];
let settled_first = generated::cabin::dispatch(&mut port, &mut provider, &mut buf);
assert_eq!(
settled_first, 0,
"the first claim's settlement fails and dispatch does not count it"
);
assert_eq!(
port.ack(first.0),
None,
"a failed settle records no outcome for the first claim's correlation"
);
let second = {
let mut client = generated::cabin::Client::new(&mut port);
client
.set_level(generated::Level::new_unchecked(2))
.expect("send second")
};
let settled_second = generated::cabin::dispatch(&mut port, &mut provider, &mut buf);
assert_eq!(
settled_second, 1,
"the second claim's settlement succeeds and dispatch counts it"
);
assert_eq!(
port.ack(second.0),
Some(Ok(())),
"the successful settlement is observable as accepted through ack"
);
assert_eq!(settled_first + settled_second, 1);
assert_eq!(provider.set_level_calls, vec![1, 2]);
}
#[test]
fn round_trip_short_caller_buffer_returns_zero_without_consuming_a_claim() {
let mut port = loopback();
{
let mut client = generated::cabin::Client::new(&mut port);
client
.set_level(generated::Level::new_unchecked(1))
.expect("send");
}
let mut provider = TestProvider::new(0);
let mut short = [0u8; 1];
let settled = generated::cabin::dispatch(&mut port, &mut provider, &mut short);
assert_eq!(settled, 0, "a short buffer settles nothing");
assert!(provider.set_level_calls.is_empty());
let mut buf = [0u8; generated::Cabin::MAX_BUFFER_SIZE];
let settled = generated::cabin::dispatch(&mut port, &mut provider, &mut buf);
assert_eq!(settled, 1);
assert_eq!(provider.set_level_calls, vec![1]);
}
#[test]
fn the_encoded_bytes_are_not_a_prefix_of_the_buffer() {
use ridl_rt::payload::{Payload, Ref};
let level = generated::Level::new_unchecked(42);
let mut buf = [0u8; <generated::Level as Payload<generated::Wire>>::MAX_SIZE];
let encoded = Ref::<generated::Level, generated::Wire>::encode(&level, &mut buf)
.expect("encode")
.bytes()
.to_vec();
assert!(
encoded.len() < buf.len(),
"the bound leaves room ahead of the value, which is what makes the \
prefix and the subslice different slices"
);
assert_ne!(
encoded.as_slice(),
&buf[..encoded.len()],
"the encoder built at the tail, so the equally long prefix is not \
the value"
);
<generated::Level as Payload<generated::Wire>>::verify(&buf[..encoded.len()])
.expect_err("the prefix of the buffer is not a payload");
let checked =
Ref::<generated::Level, generated::Wire>::verify(&encoded).expect("the subslice is");
assert_eq!(checked.decode().get(), 42);
}
#[test]
fn round_trip_unrecognized_ordinal_settles_unknown_interaction() {
let mut port = loopback();
let correlation = port
.command(
<generated::Cabin as ridl_rt::contract::Interface>::NUMBER,
ridl_rt::contract::Ordinal(99),
&[],
)
.expect("send");
let mut provider = TestProvider::new(0);
let mut buf = [0u8; generated::Cabin::MAX_BUFFER_SIZE];
let settled = generated::cabin::dispatch(&mut port, &mut provider, &mut buf);
assert_eq!(settled, 1, "an outcome was still settled");
assert!(
provider.set_level_calls.is_empty(),
"the provider must not be called for an unrecognized ordinal"
);
let outcome = port.ack(correlation).expect("settled");
assert_eq!(
outcome,
Err(ridl_rt::error::CallError::Contract(
ridl_rt::error::Contract::UnknownInteraction
)),
);
}
#[test]
fn round_trip_foreign_interface_number_settles_unknown_interaction() {
use ridl_rt::contract::Interaction;
use ridl_rt::payload::Ref;
let mut port = loopback();
let level = generated::Level::new_unchecked(50);
let mut encode_buf = [0u8; generated::Cabin::MAX_BUFFER_SIZE];
let bytes = Ref::<generated::Level, generated::Wire>::encode(&level, &mut encode_buf)
.expect("encode")
.bytes();
let ordinal = <generated::CabinSetLevel as Interaction>::MEMBER.ordinal;
let correlation = port
.command(
<generated::Horn as ridl_rt::contract::Interface>::NUMBER,
ordinal,
bytes,
)
.expect("send");
let mut provider = TestProvider::new(0);
let mut buf = [0u8; generated::Cabin::MAX_BUFFER_SIZE];
let settled = generated::cabin::dispatch(&mut port, &mut provider, &mut buf);
assert_eq!(settled, 1, "an outcome was still settled");
assert!(
provider.set_level_calls.is_empty(),
"the provider must not be called for a foreign interface number"
);
let outcome = port.ack(correlation).expect("settled");
assert_eq!(
outcome,
Err(ridl_rt::error::CallError::Contract(
ridl_rt::error::Contract::UnknownInteraction
)),
);
}
#[test]
fn round_trip_malformed_argument_bytes_settle_transport_corrupt() {
use ridl_rt::contract::Interaction;
let mut port = loopback();
let ordinal = <generated::CabinSetLevel as Interaction>::MEMBER.ordinal;
let correlation = port
.command(
<generated::Cabin as ridl_rt::contract::Interface>::NUMBER,
ordinal,
&[1, 2, 3],
)
.expect("send");
let mut provider = TestProvider::new(0);
let mut buf = [0u8; generated::Cabin::MAX_BUFFER_SIZE];
let settled = generated::cabin::dispatch(&mut port, &mut provider, &mut buf);
assert_eq!(settled, 1, "an outcome was still settled");
assert!(
provider.set_level_calls.is_empty(),
"the provider must not be called when the argument bytes fail to verify"
);
let outcome = port.ack(correlation).expect("settled");
assert_eq!(
outcome,
Err(ridl_rt::error::CallError::Transport(
ridl_rt::error::Transport::Corrupt
)),
);
}
#[test]
fn round_trip_out_of_range_argument_settles_invalid_value() {
use ridl_rt::contract::Interaction;
use ridl_rt::payload::Ref;
let mut port = loopback();
let level = generated::Level::new_unchecked(200);
let mut encode_buf = [0u8; generated::Cabin::MAX_BUFFER_SIZE];
let bytes = Ref::<generated::Level, generated::Wire>::encode(&level, &mut encode_buf)
.expect("encode")
.bytes();
let ordinal = <generated::CabinSetLevel as Interaction>::MEMBER.ordinal;
let correlation = port
.command(
<generated::Cabin as ridl_rt::contract::Interface>::NUMBER,
ordinal,
bytes,
)
.expect("send");
let mut provider = TestProvider::new(0);
let mut buf = [0u8; generated::Cabin::MAX_BUFFER_SIZE];
let settled = generated::cabin::dispatch(&mut port, &mut provider, &mut buf);
assert_eq!(settled, 1, "an outcome was still settled");
assert!(
provider.set_level_calls.is_empty(),
"the provider must not be called when the argument value breaks its typl constraints"
);
let outcome = port.ack(correlation).expect("settled");
assert_eq!(
outcome,
Err(ridl_rt::error::CallError::Contract(
ridl_rt::error::Contract::InvalidValue(ridl_rt::payload::Violation {
type_name: "Level",
rule: ridl_rt::payload::Rule::Range,
})
)),
);
}
struct MinimalSignalOnlyPort {
catalog: ridl_rt::contract::CatalogRef,
}
impl MinimalSignalOnlyPort {
fn new(package_name: &'static str) -> Self {
MinimalSignalOnlyPort {
catalog: ridl_rt::contract::CatalogRef {
name: package_name,
hash: ridl_rt::contract::CatalogHash([0u8; 32]),
},
}
}
}
impl ridl_rt::port::Attached for MinimalSignalOnlyPort {
fn catalog(&self) -> &ridl_rt::contract::CatalogRef {
&self.catalog
}
}
impl ridl_rt::port::SignalReader for MinimalSignalOnlyPort {
fn read(
&self,
_iface: InterfaceNo,
_ord: Ordinal,
_out: &mut [u8],
) -> Result<ridl_rt::port::RawSample, ridl_rt::port::ReadError> {
Ok(ridl_rt::port::RawSample {
provenance: Provenance::Init,
freshness: ridl_rt::sample::Freshness::Unbounded,
envelope: ridl_rt::sample::Envelope {
stamp: ridl_rt::sample::Timestamp(0),
seq: 0,
},
len: 0,
})
}
}
#[test]
fn ra19_a_minimal_signal_only_port_constructs_the_signal_only_client() {
let mut port = MinimalSignalOnlyPort::new("face.demo");
let client = generated::horn::Client::new(&mut port);
let sample = client.active().expect("read");
assert_eq!(
sample.provenance,
Provenance::Invalid(ridl_rt::sample::Cause::Detected(
ridl_rt::sample::Detection::Corrupt
)),
"a zero-length sample is too short for any FlatBuffers buffer",
);
}
#[test]
fn generated_new_refuses_a_value_above_the_range() {
use ridl_rt::payload::{Rule, Violation};
assert_eq!(
generated::Level::new(200).err(),
Some(Violation {
type_name: "Level",
rule: Rule::Range,
})
);
}
#[test]
fn generated_new_refuses_a_value_below_the_range() {
use ridl_rt::payload::{Rule, Violation};
assert_eq!(
generated::Level::new(-1).err(),
Some(Violation {
type_name: "Level",
rule: Rule::Range,
})
);
}
#[test]
fn generated_new_accepts_a_value_in_the_range() {
let Ok(level) = generated::Level::new(50) else {
panic!("50 is inside [0, 100]");
};
assert_eq!(level.get(), 50);
assert_eq!(
generated::Level::new(0).ok().map(generated::Level::get),
Some(0)
);
assert_eq!(
generated::Level::new(100).ok().map(generated::Level::get),
Some(100)
);
}
#[test]
fn generated_try_from_delegates_to_new() {
use ridl_rt::payload::{Rule, Violation};
assert_eq!(
generated::Level::try_from(200).err(),
Some(Violation {
type_name: "Level",
rule: Rule::Range,
})
);
assert_eq!(
generated::Level::try_from(50)
.ok()
.map(generated::Level::get),
Some(50)
);
}