diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c1c34568e..d772c25927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ and this project adheres to ## [Unreleased] +### Added + +- cosmwasm-std: Add testing macro `assert_approx_eq!` for comparing two integers + to be relatively close to each other ([#1417]). + +[#1417]: https://github.com/CosmWasm/cosmwasm/issues/1417 + ## [1.1.1] - 2022-09-15 ### Fixed diff --git a/contracts/hackatom/tests/integration.rs b/contracts/hackatom/tests/integration.rs index 03be6e6859..84bdf53153 100644 --- a/contracts/hackatom/tests/integration.rs +++ b/contracts/hackatom/tests/integration.rs @@ -18,8 +18,8 @@ //! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) use cosmwasm_std::{ - coins, from_binary, to_vec, Addr, AllBalanceResponse, BankMsg, Binary, ContractResult, Empty, - Response, SubMsg, + assert_approx_eq, coins, from_binary, to_vec, Addr, AllBalanceResponse, BankMsg, Binary, + ContractResult, Empty, Response, SubMsg, }; use cosmwasm_vm::{ call_execute, from_slice, @@ -405,9 +405,7 @@ fn execute_allocate_large_memory() { // Gas consumption is relatively small // Note: the exact gas usage depends on the Rust version used to compile Wasm, // which we only fix when using rust-optimizer, not integration tests. - let expected = 4413600000; // +/- 20% - assert!(gas_used > expected * 80 / 100, "Gas used: {}", gas_used); - assert!(gas_used < expected * 120 / 100, "Gas used: {}", gas_used); + assert_approx_eq!(gas_used, 4413600000, "0.2"); let used = deps.memory_pages(); assert_eq!(used, pages_before + 48, "Memory used: {} pages", used); pages_before += 48; diff --git a/packages/std/src/deps.rs b/packages/std/src/deps.rs index 7d6d0cdca3..4d9a9ecdce 100644 --- a/packages/std/src/deps.rs +++ b/packages/std/src/deps.rs @@ -74,7 +74,7 @@ impl<'a, C: CustomQuery> DepsMut<'a, C> { #[cfg(test)] mod tests { use super::*; - use crate::mock::{mock_dependencies, MockApi, MockQuerier, MockStorage}; + use crate::testing::{mock_dependencies, MockApi, MockQuerier, MockStorage}; use serde::{Deserialize, Serialize}; // ensure we can call these many times, eg. as sub-calls diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index 54e2a4bce3..d539f6c750 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -97,27 +97,8 @@ pub use crate::imports::{ExternalApi, ExternalQuerier, ExternalStorage}; // Exposed for testing only // Both unit tests and integration tests are compiled to native code, so everything in here does not need to compile to Wasm. - -#[cfg(not(target_arch = "wasm32"))] -mod mock; #[cfg(not(target_arch = "wasm32"))] -pub mod testing { - #[cfg(feature = "staking")] - pub use crate::mock::StakingQuerier; - pub use crate::mock::{ - digit_sum, mock_dependencies, mock_dependencies_with_balance, - mock_dependencies_with_balances, mock_env, mock_info, mock_wasmd_attr, riffle_shuffle, - BankQuerier, MockApi, MockQuerier, MockQuerierCustomHandlerResult, MockStorage, - MOCK_CONTRACT_ADDR, - }; - #[cfg(feature = "stargate")] - pub use crate::mock::{ - mock_ibc_channel, mock_ibc_channel_close_confirm, mock_ibc_channel_close_init, - mock_ibc_channel_connect_ack, mock_ibc_channel_connect_confirm, mock_ibc_channel_open_init, - mock_ibc_channel_open_try, mock_ibc_packet_ack, mock_ibc_packet_recv, - mock_ibc_packet_timeout, - }; -} +pub mod testing; // Re-exports diff --git a/packages/std/src/testing/assertions.rs b/packages/std/src/testing/assertions.rs new file mode 100644 index 0000000000..3d5cf0a2d8 --- /dev/null +++ b/packages/std/src/testing/assertions.rs @@ -0,0 +1,108 @@ +use crate::{Decimal, Uint128}; +use std::str::FromStr as _; + +/// Asserts that two expressions are approximately equal to each other. +/// +/// The `max_rel_diff` argument defines the maximum relative difference +/// of the `left` and `right` values. +/// +/// On panic, this macro will print the values of the arguments and +/// the actual relative difference. +/// +/// Like [`assert_eq!`], this macro has a second form, where a custom +/// panic message can be provided. +#[macro_export] +macro_rules! assert_approx_eq { + ($left:expr, $right:expr, $max_rel_diff:expr $(,)?) => {{ + $crate::testing::assert_approx_eq_impl($left, $right, $max_rel_diff, None); + }}; + ($left:expr, $right:expr, $max_rel_diff:expr, $($args:tt)+) => {{ + $crate::testing::assert_approx_eq_impl($left, $right, $max_rel_diff, Some(format!($($args)*))); + }}; +} + +/// Implementation for the [`cosmwasm_std::assert_approx_eq`] macro. This does not provide any +/// stability guarantees and may change any time. +#[track_caller] +#[doc(hidden)] +pub fn assert_approx_eq_impl>( + left: U, + right: U, + max_rel_diff: &str, + panic_msg: Option, +) { + let left = left.into(); + let right = right.into(); + let max_rel_diff = Decimal::from_str(max_rel_diff).unwrap(); + + let largest = std::cmp::max(left, right); + let rel_diff = Decimal::from_ratio(left.abs_diff(right), largest); + + if rel_diff > max_rel_diff { + match panic_msg { + Some(panic_msg) => panic!( + "assertion failed: `(left ≈ right)`\nleft: {}\nright: {}\nrelative difference: {}\nmax allowed relative difference: {}\n: {}", + left, right, rel_diff, max_rel_diff, panic_msg + ), + None => panic!( + "assertion failed: `(left ≈ right)`\nleft: {}\nright: {}\nrelative difference: {}\nmax allowed relative difference: {}\n", + left, right, rel_diff, max_rel_diff + ), + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn assert_approx() { + assert_approx_eq!(9_u32, 10_u32, "0.12"); + assert_approx_eq!(9_u64, 10_u64, "0.12"); + assert_approx_eq!( + 9_000_000_000_000_000_000_000_000_000_000_000_000_u128, + 10_000_000_000_000_000_000_000_000_000_000_000_000_u128, + "0.10" + ); + } + + #[test] + fn assert_approx_with_vars() { + let a = 66_u32; + let b = 67_u32; + assert_approx_eq!(a, b, "0.02"); + + let a = 66_u64; + let b = 67_u64; + assert_approx_eq!(a, b, "0.02"); + + let a = 66_u128; + let b = 67_u128; + assert_approx_eq!(a, b, "0.02"); + } + + #[test] + #[should_panic( + expected = "assertion failed: `(left ≈ right)`\nleft: 8\nright: 10\nrelative difference: 0.2\nmax allowed relative difference: 0.12\n" + )] + fn assert_approx_fail() { + assert_approx_eq!(8_u32, 10_u32, "0.12"); + } + + #[test] + #[should_panic( + expected = "assertion failed: `(left ≈ right)`\nleft: 17\nright: 20\nrelative difference: 0.15\nmax allowed relative difference: 0.12\n: some extra info about the error: Foo(8)" + )] + fn assert_approx_with_custom_panic_msg() { + let adjective = "extra"; + #[derive(Debug)] + struct Foo(u32); + assert_approx_eq!( + 17_u32, + 20_u32, + "0.12", + "some {adjective} {} about the error: {:?}", + "info", + Foo(8), + ); + } +} diff --git a/packages/std/src/mock.rs b/packages/std/src/testing/mock.rs similarity index 99% rename from packages/std/src/mock.rs rename to packages/std/src/testing/mock.rs index 1feb040228..0f74c3e3c1 100644 --- a/packages/std/src/mock.rs +++ b/packages/std/src/testing/mock.rs @@ -1550,7 +1550,7 @@ mod tests { }); match result { SystemResult::Ok(ContractResult::Err(err)) => { - assert_eq!(err, "Error parsing into type cosmwasm_std::mock::tests::wasm_querier_works::{{closure}}::MyMsg: Invalid type") + assert_eq!(err, "Error parsing into type cosmwasm_std::testing::mock::tests::wasm_querier_works::{{closure}}::MyMsg: Invalid type") } res => panic!("Unexpected result: {:?}", res), } diff --git a/packages/std/src/testing/mod.rs b/packages/std/src/testing/mod.rs new file mode 100644 index 0000000000..b317d61c48 --- /dev/null +++ b/packages/std/src/testing/mod.rs @@ -0,0 +1,23 @@ +#![cfg(not(target_arch = "wasm32"))] + +// Exposed for testing only +// Both unit tests and integration tests are compiled to native code, so everything in here does not need to compile to Wasm. + +mod assertions; +mod mock; + +pub use assertions::assert_approx_eq_impl; + +#[cfg(feature = "staking")] +pub use mock::StakingQuerier; +pub use mock::{ + digit_sum, mock_dependencies, mock_dependencies_with_balance, mock_dependencies_with_balances, + mock_env, mock_info, mock_wasmd_attr, riffle_shuffle, BankQuerier, MockApi, MockQuerier, + MockQuerierCustomHandlerResult, MockStorage, MOCK_CONTRACT_ADDR, +}; +#[cfg(feature = "stargate")] +pub use mock::{ + mock_ibc_channel, mock_ibc_channel_close_confirm, mock_ibc_channel_close_init, + mock_ibc_channel_connect_ack, mock_ibc_channel_connect_confirm, mock_ibc_channel_open_init, + mock_ibc_channel_open_try, mock_ibc_packet_ack, mock_ibc_packet_recv, mock_ibc_packet_timeout, +}; diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index 6536d75bde..59436a8dff 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -356,7 +356,7 @@ impl<'a, C: CustomQuery> QuerierWrapper<'a, C> { #[cfg(test)] mod tests { use super::*; - use crate::mock::MockQuerier; + use crate::testing::MockQuerier; use crate::{coins, from_slice, Uint128}; // this is a simple demo helper to prove we can use it