hamcrest2-rust icon indicating copy to clipboard operation
hamcrest2-rust copied to clipboard

eq matcher with temporary values

Open almondtools opened this issue 5 years ago • 5 comments

I am not certain how to succintly express an assert in following context (shortened, abstract example):

Assume following context:

#[derive(PartialEq, Debug)]
struct S {
  val:u32
}
impl S {
  pub fn new(val:u32) -> Self {
    S {val:val}
  }
}

The test (1):

fn test() {
  let s = S::new(42);
  
  assert_that!(s, eq(S::new(42)));
  assert_that!(s, not(eq(S::new(41))));
}

will not compile because s is consumed in the first assert statement.

The test (2):

fn test() {
  let s = S::new(42);
  
  assert_that!(&s, eq(S::new(42)));
  assert_that!(&s, not(eq(S::new(41))));
}

will not compile because the eq-matcher does not support unwrapping references.

The test (3):

fn test() {
  let s = S::new(42);
  
  assert_that!(&s, eq(&S::new(42)));
  assert_that!(&s, not(eq(&S::new(41))));
}

will not compile with 'temporary value dropped while borrowed'.

I could fix test (3) by introducing temporary variables, yet the test gets quite ugly with temporary variables.

My questions: Is there another solution to test what I expect? Is it planned to support something similar to test (2)?

almondtools avatar Apr 28 '19 10:04 almondtools

Why not just do this?

fn test() {
  assert_that!(&S::new(42), eq(&S::new(42)));
  assert_that!(&S::new(42), not(eq(&S::new(41))));
}

Another approach:

  #[derive(PartialEq, Debug)]
  #[cfg_attr(test, derive(Clone))]  // Notice we derive Clone in test builds
  struct S {
    val: u32
  }

  impl S {
    pub fn new(val: u32) -> Self {
      S { val }
    }
  }

  #[test]
  fn test() {
    let s = S::new(42);

    assert_that!(s.clone(), eq(S::new(42)));
    assert_that!(s.clone(), not(eq(S::new(41))));
  }

We did have support for auto-unwrapping references, but it had to be reverted because the way it was implemented broke backwards compatibility. Still looking for a way to implement that (PRs welcome, I'm out of ideas :)).

Valloric avatar Apr 29 '19 23:04 Valloric

The first solution id a variant of (3) and fails because of a dropped temporary value. You probably meant:

fn test() {
  assert_that!(S::new(42), eq(S::new(42)));
  assert_that!(S::new(42), not(eq(S::new(41))));
}

which works, yet it is harder to read (from my point of view).

Working and more concise (but also misleading in my opinion):

fn test() {
  let s = || S::new(42);
  assert_that!(s(), eq(S::new(42)));
  assert_that!(s(), not(eq(S::new(41))));
}

Using clone() works, yet reading such test code would make me feel that the clone() method is tested rather than the new(...) method.

I had several ideas, maybe you have tried some of them:

  • I did not (yet) manage to let EqualTo have multiple matches(&self, actual) methods (maybe I am to new to Rust, as I understand you it is not easy for you either).
  • Next idea: Extend the assert_that! macro (I am not familiar with macros yet)
    • by changing the macro implementation
    • by inventing a new macro assert_that_ref!
  • Another idea: Provide a new matcher eq_ref for references

almondtools avatar Apr 30 '19 04:04 almondtools

I think the variant with calls to clone is fine and pretty readable. Anyone familiar with Rust will be familiar with what .clone() does so I don't expect it would surprise folks. Your approach with a lambda is also fine.

Provide a new matcher eq_ref for references

That's my current train of thought as well.

Valloric avatar Apr 30 '19 20:04 Valloric

Yet my idea will conflict with backward compatibility, however my thoughts about this:

What is more intuitive when asserting a statement on some argument:

  1. the assertion consumes the argument (such that it is not usable any more)
  2. the assertion borrows the argument (the argument should be dropped at the end of its lifetime)

I would vote for 2. Talking about an object does not affect its state is more like borrowing than consuming. Maybe an idea for the next major version?

almondtools avatar Jun 11 '19 16:06 almondtools

I would strongly support this, I realise this is an old issue request now and maybe this repo isn't being maintained. The workarounds suggested don't always work, if something isn't cloneable for example and taking references often gives the temporary value dropped while borrowed which isn't obvious how to workaround other than cloning.

strottos avatar Nov 10 '21 10:11 strottos