Skip to content

StructMatcher procedural macro #447

Closed
@andriyDev

Description

@andriyDev

This is something I've dreamed of having in the C++ googletest library. The difference is in Rust this seems possible!

#[googletest::StructMatcher]
struct MyType {
  field_1: i32,
  field_2: String,
  ...
}

This attribute macro would generate something akin to:

#[cfg(test)]
struct MyTypeStructMatcher {
  field_1: Option<Box<dyn Matcher<ActualT = i32>>>,
  field_2: Option<Box<dyn Matcher<ActualT = String>>>,
  ...
}

#[cfg(test)]
impl googletest::matcher::Matcher for MyTypeStructMatcher {
  type ActualT = MyType;

  fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
    if let Some(field_1) = &self.field_1 {
      if field_1.matches(&actual.field_1).is_no_match() {
        return MatcherResult::NoMatch;
      }
    }
    ...
  }
}

(A supporting enum in googletest code to make it easier to construct the field matcher)

pub enum StructFieldMatcher<ActualT> {
  None,
  Matcher(Box<dyn Matcher<ActualT = ActualT>>)
}

impl<M: Matcher + 'static> From<M> for StructFieldMatcher<<M as Matcher>::ActualT> {
  fn from(value: M) -> Self {
    Self::Matcher(Box::new(value))
  }
}

This enables usage similar to the following:

#[googletest::test]
fn my_test() {
  let value = MyType {
      field_1: 3,
      field_2: "helloworld".into(),
      ...
    };
  expect_that!(value, MyTypeStructMatcher{
      field_1: lt(3).into(),
      field_2: contains_substring("llow").into(),
    });
}

Disadvantages

We need to make googletest (or some related crate) a public dependency of crates. Thankfully the #[cfg(test)] attribute should prune the code gen for the entire implementation, but we do still pay the cost of the procedural macro.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions