I have some integration tests where I end up with a bunch of values in a vec. Order doesn't matter, but they're in a vec because they don't implement Eq so I can't put them in a set. (I can't just implement Eq because it's generated code.)
I also have some assertions that I want to apply to each of these things. As far as I can tell, the only way to do this is to iterate over the checks (which return bool), and assert on an any() iterator over the values. Like this, except pretend &str doesn't implement Eq :
I don't really care about the O(mn) behaviour, because there's a small set of possible values. But I can't really get any information out of the assert. Specifically:
thread 'main' panicked at 'assertion failed: list_of_values.iter().copied().any(chk)', src/main.rs:13:25
There is also the flaw that this does not test for uniqueness of each assertion, but that's not hugely important.
I can't just put the assertions in the check function, because that won't work with any() - my tests will fail on the first assertion failure, rather than try the rest of the items in the vec.
Is there a way to (a) simplify this a bit (eg. asserting deeply nested structures leads to a lot of indentation) and (b) get more information from assertion failures?
For part A, it might be possible to use a procedural macro to implement Eq. Could you tell us more about this generated code?
For part B, if you could define your checks in a macro in order to capture their names as strings also, you could at least print the name of the failing check:
It's Cap'n Proto code. A "message" value is really just a collection of bytes that the generated code owns and knows how to read fields out of when you call accessor methods. Even the parts of the messages that can be reduced to enums don't implement Eq and must be compared with eg. matches!().
For example, a simple Cap'n Proto union-in-a-struct that can be Title or Extra looks like:
struct Config {
union {
title @1 :Text;
extra :group {
index @4 :UInt8;
settings @5 :Settings;
}
}
}
A simple check for the title in a test looks like:
let title_check = |c: ipc::conf_capnp::config::Reader| -> bool {
// c is the capnp struct
matches!(
c.which(), // which() tells you what union type it is.
Ok(ipc::conf_capnp::config::Which::Title(Ok(
"The Title"
)))
)
};
Fun times (I just wrote this this minute, there's probably a way to simplify it down. But having assertions in there instead would at least reduce the line count, if not the nesting.)
I cannot simply replace the matches!() with an ==, and I cannot take the list of ipc::conf_capnp::config::Readers and put them in a HashSet and compare the two sets for equality. I can't even use assert_matches!() because I have to apply the check to every response in a list of responses to see if any of them pass.
I reduced that abomination somewhat by remembering that { } scope blocks are expressions, so match inner.get_settings() { Ok(...) } can be replaced by { let inner_inner = ... ; condition }. Plus if guards on some of the matches.
If it's Cap'n Proto then can't you just put the byte representation in a set? Or maybe create a HashMap<&[u8], Message> so you can do assertions using the serialized message as a key, then look up the message when an assertion fails so you can print out something with a nice Debug implementation.
In theory, but this won't be very future proof. If I change the schema without affecting the actual message structure for a subset of messages, the on-the-wire representation might change, but 90% of my tests shouldn't suddenly start failing because I've added a field that affects the other 10%. (Yes, Cap'n P has the ability to do backwards compatibility changes, but I don't want to have to care about that at this stage.)
Huge thanks to @jhwgh1968 and @drewkett for their advice and macro code, in the last few weeks I've gotten over my fear of macro_rules! and discovered how useful they can be (especially in extremely repetitive test code). I've already incorporated some of it into my tests to great benefit in readability.