diff --git a/crates/foundation/apsis-core/src/constant.rs b/crates/foundation/apsis-core/src/constant.rs index c4ad0f2..8011b51 100644 --- a/crates/foundation/apsis-core/src/constant.rs +++ b/crates/foundation/apsis-core/src/constant.rs @@ -134,44 +134,3 @@ pub const STANDARD_ATMOSPHERE: f64 = 101_325.0; /// /// Exact by definition of the Celsius scale. pub const ZERO_CELSIUS_IN_KELVIN: f64 = 273.15; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn defining_constants_are_exact() { - assert_eq!(SPEED_OF_LIGHT, 299_792_458.0); - assert_eq!(BOLTZMANN, 1.380_649e-23); - assert_eq!(PLANCK, 6.626_070_15e-34); - assert_eq!(ELEMENTARY_CHARGE, 1.602_176_634e-19); - assert_eq!(ASTRONOMICAL_UNIT, 149_597_870_700.0); - assert_eq!(STANDARD_GRAVITY, 9.806_65); - } - - #[test] - fn time_constants_are_consistent() { - assert_eq!(HOUR, MINUTE * 60.0); - assert_eq!(DAY, HOUR * 24.0); - assert_eq!(JULIAN_YEAR, DAY * 365.25); - assert_eq!(JULIAN_CENTURY, JULIAN_YEAR * 100.0); - } - - #[test] - fn j2000_minus_unix_epoch_is_the_expected_span() { - // The two epochs are 10 957 days apart. - assert_eq!(JULIAN_DATE_J2000 - JULIAN_DATE_UNIX_EPOCH, 10_957.5); - assert_eq!(SECONDS_J2000_TO_UNIX, 10_957.5 * DAY); - assert_eq!(SECONDS_J2000_TO_UNIX, 946_728_000.0); - } - - #[test] - fn earth_flattening_matches_the_inverse_value() { - assert!((1.0 / EARTH_FLATTENING - 298.257_223_563).abs() < 1e-9); - } - - #[test] - fn celsius_offset_is_exact() { - assert_eq!(ZERO_CELSIUS_IN_KELVIN, 273.15); - } -} diff --git a/crates/foundation/apsis-core/src/error.rs b/crates/foundation/apsis-core/src/error.rs index 3f266de..09be988 100644 --- a/crates/foundation/apsis-core/src/error.rs +++ b/crates/foundation/apsis-core/src/error.rs @@ -168,78 +168,3 @@ impl From for Error { /// A [`Result`](core::result::Result) whose error type is [`Error`]. pub type Result = core::result::Result; - -#[cfg(test)] -mod tests { - use super::{Error, ErrorKind, Result}; - use core::fmt; - - #[derive(Debug)] - struct Underlying; - - impl fmt::Display for Underlying { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("underlying failure") - } - } - - impl core::error::Error for Underlying {} - - #[test] - fn kind_is_reported() { - let error = Error::new(ErrorKind::Parse); - assert_eq!(error.kind(), ErrorKind::Parse); - assert_eq!(error.message(), None); - } - - #[test] - fn message_is_reported() { - let error = Error::with_message(ErrorKind::OutOfRange, "semi-major axis is negative"); - assert_eq!(error.kind(), ErrorKind::OutOfRange); - assert_eq!(error.message(), Some("semi-major axis is negative")); - } - - #[test] - fn display_includes_kind_and_message() { - let error = Error::with_message(ErrorKind::Parse, "bad checksum"); - assert_eq!(error.to_string(), "parse error: bad checksum"); - assert_eq!(Error::new(ErrorKind::NotFound).to_string(), "not found"); - } - - #[test] - fn source_is_exposed() { - let error = Error::with_source(ErrorKind::Other, Underlying); - assert!(std::error::Error::source(&error).is_some()); - assert_eq!(error.kind(), ErrorKind::Other); - } - - #[test] - fn context_and_cause_replace_previous_values() { - let error = Error::new(ErrorKind::Other) - .with_context("first") - .with_context("second") - .caused_by(Underlying); - assert_eq!(error.message(), Some("second")); - assert!(std::error::Error::source(&error).is_some()); - } - - #[test] - fn converts_from_kind() { - let error: Error = ErrorKind::Unsupported.into(); - assert_eq!(error.kind(), ErrorKind::Unsupported); - } - - #[test] - fn works_in_result_position() { - fn fail() -> Result { - Err(Error::new(ErrorKind::NotFinite)) - } - - let outcome = fail(); - assert!(outcome.is_err()); - assert_eq!( - outcome.err().map(|error| error.kind()), - Some(ErrorKind::NotFinite) - ); - } -} diff --git a/crates/foundation/apsis-core/src/quantity.rs b/crates/foundation/apsis-core/src/quantity.rs index a83ee9e..db0748f 100644 --- a/crates/foundation/apsis-core/src/quantity.rs +++ b/crates/foundation/apsis-core/src/quantity.rs @@ -61,110 +61,3 @@ pub trait Quantity: Copy { U::from_value(base / ::U::factor_to_base()) } } - -#[cfg(test)] -mod tests { - use super::{Quantity, Unit}; - use core::fmt; - - #[derive(Debug, Clone, Copy, PartialEq)] - struct Length; - - #[derive(Debug, Clone, Copy)] - struct Meter; - - #[derive(Debug, Clone, Copy)] - struct Kilometer; - - impl fmt::Display for Meter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("m") - } - } - - impl fmt::Display for Kilometer { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("km") - } - } - - impl Unit for Meter { - type Dimension = Length; - - fn factor_to_base() -> f64 { - 1.0 - } - - fn symbol() -> &'static str { - "m" - } - } - - impl Unit for Kilometer { - type Dimension = Length; - - fn factor_to_base() -> f64 { - 1_000.0 - } - - fn symbol() -> &'static str { - "km" - } - } - - #[derive(Debug, Clone, Copy, PartialEq)] - struct Meters(f64); - - #[derive(Debug, Clone, Copy, PartialEq)] - struct Kilometers(f64); - - impl Quantity for Meters { - type U = Meter; - - fn value(self) -> f64 { - self.0 - } - - fn from_value(value: f64) -> Self { - Self(value) - } - } - - impl Quantity for Kilometers { - type U = Kilometer; - - fn value(self) -> f64 { - self.0 - } - - fn from_value(value: f64) -> Self { - Self(value) - } - } - - #[test] - fn value_round_trips() { - let distance = Meters::from_value(1_500.0); - assert_eq!(distance.value(), 1_500.0); - } - - #[test] - fn converts_between_units_of_one_dimension() { - let distance = Meters::from_value(2_500.0); - let in_kilometers: Kilometers = distance.to_unit(); - assert!((in_kilometers.value() - 2.5).abs() < 1e-12); - } - - #[test] - fn conversion_round_trips() { - let original = Kilometers::from_value(7.25); - let round_tripped: Kilometers = original.to_unit::().to_unit(); - assert!((round_tripped.value() - 7.25).abs() < 1e-12); - } - - #[test] - fn unit_metadata_is_available() { - assert_eq!(::symbol(), "m"); - assert_eq!(::factor_to_base(), 1_000.0); - } -} diff --git a/crates/foundation/apsis-core/tests/constant.rs b/crates/foundation/apsis-core/tests/constant.rs new file mode 100644 index 0000000..9f12951 --- /dev/null +++ b/crates/foundation/apsis-core/tests/constant.rs @@ -0,0 +1,39 @@ +//! Tests for the physical and astronomical constants. + +use apsis_core::constant::*; + +#[test] +fn defining_constants_are_exact() { + assert_eq!(SPEED_OF_LIGHT, 299_792_458.0); + assert_eq!(BOLTZMANN, 1.380_649e-23); + assert_eq!(PLANCK, 6.626_070_15e-34); + assert_eq!(ELEMENTARY_CHARGE, 1.602_176_634e-19); + assert_eq!(ASTRONOMICAL_UNIT, 149_597_870_700.0); + assert_eq!(STANDARD_GRAVITY, 9.806_65); +} + +#[test] +fn time_constants_are_consistent() { + assert_eq!(HOUR, MINUTE * 60.0); + assert_eq!(DAY, HOUR * 24.0); + assert_eq!(JULIAN_YEAR, DAY * 365.25); + assert_eq!(JULIAN_CENTURY, JULIAN_YEAR * 100.0); +} + +#[test] +fn j2000_minus_unix_epoch_is_the_expected_span() { + // The two epochs are 10 957 days apart. + assert_eq!(JULIAN_DATE_J2000 - JULIAN_DATE_UNIX_EPOCH, 10_957.5); + assert_eq!(SECONDS_J2000_TO_UNIX, 10_957.5 * DAY); + assert_eq!(SECONDS_J2000_TO_UNIX, 946_728_000.0); +} + +#[test] +fn earth_flattening_matches_the_inverse_value() { + assert!((1.0 / EARTH_FLATTENING - 298.257_223_563).abs() < 1e-9); +} + +#[test] +fn celsius_offset_is_exact() { + assert_eq!(ZERO_CELSIUS_IN_KELVIN, 273.15); +} diff --git a/crates/foundation/apsis-core/tests/error.rs b/crates/foundation/apsis-core/tests/error.rs new file mode 100644 index 0000000..a97463c --- /dev/null +++ b/crates/foundation/apsis-core/tests/error.rs @@ -0,0 +1,73 @@ +//! Tests for the workspace error type and result alias. + +use apsis_core::{Error, ErrorKind, Result}; +use core::fmt; + +#[derive(Debug)] +struct Underlying; + +impl fmt::Display for Underlying { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("underlying failure") + } +} + +impl core::error::Error for Underlying {} + +#[test] +fn kind_is_reported() { + let error = Error::new(ErrorKind::Parse); + assert_eq!(error.kind(), ErrorKind::Parse); + assert_eq!(error.message(), None); +} + +#[test] +fn message_is_reported() { + let error = Error::with_message(ErrorKind::OutOfRange, "semi-major axis is negative"); + assert_eq!(error.kind(), ErrorKind::OutOfRange); + assert_eq!(error.message(), Some("semi-major axis is negative")); +} + +#[test] +fn display_includes_kind_and_message() { + let error = Error::with_message(ErrorKind::Parse, "bad checksum"); + assert_eq!(error.to_string(), "parse error: bad checksum"); + assert_eq!(Error::new(ErrorKind::NotFound).to_string(), "not found"); +} + +#[test] +fn source_is_exposed() { + let error = Error::with_source(ErrorKind::Other, Underlying); + assert!(std::error::Error::source(&error).is_some()); + assert_eq!(error.kind(), ErrorKind::Other); +} + +#[test] +fn context_and_cause_replace_previous_values() { + let error = Error::new(ErrorKind::Other) + .with_context("first") + .with_context("second") + .caused_by(Underlying); + assert_eq!(error.message(), Some("second")); + assert!(std::error::Error::source(&error).is_some()); +} + +#[test] +fn converts_from_kind() { + let error: Error = ErrorKind::Unsupported.into(); + assert_eq!(error.kind(), ErrorKind::Unsupported); +} + +#[test] +fn works_in_result_position() { + fn fail() -> Result { + Err(Error::new(ErrorKind::NotFinite)) + } + + let outcome = fail(); + assert!(outcome.is_err()); + assert_eq!( + outcome.err().map(|error| error.kind()), + Some(ErrorKind::NotFinite) + ); +} diff --git a/crates/foundation/apsis-core/tests/quantity.rs b/crates/foundation/apsis-core/tests/quantity.rs new file mode 100644 index 0000000..20e856a --- /dev/null +++ b/crates/foundation/apsis-core/tests/quantity.rs @@ -0,0 +1,105 @@ +//! Tests for the unit and quantity marker traits. + +use apsis_core::{Quantity, Unit}; +use core::fmt; + +#[derive(Debug, Clone, Copy, PartialEq)] +struct Length; + +#[derive(Debug, Clone, Copy)] +struct Meter; + +#[derive(Debug, Clone, Copy)] +struct Kilometer; + +impl fmt::Display for Meter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("m") + } +} + +impl fmt::Display for Kilometer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("km") + } +} + +impl Unit for Meter { + type Dimension = Length; + + fn factor_to_base() -> f64 { + 1.0 + } + + fn symbol() -> &'static str { + "m" + } +} + +impl Unit for Kilometer { + type Dimension = Length; + + fn factor_to_base() -> f64 { + 1_000.0 + } + + fn symbol() -> &'static str { + "km" + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +struct Meters(f64); + +#[derive(Debug, Clone, Copy, PartialEq)] +struct Kilometers(f64); + +impl Quantity for Meters { + type U = Meter; + + fn value(self) -> f64 { + self.0 + } + + fn from_value(value: f64) -> Self { + Self(value) + } +} + +impl Quantity for Kilometers { + type U = Kilometer; + + fn value(self) -> f64 { + self.0 + } + + fn from_value(value: f64) -> Self { + Self(value) + } +} + +#[test] +fn value_round_trips() { + let distance = Meters::from_value(1_500.0); + assert_eq!(distance.value(), 1_500.0); +} + +#[test] +fn converts_between_units_of_one_dimension() { + let distance = Meters::from_value(2_500.0); + let in_kilometers: Kilometers = distance.to_unit(); + assert!((in_kilometers.value() - 2.5).abs() < 1e-12); +} + +#[test] +fn conversion_round_trips() { + let original = Kilometers::from_value(7.25); + let round_tripped: Kilometers = original.to_unit::().to_unit(); + assert!((round_tripped.value() - 7.25).abs() < 1e-12); +} + +#[test] +fn unit_metadata_is_available() { + assert_eq!(::symbol(), "m"); + assert_eq!(::factor_to_base(), 1_000.0); +}