added some tests for the struct walker
Konnor Andrews 2024-04-20
parent da80a28 · commit de2bacf
-rw-r--r--src/protocol/visitor/tag.rs11
-rw-r--r--src/walk/walkers/core/struct.rs4
-rw-r--r--tests/common/protocol/visitor.rs2
-rw-r--r--tests/walker_struct.rs344
4 files changed, 356 insertions, 5 deletions
diff --git a/src/protocol/visitor/tag.rs b/src/protocol/visitor/tag.rs
index 67ac325..0608381 100644
--- a/src/protocol/visitor/tag.rs
+++ b/src/protocol/visitor/tag.rs
@@ -13,6 +13,13 @@ use crate::{
use super::VisitResult;
+pub mod tags {
+ use super::*;
+
+ pub type Struct = TagConst<{ Symbol::new("Struct").to_int() }>;
+ pub type Map = TagConst<{ Symbol::new("Map").to_int() }>;
+}
+
pub trait TagKind: Copy + Send + Sync + 'static {
fn symbol(&self) -> Symbol;
}
@@ -105,7 +112,7 @@ impl<K: TagKind, E: Effect> HintMeta for TagProto<K, E> {
type Effect = E;
}
-#[derive(Debug)]
+#[derive(Debug, PartialEq, Clone, Copy)]
pub enum TagErrorKind<E> {
NeverWalked,
@@ -118,7 +125,7 @@ pub enum TagErrorKind<E> {
SkippedWasWalked,
}
-#[derive(Debug)]
+#[derive(Debug, PartialEq, Copy, Clone)]
#[allow(unused)]
pub struct TagError<E> {
symbol: Symbol,
diff --git a/src/walk/walkers/core/struct.rs b/src/walk/walkers/core/struct.rs
index 6a8119d..2cb4c14 100644
--- a/src/walk/walkers/core/struct.rs
+++ b/src/walk/walkers/core/struct.rs
@@ -62,7 +62,7 @@ pub trait StructTypeInfo<'ctx, M>: 'static {
) -> Future<'a, Result<Flow, Self::FieldError>, E>;
}
-#[derive(Debug)]
+#[derive(Debug, PartialEq, Clone, Copy)]
#[allow(unused)]
enum StructWalkErrorKind<T> {
/// Error with visiting a tag for the struct.
@@ -80,7 +80,7 @@ enum StructWalkErrorKind<T> {
}
/// Error from walking a struct.
-#[derive(Debug)]
+#[derive(Debug, PartialEq, Copy, Clone)]
#[allow(unused)]
pub struct StructWalkError<T> {
kind: StructWalkErrorKind<T>,
diff --git a/tests/common/protocol/visitor.rs b/tests/common/protocol/visitor.rs
index f980bc5..f19b045 100644
--- a/tests/common/protocol/visitor.rs
+++ b/tests/common/protocol/visitor.rs
@@ -20,7 +20,7 @@ any_trait! {
ValueProto<T, E>
] where
T: TypeName::MemberType,
- for<'a, 'b> TypeName::T<'a, 'b, T>: Clone + Sized,
+ for<'a, 'b> TypeName::T<'a, 'b, T>: Sized,
E: Effect,
}
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(())
+ );
+}