Diffstat (limited to 'tests/walker_struct.rs')
| -rw-r--r-- | tests/walker_struct.rs | 344 |
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(()) + ); +} |