diff options
Diffstat (limited to 'src/test/java/sevenUnits/unit/UnitDatabaseTest.java')
-rw-r--r-- | src/test/java/sevenUnits/unit/UnitDatabaseTest.java | 324 |
1 files changed, 228 insertions, 96 deletions
diff --git a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java index 9d650f0..3d6d663 100644 --- a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java +++ b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2021, 2022, 2024, 2025 Adrien Hopkins * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -23,20 +23,21 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; -import java.io.InputStream; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import sevenUnits.utils.NameSymbol; @@ -45,7 +46,7 @@ import sevenUnits.utils.UncertainDouble; /** * A test for the {@link UnitDatabase} class. This is NOT part of this program's * public API. - * + * * @author Adrien Hopkins * @since 2019-04-14 * @since v0.2.0 @@ -57,8 +58,8 @@ class UnitDatabaseTest { private V value; /** - * * @since 2021-10-07 + * @since v0.3.2 */ public SimpleEntry(K key, V value) { this.key = key; @@ -94,7 +95,7 @@ class UnitDatabaseTest { @Override public V setValue(V value) { - final V oldValue = this.value; + final var oldValue = this.value; this.value = value; return oldValue; } @@ -135,6 +136,7 @@ class UnitDatabaseTest { * @param value value in entry * @return entry * @since 2021-10-07 + * @since v0.3.2 */ private static <K, V> Map.Entry<K, V> entry(K key, V value) { return new SimpleEntry<>(key, value); @@ -146,14 +148,18 @@ class UnitDatabaseTest { * * @param loadTo database to load to * @param path path of file to load + * @return exceptions returned by file loading * @since 2021-10-04 + * @since v0.3.2 */ - private static void loadDimensionFile(UnitDatabase loadTo, String path) { - try (final InputStream testFile = UnitDatabaseTest.class + private static List<LoadingException> loadDimensionFile(UnitDatabase loadTo, + String path) { + try (final var testFile = UnitDatabaseTest.class .getResourceAsStream(path)) { - loadTo.loadDimensionsFromStream(testFile); + return loadTo.loadDimensionsFromStream(testFile); } catch (final IOException e) { fail(e.getClass() + " occurred upon loading file \"" + path + "\"."); + return Collections.emptyList(); } } @@ -163,26 +169,89 @@ class UnitDatabaseTest { * * @param loadTo database to load to * @param path path of file to load + * @return exceptions returned by file loading * @since 2021-09-22 + * @since v0.3.2 */ - private static void loadUnitsFile(UnitDatabase loadTo, String path) { - try (final InputStream testFile = UnitDatabaseTest.class + private static List<LoadingException> loadUnitsFile(UnitDatabase loadTo, + String path) { + try (final var testFile = UnitDatabaseTest.class .getResourceAsStream(path)) { - loadTo.loadUnitsFromStream(testFile); + return loadTo.loadUnitsFromStream(testFile); } catch (final IOException e) { fail(e.getClass() + " occurred upon loading file \"" + path + "\"."); + return Collections.emptyList(); } } + private static final Stream<Arguments> testEvaluateExpressionInvalid() { + return Stream.of(Arguments.of("K^K"), Arguments.of("1 + K")); + } + + private static final Stream<Arguments> testEvaluateExpressionValid() { + final var uncertainTwoThirds = UncertainDouble.of(2.0, 1.0) + .dividedBy(UncertainDouble.of(3.0, 1.0)); + return Stream.of( + Arguments.of("J + (2 * 3) J + (20 / 4) J", + LinearUnitValue.of(J, + UncertainDouble.of(12, Math.sqrt(14.625)))), + Arguments.of("J + 2 * 3 * J + 20 / 4 * J", + LinearUnitValue.of(J, + UncertainDouble.of(12, Math.sqrt(14.625)))), + Arguments.of("J - -1 * J", + LinearUnitValue.of(J, UncertainDouble.of(2, 1))), + Arguments.of("K^2", + LinearUnitValue.of(K.times(K), UncertainDouble.of(1, 0))), + Arguments.of("2 J / 3 J", + LinearUnitValue.of(J.dividedBy(J), uncertainTwoThirds))); + } + + private static final Stream<Arguments> testFormatExpression() { + return Stream.of(Arguments.of("1*2", "1 * 2"), + Arguments.of("1/2", "1 / 2"), Arguments.of("1|2", "1 | 2"), + Arguments.of("1^2", "1 ^ 2"), Arguments.of("1 * 2", "1 * 2"), + Arguments.of("+1", "+1"), Arguments.of("-1", "-1"), + Arguments.of("1.1e+5", "1.1e+5"), + Arguments.of("1.25e-5", "1.25e-5")); + } + + /** + * Tests expressions that are valid to the parser, but semantically invalid + * (e.g. adding different dimensions) + * + * @param expression expression to test - should throw + * {@link IllegalArgumentException} + */ + @ParameterizedTest + @MethodSource + public void testEvaluateExpressionInvalid(String expression) { + final var database = new UnitDatabase(); + + database.addUnit("J", J); + database.addUnit("K", K); + + database.addPrefix("A", A); + database.addPrefix("B", B); + database.addPrefix("C", C); + + assertThrows(IllegalArgumentException.class, + () -> database.getUnitFromExpression(expression)); + assertThrows(IllegalArgumentException.class, + () -> database.evaluateUnitExpression(expression)); + } + /** * A test for the {@link UnitDatabase#evaluateUnitExpression(String)} * function. Simple because the expression parser has its own test. - * + * * @since 2021-09-27 + * @since v0.3.2 */ - @Test - public void testEvaluateExpression() { - final UnitDatabase database = new UnitDatabase(); + @ParameterizedTest + @MethodSource + public void testEvaluateExpressionValid(String expression, + LinearUnitValue expected) { + final var database = new UnitDatabase(); database.addUnit("J", J); database.addUnit("K", K); @@ -191,27 +260,30 @@ class UnitDatabaseTest { database.addPrefix("B", B); database.addPrefix("C", C); - final LinearUnitValue expected = LinearUnitValue.of(J, - UncertainDouble.of(12, Math.sqrt(14.625))); - // note: units are exact, each number has an uncertainty of 1 - final LinearUnitValue actual = database - .evaluateUnitExpression("J + (2 * 3) J + (20 / 4) J"); + final var actual = database.evaluateUnitExpression(expression); assertEquals(expected, actual); - // check that negation works properly - assertEquals(2, - database.evaluateUnitExpression("J - -1 * J").getValueExact()); + final var expectedU = expected.getUnit().times(expected.getValueExact()); + final var actualU = database.getUnitFromExpression(expression); + assertEquals(expectedU, actualU); + } + + @ParameterizedTest + @MethodSource + public void testFormatExpression(String expression, String expected) { + assertEquals(expected, UnitDatabase.formatExpression(expression)); } /** * Test for {@link UnitDatabase#getUnit}, {@link UnitDatabase#getLinearUnit} * and {@link UnitDatabase#getLinearUnitValue}. - * + * * @since 2021-10-07 + * @since v0.3.2 */ @Test public void testGetUnit() { - final UnitDatabase database = new UnitDatabase(); + final var database = new UnitDatabase(); database.addUnit("m", Metric.METRE); database.addUnit("meter", Metric.METRE); @@ -239,14 +311,15 @@ class UnitDatabaseTest { /** * Confirms that operations that shouldn't function for infinite databases * throw an {@code IllegalStateException}. - * + * * @since 2019-05-03 + * @since v0.3.0 */ // @Test // @Timeout(value = 1, unit = TimeUnit.SECONDS) public void testInfiniteSetExceptions() { // load units - final UnitDatabase infiniteDatabase = new UnitDatabase(); + final var infiniteDatabase = new UnitDatabase(); infiniteDatabase.addUnit("J", J); infiniteDatabase.addUnit("K", K); @@ -255,9 +328,8 @@ class UnitDatabaseTest { infiniteDatabase.addPrefix("B", B); infiniteDatabase.addPrefix("C", C); - final Set<Entry<String, Unit>> entrySet = infiniteDatabase.unitMap() - .entrySet(); - final Set<String> keySet = infiniteDatabase.unitMap().keySet(); + final var entrySet = infiniteDatabase.unitMap().entrySet(); + final var keySet = infiniteDatabase.unitMap().keySet(); assertThrows(IllegalStateException.class, () -> entrySet.toArray()); assertThrows(IllegalStateException.class, () -> keySet.toArray()); } @@ -267,18 +339,20 @@ class UnitDatabaseTest { * * @param num which file to test * @since 2021-10-04 + * @since v0.3.2 */ @ParameterizedTest @ValueSource(ints = { 1, 2, 3 }) public void testLoadingInvalidDimensionFile(int num) { - final UnitDatabase database = new UnitDatabase(); + final var database = new UnitDatabase(); database.addDimension("LENGTH", Metric.Dimensions.LENGTH); database.addDimension("MASS", Metric.Dimensions.MASS); database.addDimension("TIME", Metric.Dimensions.TIME); - final String filename = String.format("/test-dimensionfile-invalid%d.txt", + final var filename = String.format("/test-dimensionfile-invalid%d.txt", num); - final RuntimeException e = assertThrows(RuntimeException.class, - () -> loadDimensionFile(database, filename)); + final var errs = loadDimensionFile(database, filename); + assertFalse(errs.isEmpty(), "no error from invalid file " + filename); + final var e = errs.get(0).problem(); assertTrue(e instanceof IllegalArgumentException || e instanceof NoSuchElementException); } @@ -288,27 +362,29 @@ class UnitDatabaseTest { * * @param num which file to test * @since 2021-09-27 + * @since v0.3.2 */ @ParameterizedTest @ValueSource(ints = { 1, 2, 3, 4, 5 }) public void testLoadingInvalidUnitFile(int num) { - final UnitDatabase database = new UnitDatabase(); - final String filename = String.format("/test-unitsfile-invalid%d.txt", - num); - final RuntimeException e = assertThrows(RuntimeException.class, - () -> loadUnitsFile(database, filename)); + final var database = new UnitDatabase(); + final var filename = String.format("/test-unitsfile-invalid%d.txt", num); + final var errs = loadUnitsFile(database, filename); + assertFalse(errs.isEmpty(), "no error from invalid file " + filename); + final var e = errs.get(0).problem(); assertTrue(e instanceof IllegalArgumentException || e instanceof NoSuchElementException); } /** * Tests loading a valid dimension-file with some derived dimensions. - * + * * @since 2021-10-04 + * @since v0.3.2 */ @Test public void testLoadingValidDimensions() { - final UnitDatabase database = new UnitDatabase(); + final var database = new UnitDatabase(); database.addDimension("LENGTH", Metric.Dimensions.LENGTH); database.addDimension("MASS", Metric.Dimensions.MASS); database.addDimension("TIME", Metric.Dimensions.TIME); @@ -321,27 +397,33 @@ class UnitDatabaseTest { /** * Tests loading a valid unitfile with some prefixes and no units. - * + * * @since 2021-09-22 + * @since v0.3.2 */ @Test public void testLoadingValidPrefixes() { - final UnitDatabase database = new UnitDatabase(); + final var database = new UnitDatabase(); loadUnitsFile(database, "/test-unitsfile-valid2.txt"); assertEquals(7, database.getPrefix("A").getMultiplier()); assertEquals(11, database.getPrefix("B").getMultiplier()); assertEquals(13, database.getPrefix("C").getMultiplier()); + + // test invalid prefixes + assertThrows(NoSuchElementException.class, + () -> database.getPrefix("N/A")); } /** * Tests loading a valid unitfile with some units and preloaded prefixes - * + * * @since 2021-09-22 + * @since v0.3.2 */ @Test public void testLoadingValidUnits() { - final UnitDatabase database = new UnitDatabase(); + final var database = new UnitDatabase(); database.addUnit("U", U); database.addUnit("V", V); @@ -357,21 +439,21 @@ class UnitDatabaseTest { final Unit expected1 = ((LinearUnit) U).withPrefix(A).withPrefix(B) .withPrefix(C); - final Unit actual1 = database.getUnit("test1"); + final var actual1 = database.getUnit("test1"); assertEquals(expected1, actual1); final Unit expected2 = ((LinearUnit) W).withPrefix(B) .times(((LinearUnit) V).withPrefix(C)); - final Unit actual2 = database.getUnit("test2"); + final var actual2 = database.getUnit("test2"); assertEquals(expected2, actual2); final Unit expected3 = ((LinearUnit) U) .times(A.getMultiplier() + C.getMultiplier() - B.getMultiplier()); - final Unit actual3 = database.getUnit("test3"); + final var actual3 = database.getUnit("test3"); assertEquals(expected3, actual3); - final UnitValue expected4 = UnitValue.of(U, 1); - final UnitValue actual4 = database + final var expected4 = UnitValue.of(U, 1); + final var actual4 = database .evaluateUnitExpression("-5 * U + -3 * U + 12 * U - 3 * U") .asUnitValue(); assertEquals(expected4, actual4); @@ -382,21 +464,21 @@ class UnitDatabaseTest { /** * Tests the iterator of the prefixless unit map. These tests are simple, as * the unit map iterator is simple. - * + * * @since 2021-10-07 + * @since v0.3.2 */ @Test public void testPrefixedUnitMapIterator() { - final UnitDatabase database1 = new UnitDatabase(); + final var database1 = new UnitDatabase(); database1.addUnit("U", U); database1.addUnit("V", V); database1.addUnit("W", W); - final Map<String, Unit> map1 = database1.unitMap(); - final Iterator<String> keyIterator1 = map1.keySet().iterator(); - final Iterator<Map.Entry<String, Unit>> entryIterator1 = map1.entrySet() - .iterator(); + final var map1 = database1.unitMap(); + final var keyIterator1 = map1.keySet().iterator(); + final var entryIterator1 = map1.entrySet().iterator(); final Set<String> expectedKeys = Set.of("U", "V", "W"); final Set<String> actualKeys = new HashSet<>(); @@ -418,13 +500,13 @@ class UnitDatabaseTest { /** * Test that prefixes correctly apply to units. - * + * * @since 2019-04-14 * @since v0.2.0 */ @Test public void testPrefixes() { - final UnitDatabase database = new UnitDatabase(); + final var database = new UnitDatabase(); database.addUnit("U", U); database.addUnit("V", V); @@ -439,29 +521,28 @@ class UnitDatabaseTest { assertEquals(expected, database.getPrefixesFromName("ABCU")); // get the product - final Unit abcuNonlinear = database.getUnit("ABCU"); + final var abcuNonlinear = database.getUnit("ABCU"); assert abcuNonlinear instanceof LinearUnit; - final LinearUnit abcu = (LinearUnit) abcuNonlinear; + final var abcu = (LinearUnit) abcuNonlinear; assertEquals(A.getMultiplier() * B.getMultiplier() * C.getMultiplier(), abcu.getConversionFactor(), 1e-15); } /** * Tests the functionnalites of the prefixless unit map. - * + * * <p> * The map should be an auto-updating view of the units in the database. * </p> - * + * * @since 2019-04-14 * @since v0.2.0 */ @Test public void testPrefixlessUnitMap() { - final UnitDatabase database = new UnitDatabase(); - final Map<String, Unit> prefixlessUnits = database - .unitMapPrefixless(true); + final var database = new UnitDatabase(); + final var prefixlessUnits = database.unitMapPrefixless(true); database.addUnit("U", U); database.addUnit("V", V); @@ -478,13 +559,13 @@ class UnitDatabaseTest { /** * Tests that the database correctly stores and retrieves units, ignoring * prefixes. - * + * * @since 2019-04-14 * @since v0.2.0 */ @Test public void testPrefixlessUnits() { - final UnitDatabase database = new UnitDatabase(); + final var database = new UnitDatabase(); database.addUnit("U", U); database.addUnit("V", V); @@ -517,7 +598,7 @@ class UnitDatabaseTest { @Test public void testToString() { - final UnitDatabase database = new UnitDatabase(); + final var database = new UnitDatabase(); database.addUnit("J", J); database.addUnit("K", J); @@ -538,14 +619,14 @@ class UnitDatabaseTest { /** * Test that unit expressions return the correct value. - * + * * @since 2019-04-14 * @since v0.2.0 */ @Test public void testUnitExpressions() { // load units - final UnitDatabase database = new UnitDatabase(); + final var database = new UnitDatabase(); database.addUnit("U", U); database.addUnit("V", V); @@ -560,13 +641,13 @@ class UnitDatabaseTest { // first test - test prefixes and operations final Unit expected1 = J.withPrefix(A).withPrefix(B).withPrefix(C) .withPrefix(C); - final Unit actual1 = database.getUnitFromExpression("ABV * CU^2 / W / W"); + final var actual1 = database.getUnitFromExpression("ABV * CU^2 / W / W"); assertEquals(expected1, actual1); // second test - test addition and subtraction final Unit expected2 = J.times(58); - final Unit actual2 = database.getUnitFromExpression("2 fj + 6 ej"); + final var actual2 = database.getUnitFromExpression("2 fj + 6 ej"); assertEquals(expected2, actual2); @@ -579,14 +660,14 @@ class UnitDatabaseTest { /** * Tests both the unit name iterator and the name-unit entry iterator - * + * * @since 2019-04-14 * @since v0.2.0 */ @Test public void testUnitIterator() { // load units - final UnitDatabase database = new UnitDatabase(); + final var database = new UnitDatabase(); database.addUnit("J", J); database.addUnit("K", K); @@ -595,19 +676,17 @@ class UnitDatabaseTest { database.addPrefix("B", B); database.addPrefix("C", C); - final int NUM_UNITS = database.unitMapPrefixless(true).size(); - final int NUM_PREFIXES = database.prefixMap(true).size(); + final var NUM_UNITS = database.unitMapPrefixless(true).size(); + final var NUM_PREFIXES = database.prefixMap(true).size(); - final Iterator<String> nameIterator = database.unitMap().keySet() - .iterator(); - final Iterator<Entry<String, Unit>> entryIterator = database.unitMap() - .entrySet().iterator(); + final var nameIterator = database.unitMap().keySet().iterator(); + final var entryIterator = database.unitMap().entrySet().iterator(); - int expectedLength = 1; - int unitsWithThisLengthSoFar = 0; + var expectedLength = 1; + var unitsWithThisLengthSoFar = 0; // loop 1000 times - for (int i = 0; i < 1000; i++) { + for (var i = 0; i < 1000; i++) { // expected length of next if (unitsWithThisLengthSoFar >= NUM_UNITS * (int) Math.pow(NUM_PREFIXES, expectedLength - 1)) { @@ -616,9 +695,9 @@ class UnitDatabaseTest { } // test that stuff is valid - final String nextName = nameIterator.next(); - final Unit nextUnit = database.getUnit(nextName); - final Entry<String, Unit> nextEntry = entryIterator.next(); + final var nextName = nameIterator.next(); + final var nextUnit = database.getUnit(nextName); + final var nextEntry = entryIterator.next(); assertEquals(expectedLength, nextName.length()); assertEquals(nextName, nextEntry.getKey()); @@ -628,13 +707,13 @@ class UnitDatabaseTest { } // test toString for consistency - final String entryIteratorString = entryIterator.toString(); - for (int i = 0; i < 3; i++) { + final var entryIteratorString = entryIterator.toString(); + for (var i = 0; i < 3; i++) { assertEquals(entryIteratorString, entryIterator.toString()); } - final String nameIteratorString = nameIterator.toString(); - for (int i = 0; i < 3; i++) { + final var nameIteratorString = nameIterator.toString(); + for (var i = 0; i < 3; i++) { assertEquals(nameIteratorString, nameIterator.toString()); } } @@ -646,14 +725,14 @@ class UnitDatabaseTest { * For example, "ABCU" could mean "A-B-C-U", "AB-C-U", or "A-BC-U". In this * case, "AB-C-U" is the correct choice. * </p> - * + * * @since 2019-04-14 * @since v0.2.0 */ @Test public void testUnitPrefixCombinations() { // load units - final UnitDatabase database = new UnitDatabase(); + final var database = new UnitDatabase(); database.addUnit("J", J); @@ -665,7 +744,7 @@ class UnitDatabaseTest { // test 1 - AB-C-J vs A-BC-J vs A-B-C-J final Unit expected1 = J.withPrefix(AB).withPrefix(C); - final Unit actual1 = database.getUnit("ABCJ"); + final var actual1 = database.getUnit("ABCJ"); assertEquals(expected1, actual1); @@ -674,8 +753,61 @@ class UnitDatabaseTest { database.addPrefix("ABC", UnitPrefix.valueOf(17)); final Unit expected2 = J.times(17); - final Unit actual2 = database.getUnit("ABCJ"); + final var actual2 = database.getUnit("ABCJ"); assertEquals(expected2, actual2); } + + /** + * Tests the ability to create, read, and delete unit sets. + * + * @since 2025-04-30 + * @since v1.0.0 + */ + @Test + void testUnitSetsInvalid() { + final List<LinearUnit> units = List.of(Metric.SECOND, + BritishImperial.Length.INCH); + + final var database = new UnitDatabase(); + + assertThrows(IllegalArgumentException.class, + () -> database.addUnitSet("badtest", units)); + assertThrows(NoSuchElementException.class, + () -> database.getUnitSet("badtest")); + + database.addUnit("ft", BritishImperial.Length.FOOT); + database.addUnit("s", Metric.SECOND); + database.addUnit("dC", Metric.CELSIUS); + database.addUnit("K", Metric.KELVIN); + + assertThrows(IllegalArgumentException.class, + () -> database.getUnitSetFromExpression("K; dC"), + "getUnitSetFromExpression allowed nonlinear unit."); + assertThrows(IllegalArgumentException.class, + () -> database.getUnitSetFromExpression("ft; s"), + "getUnitSetFromExpression allowed units of different dimension."); + } + + /** + * Tests the ability to create, read, and delete unit sets. + * + * @since 2025-04-30 + * @since v1.0.0 + */ + @Test + void testUnitSetsValid() { + final List<LinearUnit> units = List.of(BritishImperial.Length.FOOT, + BritishImperial.Length.INCH); + + final var database = new UnitDatabase(); + + database.addUnitSet("ftintest", units); + assertEquals(units, database.getUnitSet("ftintest")); + + database.addUnit("ft", BritishImperial.Length.FOOT); + database.addUnit("in", BritishImperial.Length.INCH); + assertEquals(units, database.getUnitSetFromExpression("ft; in")); + } + } |