[wpiunits] Add subproject for a Java typesafe unit system (#5371)

# Background

Unit safety has always been a problem in WPILib. Any value corresponding to a physical measurement, such as current draw or distance traveled, is represented by a bare number with no unit tied to it; it's up to the programmer to know what units they're working and take care to remember that while working on their robot program. This leads to bugs when programmers accidentally mix units without knowing, or measure something (such as a wheel diameter) in one unit and program using another. `wpiunits` is intended to eliminate that class of bugs.

Another source of friction is the controllers and models in `wpimath` that expect all inputs to be in terms of SI units (meter, kilogram, and so on), while most FRC teams are US-based and most commonly use imperial units. wpimath does a good job of noting unit types in method names and argument names; however, it still relies on users properly converting values (and knowing they even have to do so).

# API

There are really only two core classes in this library: `Unit` and `Measure`. A `Unit` represents some dimension like distance or time. `Unit` is subclassed to define specific dimensions (eg `Distance` and `Time`) and those subclasses are instantiated to defined particular units in those dimensions, such as `Meters` and `Feet` being instances of the `Distance` class.

A `Measure` is a value tied to a particular dimension like distance and knows what unit that value is tied to. `Measure` has two implementations - one immutable and one mutable. The `Measure` interface only defines *read-only* operations; any API working with measurements should use the interface. The default implementation is `ImmutableMeasure`, which only implements those read-only operations and is useful for tracking constants. `MutableMeasure` also adds some methods that will allow for mutation of its internal state; this class is intended for use for things like sensors and controllers that track internal state and don't want to allocate new `Measure` objects every time something like `myEncoder.getDistance()` is called. However, the APIs for those methods should still only expose the read-only `Measure` interface so users can't (without casting or reflection) change the internal values.

A `Units` class provides convenient definitions for most of the commonly used unit types, such as `Meters`, `Feet`, and `Milliseconds`. I recommend static importing these units eg `import static edu.wpi.first.units.Units.Meters`) so they can be used like `Meters.of(1.234)` instead of `Units.Meters.of(1.234)`


# Examples

These examples are admittedly contrived. Users shouldn't be interacting much with measure objects themselves, since wpimath and wpilibj classes will be updated to support working with them; users will often just have to take a `Measure` output from one place (such as an encoder) and feed it as input to something else (such as a PID controller or kinematics model)

```java
// Using raw units
Encoder encoder = ...

int kPulsesPerRev = 2048;
double kWheelDiameterMeters = Units.inchesToMeters(6);
double kGearRatio = 10.86;
 // always have to remember this encoder will output in meters!
encoder.setDistancePerPulse(kWheelDiameterMeters * Math.PI / (kGearRatio * kPulsesPerRev));

Command driveDistance(double distance) {
  // have to know the distance argument needs to be in meters!
  return run(this::driveStraight).until(() -> encoder.getDistance() >= distance);
}

// Oops! This will go 16 feet, not 5!
Command driveFiveFeet = driveDistance(5);
Command driveOneMeter = driveDistance(1);
```
```java
// Using wpiunits

Encoder encoder = ...

int kPulsesPerRev = 2048;
Measure<Distance> kWheelDiameter = Inches.of(6);
double kGearRatio = 10.86;
encoder.setDistancePerPulse(kWheelDiameter.times(Math.PI).divide(kGearRatio * kPulsesPerRev));

Command driveDistance(Measure<Distance> distance) {
  // Measure#gte automatically handles unit conversions
  return run(this::driveStraight).until(() -> encoder.getDistance().gte(distance));
}

// Users HAVE to be explicit about their units
Command driveFiveFeet = driveDistance(Feet.of(5));
Command driveOneMeter = driveDistance(Meters.of(1));
```

```java
SmartDashboard.putNumber("Temperature (C)", pdp.getTemperature().in(Celsius));
SmartDashboard.putNumber("Temperature (F)", pdp.getTemperature().in(Fahrenheit));
```

```java
var InchSecond = Inch.mult(Second); // new combined unit types can be user-defined
var InchPerSecond = Inch.per(Second);

PIDController<Distance, ElectricPotential> heightController = new PIDController<>(
  /* kP */ Volts.of(0.2).per(Inch),
  /* kI */ Volts.of(0.002).per(InchSecond),
  /* kD */ Volts.of(0.008).per(InchPerSecond)
);

var elevatorTop = Feet.of(4).plus(Inches.of(6.125));
elevatorMotor.setVoltage(heightController.calculate(encoder.getDistance(), elevatorTop));
```
This commit is contained in:
Sam Carlberg
2023-07-23 17:18:17 -04:00
committed by GitHub
parent 44acca7c00
commit c065ae1fcf
40 changed files with 3931 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.units;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class CurrentTest {
@Test
void testAmpsTimesVolts() {
Power combined = Units.Amps.times(Units.Volts, "Watt", "w");
assertTrue(combined.equivalent(Units.Watts));
}
@Test
void testMilliAmpsTimesMilliVolts() {
// results in microwatts
assertTrue(
Units.Milliamps.times(Units.Millivolts, "Microwatt", "uW")
.equivalent(Units.Milli(Units.Milliwatts)));
}
}

View File

@@ -0,0 +1,34 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.units;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class DistanceTest {
@Test
void testBaseUnitDistancePerTime() {
Velocity<Distance> anonBaseUnit = new Distance(1, "D", "d").per(new Time(1, "T", "t"));
assertTrue(BaseUnits.Velocity.equivalent(anonBaseUnit));
}
@Test
void testFeetPerSecond() {
Velocity<Distance> feetPerMillisecond = Units.Feet.per(Units.Milliseconds);
// one foot per millisecond
// = (1 / 3.28084) meters per (1 / 1000) seconds
// = (1000 / 3.28084) meters per second
double asBaseMeasure = feetPerMillisecond.of(1).in(Units.MetersPerSecond);
assertEquals(1000 / 3.28084, asBaseMeasure, 1e-3);
// one meter per second = 1 mm per millisecond = 0.00328084 feet per millisecond
double asContrivedMeasure = Units.MetersPerSecond.of(1).in(feetPerMillisecond);
assertEquals(3.28084 / 1000, asContrivedMeasure, 1e-8);
}
}

View File

@@ -0,0 +1,92 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.units;
import static edu.wpi.first.units.Units.Inches;
import static edu.wpi.first.units.Units.Revolutions;
import static edu.wpi.first.units.Units.Second;
import static edu.wpi.first.units.Units.Value;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class EncoderTest {
static class Encoder<U extends Unit<U>> {
int m_ticks; // = 0
private Measure<U> m_distancePerPulse;
private MutableMeasure<U> m_distance;
private MutableMeasure<Velocity<U>> m_rate;
void setDistancePerPulse(Measure<U> distancePerPulse) {
m_distancePerPulse = distancePerPulse;
m_distance = MutableMeasure.zero(distancePerPulse.unit());
m_rate = MutableMeasure.zero(distancePerPulse.unit().per(Second));
}
Measure<U> getDistance() {
return m_distance;
}
Measure<Velocity<U>> getRate() {
return m_rate;
}
void setTicks(int ticks) {
// pretend we read from JNI here instead of being passed a specific value
var change = ticks - m_ticks;
m_ticks = ticks;
m_distance.mut_setMagnitude(m_distancePerPulse.magnitude() * ticks);
// assumes the last update was 1 second ago - fine for tests
m_rate.mut_setMagnitude(m_distancePerPulse.magnitude() * change);
}
}
@Test
void testAsDistance() {
var ticksPerRevolution = Value.of(2048);
var encoder = new Encoder<Distance>();
// distance per rotation = (wheel circumference / gear ratio)
// distance per tick = distance per rotation / ticks per rotation
var wheelDiameter = Inches.of(6);
var gearRatio = Value.of(10); // 10:1 ratio
Measure<Distance> distancePerPulse =
wheelDiameter.times(Math.PI).divide(gearRatio).divide(ticksPerRevolution);
encoder.setDistancePerPulse(distancePerPulse);
encoder.m_ticks = 0;
assertEquals(0, encoder.getDistance().in(Inches), Measure.EQUIVALENCE_THRESHOLD);
assertEquals(0, encoder.getRate().in(Inches.per(Second)), Measure.EQUIVALENCE_THRESHOLD);
// one full encoder turn, 1/10th of a wheel rotation
encoder.setTicks(2048);
assertEquals(6 * Math.PI / 10, encoder.getDistance().in(Inches), Measure.EQUIVALENCE_THRESHOLD);
// one full encoder turn back, 1/10th of a wheel rotation - rate should be negative
encoder.setTicks(0);
assertEquals(
-6 * Math.PI / 10, encoder.getRate().in(Inches.per(Second)), Measure.EQUIVALENCE_THRESHOLD);
}
@Test
void testAsRevolutions() {
var ticksPerRevolution = Value.of(2048);
var encoder = new Encoder<Angle>();
Measure<Angle> distancePerPulse = Revolutions.of(1).divide(ticksPerRevolution);
encoder.setDistancePerPulse(distancePerPulse);
encoder.m_ticks = 0;
assertEquals(0, encoder.getDistance().in(Revolutions), Measure.EQUIVALENCE_THRESHOLD);
assertEquals(0, encoder.getRate().in(Revolutions.per(Second)), Measure.EQUIVALENCE_THRESHOLD);
encoder.setTicks(2048); // one full encoder turn, 1/10th of a wheel rotation
assertEquals(1, encoder.getDistance().in(Revolutions), Measure.EQUIVALENCE_THRESHOLD);
assertEquals(1, encoder.getRate().in(Revolutions.per(Second)), Measure.EQUIVALENCE_THRESHOLD);
}
}

View File

@@ -0,0 +1,19 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.units;
class ExampleUnit extends Unit<ExampleUnit> {
ExampleUnit(double baseUnitEquivalent) {
this(baseUnitEquivalent, "Example", "ex");
}
ExampleUnit(UnaryFunction toBase, UnaryFunction fromBase, String name, String symbol) {
super(ExampleUnit.class, toBase, fromBase, name, symbol);
}
ExampleUnit(double baseUnitEquivalent, String name, String symbol) {
super(ExampleUnit.class, baseUnitEquivalent, name, symbol);
}
}

View File

@@ -0,0 +1,312 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.units;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class MeasureTest {
@Test
void testBasics() {
Unit<Distance> unit = Units.Feet;
double magnitude = 10;
Measure<Distance> m = unit.of(magnitude);
assertEquals(unit, m.unit(), "Wrong units");
assertEquals(magnitude, m.magnitude(), 0, "Wrong magnitude");
}
@Test
void testMultiply() {
Measure<Distance> m = Units.Feet.of(1);
Measure<Distance> m2 = m.times(10);
assertEquals(10, m2.magnitude(), 1e-12);
assertNotSame(m2, m); // make sure state wasn't changed
}
@Test
void testDivide() {
Measure<Distance> m = Units.Meters.of(1);
Measure<Distance> m2 = m.divide(10);
assertEquals(0.1, m2.magnitude(), 0);
assertNotSame(m2, m);
}
@Test
void testAdd() {
Measure<Distance> m1 = Units.Feet.of(1);
Measure<Distance> m2 = Units.Inches.of(2);
assertTrue(m1.plus(m2).isEquivalent(Units.Feet.of(1 + 2 / 12d)));
assertTrue(m2.plus(m1).isEquivalent(Units.Inches.of(14)));
}
@Test
void testSubtract() {
Measure<Distance> m1 = Units.Feet.of(1);
Measure<Distance> m2 = Units.Inches.of(2);
assertTrue(m1.minus(m2).isEquivalent(Units.Feet.of(1 - 2 / 12d)));
assertTrue(m2.minus(m1).isEquivalent(Units.Inches.of(-10)));
}
@Test
void testNegate() {
Measure<Distance> m = Units.Feet.of(123);
Measure<Distance> n = m.negate();
assertEquals(-m.magnitude(), n.magnitude(), 1e-12);
assertEquals(m.unit(), n.unit());
}
@Test
void testEquivalency() {
Measure<Distance> inches = Units.Inches.of(12);
Measure<Distance> feet = Units.Feet.of(1);
assertTrue(inches.isEquivalent(feet));
assertTrue(feet.isEquivalent(inches));
}
@Test
void testAs() {
Measure<Distance> m = Units.Inches.of(12);
assertEquals(1, m.in(Units.Feet), Measure.EQUIVALENCE_THRESHOLD);
}
@Test
void testPerUnitTime() {
var measure = Units.Kilograms.of(144);
var dt = Units.Milliseconds.of(53);
// 144 Kg / (53 ms) = (1000 / 53) * 144 Kg/s = (144,000 / 53) Kg/s
var result = measure.per(dt);
assertEquals(144_000.0 / 53, result.baseUnitMagnitude(), 1e-5);
assertEquals(Units.Kilograms.per(Units.Milliseconds), result.unit());
}
@Test
void testTimesMeasure() {
var m1 = Units.Volts.of(1.567);
var m2 = Units.Kilograms.of(8.4e-5);
assertEquals(Units.Volts.mult(Units.Kilograms).of(1.567 * 8.4e-5), m1.times(m2));
}
@Test
void testTimesUnitless() {
var unit = new ExampleUnit(6);
var measure = unit.of(2.5);
var multiplier = Units.Percent.of(125); // 125% or 1.25x
Measure<?> result = measure.times(multiplier);
assertSame(unit, result.unit());
assertEquals(2.5 * 1.25, result.magnitude());
assertEquals(2.5 * 1.25 * 6, result.baseUnitMagnitude());
}
@Test
void testTimesPerWithDimensionalAnalysis() {
var measureA = Units.Feet.of(62); // 62 feet
var measureB = Units.Radians.of(6).per(Units.Inches); // 6 radians per inch
Measure<?> aTimesB = measureA.times(measureB); // (62 feet) * (6 rad/inch) = 4464 rad
assertEquals(Units.Radians, aTimesB.unit());
assertEquals(4464, aTimesB.magnitude(), 1e-12);
Measure<?> bTimesA = measureB.times(measureA); // should be identical to the above
assertTrue(bTimesA.isEquivalent(aTimesB));
assertTrue(aTimesB.isEquivalent(bTimesA));
}
@Test
void testPerTimesPerWithDimensionalAnalysis() {
var measureA = Units.Inches.of(16).per(Units.Volts);
var measureB = Units.Millivolts.of(14).per(Units.Meters);
Measure<?> aTimesB = measureA.times(measureB);
assertEquals(Units.Value, aTimesB.unit());
assertEquals((16 * 25.4 / 1000) * (14 / 1000.0), aTimesB.magnitude());
assertEquals((16 * 25.4 / 1000) * (14 / 1000.0), aTimesB.baseUnitMagnitude());
Measure<?> bTimesA = measureB.times(measureA); // should be identical to the above
assertTrue(bTimesA.isEquivalent(aTimesB));
assertTrue(aTimesB.isEquivalent(bTimesA));
}
@Test
void testPerTimesMeasure() {
var m1 = Units.Feet.per(Units.Milliseconds).of(19);
var m2 = Units.Seconds.of(44);
// 19 ft/ms = 19,000 ft/s
// 19,000 ft/s * 44s = 836,000 ft
assertTrue(Units.Feet.of(836_000).isNear(m1.times(m2), 1e-12));
// 42 ex per foot * 17mm = 42 ex * 17mm / (304.8mm/ft) = 42 * 17 / 304.8 = 2.34252
var exampleUnit = new ExampleUnit(1);
var m3 = exampleUnit.per(Units.Feet).of(42);
var m4 = Units.Millimeters.of(17);
assertEquals(exampleUnit.of(42 * 17 / (12 * 25.4)), m3.times(m4));
}
@Test
void testToShortString() {
var measure = Units.Volts.of(343);
assertEquals("3.430e+02 V", measure.toShortString());
}
@Test
void testToLongString() {
var measure = Units.Volts.of(343);
assertEquals("343.0 Volt", measure.toLongString());
assertEquals("343.0001 Volt", Units.Volts.of(343.0001).toLongString());
assertEquals("1.2345678912345679E8 Volt", Units.Volts.of(123456789.123456789).toLongString());
}
@Test
void testOfBaseUnits() {
var unit = new ExampleUnit(16);
var measure = unit.ofBaseUnits(1);
assertEquals(unit, measure.unit());
assertEquals(1, measure.baseUnitMagnitude());
assertEquals(1 / 16.0, measure.magnitude());
}
@Test
void testCompare() {
var unit = new ExampleUnit(7);
var base = unit.of(1);
var less = unit.of(0.5);
var more = unit.of(2);
assertEquals(0, base.compareTo(base));
assertEquals(-1, base.compareTo(more));
assertEquals(1, base.compareTo(less));
// lt, lte, gt, gte helper functions
assertTrue(base.lt(more));
assertTrue(base.lte(more));
assertFalse(base.gt(more));
assertFalse(base.gte(more));
assertTrue(base.gt(less));
assertTrue(base.gte(less));
assertFalse(base.lt(less));
assertFalse(base.lte(less));
assertTrue(base.lte(base));
assertTrue(base.gte(base));
assertFalse(base.lt(base));
assertFalse(base.gt(base));
}
@Test
void testTimesScalar() {
var unit = new ExampleUnit(42);
var measure = unit.of(4.2);
var scalar = 18;
var result = measure.times(scalar);
assertNotSame(measure, result);
assertSame(unit, result.unit());
assertEquals(4.2 * 18, result.magnitude());
assertEquals(4.2 * 42 * 18, result.baseUnitMagnitude());
}
@Test
void testPerUnit() {
var unitA = new ExampleUnit(10);
var unitB = new ExampleUnit(12);
var measure = unitA.of(1.2);
var result = measure.per(unitB);
assertEquals(Per.combine(unitA, unitB), result.unit()); // A/B has base equivalent of 10/12
assertEquals(1, result.baseUnitMagnitude()); // 10/12 * 12/10 = 1
assertEquals(measure.magnitude(), result.magnitude());
}
@Test
void testAddMeasureSameUnit() {
var unit = new ExampleUnit(8.2);
var measureA = unit.of(3.1);
var measureB = unit.of(91.6);
var result = measureA.plus(measureB);
assertEquals(unit, result.unit());
assertEquals(94.7, result.magnitude(), 1e-12);
}
@Test
void testAddMeasuresDifferentUnits() {
var unitA = new ExampleUnit(8.2);
var unitB = new ExampleUnit(7.3);
var measureA = unitA.of(5);
var measureB = unitB.of(16);
var aPlusB = measureA.plus(measureB);
assertEquals(unitA, aPlusB.unit());
assertEquals(8.2 * 5 + 7.3 * 16, aPlusB.baseUnitMagnitude(), 1e-12);
assertEquals(5 + (16 * 7.3 / 8.2), aPlusB.magnitude(), 1e-12);
var bPlusA = measureB.plus(measureA);
assertEquals(unitB, bPlusA.unit());
assertEquals(8.2 * 5 + 7.3 * 16, bPlusA.baseUnitMagnitude(), 1e-12);
assertEquals(16 + (5 * 8.2 / 7.3), bPlusA.magnitude(), 1e-12);
}
@Test
void testMinNoArgs() {
var min = Measure.min();
assertNull(min);
}
@Test
void testMin() {
var unit = new ExampleUnit(56.1);
var one = unit.of(1);
var two = unit.of(2);
var zero = unit.of(0);
var veryNegative = unit.of(-12839712);
var min = Measure.min(one, two, zero, veryNegative);
assertSame(veryNegative, min);
}
@Test
void testMaxNoArgs() {
var min = Measure.max();
assertNull(min);
}
@Test
void testMax() {
var unit = new ExampleUnit(6.551);
var one = unit.of(1);
var two = unit.of(2);
var zero = unit.of(0);
var veryLarge = unit.of(8217234);
var max = Measure.max(one, two, zero, veryLarge);
assertSame(veryLarge, max);
}
@Test
void testIsNear() {
var unit = new ExampleUnit(92);
var measureA = unit.of(1.21);
var measureB = unit.ofBaseUnits(64);
// A = 1.21 * 92 base units, or 111.32
// B = 64 base units
// ratio = 111.32 / 64 = 1.739375 = 173.9375%
assertTrue(measureA.isNear(measureA, 0));
assertTrue(measureB.isNear(measureB, 0));
assertFalse(measureA.isNear(measureB, 0));
assertFalse(measureA.isNear(measureB, 0.50));
assertFalse(measureA.isNear(measureB, 0.739370));
assertTrue(measureA.isNear(measureB, 0.739375));
assertTrue(measureA.isNear(measureB, 100)); // some stupidly large range +/- 10000%
}
}

View File

@@ -0,0 +1,37 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.units;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import org.junit.jupiter.api.Test;
class MultTest {
@Test
void testAutomaticNames() {
var unitA = new ExampleUnit(1, "Ay", "a");
var unitB = new ExampleUnit(1, "Bee", "b");
var mult = Mult.combine(unitA, unitB);
assertEquals("Ay-Bee", mult.name());
assertEquals("a*b", mult.symbol());
}
@Test
void testCombine() {
var unitA = new ExampleUnit(100);
var unitB = new ExampleUnit(0.912);
var mult = Mult.combine(unitA, unitB);
assertEquals(91.2, mult.toBaseUnits(1));
}
@Test
void testCaches() {
var unitA = new ExampleUnit(1);
var unitB = new ExampleUnit(2);
var mult = Mult.combine(unitA, unitB);
assertSame(mult, Mult.combine(unitA, unitB));
}
}

View File

@@ -0,0 +1,133 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.units;
import static edu.wpi.first.units.Units.Centimeters;
import static edu.wpi.first.units.Units.FeetPerSecond;
import static edu.wpi.first.units.Units.InchesPerSecond;
import static edu.wpi.first.units.Units.Meters;
import static edu.wpi.first.units.Units.Millisecond;
import static edu.wpi.first.units.Units.Millivolts;
import static edu.wpi.first.units.Units.Second;
import static edu.wpi.first.units.Units.Volts;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import org.junit.jupiter.api.Test;
class MutableMeasureTest {
@Test
void testWrapper() {
var unit = new ExampleUnit(1);
var measure = unit.of(1234);
var mutable = MutableMeasure.mutable(measure);
assertEquals(measure.magnitude(), mutable.magnitude(), 0);
assertEquals(measure.unit(), mutable.unit());
}
@Test
void testSetMagnitude() {
var measure = MutableMeasure.ofRelativeUnits(0, FeetPerSecond);
double newMagnitude = 48.123;
measure.mut_setMagnitude(newMagnitude);
assertEquals(newMagnitude, measure.magnitude(), 0); // should be an exact match
double feetToMeters = 0.3048;
assertEquals(newMagnitude * feetToMeters, measure.baseUnitMagnitude(), 1e-12);
}
@Test
void testMut_times() {
var measure = MutableMeasure.ofRelativeUnits(18, FeetPerSecond);
double scalar = Math.PI;
var result = measure.mut_times(scalar);
assertSame(measure, result, "mut_times should return the same object");
assertEquals(18 * Math.PI, measure.magnitude(), 1e-12);
}
@Test
void testMut_divide() {
var measure = MutableMeasure.ofRelativeUnits(18, FeetPerSecond);
double scalar = Math.PI;
var result = measure.mut_divide(scalar);
assertSame(measure, result, "mut_times should return the same object");
assertEquals(18 / Math.PI, measure.magnitude(), 1e-12);
}
@Test
void testReplaceMeasure() {
var measure = MutableMeasure.ofRelativeUnits(17.6, FeetPerSecond);
var replacer = Meters.per(Millisecond).of(94.872);
var result = measure.mut_replace(replacer);
assertSame(measure, result, "Replacing should return the mutable measure");
assertSame(replacer.unit(), measure.unit());
assertEquals(replacer.magnitude(), measure.magnitude(), 0);
}
@Test
void testReplaceRaw() {
var measure = MutableMeasure.ofRelativeUnits(-542, FeetPerSecond);
var result = measure.mut_replace(62, Meters.per(Millisecond));
assertSame(measure, result, "Replacing should return the mutable measure");
assertSame(Meters.per(Millisecond), measure.unit());
assertEquals(62, measure.magnitude(), 0);
}
@Test
void testAccMeasure() {
var measure = MutableMeasure.ofRelativeUnits(8.5431, FeetPerSecond);
var acc = Meters.per(Millisecond).of(-23.62);
var result = measure.mut_acc(acc);
assertSame(measure, result, "Acc should return the mutable measure");
assertSame(FeetPerSecond, measure.unit(), "Unit shouldn't change");
assertEquals(8.5431 - (23.62 * (1 / 0.3048) * 1000), measure.magnitude(), 1e-10);
}
@Test
void testAccRaw() {
var measure = MutableMeasure.ofRelativeUnits(99.999999, FeetPerSecond);
var result = measure.mut_acc(22.981);
assertSame(measure, result);
assertSame(FeetPerSecond, measure.unit());
assertEquals(122.980999, measure.magnitude(), 0);
}
@Test
void testMutPlusMeasure() {
var measure = MutableMeasure.ofRelativeUnits(400, InchesPerSecond);
var other = Centimeters.per(Second).of(41.312);
var result = measure.mut_plus(other);
assertSame(measure, result);
assertSame(InchesPerSecond, result.unit());
assertEquals(416.2645669291339, measure.magnitude(), 1e-12);
}
@Test
void testMutPlusRaw() {
var measure = MutableMeasure.ofRelativeUnits(31.51, Volts);
var result = measure.mut_plus(66.641, Millivolts);
assertSame(measure, result);
assertSame(Volts, result.unit());
assertEquals(31.576641, result.magnitude(), 1e-10);
}
@Test
void testMutMinusMeasure() {
var measure = MutableMeasure.ofRelativeUnits(400, InchesPerSecond);
var other = Centimeters.per(Second).of(41.312);
var result = measure.mut_minus(other);
assertSame(measure, result);
assertSame(InchesPerSecond, result.unit());
assertEquals(383.7354330708662, measure.magnitude(), 1e-12);
}
@Test
void testMutMinusRaw() {
var measure = MutableMeasure.ofRelativeUnits(31.51, Volts);
var result = measure.mut_minus(66.641, Millivolts);
assertSame(measure, result);
assertSame(Volts, result.unit());
assertEquals(31.443359, result.magnitude(), 1e-10);
}
}

View File

@@ -0,0 +1,102 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.units;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class UnaryFunctionTest {
@Test
void testApply() {
UnaryFunction f = x -> x * x;
assertEquals(1, f.apply(1));
assertEquals(4, f.apply(2));
assertEquals(2, f.apply(1.414), 1e-3);
assertEquals(64, f.apply(-8));
}
@Test
void testPipeTo() {
UnaryFunction f = x -> x * x;
UnaryFunction g = x -> x + 1;
var h = f.pipeTo(g); // h(x) = g(f(x)) = x^2 + 1
assertEquals(2, h.apply(1));
assertEquals(65, h.apply(8));
}
@Test
void testMult() {
UnaryFunction f = x -> x * x;
UnaryFunction g = x -> x + 1;
var h = f.mult(g); // h(x) = f(x) * g(x) = (x^2)(x + 1) = x^3 + x^2
assertEquals(2, h.apply(1));
assertEquals(216 + 36, h.apply(6));
assertEquals(12, h.apply(2));
}
@Test
void testMultScalar() {
UnaryFunction f = x -> x * x;
var scalar = 2.5;
var h = f.mult(scalar); // h(x) = 2.5 x^2
assertEquals(250, h.apply(10));
assertEquals(2.5, h.apply(1));
assertEquals(2.5, h.apply(-1));
assertEquals(10, h.apply(2));
}
@Test
void testDivZeroDenominator() {
UnaryFunction f = x -> x;
UnaryFunction g = x -> 0;
var h = f.div(g);
assertEquals(0, h.apply(0));
assertEquals(Double.POSITIVE_INFINITY, h.apply(1));
assertEquals(Double.NEGATIVE_INFINITY, h.apply(-1));
}
@Test
void testDivNonzeroDenominator() {
UnaryFunction f = x -> 2 * x;
UnaryFunction g = x -> x + 1;
var h = f.div(g);
assertEquals(0, h.apply(0));
assertEquals(4, h.apply(-2));
assertEquals(16.0 / 9, h.apply(8));
}
@Test
void testDivScalar() {
UnaryFunction f = x -> x * x;
var scalar = 2.5;
var h = f.div(scalar); // h(x) = x^2 / 2.5
assertEquals(10, h.apply(5));
}
@Test
void testExp() {
UnaryFunction f = x -> x * x;
UnaryFunction g = x -> x + 2;
var h = f.exp(g); // h(x) = f(x) ^ g(x) = (x^2)^(x + 2)
assertEquals(1, h.apply(1));
assertEquals(0, h.apply(0));
assertEquals(1, h.apply(-2));
assertEquals(256, h.apply(2)); // (2^2)^(2 + 2) = 4^4
assertEquals(59049, h.apply(3)); // (3^2)^(3 + 2) = 9^5
}
@Test
void testExpScalar() {
UnaryFunction f = x -> x * x;
var scalar = 4;
var h = f.exp(scalar); // h(x) = f(x)^4 = x^8
assertEquals(0, h.apply(0));
assertEquals(1, h.apply(1));
assertEquals(1, h.apply(-1));
assertEquals(256, h.apply(2));
assertEquals(6561, h.apply(3));
}
}

View File

@@ -0,0 +1,19 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.units;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class UnitTest { // :)
@Test
void testOf() {
ExampleUnit u = new ExampleUnit(1);
Measure<ExampleUnit> fiveOfSomething = u.of(5);
assertEquals(5, fiveOfSomething.magnitude(), 0);
assertEquals(u, fiveOfSomething.unit());
}
}

View File

@@ -0,0 +1,348 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.units;
import static edu.wpi.first.units.Units.Amps;
import static edu.wpi.first.units.Units.Celsius;
import static edu.wpi.first.units.Units.Centimeters;
import static edu.wpi.first.units.Units.Degrees;
import static edu.wpi.first.units.Units.Fahrenheit;
import static edu.wpi.first.units.Units.Feet;
import static edu.wpi.first.units.Units.FeetPerSecond;
import static edu.wpi.first.units.Units.Grams;
import static edu.wpi.first.units.Units.Gs;
import static edu.wpi.first.units.Units.Horsepower;
import static edu.wpi.first.units.Units.Inches;
import static edu.wpi.first.units.Units.Kelvin;
import static edu.wpi.first.units.Units.Kilo;
import static edu.wpi.first.units.Units.Kilograms;
import static edu.wpi.first.units.Units.Meters;
import static edu.wpi.first.units.Units.MetersPerSecond;
import static edu.wpi.first.units.Units.MetersPerSecondPerSecond;
import static edu.wpi.first.units.Units.Microseconds;
import static edu.wpi.first.units.Units.Milli;
import static edu.wpi.first.units.Units.Milliamps;
import static edu.wpi.first.units.Units.Millimeters;
import static edu.wpi.first.units.Units.Milliseconds;
import static edu.wpi.first.units.Units.Millivolts;
import static edu.wpi.first.units.Units.Milliwatts;
import static edu.wpi.first.units.Units.Minutes;
import static edu.wpi.first.units.Units.Ounces;
import static edu.wpi.first.units.Units.Percent;
import static edu.wpi.first.units.Units.Pounds;
import static edu.wpi.first.units.Units.Radians;
import static edu.wpi.first.units.Units.Revolutions;
import static edu.wpi.first.units.Units.Second;
import static edu.wpi.first.units.Units.Seconds;
import static edu.wpi.first.units.Units.Value;
import static edu.wpi.first.units.Units.Volts;
import static edu.wpi.first.units.Units.Watts;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class UnitsTest {
// Be accurate to 0.01%
private static final double thresh = 1e-5;
void testBaseUnit(Unit<?> baseUnit) {
assertEquals(0, baseUnit.of(0).baseUnitMagnitude(), 0);
assertEquals(1, baseUnit.of(1).baseUnitMagnitude(), 0);
assertEquals(-1, baseUnit.of(-1).baseUnitMagnitude(), 0);
assertEquals(100, baseUnit.of(100).baseUnitMagnitude(), 0);
assertEquals(8.281723, baseUnit.of(8.281723).baseUnitMagnitude(), 0);
assertEquals(Float.MAX_VALUE, baseUnit.of(Float.MAX_VALUE).baseUnitMagnitude(), 0);
assertEquals(Float.MIN_VALUE, baseUnit.of(Float.MIN_VALUE).baseUnitMagnitude(), 0);
}
// Distances
@Test
void testMeters() {
testBaseUnit(Meters);
assertEquals("Meter", Meters.name());
assertEquals("m", Meters.symbol());
}
@Test
void testMillimeters() {
assertEquals(1000, Millimeters.convertFrom(1, Meters), thresh);
assertEquals(1, Meters.convertFrom(1000, Millimeters), thresh);
assertEquals("Millimeter", Millimeters.name());
assertEquals("mm", Millimeters.symbol());
}
@Test
void testCentimeters() {
assertEquals(100, Centimeters.convertFrom(1, Meters), thresh);
assertEquals(1, Meters.convertFrom(100, Centimeters), thresh);
assertEquals("Centimeter", Centimeters.name());
assertEquals("cm", Centimeters.symbol());
}
@Test
void testInches() {
assertEquals(1, Meters.convertFrom(39.3701, Inches), thresh);
assertEquals(39.3701, Inches.convertFrom(1, Meters), 1e-4);
assertEquals(1 / 25.4, Inches.convertFrom(1, Millimeters), 0); // should be exact
assertEquals(12, Inches.convertFrom(1, Feet), thresh);
assertEquals("Inch", Inches.name());
assertEquals("in", Inches.symbol());
}
@Test
void testFeet() {
assertEquals(3.28084, Feet.convertFrom(1, Meters), thresh);
assertEquals(1 / 12.0, Feet.convertFrom(1, Inches), thresh);
assertEquals("Foot", Feet.name());
assertEquals("ft", Feet.symbol());
}
// Velocities
@Test
void testMetersPerSecond() {
testBaseUnit(MetersPerSecond);
assertEquals("Meter per Second", MetersPerSecond.name());
assertEquals("m/s", MetersPerSecond.symbol());
}
@Test
void testFeetPerSecond() {
assertEquals(3.28084, FeetPerSecond.convertFrom(1, MetersPerSecond), thresh);
assertEquals(1 / 3.28084, MetersPerSecond.convertFrom(1, FeetPerSecond), thresh);
assertEquals("Foot per Second", FeetPerSecond.name());
assertEquals("ft/s", FeetPerSecond.symbol());
}
// Accelerations
@Test
void testMetersPerSecondPerSecond() {
testBaseUnit(MetersPerSecondPerSecond);
assertEquals("Meter per Second per Second", MetersPerSecondPerSecond.name());
assertEquals("m/s/s", MetersPerSecondPerSecond.symbol());
assertEquals(MetersPerSecond, MetersPerSecondPerSecond.getUnit());
assertEquals(Seconds, MetersPerSecondPerSecond.getPeriod());
}
@Test
void testGs() {
assertEquals(32.17405, Gs.of(1).in(FeetPerSecond.per(Second)), thresh);
assertEquals(9.80665, Gs.of(1).in(MetersPerSecondPerSecond), thresh);
assertEquals("G", Gs.name());
assertEquals("G", Gs.symbol());
assertEquals(Units.AnonymousBaseUnit, Gs.getUnit());
assertEquals(Seconds, Gs.getPeriod());
}
// Time
@Test
void testSeconds() {
testBaseUnit(Seconds);
assertEquals("Second", Seconds.name());
assertEquals("s", Seconds.symbol());
}
@Test
void testMillisecond() {
assertEquals(1000, Milliseconds.convertFrom(1, Seconds), thresh);
assertEquals("Millisecond", Milliseconds.name());
assertEquals("ms", Milliseconds.symbol());
}
@Test
void testMicrosecond() {
assertEquals(1e6, Microseconds.convertFrom(1, Second), 0);
assertEquals("Microsecond", Microseconds.name());
assertEquals("us", Microseconds.symbol());
}
@Test
void testMinutes() {
assertEquals(60, Seconds.convertFrom(1, Minutes), thresh);
assertEquals("Minute", Minutes.name());
assertEquals("min", Minutes.symbol());
}
// Mass
@Test
void testKilograms() {
testBaseUnit(Kilograms);
assertEquals("Kilogram", Kilograms.name());
assertEquals("Kg", Kilograms.symbol());
}
@Test
void testGrams() {
assertEquals(1000, Grams.convertFrom(1, Kilograms), thresh);
assertEquals("Gram", Grams.name());
assertEquals("g", Grams.symbol());
}
@Test
void testPounds() {
assertEquals(453.592, Grams.convertFrom(1, Pounds), thresh);
assertEquals("Pound", Pounds.name());
assertEquals("lb.", Pounds.symbol());
}
@Test
void testOunces() {
assertEquals(16, Ounces.convertFrom(1, Pounds), thresh);
assertEquals("Ounce", Ounces.name());
assertEquals("oz.", Ounces.symbol());
}
// Angle
@Test
void testRevolutions() {
testBaseUnit(Revolutions);
assertEquals("Revolution", Revolutions.name());
assertEquals("R", Revolutions.symbol());
}
@Test
void testRadians() {
assertEquals(2 * Math.PI, Radians.convertFrom(1, Revolutions), thresh);
assertEquals(2 * Math.PI, Radians.convertFrom(360, Degrees), thresh);
assertEquals("Radian", Radians.name());
assertEquals("rad", Radians.symbol());
}
@Test
void testDegrees() {
assertEquals(360, Degrees.convertFrom(1, Revolutions), thresh);
assertEquals(360, Degrees.convertFrom(2 * Math.PI, Radians), thresh);
assertEquals("Degree", Degrees.name());
assertEquals("°", Degrees.symbol());
}
// Unitless
@Test
void testValue() {
testBaseUnit(Value);
assertEquals("<?>", Value.name());
assertEquals("<?>", Value.symbol());
}
@Test
void testPercent() {
assertEquals(100, Percent.convertFrom(1, Value), thresh);
assertEquals("Percent", Percent.name());
assertEquals("%", Percent.symbol());
}
// Electric potential
@Test
void testVolts() {
testBaseUnit(Volts);
assertEquals("Volt", Volts.name());
assertEquals("V", Volts.symbol());
}
@Test
void testMillivolts() {
assertEquals(1000, Millivolts.convertFrom(1, Volts), thresh);
assertEquals("Millivolt", Millivolts.name());
assertEquals("mV", Millivolts.symbol());
}
// Electric current
@Test
void testAmps() {
testBaseUnit(Amps);
assertEquals("Amp", Amps.name());
assertEquals("A", Amps.symbol());
}
@Test
void testMilliamps() {
assertEquals(1000, Milliamps.convertFrom(1, Amps), thresh);
assertEquals("Milliamp", Milliamps.name());
assertEquals("mA", Milliamps.symbol());
}
// Power
@Test
void testWatts() {
testBaseUnit(Watts);
assertEquals("Watt", Watts.name());
assertEquals("W", Watts.symbol());
}
@Test
void testMilliwatts() {
assertEquals(1000, Milliwatts.convertFrom(1, Watts), thresh);
assertEquals("Milliwatt", Milliwatts.name());
assertEquals("mW", Milliwatts.symbol());
}
@Test
void testHorsepower() {
assertEquals(745.7, Watts.convertFrom(1, Horsepower), thresh);
assertEquals("Horsepower", Horsepower.name());
assertEquals("HP", Horsepower.symbol());
}
// Temperature
@Test
void testKelvin() {
testBaseUnit(Kelvin);
assertEquals("Kelvin", Kelvin.name());
assertEquals("K", Kelvin.symbol()); // note: there's no degree symbol for Kelvin!
}
@Test
void testCelsius() {
assertEquals(0, Celsius.of(-273.15).in(Kelvin), thresh);
assertEquals(273.15, Celsius.of(0).in(Kelvin), thresh);
assertEquals(0, Kelvin.of(273.15).in(Celsius), thresh);
assertTrue(Celsius.of(0).isEquivalent(Kelvin.of(273.15)));
assertTrue(Celsius.of(-273.15).isEquivalent(Kelvin.of(0)));
assertEquals("Celsius", Celsius.name());
assertEquals("°C", Celsius.symbol());
}
@Test
void testFahrenheit() {
assertEquals(0, Fahrenheit.of(32).in(Celsius), thresh);
assertEquals(100, Fahrenheit.of(212).in(Celsius), thresh);
assertEquals(-459.67, Kelvin.of(0).in(Fahrenheit), thresh);
assertEquals(273.15, Fahrenheit.of(32).in(Kelvin), thresh);
assertEquals(32, Kelvin.of(273.15).in(Fahrenheit), thresh);
assertTrue(Fahrenheit.of(32).isEquivalent(Celsius.of(0)));
assertTrue(Fahrenheit.of(212).isEquivalent(Celsius.of(100)));
assertEquals("Fahrenheit", Fahrenheit.name());
assertEquals("°F", Fahrenheit.symbol());
}
// Helpers
@Test
void testKilo() {
ExampleUnit unit = new ExampleUnit(1);
ExampleUnit kiloUnit = Kilo(unit);
assertEquals(1e3, unit.convertFrom(1, kiloUnit), 0);
assertEquals(1e-3, kiloUnit.convertFrom(1, unit), 0);
}
@Test
void testMilli() {
ExampleUnit unit = new ExampleUnit(1);
ExampleUnit milliUnit = Milli(unit);
assertEquals(1e-3, unit.convertFrom(1, milliUnit), 0);
assertEquals(1e3, milliUnit.convertFrom(1, unit), 0);
}
}

View File

@@ -0,0 +1,58 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.units;
import static edu.wpi.first.units.Units.Feet;
import static edu.wpi.first.units.Units.FeetPerSecond;
import static edu.wpi.first.units.Units.Meters;
import static edu.wpi.first.units.Units.MetersPerSecond;
import static edu.wpi.first.units.Units.MetersPerSecondPerSecond;
import static edu.wpi.first.units.Units.Millisecond;
import static edu.wpi.first.units.Units.Second;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class VelocityTest {
@Test
void testBaseUnit() {
assertTrue(MetersPerSecond.equivalent(BaseUnits.Velocity));
assertTrue(Meters.per(Second).equivalent(BaseUnits.Velocity));
}
@Test
void testToAcceleration() {
Velocity<Velocity<Distance>> metersPerSecondPerMillisecond = MetersPerSecond.per(Millisecond);
assertEquals(1000, metersPerSecondPerMillisecond.of(1).in(MetersPerSecondPerSecond), 0);
assertEquals(0, metersPerSecondPerMillisecond.of(0).in(MetersPerSecondPerSecond), 0);
}
@Test
void testCache() {
assertSame(
FeetPerSecond, Feet.per(Second), "Feet.per(Second) should return a cached object instance");
// completely arbitrary units chosen because they won't have already been cached
var someDistance = new ExampleUnit(5);
var someTime = new ExampleUnit(600);
var firstInvocation = someDistance.per(someTime);
var secondInvocation = someDistance.per(someTime);
assertSame(
firstInvocation,
secondInvocation,
firstInvocation + " was not the same object as " + secondInvocation);
}
@Test
void testMult() {
var baseUnit = new ExampleUnit(92);
var vel = baseUnit.per(Millisecond);
var mult = vel.mult(Second);
assertEquals(92_000, mult.toBaseUnits(1), 1e-5);
}
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.units;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class VoltageTest {
@Test
void testVoltsTimesAmps() {
assertTrue(Units.Volts.times(Units.Amps, "", "").equivalent(Units.Watts));
}
@Test
void testMilliVoltsTimesMilliAmps() {
// results in microwatts
assertTrue(
Units.Millivolts.times(Units.Milliamps, "", "").equivalent(Units.Milli(Units.Milliwatts)));
}
}

View File

@@ -0,0 +1,96 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.units.collections;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class LongToObjectHashMapTest {
@Test
void put() {
var map = new LongToObjectHashMap<String>();
assertEquals(0, map.size());
long key = 42;
var previousValue = map.put(key, "The Meaning of Life");
assertNull(previousValue);
assertEquals(1, map.size());
assertEquals("The Meaning of Life", map.get(key));
assertTrue(map.containsKey(key));
previousValue = map.put(key, "Addendum: Live Long and Prosper");
assertEquals("The Meaning of Life", previousValue);
assertEquals(1, map.size(), "The map should not have grown when changing the value for a key");
assertTrue(map.containsKey(key));
var removed = map.remove(key);
assertEquals("Addendum: Live Long and Prosper", removed);
assertEquals(0, map.size());
assertFalse(map.containsKey(key));
}
@Test
void testGrowth() {
var map = new LongToObjectHashMap<String>();
int numInserts = 8000;
for (int i = 0; i < numInserts; i++) {
map.put(i, "value-" + i);
}
assertEquals(numInserts, map.size());
assertEquals(16384, map.capacity());
for (int i = 0; i < numInserts; i++) {
assertEquals("value-" + i, map.get(i), "key " + i + " had an unexpected value");
assertTrue(map.containsKey(i));
}
for (int i = 0; i < numInserts; i++) {
map.remove(i);
}
// all items should have been removed
assertEquals(0, map.size());
// ... but the map shouldn't resize
assertEquals(16384, map.capacity());
}
@Test
void testClear() {
var map = new LongToObjectHashMap<>();
for (int i = 0; i < 10_000; i++) {
map.put(i, i * i);
}
assertEquals(10_000, map.size());
assertEquals(16384, map.capacity());
map.clear();
assertEquals(0, map.size());
assertEquals(16384, map.capacity());
map.clear(); // should be a NOP
assertEquals(0, map.size());
assertEquals(16384, map.capacity());
}
@Test
void testKeySet() {
var map = new LongToObjectHashMap<String>();
int numInserts = 8000;
for (int i = 0; i < numInserts; i++) {
map.put(i, "value-" + i);
}
var keySet = map.keySet();
assertEquals(map.size(), keySet.size());
for (int i = 0; i < numInserts; i++) {
assertTrue(keySet.contains(i), "keySet does not contain key " + i);
}
}
}