From acc1de4e6891c3252d0ddea9d78cdd6886022030 Mon Sep 17 00:00:00 2001 From: haraldmaida Date: Sat, 7 Jun 2025 15:12:01 +0200 Subject: [PATCH 1/4] feat: number specific assertions for `bigdecimal::BigDecimal` --- .github/workflows/ci.yml | 2 +- Cargo.toml | 9 ++- examples/fixture/mod.rs | 2 + justfile | 4 +- src/bigdecimal/mod.rs | 55 ++++++++++++++ src/bigdecimal/tests.rs | 157 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 + tests/version_numbers.rs | 4 +- 8 files changed, 229 insertions(+), 7 deletions(-) create mode 100644 src/bigdecimal/mod.rs create mode 100644 src/bigdecimal/tests.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e587cc3..59f8827 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,7 +94,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --no-default-features --features "colored, float-cmp, num-bigint, rust-decimal" + args: --no-default-features --features "colored, float-cmp, num-bigint, rust-decimal, bigdecimal" msrv: name: Build with MSRV diff --git a/Cargo.toml b/Cargo.toml index 4f63753..c93d1c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,27 +20,30 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["std", "colored", "float-cmp", "panic", "regex"] +bigdecimal = ["dep:bigdecimal"] colored = ["dep:sdiff"] float-cmp = ["dep:float-cmp"] num-bigint = ["dep:num-bigint", "dep:lazy_static"] rust-decimal = ["dep:rust_decimal"] panic = ["std"] regex = ["dep:regex"] -std = [] +std = ["bigdecimal/std", "rust_decimal/std"] [dependencies] hashbrown = "0.15" # optional +bigdecimal = { version = "0.4", optional = true, default-features = false } float-cmp = { version = "0.10", optional = true } lazy_static = { version = "1", optional = true } -num-bigint = { version = "0.4", default-features = false, optional = true } -rust_decimal = { version = "1", default-features = false, optional = true } +num-bigint = { version = "0.4", optional = true, default-features = false } regex = { version = "1", optional = true } +rust_decimal = { version = "1", optional = true, default-features = false } sdiff = { version = "0.1", optional = true } [dev-dependencies] anyhow = "1" +num-bigint = { version = "0.4", default-features = false } proptest = "1" serial_test = "3" time = { version = "0.3", default-features = false, features = ["macros"] } diff --git a/examples/fixture/mod.rs b/examples/fixture/mod.rs index 8229bfb..1416ce9 100644 --- a/examples/fixture/mod.rs +++ b/examples/fixture/mod.rs @@ -2,6 +2,8 @@ // Rust issue [#95513](https://github.com/rust-lang/rust/issues/95513) is fixed mod dummy_extern_uses { use anyhow as _; + #[cfg(feature = "bigdecimal")] + use bigdecimal as _; #[cfg(feature = "float-cmp")] use float_cmp as _; use hashbrown as _; diff --git a/justfile b/justfile index b57c821..426239b 100644 --- a/justfile +++ b/justfile @@ -37,7 +37,7 @@ lint-all-features: # linting code using Clippy for no-std environment lint-no-std: - cargo clippy --all-targets --no-default-features --features "colored, float-cmp, num-bigint, rust-decimal" + cargo clippy --all-targets --no-default-features --features "colored, float-cmp, num-bigint, rust-decimal, bigdecimal" # linting code using Clippy with no features enabled lint-no-features: @@ -55,7 +55,7 @@ test-all-features: # run tests for no-std environment test-no-std: - cargo test --no-default-features --features "colored, float-cmp, num-bigint, rust-decimal" + cargo test --no-default-features --features "colored, float-cmp, num-bigint, rust-decimal, bigdecimal" # run tests with no features enabled test-no-features: diff --git a/src/bigdecimal/mod.rs b/src/bigdecimal/mod.rs new file mode 100644 index 0000000..e85bda6 --- /dev/null +++ b/src/bigdecimal/mod.rs @@ -0,0 +1,55 @@ +use crate::properties::{AdditiveIdentityProperty, MultiplicativeIdentityProperty, SignumProperty}; +use bigdecimal::{BigDecimal, One, Signed, Zero}; +use lazy_static::lazy_static; + +lazy_static! { + static ref BIGDECIMAL_ZERO: BigDecimal = bigdecimal_zero(); + static ref BIGDECIMAL_ONE: BigDecimal = bigdecimal_one(); +} + +#[inline] +fn bigdecimal_zero() -> BigDecimal { + BigDecimal::zero() +} + +#[inline] +fn bigdecimal_one() -> BigDecimal { + BigDecimal::one() +} + +impl SignumProperty for BigDecimal { + fn is_negative_property(&self) -> bool { + self.is_negative() + } + + fn is_positive_property(&self) -> bool { + self.is_positive() + } +} + +impl AdditiveIdentityProperty for BigDecimal { + fn additive_identity() -> Self { + bigdecimal_zero() + } +} + +impl AdditiveIdentityProperty for &BigDecimal { + fn additive_identity() -> Self { + &BIGDECIMAL_ZERO + } +} + +impl MultiplicativeIdentityProperty for BigDecimal { + fn multiplicative_identity() -> Self { + bigdecimal_one() + } +} + +impl MultiplicativeIdentityProperty for &BigDecimal { + fn multiplicative_identity() -> Self { + &BIGDECIMAL_ONE + } +} + +#[cfg(test)] +mod tests; diff --git a/src/bigdecimal/tests.rs b/src/bigdecimal/tests.rs new file mode 100644 index 0000000..39d9195 --- /dev/null +++ b/src/bigdecimal/tests.rs @@ -0,0 +1,157 @@ +use crate::prelude::*; +use bigdecimal::BigDecimal; +use num_bigint::BigInt; + +#[test] +fn bigdecimal_is_equal_to_other() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(subject).is_equal_to(BigDecimal::new(BigInt::from(42_831), 3)); + + assert_that(BigDecimal::new(BigInt::from(42_831), 3)) + .is_equal_to(BigDecimal::new(BigInt::from(428_310), 4)); + assert_that(BigDecimal::new(BigInt::from(0), 0)) + .is_equal_to(BigDecimal::new(BigInt::from(0), 2)); + assert_that(BigDecimal::new(BigInt::from(-0), 0)) + .is_equal_to(BigDecimal::new(BigInt::from(0), 0)); +} + +#[test] +fn verify_bigdecimal_is_equal_to_other_fails() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + let failures = verify_that(subject) + .is_equal_to(BigDecimal::new(BigInt::from(-42_831), 3)) + .display_failures(); + + assert_eq!( + failures, + &[ + r"assertion failed: expected subject is equal to BigDecimal(sign=Minus, scale=3, digits=[42831]) + but was: BigDecimal(sign=Plus, scale=3, digits=[42831]) + expected: BigDecimal(sign=Minus, scale=3, digits=[42831]) +" + ] + ); +} + +#[test] +fn bigdecimal_is_not_equal_to_other() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(subject).is_not_equal_to(BigDecimal::new(BigInt::from(42_831), 2)); +} + +#[test] +fn borrowed_bigdecimal_is_equal_to_other() { + let subject = BigDecimal::new(BigInt::from(-42_831), 3); + + assert_that(&subject).is_equal_to(&BigDecimal::new(BigInt::from(-42_831), 3)); +} + +#[test] +fn bigdecimal_is_less_than_other() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(&subject).is_less_than(&BigDecimal::new(BigInt::from(1_592_834), 3)); + assert_that(subject).is_less_than(BigDecimal::new(BigInt::from(42_832), 3)); +} + +#[test] +fn bigdecimal_is_greater_than_other() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(&subject).is_greater_than(&BigDecimal::new(BigInt::from(-232_199), 3)); + assert_that(subject).is_greater_than(BigDecimal::new(BigInt::from(42_830), 3)); +} + +#[test] +fn bigdecimal_is_at_least_other() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(&subject).is_at_least(&BigDecimal::new(BigInt::from(42_831), 3)); + assert_that(&subject).is_at_least(&BigDecimal::new(BigInt::from(42_830), 3)); + assert_that(subject).is_at_least(BigDecimal::new(BigInt::from(-332), 3)); +} + +#[test] +fn bigdecimal_is_at_most_other() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(&subject).is_at_most(&BigDecimal::new(BigInt::from(42_831), 3)); + assert_that(&subject).is_at_most(&BigDecimal::new(BigInt::from(42_832), 3)); + assert_that(subject).is_at_most(BigDecimal::new(BigInt::from(65_587_929), 3)); +} + +#[test] +fn bigdecimal_is_negative() { + let subject = BigDecimal::new(BigInt::from(-42_831), 3); + + assert_that(&subject).is_negative(); +} + +#[test] +fn bigdecimal_is_not_negative() { + assert_that(&BigDecimal::new(BigInt::from(42_831), 3)).is_not_negative(); + assert_that(BigDecimal::new(BigInt::from(0), 0)).is_not_negative(); +} + +#[test] +fn bigdecimal_is_positive() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(&subject).is_positive(); +} + +#[test] +fn bigdecimal_is_not_positive() { + assert_that(&BigDecimal::new(BigInt::from(-42_831), 3)).is_not_positive(); + assert_that(BigDecimal::new(BigInt::from(0), 0)).is_not_positive(); +} + +#[test] +fn bigdecimal_signum_of_zero() { + assert_that(BigDecimal::new(BigInt::from(0), 0)).is_zero(); +} + +#[test] +fn borrowed_bigdecimal_is_negative() { + assert_that(&BigDecimal::new(BigInt::from(-42_831), 3)).is_negative(); +} + +#[test] +fn borrowed_bigdecimal_is_positive() { + assert_that(&BigDecimal::new(BigInt::from(42_831), 3)).is_positive(); +} + +#[test] +fn mutable_borrowed_bigdecimal_is_negative() { + assert_that(&mut BigDecimal::new(BigInt::from(-42_831), 3)).is_negative(); +} + +#[test] +fn mutable_borrowed_bigdecimal_is_positive() { + assert_that(&mut BigDecimal::new(BigInt::from(42_831), 3)).is_positive(); +} + +#[test] +fn bigdecimal_is_zero() { + assert_that(BigDecimal::new(BigInt::from(0), 0)).is_zero(); + assert_that(BigDecimal::new(BigInt::from(-0), 0)).is_zero(); + assert_that(BigDecimal::new(BigInt::from(0), 2)).is_zero(); +} + +#[test] +fn bigdecimal_is_one() { + assert_that(BigDecimal::new(BigInt::from(1), 0)).is_one(); +} + +#[test] +fn borrowed_bigdecimal_is_zero() { + assert_that(&BigDecimal::new(BigInt::from(0), 0)).is_zero(); +} + +#[test] +fn borrowed_bigdecimal_is_one() { + assert_that(&BigDecimal::new(BigInt::from(1), 0)).is_one(); +} diff --git a/src/lib.rs b/src/lib.rs index 50bc9bb..ec84759 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -671,6 +671,8 @@ pub mod prelude; pub mod properties; pub mod spec; +#[cfg(feature = "bigdecimal")] +mod bigdecimal; mod boolean; mod c_string; mod char; @@ -711,6 +713,7 @@ type TestCodeSnippetsInReadme = (); // Rust issue [#95513](https://github.com/rust-lang/rust/issues/95513) is fixed #[cfg(test)] mod dummy_extern_uses { + use num_bigint as _; use proptest as _; use serial_test as _; use time as _; diff --git a/tests/version_numbers.rs b/tests/version_numbers.rs index 46e42aa..3b80e13 100644 --- a/tests/version_numbers.rs +++ b/tests/version_numbers.rs @@ -6,12 +6,14 @@ mod dummy_extern_uses { use anyhow as _; use asserting as _; + #[cfg(feature = "bigdecimal")] + use bigdecimal as _; #[cfg(feature = "float-cmp")] use float_cmp as _; use hashbrown as _; #[cfg(feature = "num-bigint")] use lazy_static as _; - #[cfg(feature = "num-bigint")] + #[cfg(any(feature = "num-bigint", test))] use num_bigint as _; use proptest as _; #[cfg(feature = "regex")] From 91c00362b84bc4b1f064da3b2214f6b02f473706 Mon Sep 17 00:00:00 2001 From: haraldmaida Date: Sat, 7 Jun 2025 15:32:03 +0200 Subject: [PATCH 2/4] feat: number specific assertions for `bigdecimal::BigDecimalRef` --- src/bigdecimal/mod.rs | 29 ++++++++++++-- src/bigdecimal/tests.rs | 84 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 3 deletions(-) diff --git a/src/bigdecimal/mod.rs b/src/bigdecimal/mod.rs index e85bda6..71089ef 100644 --- a/src/bigdecimal/mod.rs +++ b/src/bigdecimal/mod.rs @@ -1,5 +1,6 @@ use crate::properties::{AdditiveIdentityProperty, MultiplicativeIdentityProperty, SignumProperty}; -use bigdecimal::{BigDecimal, One, Signed, Zero}; +use bigdecimal::num_bigint::Sign; +use bigdecimal::{BigDecimal, BigDecimalRef, One, Zero}; use lazy_static::lazy_static; lazy_static! { @@ -19,11 +20,11 @@ fn bigdecimal_one() -> BigDecimal { impl SignumProperty for BigDecimal { fn is_negative_property(&self) -> bool { - self.is_negative() + self.sign() == Sign::Minus } fn is_positive_property(&self) -> bool { - self.is_positive() + self.sign() == Sign::Plus } } @@ -51,5 +52,27 @@ impl MultiplicativeIdentityProperty for &BigDecimal { } } +impl SignumProperty for BigDecimalRef<'_> { + fn is_negative_property(&self) -> bool { + self.sign() == Sign::Minus + } + + fn is_positive_property(&self) -> bool { + self.sign() == Sign::Plus + } +} + +impl AdditiveIdentityProperty for BigDecimalRef<'_> { + fn additive_identity() -> Self { + BIGDECIMAL_ZERO.to_ref() + } +} + +impl MultiplicativeIdentityProperty for BigDecimalRef<'_> { + fn multiplicative_identity() -> Self { + BIGDECIMAL_ONE.to_ref() + } +} + #[cfg(test)] mod tests; diff --git a/src/bigdecimal/tests.rs b/src/bigdecimal/tests.rs index 39d9195..a4aec0b 100644 --- a/src/bigdecimal/tests.rs +++ b/src/bigdecimal/tests.rs @@ -155,3 +155,87 @@ fn borrowed_bigdecimal_is_zero() { fn borrowed_bigdecimal_is_one() { assert_that(&BigDecimal::new(BigInt::from(1), 0)).is_one(); } + +#[test] +fn bigdecimalref_is_equal_to_other() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(subject.to_ref()).is_equal_to(BigDecimal::new(BigInt::from(42_831), 3).to_ref()); + + assert_that(BigDecimal::new(BigInt::from(42_831), 3).to_ref()) + .is_equal_to(BigDecimal::new(BigInt::from(428_310), 4).to_ref()); + assert_that(BigDecimal::new(BigInt::from(0), 0).to_ref()) + .is_equal_to(BigDecimal::new(BigInt::from(0), 2).to_ref()); + assert_that(BigDecimal::new(BigInt::from(-0), 0).to_ref()) + .is_equal_to(BigDecimal::new(BigInt::from(0), 0).to_ref()); +} + +#[test] +fn verify_bigdecimalref_is_equal_to_other_fails() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + let failures = verify_that(subject.to_ref()) + .is_equal_to(BigDecimal::new(BigInt::from(-42_831), 3).to_ref()) + .display_failures(); + + assert_eq!( + failures, + &[ + r"assertion failed: expected subject is equal to BigDecimalRef { sign: Minus, digits: 42831, scale: 3 } + but was: BigDecimalRef { sign: Plus, digits: 42831, scale: 3 } + expected: BigDecimalRef { sign: Minus, digits: 42831, scale: 3 } +" + ] + ); +} + +#[test] +fn bigdecimalref_is_not_equal_to_other() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(subject.to_ref()) + .is_not_equal_to(BigDecimal::new(BigInt::from(42_831), 2).to_ref()); +} + +#[test] +fn bigdecimalref_is_negative() { + let subject = BigDecimal::new(BigInt::from(-42_831), 3); + + assert_that(subject.to_ref()).is_negative(); +} + +#[test] +fn bigdecimalref_is_not_negative() { + assert_that(BigDecimal::new(BigInt::from(42_831), 3).to_ref()).is_not_negative(); + assert_that(BigDecimal::new(BigInt::from(0), 0).to_ref()).is_not_negative(); +} + +#[test] +fn bigdecimalref_is_positive() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(subject.to_ref()).is_positive(); +} + +#[test] +fn bigdecimalref_is_not_positive() { + assert_that(BigDecimal::new(BigInt::from(-42_831), 3).to_ref()).is_not_positive(); + assert_that(BigDecimal::new(BigInt::from(0), 0).to_ref()).is_not_positive(); +} + +#[test] +fn bigdecimalref_signum_of_zero() { + assert_that(BigDecimal::new(BigInt::from(0), 0).to_ref()).is_zero(); +} + +#[test] +fn bigdecimalref_is_zero() { + assert_that(BigDecimal::new(BigInt::from(0), 0).to_ref()).is_zero(); + assert_that(BigDecimal::new(BigInt::from(-0), 0).to_ref()).is_zero(); + assert_that(BigDecimal::new(BigInt::from(0), 2).to_ref()).is_zero(); +} + +#[test] +fn bigdecimalref_is_one() { + assert_that(BigDecimal::new(BigInt::from(1), 0).to_ref()).is_one(); +} From 26a9d86c056d73acd93a5c0dd3747ccba9b6e792 Mon Sep 17 00:00:00 2001 From: haraldmaida Date: Sat, 7 Jun 2025 15:36:11 +0200 Subject: [PATCH 3/4] doc: add `bigdecimal::BigDecimal` and `bigdecimal::BigDecimalRef` to available assertions in README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index cbfa6be..c9ab5e9 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,8 @@ for numbers of types * integer primitives: `i8`, `i16`, `i32`, `i64`, `i128` and `isize` * floating point numbers: `f32` and `f64` * `num_bigint::BigInt` (requires crate feature `num-bigint`) +* `bigdecimal:BigDecimal` and `bigdecimal:BigDecimalRef` (requires crate feature `bigdecimal`) +* `rust_decimal::Decimal` (requires crate feature `rust-decimal`) | assertion | description | |-----------------|------------------------------------------------------| @@ -182,6 +184,7 @@ for numbers of types and `usize` * floating point numbers: `f32` and `f64` * `num_bigint::BigInt` and `num_bigint::BigUint` (requires crate feature `num-bigint`) +* `bigdecimal:BigDecimal` and `bigdecimal:BigDecimalRef` (requires crate feature `bigdecimal`) * `rust_decimal::Decimal` (requires crate feature `rust-decimal`) | assertion | description | From 43487c0b2cccbf64f9a4a6e8a031b93c8af9a9e3 Mon Sep 17 00:00:00 2001 From: haraldmaida Date: Sat, 7 Jun 2025 15:42:06 +0200 Subject: [PATCH 4/4] chore: use re-export `bigdecimal::num-bigint::BigInt` instead from crate `num-bigint` in tests --- Cargo.toml | 1 - src/bigdecimal/tests.rs | 2 +- src/lib.rs | 1 - tests/version_numbers.rs | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c93d1c7..c08d333 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,6 @@ sdiff = { version = "0.1", optional = true } [dev-dependencies] anyhow = "1" -num-bigint = { version = "0.4", default-features = false } proptest = "1" serial_test = "3" time = { version = "0.3", default-features = false, features = ["macros"] } diff --git a/src/bigdecimal/tests.rs b/src/bigdecimal/tests.rs index a4aec0b..b8d2713 100644 --- a/src/bigdecimal/tests.rs +++ b/src/bigdecimal/tests.rs @@ -1,6 +1,6 @@ use crate::prelude::*; +use bigdecimal::num_bigint::BigInt; use bigdecimal::BigDecimal; -use num_bigint::BigInt; #[test] fn bigdecimal_is_equal_to_other() { diff --git a/src/lib.rs b/src/lib.rs index ec84759..ab92785 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -713,7 +713,6 @@ type TestCodeSnippetsInReadme = (); // Rust issue [#95513](https://github.com/rust-lang/rust/issues/95513) is fixed #[cfg(test)] mod dummy_extern_uses { - use num_bigint as _; use proptest as _; use serial_test as _; use time as _; diff --git a/tests/version_numbers.rs b/tests/version_numbers.rs index 3b80e13..84ecc1c 100644 --- a/tests/version_numbers.rs +++ b/tests/version_numbers.rs @@ -13,7 +13,7 @@ mod dummy_extern_uses { use hashbrown as _; #[cfg(feature = "num-bigint")] use lazy_static as _; - #[cfg(any(feature = "num-bigint", test))] + #[cfg(feature = "num-bigint")] use num_bigint as _; use proptest as _; #[cfg(feature = "regex")]