#[allow(unused_imports)]
pub use super::*;
#[cfg(test)]
#[allow(clippy::float_cmp)] mod autotest_generated {
use core::{
cell::Cell,
hash::{Hash, Hasher},
};
use super::*;
fn reading(kind: SensorKind, x: f32, y: f32, z: f32) -> SensorReading {
SensorReading {
kind,
x,
y,
z,
timestamp_ms: 0,
}
}
fn accel(x: f32, y: f32, z: f32) -> SensorReading {
reading(SensorKind::Accelerometer, x, y, z)
}
fn approx(a: f32, b: f32, rel: f32) -> bool {
let scale = a.abs().max(b.abs()).max(1.0);
(a - b).abs() <= rel * scale
}
struct Fnv(u64);
impl Default for Fnv {
fn default() -> Self {
Self(0xcbf2_9ce4_8422_2325)
}
}
impl Hasher for Fnv {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
for b in bytes {
self.0 ^= u64::from(*b);
self.0 = self.0.wrapping_mul(0x100_0000_01b3);
}
}
}
fn hash_of<T: Hash>(v: &T) -> u64 {
let mut h = Fnv::default();
v.hash(&mut h);
h.finish()
}
#[test]
fn magnitude_pythagorean_is_exact() {
assert_eq!(accel(3.0, 4.0, 0.0).magnitude(), 5.0);
assert_eq!(accel(0.0, 3.0, 4.0).magnitude(), 5.0);
assert_eq!(accel(1.0, 2.0, 2.0).magnitude(), 3.0);
assert_eq!(accel(0.0, 0.0, 0.0).magnitude(), 0.0);
}
#[test]
fn magnitude_single_axis_equals_abs() {
for v in [1.0_f32, -1.0, 0.5, -3.25, 7.5, -1024.0, 65_536.0] {
assert_eq!(accel(v, 0.0, 0.0).magnitude(), v.abs(), "x = {v}");
assert_eq!(accel(0.0, v, 0.0).magnitude(), v.abs(), "y = {v}");
assert_eq!(accel(0.0, 0.0, v).magnitude(), v.abs(), "z = {v}");
}
}
#[test]
fn magnitude_negative_zero_yields_positive_zero() {
let m = accel(-0.0, -0.0, -0.0).magnitude();
assert_eq!(m, 0.0);
assert!(m.is_sign_positive(), "expected +0.0, got {m:?}");
}
#[test]
fn magnitude_is_sign_invariant() {
let base = accel(1.5, 2.5, 3.5).magnitude();
for &sx in &[1.0_f32, -1.0] {
for &sy in &[1.0_f32, -1.0] {
for &sz in &[1.0_f32, -1.0] {
let m = accel(1.5 * sx, 2.5 * sy, 3.5 * sz).magnitude();
assert_eq!(m.to_bits(), base.to_bits(), "signs {sx}/{sy}/{sz}");
}
}
}
}
#[test]
fn magnitude_is_never_negative() {
let vals = [
0.0_f32,
-0.0,
1.0,
-1.0,
9.81,
-9.81,
1e-20,
-1e-20,
1e20,
-1e20,
f32::MAX,
f32::MIN,
f32::MIN_POSITIVE,
f32::INFINITY,
f32::NEG_INFINITY,
];
for &x in &vals {
for &y in &vals {
for &z in &vals {
let m = accel(x, y, z).magnitude();
assert!(!m.is_nan(), "unexpected NaN for ({x}, {y}, {z})");
assert!(m >= 0.0, "negative magnitude {m} for ({x}, {y}, {z})");
assert!(
m.is_sign_positive(),
"negatively-signed magnitude {m:?} for ({x}, {y}, {z})"
);
}
}
}
}
#[test]
fn magnitude_nan_on_any_axis_is_nan() {
assert!(accel(f32::NAN, 0.0, 0.0).magnitude().is_nan());
assert!(accel(0.0, f32::NAN, 0.0).magnitude().is_nan());
assert!(accel(0.0, 0.0, f32::NAN).magnitude().is_nan());
assert!(accel(f32::NAN, f32::NAN, f32::NAN).magnitude().is_nan());
assert!(accel(f32::INFINITY, f32::NAN, 0.0).magnitude().is_nan());
assert!(accel(f32::NEG_INFINITY, 0.0, f32::NAN).magnitude().is_nan());
}
#[test]
fn magnitude_infinite_axis_is_positive_infinity() {
for inf in [f32::INFINITY, f32::NEG_INFINITY] {
assert_eq!(accel(inf, 0.0, 0.0).magnitude(), f32::INFINITY);
assert_eq!(accel(0.0, inf, 0.0).magnitude(), f32::INFINITY);
assert_eq!(accel(1.0, 2.0, inf).magnitude(), f32::INFINITY);
}
assert_eq!(
accel(f32::INFINITY, f32::NEG_INFINITY, f32::INFINITY).magnitude(),
f32::INFINITY
);
}
#[test]
fn magnitude_overflows_to_infinity_on_huge_finite_input() {
assert!(accel(f32::MAX, 0.0, 0.0).magnitude().is_infinite());
assert!(accel(0.0, f32::MIN, 0.0).magnitude().is_infinite());
assert!(accel(f32::MAX, f32::MAX, f32::MAX)
.magnitude()
.is_infinite());
}
#[test]
fn magnitude_overflow_cliff_per_term_and_in_the_sum() {
let below = accel(1.8e19, 0.0, 0.0).magnitude();
assert!(below.is_finite(), "expected finite, got {below}");
assert!(approx(below, 1.8e19, 1e-5), "{below} !~ 1.8e19");
assert!(accel(1.9e19, 0.0, 0.0).magnitude().is_infinite());
let two_terms = accel(1.3e19, 1.3e19, 0.0).magnitude();
assert!(two_terms.is_finite(), "expected finite, got {two_terms}");
assert!(approx(two_terms, 1.838_477e19, 1e-4), "{two_terms}");
assert!(
accel(1.3e19, 1.3e19, 1.3e19).magnitude().is_infinite(),
"sum of three 1.69e38 squares must overflow to +inf"
);
}
#[test]
fn magnitude_underflows_to_zero_on_tiny_input() {
assert_eq!(accel(1e-30, 0.0, 0.0).magnitude(), 0.0);
assert_eq!(accel(0.0, -1e-30, 1e-30).magnitude(), 0.0);
assert_eq!(accel(f32::MIN_POSITIVE, 0.0, 0.0).magnitude(), 0.0);
assert_eq!(accel(f32::from_bits(1), 0.0, 0.0).magnitude(), 0.0);
}
#[test]
fn magnitude_near_underflow_cliff_is_finite_and_close() {
let m = accel(1e-19, 0.0, 0.0).magnitude();
assert!(m.is_finite() && m > 0.0, "expected small positive, got {m}");
assert!(approx(m, 1e-19, 1e-2), "{m} !~ 1e-19");
}
#[test]
fn magnitude_scaling_by_power_of_two_is_exact() {
let (x, y, z) = (1.5_f32, -2.25_f32, 0.75_f32);
let base = accel(x, y, z).magnitude();
let doubled = accel(2.0 * x, 2.0 * y, 2.0 * z).magnitude();
let halved = accel(0.5 * x, 0.5 * y, 0.5 * z).magnitude();
assert_eq!(doubled, 2.0 * base);
assert_eq!(halved, 0.5 * base);
}
#[test]
fn magnitude_is_permutation_stable() {
let (x, y, z) = (0.1_f32, 12_345.678_f32, -0.000_31_f32);
let base = accel(x, y, z).magnitude();
for (a, b, c) in [(x, z, y), (y, x, z), (y, z, x), (z, x, y), (z, y, x)] {
let m = accel(a, b, c).magnitude();
assert!(approx(m, base, 1e-6), "{m} !~ {base} for ({a}, {b}, {c})");
}
}
#[test]
fn magnitude_accelerometer_at_rest_is_about_9_81() {
for r in [
accel(0.0, -9.81, 0.0),
accel(9.81, 0.0, 0.0),
accel(0.0, 0.0, -9.81),
accel(5.663_8, -5.663_8, -5.663_8), ] {
let m = r.magnitude();
assert!(approx(m, 9.81, 1e-3), "{m} !~ 9.81 for {r:?}");
}
}
#[test]
fn magnitude_ignores_kind_and_timestamp() {
let expect = accel(1.0, 2.0, 2.0).magnitude().to_bits();
for kind in [
SensorKind::Accelerometer,
SensorKind::Gyroscope,
SensorKind::Magnetometer,
] {
for timestamp_ms in [0_u64, 1, u64::MAX / 2, u64::MAX] {
let r = SensorReading {
kind,
x: 1.0,
y: 2.0,
z: 2.0,
timestamp_ms,
};
assert_eq!(r.magnitude().to_bits(), expect, "{kind:?} @ {timestamp_ms}");
}
}
}
#[test]
fn magnitude_is_deterministic_and_leaves_the_reading_intact() {
let r = accel(0.1, -0.2, 9.79);
let before = r;
let a = r.magnitude();
let b = r.magnitude();
assert_eq!(a.to_bits(), b.to_bits());
assert_eq!(r, before, "magnitude() must not mutate the reading");
}
#[test]
fn reading_with_nan_axis_is_not_equal_to_itself() {
let nan = accel(f32::NAN, 0.0, 0.0);
let bit_identical = nan; assert_ne!(
nan, bit_identical,
"a NaN axis makes PartialEq non-reflexive"
);
let opt = OptionSensorReading::Some(nan);
let opt_copy = opt;
assert_ne!(opt, opt_copy);
let ok = accel(1.0, 2.0, 2.0);
assert_eq!(ok, accel(1.0, 2.0, 2.0));
}
#[test]
fn sensor_kind_ordering_and_hash_are_consistent() {
use SensorKind::{Accelerometer, Gyroscope, Magnetometer};
assert!(Accelerometer < Gyroscope);
assert!(Gyroscope < Magnetometer);
assert!(Accelerometer < Magnetometer);
assert_eq!(hash_of(&Accelerometer), hash_of(&Accelerometer));
assert_ne!(hash_of(&Accelerometer), hash_of(&Gyroscope));
assert_ne!(hash_of(&Gyroscope), hash_of(&Magnetometer));
let k = Gyroscope;
let copied = k;
assert_eq!(k, copied);
}
#[test]
fn sensor_kind_is_a_c_int() {
assert_eq!(core::mem::size_of::<SensorKind>(), 4);
assert!(
core::mem::size_of::<OptionSensorReading>() > core::mem::size_of::<SensorReading>()
);
}
#[test]
fn option_default_is_none() {
let d = OptionSensorReading::default();
assert!(d.is_none());
assert!(!d.is_some());
assert_eq!(d.as_option(), None);
assert_eq!(d.as_ref(), None);
assert_eq!(d.into_option(), None);
}
#[test]
fn option_roundtrips_through_the_ffi_wrapper() {
let r = accel(1.0, 2.0, 2.0);
let wrapped: OptionSensorReading = Some(r).into();
assert!(wrapped.is_some());
assert_eq!(wrapped.into_option(), Some(r));
assert_eq!(Option::<SensorReading>::from(wrapped), Some(r));
assert_eq!(wrapped.as_option(), Some(&r));
let none: OptionSensorReading = None.into();
assert!(none.is_none());
assert_eq!(none.into_option(), None);
assert_eq!(Option::<SensorReading>::from(none), None);
for o in [wrapped, none] {
assert_ne!(o.is_some(), o.is_none());
}
}
#[test]
fn option_replace_returns_the_previous_value() {
let first = accel(1.0, 0.0, 0.0);
let second = accel(0.0, 3.0, 4.0);
let mut o = OptionSensorReading::None;
let prev = o.replace(first);
assert!(prev.is_none(), "replacing None must hand back None");
assert_eq!(o.into_option(), Some(first));
let prev = o.replace(second);
assert_eq!(prev.into_option(), Some(first));
assert_eq!(o.into_option(), Some(second));
assert_eq!(o.as_option().map(SensorReading::magnitude), Some(5.0));
}
#[test]
fn option_as_mut_mutates_the_payload() {
let mut o = OptionSensorReading::Some(accel(0.0, 0.0, 0.0));
assert_eq!(o.as_option().map(SensorReading::magnitude), Some(0.0));
let slot = o.as_mut().expect("Some");
slot.x = 3.0;
slot.y = 4.0;
assert_eq!(o.as_option().map(SensorReading::magnitude), Some(5.0));
let mut none = OptionSensorReading::None;
assert!(none.as_mut().is_none());
}
#[test]
fn option_map_and_and_then_are_lazy_on_none() {
let calls = Cell::new(0_u32);
let mapped = OptionSensorReading::None.map(|r| {
calls.set(calls.get() + 1);
r.magnitude()
});
assert_eq!(mapped, None);
assert_eq!(calls.get(), 0, "closure must not run on None");
let chained = OptionSensorReading::None.and_then(|r| Some(r.magnitude()));
assert_eq!(chained, None);
let some = OptionSensorReading::Some(accel(3.0, 4.0, 0.0));
let mapped = some.map(|r| {
calls.set(calls.get() + 1);
r.magnitude()
});
assert_eq!(mapped, Some(5.0));
assert_eq!(calls.get(), 1, "closure must run exactly once on Some");
assert_eq!(some.and_then(|r| Some(r.magnitude())), Some(5.0));
assert_eq!(
some.and_then(|_| Option::<f32>::None),
None,
"and_then must be able to collapse Some -> None"
);
}
#[test]
fn option_is_copy_and_the_source_survives() {
let o = OptionSensorReading::Some(accel(0.0, 3.0, 4.0));
let mut copy = o;
copy.replace(accel(1.0, 0.0, 0.0));
assert_eq!(o.as_option().map(SensorReading::magnitude), Some(5.0));
assert_eq!(copy.as_option().map(SensorReading::magnitude), Some(1.0));
}
}
#[test]
fn the_sensor_kind_discriminants_are_the_jni_wire_codes() {
use crate::sensors::SensorKind;
for (kind, code) in [
(SensorKind::Accelerometer, 0),
(SensorKind::Gyroscope, 1),
(SensorKind::Magnetometer, 2),
(SensorKind::RotationVector, 3),
(SensorKind::Gravity, 4),
(SensorKind::LinearAcceleration, 5),
(SensorKind::AmbientLight, 6),
(SensorKind::Proximity, 7),
(SensorKind::Barometer, 8),
(SensorKind::StepCounter, 9),
(SensorKind::HingeAngle, 10),
] {
assert_eq!(
kind as i32, code,
"{kind:?} moved to {}, but AzulSensors.java still sends {code}",
kind as i32
);
}
}
#[cfg(test)]
mod proximity_tests {
use super::*;
#[test]
fn a_distance_converts_between_its_units_without_inventing_precision() {
let mm = ProximityDistance {
value: 250.0,
unit: DistanceUnit::Millimeters,
};
assert_eq!(mm.in_centimeters(), 25.0);
assert_eq!(mm.in_meters(), 0.25);
let m = ProximityDistance {
value: 0.05,
unit: DistanceUnit::Meters,
};
assert_eq!(m.in_millimeters(), 50.0);
assert_eq!(m.in_centimeters(), 5.0);
let cm = ProximityDistance {
value: 3.0,
unit: DistanceUnit::Centimeters,
};
assert_eq!(cm.in_millimeters(), 30.0);
}
#[test]
fn only_the_binary_answers_are_verdicts() {
assert_eq!(Proximity::Near.is_near(), Some(true));
assert_eq!(Proximity::Far.is_near(), Some(false));
assert_eq!(
Proximity::Distance(ProximityDistance {
value: 1.0,
unit: DistanceUnit::Centimeters
})
.is_near(),
None
);
}
}