Conversation
ethanfrey
left a comment
There was a problem hiding this comment.
Nice initiative and you seem to be finding your way around the repo quite well.
Added some comments for a few requested changes, otherwise looks good.
| staking_msg: &StakingMsg, | ||
| permissions: Permissions, | ||
| ) -> Result<bool, ContractError> { | ||
| ) -> Result<(), ContractError> { |
There was a problem hiding this comment.
Why return a bool here if we only have Ok(true) (never Ok(false)).
It suggests there are 3 possible cases to handle: Ok(true), Ok(false), Err(_), when there are only 2: Ok(_) and Err(_)
There was a problem hiding this comment.
Yes, it is exactly why I changed it from bool to ZST - it was hurting my eyes (and also optimizer - in case when one type of Result is ZST, the Result itself becomes transparent).
There was a problem hiding this comment.
I returned Result<(), ContractError> as it captured more failure cases than 1 (a bool would just capture one success and one failure case).
We have 1 success case and 4 failure cases.
Ahhh.... I misread the diff... I thought you made the bool. You removed it. 👀
Very good change
| pub fn check_distribution_permissions( | ||
| distribution_msg: &DistributionMsg, | ||
| permissions: Permissions, | ||
| ) -> Result<bool, ContractError> { |
There was a problem hiding this comment.
Maybe you want to rename to assert_*_permissions? Then the return type would make more sense
There was a problem hiding this comment.
I mean I find naming "check_" functions returning Result on failure handy (if I would like to return bool then I would call it more like question, let say - has_distribution_permissions). assert_* suggests, panic on failure from my POV (which basically happens here in one branch which I don't like - I would prefer debug assertion to check invalid branch as prerequirement, but on release I would just return error here).
There was a problem hiding this comment.
Nevermind, remove this suggestion. I misread the diff and the comments up here are wrong.
Your way is better (and what I was arguing for)
| &initial_allowances, | ||
| &initial_expirations, | ||
| ); | ||
| let Suite { deps, .. } = SuiteConfig { |
There was a problem hiding this comment.
I see the idea of the Suite to handle many things more clearly.
Somehow the usage seems more verbose than the older setup code.
I wonder if it is possible to make usage more compact. With eg. 2 helpers:
Spender::with_allowance and Spender::with_expiring_allowance. That would reduce those 4-5 line struct inits into 1 line, and then the usage of the new Suite would really shine
There was a problem hiding this comment.
I am actually thinking about this - this is very generic proposal, but suggestions are very much welcome. I even considered to split initialization and early execution, adding utility for executes, to achieve something like:
let Suite { deps, owner_info: owner, .. } = SuiteConfig {
// Only minimal config, or maybe just `Suite::init(...)` with args required to setup properly
}
.init()
.execute_increase_allowance(&owner, SENDER1, vec![coin(1111, TOKEN)], None)
.execute_increase_allowance(&owner, SENDER2, vec![coin(2222, TOKEN)], None);
This would benefit also redability of test logic imho. There can be argument, that this thing hides what is actually called (the execute itself), but wrapping it in init hides it even more, so I am ok with that.
| }, | ||
| ]; | ||
|
|
||
| assert_sorted_eq!( |
There was a problem hiding this comment.
I like the check here to sort before compare.
| /// | ||
| /// This is implemented as a macro instead of function to throw panic in the place of macro | ||
| /// usage instead of from function called inside test. | ||
| macro_rules! assert_sorted_eq { |
There was a problem hiding this comment.
Interesting macro trick.
I write a number of helper functions and always re-run with RUST_BACKTRACE=1 when they fail to find the real location. This does seem more ergonomic.
We need to reconsider the macro-resistance in this codebase.
| &initial_allowances, | ||
| &initial_expirations, | ||
| ); | ||
| // let's try pagination. |
|
For some reason I cannot reply to this, but I will inline it:
I am happy adding all kinds of magic helpers to SuiteConfig / init. Anything to build up the Suite. This is just to set up initial state, we trust that code works. The code that is later being tested (execute), I would like to explicitly call. |
ethanfrey
left a comment
There was a problem hiding this comment.
Misread. I agree with the points on top.
Only comments relate to test setup helpers
| staking_msg: &StakingMsg, | ||
| permissions: Permissions, | ||
| ) -> Result<bool, ContractError> { | ||
| ) -> Result<(), ContractError> { |
There was a problem hiding this comment.
I returned Result<(), ContractError> as it captured more failure cases than 1 (a bool would just capture one success and one failure case).
We have 1 success case and 4 failure cases.
Ahhh.... I misread the diff... I thought you made the bool. You removed it. 👀
Very good change
| pub fn check_distribution_permissions( | ||
| distribution_msg: &DistributionMsg, | ||
| permissions: Permissions, | ||
| ) -> Result<bool, ContractError> { |
There was a problem hiding this comment.
Nevermind, remove this suggestion. I misread the diff and the comments up here are wrong.
Your way is better (and what I was arguing for)
| @@ -149,13 +149,13 @@ pub fn check_staking_permissions( | |||
| } | |||
| s => panic!("Unsupported staking message: {:?}", s), | |||
There was a problem hiding this comment.
This should return Err().
Good eye
maurolacy
left a comment
There was a problem hiding this comment.
I like these test helper structs / macros.
What we miss in being able to reference existing variables in test assertions, we gain in clarity and conciseness. Good work.
| staking_msg: &StakingMsg, | ||
| permissions: Permissions, | ||
| ) -> Result<bool, ContractError> { | ||
| ) -> Result<(), ContractError> { |
| assert_sorted_eq!( | ||
| expected, | ||
| [batch1, batch2].concat(), | ||
| |l: &AllowanceInfo, r: &AllowanceInfo| l.spender.cmp(&r.spender) |
There was a problem hiding this comment.
Nice trick relying on PartialEq for sorting, instead of requiring Ord here.
I understand it would make sense to just add Ord (and Eq) to AllowanceInfo, and simplify this.
As that can require adding them to other stuff, including some structs in cosmwasm-std, I would say, let's create an issue to do just this; at least for cosmwasm-std. And then we can revisit this.
There was a problem hiding this comment.
Defienetely implementing Ord for AllowanceInfo does not make sense - Ord is for types with reasonable sorting, and I find implementing it for this kind of type a missuse. Note that even standard library doesn't implement sort for types like floats (even if you could technically provide implementation which would somehow properly sort it). Actually it is questionable if there is proper way to pick proper key for comparison - is this anyhow intuitive that allowance with sender earlier in alphabet is in any way smaller than the other one?
My personal opinion is, that even if this adds some code, it avoids any confusion how things are sorted (and why). I think about two possible simplifications:
- try to write macro in the way, that it would be more elision friendly so type hints are not needed (actually type hints are half the length of the closure!) - unfortunatelly I have no idea how to approach this
- extract the comparison function to
AllowanceInfo, but not as an implementation ofOrd, but rather testing purpose method likeAllowanceInfo::cmp_by_spender- actually I like the idea
There was a problem hiding this comment.
2. extract the comparison function to
AllowanceInfo, but not as an implementation ofOrd, but rather testing purpose method likeAllowanceInfo::cmp_by_spender- actually I like the idea
Sounds good. That, and implementing these helpers only for tests. That could include deriving something like Ord, which does not make sense, or adds unnecessary complexity, only for tests using conditional compilation.
836160a to
90285f5
Compare
ethanfrey
left a comment
There was a problem hiding this comment.
Very good stuff! Do you want to take it out of draft?
I made a few minor points, but the code looks great. The only concern I raise is two test cases are marked ignore. If you aren't sure of the expected conditions to finish them, please add a comment on them and either Mauro or I can help.
Other than that, I'd approve it.
| pub permissions: Permissions, | ||
| } | ||
|
|
||
| #[cfg(any(test, feature = "test-utils"))] |
There was a problem hiding this comment.
Nice feature flag for imports
|
|
||
| #[test] | ||
| fn query() { | ||
| let Suite { deps, .. } = SuiteConfig::new() |
There was a problem hiding this comment.
I'm loving the API design here.
Super concise and flexible. The builder-pattern SuiteConfig is working out very well. Much better than a number of setup functions with different args... so much more power here.
| self | ||
| } | ||
|
|
||
| fn expire_allowances(mut self, spender: &'static str, expires: Expiration) -> Self { |
There was a problem hiding this comment.
Okay, so these are different calls that can be combined.
with_allowance(SPENDER2, ..).expire_allowances(SPENDER2, ..) works.
And expire_allowances(SPENDER2, ..).with_allowance(SPENDER2, ..) should also work, right?
That is a very nice API avoiding N^2 init function issues. I would love a test case (on SuiteConfig) showing this works regardless of ordering. But design is 🥇
There was a problem hiding this comment.
I personally don't like to test this simple functions as it turns out to be testing language instead of logic, but possibly it could be tested. The order should not matter as it just sets state, but making API so it matters is obviously possible, I just don't see a point to complicate testing utilities.
There was a problem hiding this comment.
If there is a bug in that testing utilities, then some test case below will surely fail.
I would only write them if a test fails unexpectedly and it seems to be a util bug.
| }, | ||
| ]; | ||
|
|
||
| assert_sorted_eq!( |
There was a problem hiding this comment.
This concat (after we paginate through, we get all the values exactly once) is a nice test-case.
Cleaner to read and more stable if we change any implementation details, but captures business requirements
| assert_eq!(rsp.data, None); | ||
|
|
||
| assert_eq!( | ||
| query_all_allowances(deps.as_ref(), None, None) |
There was a problem hiding this comment.
since there is only one SPENDER here, why not use query_allowance?
It seems like it would be a bit simpler.
There was a problem hiding this comment.
Because as I am touching allowance, I want to be sure that only my specific allowance changed, and no side effect occurred. It is surprisingly almost the same (just one encapsulation more), and more generic test.
| balance: NativeBalance(vec![allow1.clone(), allow2.clone()]), | ||
| expires: expires_height, | ||
| #[test] | ||
| #[ignore] |
There was a problem hiding this comment.
why ignore? does this fail?
There was a problem hiding this comment.
Yes it fails. I am pretty sure this test is correct (but double check if I understand logic), and it should be implemented, and then test would be "unignored".
| balance: NativeBalance(vec![allow1.clone()]), | ||
| expires: expires_never, | ||
| #[test] | ||
| #[ignore] |
Co-authored-by: Mauro Lacy <maurolacy@users.noreply.github.com>
Co-authored-by: Mauro Lacy <maurolacy@users.noreply.github.com>
Co-authored-by: Mauro Lacy <maurolacy@users.noreply.github.com>
Co-authored-by: DzikiChrzan <dzikichrzan@tuta.io>
|
I just removed draft flag. One thing I wanted to have before merging is getting rid of |
ethanfrey
left a comment
There was a problem hiding this comment.
Looks good to me.
Let's merge it and I can inspect the item about independence when I have a bit of space and comment on that. (And then have a smaller PR to review there).
Maybe you can pull that out into another mod (mod old_tests) before merging, so it is clear which are using which util functions? Or just leave it as is, it should be clear enough what is missing still
As there is a request to create additional UTs for some contracts: #105, I went through the whitelist contract UT and found that kind of problem with adding many tests there is repetitive boilerplate in every test, which is not actually easy to read. Also because of some edge cases, some test may be actually incorrect (eg. if in future some items would be returned in different order because of any reason, UTs would start failing - while ordering in many cases shouldn't matter).
Therefore I came up with proposal to kind of unify the UTs structure to have visible setup => execute => verify structure, with - what is important - clear setup, so it is easier to figure out what is the test about.
Additional changes I am considering to make execution and possibly verify part simpler is to add
execute/querymethods directly onSuite(query via router), so thedepsandmock_envare hidden. Additionally it would make testing queries through whole ecosystem, not just locally which might be more complete.At the end I think, that the
Suitestructure could be partially generalized and extracted tomulti-test, to encourage having clean an uniform test through the smart contracts.