Make assert failure message unambigious

Given:

use std::cmp;

fn main() {
    assert_eq!(2, cmp::min(1, 2));
}

We get:

thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `2`,
 right: `1`', src/main.rs:4:5

This tells me absolutely nothing when reading the logs. What would be helpful is this:

thread 'main' panicked at 'assertion failed: `(expected == actual)`
  expected: `2`,
 actual: `1`', src/main.rs:4:5

I have definitely seen (and probably written) test code that puts the expected value on both the left and the right, so ascribing a meaning to the positions now would make a lot of existing code wrong.

19 Likes

Yeah, I prefer to have the expected value second myself.

3 Likes

Yeah, I think this would be a breaking change. Your only realistic hope is to write your own assertion macro that changes the message to what you want.

1 Like

A problem with that is that which side is the actual value and which side is the expected value is completely arbitrary. In fact I think I have code bases that, while each internally consistent on this point, disagree with each other w.r.t. convention.

1 Like

Also, if you want more meaningful error messages, it is better to do:

let expected = 2;
let actual =  cmp::min(1, 2);
assert_eq!(expected,actual,"Expected {expected} got {actual}");

Even better if the message says something more about the nature of the test.

8 Likes

assert_eq! should output both the stringized versions of the parameters and their values, IMO, which would also in most cases make clear which one is the expected one, in addition to providing more information without having to delve into the code.

10 Likes

I think assert_eq! is fundamentally the wrong interface to say one side or the other is the expected.

If we wanted that, it would be better as something like https://fluentassertions.com/ or AssertJ - fluent assertions java library.

With x.should().equal(2), it's very clear which is the expected, in a way where I'd say "oh, yeah, that PR to fix the order makes it obviously better", which isn't the case for the symmetric macro.

2 Likes

I have definitely seen (and probably written) test code that puts the expected value on both the left and the right, so ascribing a meaning to the positions now would make a lot of existing code wrong.

It'd also make the code lot less messy. Imagine it was Tuesday and raining, so one developer put the expected value at the left, while on a sunny Friday, another dev put it on the right in the same test class.

A problem with that is that which side is the actual value and which side is the expected value is completely arbitrary.

Right, but there's nothing wrong in adopting a convention. JUnit Assert, for example, puts expected on the left.

assert_eq!(expected,actual,"Expected {expected} got {actual}")

Boilerplate that I shouldn't have to write.

x.should().equal(2) , it's very clear which is the expected

Agreed that'll be better, but the question is, do you want to do nothing and perpetuate the confusion, or do something that helps, even though a little.

Yes. Boilerplate. Instead write a meaningful message that says something about the specific test.

1 Like

There isn't any confusion. Right now, if you said left was expected and right was actual you'd have A LOT of code that would be backwards of that and that WOULD be confusing. Not just tests. The right way to disambiguate (if needed) is to use the optional message as I previously mentioned.

8 Likes

How does your proposal deal with this issue? In the status quo, using your example, you have

thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `2`,
 right: `1`', src/main.rs:4:5

and

thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `1`,
 right: `2`', src/main.rs:4:5

Of which both of these messages are correct.

As I understand it, if a developer makes the same mistake, your proposal would result in:

thread 'main' panicked at 'assertion failed: `(expected == actual)`
  expected: `2`,
 actual: `1`', src/main.rs:4:5

and

thread 'main' panicked at 'assertion failed: `(expected == actual)`
  expected: `1`,
 actual: `2`', src/main.rs:4:5

Of which, only one of these messages is correct.

The compiler can't enforce that the expected value is always placed in one position and the actual value in the other. This is a classic "gotcha" across many other languages and testing frameworks.

3 Likes

There may not even be an expected value, just an expectation of equality between two values.

16 Likes

I'm not convinced there's too much of a problem here in the first place: Most of the times, even if you knew which one of the value was the expected, you'd still need to look into the code (at the point kindly pointed out by the error message) in order to understand the necessaryt context, i. e. what the assertion even means in the first place. Once you look at the code, it's typically trivial to understand which of the values was the expected one. Arguably it would be more confusing and harder to relate the error message to the code if the error message didn't clearly specify which one of the values is from the left expression and which one from the right one.

In order to even have a chance of, sometimes, being able to skip looking into the code, the error message would probably — at least — also have to specify what the expressions look like that were being evaluated. (And once you see the expressions, you might often already know which value was the expected one.)

3 Likes

Agreed. My personal experience is that "expected X, got Y" messages don't help me at all, and I usually still need to look at the test code to figure out what was the semantic.

2 Likes

The exception to this in my experience is when the output is presented in diff form. This often provides enough context as well, but requires formatting and is mostly applicable to response style tests, although I find it useful elsewhere. (it also does not belong in std)