From b8a211feee27df793dd9609de003877661066cd9 Mon Sep 17 00:00:00 2001 From: Jacek Generowicz Date: Mon, 15 Aug 2022 10:38:52 +0200 Subject: [PATCH 1/2] Add concise tests summarizing which binops work --- Cargo.toml | 1 + src/tests/impl_ops.rs | 197 ++++++++++++++++++++++++++++++++++++++++++ src/tests/mod.rs | 1 + 3 files changed, 199 insertions(+) create mode 100644 src/tests/impl_ops.rs diff --git a/Cargo.toml b/Cargo.toml index ee755126..b051b28b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ approx = "0.5" quickcheck = "1.0" serde_json = "1.0" static_assertions = "1.1" +paste = "1.0.9" [features] default = ["autoconvert", "f32", "f64", "si", "std"] diff --git a/src/tests/impl_ops.rs b/src/tests/impl_ops.rs new file mode 100644 index 00000000..9e0974ba --- /dev/null +++ b/src/tests/impl_ops.rs @@ -0,0 +1,197 @@ +//! Tests summarizing which binary operations are implemented. +//! +//! This includes: +//! +//! + the operations implemented using the `impl_ops!` macro. +//! + the comparison operators: `==`, `<`, `>`, `<=` and `>=`. +//! +//! The purpose is not to test the implementations exhaustively, but rather to +//! give a clear overview of which ones are present or absent, depending on +//! factors like: +//! +//! + `autoconvert` being enabled or disabled +//! + whether the operands are values or refs + +// Helper functions for constructing a variety of Quantities without excessive +// syntactic noise. +#[allow(unused_macros)] +mod concise { + + // Bring the units used by the constructors into a limited scope + mod units { + pub use crate::si::{ + area::square_meter, + frequency::hertz, + length::meter, + ratio::ratio, + time::second, + velocity::meter_per_second, + }; + } + + // Define a set of quantities with different base units, for testing of `autoconvert` + pub mod cgs { + ISQ!(crate::si, f32, (centimeter, gram, second, ampere, kelvin, mole, candela)); + } + + /// Generate a module containing concise constructors for Quantities based + /// on a given storage type, in order to allow writing pithy tests + macro_rules! constructors { + ($storage_type:ident $($function:item)*) => { + #[allow(unused)] + pub mod $storage_type { + use crate::si::$storage_type as si; + use crate::tests::impl_ops::concise::cgs; + use crate::tests::impl_ops::concise::units::*; + type T = $storage_type; + $($function)* + } + }; + } + + /// Generate a function called NAME which returns QUANTITY by interpreting + /// its argument as UNIT + /// + /// wrap!(NAME(QUANTITY) UNIT); + macro_rules! wrap { + ($name:ident ($quantity:ty) $unit:ident ) => { + pub fn $name(x: T) -> $quantity { + <$quantity>::new::<$unit>(x) + } + }; + } + + // Use the above macros to generate terse Quantity constructors. + #[cfg(feature = "f32")] + constructors! { f32 + wrap!( m( si::Length) meter); + wrap!( cgs_m(cgs::Length) meter); + wrap!( s( si::Time) second); + wrap!( one( si::Ratio) ratio); + wrap!(cgs_one(cgs::Ratio) ratio); + wrap!( m2( si::Area) square_meter); + wrap!( mps( si::Velocity) meter_per_second); + wrap!( hz(si::Frequency) hertz); + } + +} + +// The tests will come in 4 varieties: `vv`, `vr`, `rv` & `rr`. with `r` and `v` +// standing for 'value' and 'reference', describing the left and right operands +// of the operator being tested. + +/// Generate a test that checks the result of a binary operation +/// +/// There are two different kinds of tests: +/// +/// 1. verify the result of a binary operation expression +/// 2. verify the side effect of an assignment operator on the left operand +/// +/// Syntactically these look like +/// +/// 1. test!(TEST_NAME LHS OPERATOR RHS, EXPECTED); +/// 2. test!(TEST_NAME \[ LHS \] OPERATOR RHS, EXPECTED); +/// +/// In other words, the side-effect test is distinguished from the expression +/// test by wrapping the left operand (the one which will be mutated by the +/// operator) in square brackets. +/// +/// Additionally, some tests should only be run when the `autoconvert` features +/// is enabled. These are identified by using the `ac` token at the start of the +/// input to `test!(...)`: +/// +/// 1. Run test always: `test!( ...)` +/// 2. Run test only when `autoconvert` enabled: `test!(ac ...)` +/// +/// Furthermore, the `ac` variant of `test!` appends `_autoconvert` to +/// TEST_NAME, in order to distinguish automatically between tests which require +/// autoconvert and those that don't. +#[allow(unused_macros)] +macro_rules! test { + // The first two arms (with `ac` as the first input token) forward the + // request to the last two arms, after injecting two changes: + // 1. add `#[cfg(feature = "autoconvert")]` + // 2. append `_autoconvert` to the test name + (ac $test_name:ident [$lhs:expr] $op:tt $rhs:expr, $expected:expr) => { + paste::paste! { + #[cfg(feature = "autoconvert")] + test!([<$test_name _ autoconvert>] [$lhs] $op $rhs, $expected); + } + }; + (ac $test_name:ident $lhs:expr, $rhs:expr) => { + paste::paste! { + #[cfg(feature = "autoconvert")] + test!([<$test_name _ autoconvert>] $lhs, $rhs); + } + }; + // ---------------------------------------------------------------- + // test assignment operations: let mut l; l += r + ($test_name:ident [$lhs:expr] $op:tt $rhs:expr, $expected:expr) => { + #[test] + fn $test_name() { + let mut x = $lhs; + x $op $rhs; + assert_eq!(x, $expected); + } + }; + // test binary operator expressions: l + r + ($test_name:ident $lhs:expr, $rhs:expr) => { + #[test] + fn $test_name() { + assert_eq!($lhs, $rhs); + } + }; +} + +// The number in the comment after each test indicates which implementation +// provides the functionality being tested, according to the table shown here: +// https://github.com/iliekturtles/uom/pull/307#issuecomment-1186208970 +#[rustfmt::skip] +#[cfg(feature = "f32")] +mod vv { + use super::concise::f32::*; + //#[cfg(feature = "autoconvert")] + + // NOTE: tests labelled with `ac` just inside `test!(` require the + // `autoconvert` feature to be enabled. + + test!(ac add m(1.) + cgs_m(2.), m(3.)); // 1 + test!(ac sub m(8.) - cgs_m(2.), m(6.)); // 1 + test!( add m(1.) + m(2.), m(3.)); // 2 + test!( sub m(8.) - m(2.), m(6.)); // 2 + test!(ac add_assign [ m(1.) ] += cgs_m(2.), m(3.)); // 3 + test!(ac sub_assign [ m(8.) ] -= cgs_m(2.), m(6.)); // 3 + test!( add_assign [ m(1.) ] += m(2.), m(3.)); // 4 + test!( sub_assign [ m(8.) ] -= m(2.), m(6.)); // 4 + test!(ac mul m(4.) * cgs_m(2.), m2(8.)); // 5 + test!(ac div m(6.) / cgs_m(2.), one(3.)); // 5 + test!( mul m(4.) * m(2.), m2(8.)); // 6 + test!( div m(6.) / s(2.), mps(3.)); // 6 + test!( mul_ratio_left one(4.) * m(2.), m(8.)); // 6 + test!( div_ratio_left one(6.) / s(2.), hz(3.)); // 6 + test!( mul_ratio_right m(4.) * one(2.), m(8.)); // 6 c.f. AAA below + test!( div_ratio_right m(6.) / one(2.), m(3.)); // 6 c.f. BBB below + test!(ac mul_ratio_right m(4.) * cgs_one(2.), m(8.)); // 6 c.f. CCC below + test!(ac div_ratio_right m(6.) / cgs_one(2.), m(3.)); // 6 c.f. DDD below + // test!( mul_assign_ratio [ m(4.) ] *= one(2.), m(8.)); // ERR c.f. AAA above + // test!( div_assign_ratio [ m(6.) ] /= one(2.), m(3.)); // ERR c.f. BBB above + // test!(ac mul_assign_ratio [ m(4.) ] *= cgs_one(2.), m(8.)); // ERR c.f. CCC above + // test!(ac div_assign_ratio [ m(6.) ] /= cgs_one(2.), m(3.)); // ERR c.f. DDD above + test!( mul_bare_right m(4.) * 2. , m(8.)); // 7 + test!( div_bare_right m(6.) / 2. , m(3.)); // 7 + test!( mul_assign_bare [ m(2.) ] *= 3. , m(6.)); // 8 + test!( div_assign_bare [ m(6.) ] /= 3. , m(2.)); // 8 + test!( mul_bare_left 4. * m(2.), m(8.)); // 9 + test!( div_bare_left 6. / s(2.), hz(3.)); // 9 + + test!( eq m(1.) == m(2.), false); + test!(ac eq m(1.) == cgs_m(2.), false); + test!( lt m(1.) < m(2.), true ); + test!(ac lt m(1.) < cgs_m(2.), true ); + test!( gt m(1.) > m(2.), false); + test!(ac gt m(1.) > cgs_m(2.), false); + test!( le m(1.) <= m(2.), true ); + test!(ac le m(1.) <= cgs_m(2.), true ); + test!( ge m(1.) >= m(2.), false); + test!(ac ge m(1.) >= cgs_m(2.), false); +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index c6e705f0..c7fc9248 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -263,6 +263,7 @@ mod a_struct { } mod asserts; +mod impl_ops; mod quantities; mod quantity; mod system; From 28699c62c094833537b726f875375faba25b0c0a Mon Sep 17 00:00:00 2001 From: Jacek Generowicz Date: Thu, 18 Aug 2022 10:20:33 +0200 Subject: [PATCH 2/2] Implement MulDivAssign with Ratio on RHS --- src/system.rs | 16 ++++++++++++++++ src/tests/impl_ops.rs | 8 ++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/system.rs b/src/system.rs index a9fb3b70..ff4322f7 100644 --- a/src/system.rs +++ b/src/system.rs @@ -553,6 +553,22 @@ macro_rules! system { } } + impl $crate::lib::ops::$MulDivAssignTrait> for Quantity + where + D: Dimension + ?Sized, + D::Kind: $crate::marker::$MulDivAssignTrait, + Ul: Units + ?Sized, + Ur: Units + ?Sized, + V: $crate::num::Num + $crate::Conversion + + $crate::lib::ops::$MulDivAssignTrait, + { + #[inline(always)] + fn $muldivassign_fun(&mut self, rhs: Quantity) { + self.value $muldivassign_op rhs.value; + // change_base is needed for autoconvert! version + } + } + #[doc(hidden)] mod $Mod { storage_types! { diff --git a/src/tests/impl_ops.rs b/src/tests/impl_ops.rs index 9e0974ba..f4ce55b0 100644 --- a/src/tests/impl_ops.rs +++ b/src/tests/impl_ops.rs @@ -173,10 +173,10 @@ mod vv { test!( div_ratio_right m(6.) / one(2.), m(3.)); // 6 c.f. BBB below test!(ac mul_ratio_right m(4.) * cgs_one(2.), m(8.)); // 6 c.f. CCC below test!(ac div_ratio_right m(6.) / cgs_one(2.), m(3.)); // 6 c.f. DDD below - // test!( mul_assign_ratio [ m(4.) ] *= one(2.), m(8.)); // ERR c.f. AAA above - // test!( div_assign_ratio [ m(6.) ] /= one(2.), m(3.)); // ERR c.f. BBB above - // test!(ac mul_assign_ratio [ m(4.) ] *= cgs_one(2.), m(8.)); // ERR c.f. CCC above - // test!(ac div_assign_ratio [ m(6.) ] /= cgs_one(2.), m(3.)); // ERR c.f. DDD above + test!( mul_assign_ratio [ m(4.) ] *= one(2.), m(8.)); // c.f. AAA above + test!( div_assign_ratio [ m(6.) ] /= one(2.), m(3.)); // c.f. BBB above + test!(ac mul_assign_ratio [ m(4.) ] *= cgs_one(2.), m(8.)); // c.f. CCC above + test!(ac div_assign_ratio [ m(6.) ] /= cgs_one(2.), m(3.)); // c.f. DDD above test!( mul_bare_right m(4.) * 2. , m(8.)); // 7 test!( div_bare_right m(6.) / 2. , m(3.)); // 7 test!( mul_assign_bare [ m(2.) ] *= 3. , m(6.)); // 8