Diffstat (limited to 'tests/walker_struct.rs')
-rw-r--r--tests/walker_struct.rs344
1 files changed, 344 insertions, 0 deletions
diff --git a/tests/walker_struct.rs b/tests/walker_struct.rs
new file mode 100644
index 0000000..b467a4c
--- /dev/null
+++ b/tests/walker_struct.rs
@@ -0,0 +1,344 @@
+use mockall::predicate::eq;
+use treaty::{
+ any::{BorrowedStatic, BorrowedStaticHrt, OwnedStatic, TypeNameId},
+ effect::{Blocking, Effect, Future},
+ protocol::{
+ visitor::{tags, SequenceProto, TagConst, TagProto, ValueProto, VisitResult},
+ DynVisitor,
+ },
+ walkers::core::r#struct::{StructTypeInfo, StructWalker},
+ Builder, DefaultMode, Flow, Walker,
+};
+
+use crate::common::{
+ builder::MockBuilder,
+ protocol::{
+ sequence::{MockSequenceVisitor, SequenceScopeFactory},
+ tag::MockTagVisitor,
+ visitor::MockValueVisitor,
+ },
+};
+
+mod common;
+
+struct X {
+ a: bool,
+ b: i32,
+}
+
+struct Info;
+
+// This gives the struct walker enough information to walk the X struct.
+impl<'ctx, M> StructTypeInfo<'ctx, M> for Info {
+ const NAME: &'static str = "X";
+
+ const FIELDS: &'static [&'static str] = &["a", "b"];
+
+ type FieldError = ();
+
+ type T = X;
+
+ fn walk_field<'a, E: Effect>(
+ index: usize,
+ value: &'ctx Self::T,
+ mut visitor: DynVisitor<'a, 'ctx>,
+ ) -> Future<'a, Result<Flow, Self::FieldError>, E> {
+ E::wrap(async move {
+ match index {
+ // A real impl would be expected to tag these values with the field name.
+ 0 => {
+ // Basic visit for the field value.
+ let obj = visitor
+ .upcast_mut::<ValueProto<OwnedStatic<bool>, E>>()
+ .unwrap();
+
+ // Emit the field value.
+ obj.visit(OwnedStatic(value.a)).await;
+
+ // There are more fields.
+ Ok(Flow::Continue)
+ }
+ 1 => {
+ // Basic visit for the field value.
+ let obj = visitor
+ .upcast_mut::<ValueProto<OwnedStatic<i32>, E>>()
+ .unwrap();
+
+ // Emit the field value.
+ obj.visit(OwnedStatic(value.b)).await;
+
+ // There are no more fields.
+ Ok(Flow::Done)
+ }
+ _ => Ok(Flow::Done),
+ }
+ })
+ }
+}
+
+/// Tests that a struct walker in its most basic form just emits a sequence of field values.
+///
+/// This makes structs behave like tuples (lists) if the builder doesn't know about any of the
+/// extra tags.
+#[test]
+fn sequence_of_field_values() {
+ // The struct value to walk.
+ let value = X { a: true, b: 42 };
+
+ // The struct walker using the info we provided about the struct.
+ let walker = StructWalker::<X, Info, DefaultMode, Blocking>::new(&value);
+
+ let mut builder = MockBuilder::<(), (), ()>::new();
+
+ // Expect a visit on the sequence protocol for the struct fields.
+ builder
+ .expect_traits_mut()
+ .once()
+ .with(eq(TypeNameId::of::<SequenceProto<Blocking>>()))
+ .returning(|_| {
+ let mut visitor = MockSequenceVisitor::<Blocking>::new();
+
+ // Expect the sequence visitor to be used.
+ visitor.expect_visit().once().return_const(
+ (|_, scope| {
+ // The struct should have exactly 2 fields.
+ assert_eq!(scope.size_hint().into_inner(), (2, Some(2)));
+
+ // Get the first field value.
+ {
+ let mut visitor = MockBuilder::<(), (), ()>::new();
+
+ // Expect a bool value for the field.
+ visitor
+ .expect_traits_mut()
+ .once()
+ .with(eq(
+ TypeNameId::of::<ValueProto<OwnedStatic<bool>, Blocking>>(),
+ ))
+ .returning(|_| {
+ let mut visitor =
+ MockValueVisitor::<OwnedStatic<bool>, Blocking>::new();
+
+ // Expect true.
+ visitor
+ .expect_visit()
+ .once()
+ .with(eq(OwnedStatic(true)))
+ .return_const(Flow::Done);
+
+ Some(Box::new(visitor))
+ });
+
+ scope.next(Builder::<Blocking>::as_visitor(&mut visitor));
+ }
+
+ // Get the second field value.
+ {
+ let mut visitor = MockBuilder::<(), (), ()>::new();
+
+ // Expect a i32 value.
+ visitor
+ .expect_traits_mut()
+ .once()
+ .with(eq(
+ TypeNameId::of::<ValueProto<OwnedStatic<i32>, Blocking>>(),
+ ))
+ .returning(|_| {
+ let mut visitor =
+ MockValueVisitor::<OwnedStatic<i32>, Blocking>::new();
+
+ // Expect a 42.
+ visitor
+ .expect_visit()
+ .once()
+ .with(eq(OwnedStatic(42)))
+ .return_const(Flow::Done);
+
+ Some(Box::new(visitor))
+ });
+
+ scope.next(Builder::<Blocking>::as_visitor(&mut visitor));
+ }
+
+ // We are done with the sequence of fields.
+ VisitResult::Control(Flow::Done)
+ }) as SequenceScopeFactory<Blocking>,
+ );
+
+ Some(Box::new(visitor))
+ });
+
+ // All other protocols are not used for this test.
+ builder.expect_traits_mut().return_var(None);
+
+ // Walk the struct.
+ assert_eq!(
+ walker
+ .walk(Builder::<Blocking>::as_visitor(&mut builder))
+ .into_inner(),
+ Ok(())
+ );
+}
+
+#[test]
+fn has_struct_tag() {
+ // The struct value to walk.
+ let value = X { a: true, b: 42 };
+
+ // The struct walker using the info we provided about the struct.
+ let walker = StructWalker::<X, Info, DefaultMode, Blocking>::new(&value);
+
+ let mut builder = MockBuilder::<(), (), ()>::new();
+
+ let mut seq = mockall::Sequence::new();
+
+ // Skip the request hint, borrowed value, and type id.
+ builder
+ .expect_traits_mut()
+ .times(4)
+ .in_sequence(&mut seq)
+ .return_var(None);
+
+ // Expect that the tag for a struct is emmited.
+ builder
+ .expect_traits_mut()
+ .once()
+ .in_sequence(&mut seq)
+ .with(eq(TypeNameId::of::<TagProto<tags::Struct, Blocking>>()))
+ .returning(|_| {
+ let mut visitor = MockTagVisitor::<tags::Struct, Blocking>::new();
+
+ visitor.expect_visit().once().returning(|TagConst, walker| {
+ let mut visitor = MockBuilder::<(), (), ()>::new();
+
+ // Walk the noop walker so there isn't an error.
+ assert_eq!(
+ walker.walk(DynVisitor(&mut visitor)).into_inner(),
+ Flow::Done
+ );
+
+ // We are done, the walker should now stop early.
+ VisitResult::Control(Flow::Done)
+ });
+
+ Some(Box::new(visitor))
+ });
+
+ // Walk the struct.
+ assert_eq!(
+ walker
+ .walk(Builder::<Blocking>::as_visitor(&mut builder))
+ .into_inner(),
+ Ok(())
+ );
+}
+
+#[test]
+fn has_map_backup_tag() {
+ // The struct value to walk.
+ let value = X { a: true, b: 42 };
+
+ // The struct walker using the info we provided about the struct.
+ let walker = StructWalker::<X, Info, DefaultMode, Blocking>::new(&value);
+
+ let mut builder = MockBuilder::<(), (), ()>::new();
+
+ let mut seq = mockall::Sequence::new();
+
+ // Skip the request hint, borrowed value type id, and struct tag.
+ builder
+ .expect_traits_mut()
+ .times(6)
+ .in_sequence(&mut seq)
+ .return_var(None);
+
+ // Expect that the tag for a map is emmited.
+ //
+ // This only happens if the struct tag isn't supported by the visitor.
+ builder
+ .expect_traits_mut()
+ .once()
+ .in_sequence(&mut seq)
+ .with(eq(TypeNameId::of::<TagProto<tags::Map, Blocking>>()))
+ .returning(|_| {
+ let mut visitor = MockTagVisitor::<tags::Map, Blocking>::new();
+
+ visitor.expect_visit().once().returning(|TagConst, walker| {
+ let mut visitor = MockBuilder::<(), (), ()>::new();
+
+ // Walk the noop walker so there isn't an error.
+ assert_eq!(
+ walker.walk(DynVisitor(&mut visitor)).into_inner(),
+ Flow::Done
+ );
+
+ // We are done, the walker should now stop early.
+ VisitResult::Control(Flow::Done)
+ });
+
+ Some(Box::new(visitor))
+ });
+
+ // Walk the struct.
+ assert_eq!(
+ walker
+ .walk(Builder::<Blocking>::as_visitor(&mut builder))
+ .into_inner(),
+ Ok(())
+ );
+}
+
+#[test]
+fn borrowed_value_directly() {
+ // The struct value to walk.
+ let value = X { a: true, b: 42 };
+
+ // The struct walker using the info we provided about the struct.
+ let walker = StructWalker::<X, Info, DefaultMode, Blocking>::new(&value);
+
+ let mut builder = MockBuilder::<(), (), ()>::new();
+
+ let mut seq = mockall::Sequence::new();
+
+ // Skip the request hint.
+ builder
+ .expect_traits_mut()
+ .once()
+ .in_sequence(&mut seq)
+ .return_var(None);
+
+ // Get the struct value directly from the walker.
+ builder
+ .expect_traits_mut()
+ .once()
+ .in_sequence(&mut seq)
+ .with(eq(TypeNameId::of::<
+ ValueProto<BorrowedStaticHrt<X>, Blocking>,
+ >()))
+ .returning(|_| {
+ let mut visitor = MockValueVisitor::<BorrowedStaticHrt<X>, Blocking>::new();
+
+ // Expect the struct value.
+ visitor
+ .expect_visit()
+ .once()
+ .returning(|BorrowedStatic(value)| {
+ // Check the value to be the same.
+ assert!(value.a);
+ assert_eq!(value.b, 42);
+
+ // We are done, the walker should now stop early.
+ VisitResult::Control(Flow::Done)
+ });
+
+ Some(Box::new(visitor))
+ });
+
+ // Walk the struct.
+ assert_eq!(
+ walker
+ .walk(Builder::<Blocking>::as_visitor(&mut builder))
+ .into_inner(),
+ Ok(())
+ );
+}