From 544ba103f9903bf885e346d34639c05934655f3f Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Mon, 27 Sep 2021 17:31:22 -0500 Subject: Added some tests for invalid unitfiles --- src/main/java/sevenUnits/unit/UnitDatabase.java | 9 +++++---- src/main/java/sevenUnits/utils/ExpressionParser.java | 8 ++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) (limited to 'src/main/java/sevenUnits') diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index b45d9cf..a1dbb0a 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -1427,10 +1427,11 @@ public final class UnitDatabase { final String expression = lineMatcher.group(2); - if (name.endsWith(" ")) { - System.err.printf("Warning - line %d's unit name ends in a space", - lineCounter); - } + // this code should never occur + // if (name.endsWith(" ")) { + // System.err.printf("Warning - line %d's unit name ends in a space", + // lineCounter); + // } // if expression is "!", search for an existing unit // if no unit found, throw an error diff --git a/src/main/java/sevenUnits/utils/ExpressionParser.java b/src/main/java/sevenUnits/utils/ExpressionParser.java index 1d3d44d..3c4abad 100644 --- a/src/main/java/sevenUnits/utils/ExpressionParser.java +++ b/src/main/java/sevenUnits/utils/ExpressionParser.java @@ -461,6 +461,8 @@ public final class ExpressionParser { * * @param expression expression * @return expression in RPN + * @throws IllegalArgumentException if expression is invalid (e.g. + * "{@code 3 *}") * @since 2019-03-17 * @since v0.2.0 */ @@ -541,6 +543,9 @@ public final class ExpressionParser { switch (this .getTokenType(components.get(highestPriorityOperatorPosition))) { case UNARY_OPERATOR: + if (components.size() < 2) + throw new IllegalArgumentException( + "Invalid expression \"" + expression + "\""); final String unaryOperator = components .remove(highestPriorityOperatorPosition); final String operand = components @@ -549,6 +554,9 @@ public final class ExpressionParser { operand + " " + unaryOperator); break; case BINARY_OPERATOR: + if (components.size() < 3) + throw new IllegalArgumentException( + "Invalid expression \"" + expression + "\""); final String binaryOperator = components .remove(highestPriorityOperatorPosition); final String operand1 = components -- cgit v1.2.3 From a7584b843a64c806ec965a4f38341eb7dbd86e5f Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Mon, 27 Sep 2021 18:29:36 -0500 Subject: Added a test for evaluating an expression & fixed a bug it found MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bug: non-exact LinearUnitValue instances have their value printed as "(", i.e. "(5.0 ± 0.1) m" will be printed as "( m" --- src/main/java/sevenUnits/unit/LinearUnitValue.java | 12 +++++----- .../java/sevenUnits/unit/UnitDatabaseTest.java | 27 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) (limited to 'src/main/java/sevenUnits') diff --git a/src/main/java/sevenUnits/unit/LinearUnitValue.java b/src/main/java/sevenUnits/unit/LinearUnitValue.java index a36d568..2219ffd 100644 --- a/src/main/java/sevenUnits/unit/LinearUnitValue.java +++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java @@ -324,12 +324,12 @@ public final class LinearUnitValue { // get rounded strings // if showUncertainty is true, add brackets around the string - final String valueString = showUncertainty ? "(" - : "" + this.value.toString(showUncertainty) - + (showUncertainty ? ")" : ""); - final String baseValueString = showUncertainty ? "(" - : "" + baseValue.toString(showUncertainty) - + (showUncertainty ? ")" : ""); + final String valueString = (showUncertainty ? "(" : "") + + this.value.toString(showUncertainty) + + (showUncertainty ? ")" : ""); + final String baseValueString = (showUncertainty ? "(" : "") + + baseValue.toString(showUncertainty) + + (showUncertainty ? ")" : ""); // create string if (primaryName.isEmpty() && symbol.isEmpty()) diff --git a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java index 31323a7..7612fc5 100644 --- a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java +++ b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java @@ -36,6 +36,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import sevenUnits.utils.UncertainDouble; + /** * A test for the {@link UnitDatabase} class. This is NOT part of this program's * public API. @@ -87,6 +89,31 @@ class UnitDatabaseTest { } } + /** + * A test for the {@link UnitDatabase#evaluateUnitExpression(String)} + * function. Simple because the expression parser has its own test. + * + * @since 2021-09-27 + */ + @Test + public void testEvaluateExpression() { + final UnitDatabase database = new UnitDatabase(); + + database.addUnit("J", J); + database.addUnit("K", K); + + database.addPrefix("A", A); + 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"); + assertEquals(expected, actual); + } + /** * Confirms that operations that shouldn't function for infinite databases * throw an {@code IllegalStateException}. -- cgit v1.2.3 From 1555a741bb2d12de8591b6195d2c0af7981a58e8 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Mon, 27 Sep 2021 18:31:28 -0500 Subject: The ExpressionParser test is now a paramaterized test --- .../java/sevenUnits/utils/ExpressionParser.java | 4 +- .../sevenUnits/utils/ExpressionParserTest.java | 70 +++++++++++++++------- 2 files changed, 51 insertions(+), 23 deletions(-) (limited to 'src/main/java/sevenUnits') diff --git a/src/main/java/sevenUnits/utils/ExpressionParser.java b/src/main/java/sevenUnits/utils/ExpressionParser.java index 3c4abad..941c2a4 100644 --- a/src/main/java/sevenUnits/utils/ExpressionParser.java +++ b/src/main/java/sevenUnits/utils/ExpressionParser.java @@ -466,7 +466,7 @@ public final class ExpressionParser { * @since 2019-03-17 * @since v0.2.0 */ - private String convertExpressionToReversePolish(final String expression) { + String convertExpressionToReversePolish(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); final List components = new ArrayList<>(); @@ -680,7 +680,7 @@ public final class ExpressionParser { * @since 2019-03-14 * @since v0.2.0 */ - private T parseReversePolishExpression(final String expression) { + T parseReversePolishExpression(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); final Deque stack = new ArrayDeque<>(); diff --git a/src/test/java/sevenUnits/utils/ExpressionParserTest.java b/src/test/java/sevenUnits/utils/ExpressionParserTest.java index 29648ee..a954b12 100644 --- a/src/test/java/sevenUnits/utils/ExpressionParserTest.java +++ b/src/test/java/sevenUnits/utils/ExpressionParserTest.java @@ -18,37 +18,65 @@ package sevenUnits.utils; import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.Test; +import java.util.List; +import java.util.stream.IntStream; +import java.util.stream.Stream; -import sevenUnits.utils.ExpressionParser; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +// TODO add tests for expression-to-RPN and RPN-to-result /** - * A test for the {@code ExpressionParser} class. This is NOT part of this program's public API. + * A test for the {@code ExpressionParser} class. This is NOT part of this + * program's public API. * * @author Adrien Hopkins * @since 2019-03-22 * @since v0.2.0 */ class ExpressionParserTest { - private static final ExpressionParser numberParser = new ExpressionParser.Builder<>(Integer::parseInt) - .addBinaryOperator("+", (o1, o2) -> o1 + o2, 0).addBinaryOperator("-", (o1, o2) -> o1 - o2, 0) - .addBinaryOperator("*", (o1, o2) -> o1 * o2, 1).addBinaryOperator("/", (o1, o2) -> o1 / o2, 1) - .addBinaryOperator("^", (o1, o2) -> (int) Math.pow(o1, o2), 2).build(); - + private static final ExpressionParser numberParser = new ExpressionParser.Builder<>( + Integer::parseInt).addBinaryOperator("+", (o1, o2) -> o1 + o2, 0) + .addBinaryOperator("-", (o1, o2) -> o1 - o2, 0) + .addBinaryOperator("*", (o1, o2) -> o1 * o2, 1) + .addBinaryOperator("/", (o1, o2) -> o1 / o2, 1) + .addBinaryOperator("^", (o1, o2) -> (int) Math.pow(o1, o2), 2) + .build(); + /** - * Test method for {@link sevenUnits.utils.ExpressionParser#parseExpression(java.lang.String)}. + * The expressions used in the expression parsing tests */ - @Test - public void testParseExpression() { - // test parsing of expressions - assertEquals((int) numberParser.parseExpression("1 + 2 ^ 5 * 3"), 97); - assertEquals((int) numberParser.parseExpression("(1 + 2) ^ 5 * 3"), 729); - - // ensure it normally goes left to right - assertEquals((int) numberParser.parseExpression("1 + 2 + 3 + 4"), 10); - assertEquals((int) numberParser.parseExpression("12 - 4 - 3"), 5); - assertEquals((int) numberParser.parseExpression("12 - (4 - 3)"), 11); - assertEquals((int) numberParser.parseExpression("1 / 2 + 3"), 3); + private static final List TEST_EXPRESSIONS = List.of( + // test parsing of expressions + "1 + 2 ^ 5 * 3", "(1 + 2) ^ 5 * 3", + "12 * 5 + (3 ^ (2 * 3) - 72) / (3 + 3 * 2)", + + // ensure it normally goes from left to right + "1 + 2 + 3 + 4", "12 - 4 - 3", "12 - (4 - 3)", "1 / 2 + 3"); + + /** + * The expected results for evaluating these expressions + */ + private static final int[] RESULTS = { 97, 729, 133, 10, 5, 11, 3 }; + + /** + * @return A stream of objects, where each one is an expression and the + * expected result + * @since 2021-09-27 + */ + private static final Stream testParseExpressionData() { + return IntStream.range(0, TEST_EXPRESSIONS.size()) + .mapToObj(i -> Arguments.of(TEST_EXPRESSIONS.get(i), RESULTS[i])); + } + + /** + * Test method for + * {@link sevenUnits.utils.ExpressionParser#parseExpression(java.lang.String)}. + */ + @ParameterizedTest + @MethodSource("testParseExpressionData") + public void testParseExpression(String expression, int value) { + assertEquals(value, numberParser.parseExpression(expression)); } - } -- cgit v1.2.3 From b59082c6b558705d4bd5effce2ae4b98c8a3ebe5 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Mon, 4 Oct 2021 18:26:58 -0500 Subject: Added tests for valid & invalid dimension files --- src/main/java/sevenUnits/unit/UnitDatabase.java | 32 +++++++--- .../java/sevenUnits/unit/UnitDatabaseTest.java | 68 +++++++++++++++++++++- src/test/resources/test-dimensionfile-invalid1.txt | 3 + src/test/resources/test-dimensionfile-invalid2.txt | 1 + src/test/resources/test-dimensionfile-invalid3.txt | 5 ++ src/test/resources/test-dimensionfile-valid1.txt | 12 ++++ 6 files changed, 110 insertions(+), 11 deletions(-) create mode 100644 src/test/resources/test-dimensionfile-invalid1.txt create mode 100644 src/test/resources/test-dimensionfile-invalid2.txt create mode 100644 src/test/resources/test-dimensionfile-invalid3.txt create mode 100644 src/test/resources/test-dimensionfile-valid1.txt (limited to 'src/main/java/sevenUnits') diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index a1dbb0a..7c72570 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -1344,10 +1344,10 @@ public final class UnitDatabase { final String name = lineMatcher.group(1); final String expression = lineMatcher.group(2); - if (name.endsWith(" ")) { - System.err.printf("Warning - line %d's dimension name ends in a space", - lineCounter); - } + // if (name.endsWith(" ")) { + // System.err.printf("Warning - line %d's dimension name ends in a space", + // lineCounter); + // } // if expression is "!", search for an existing dimension // if no unit found, throw an error @@ -1360,7 +1360,7 @@ public final class UnitDatabase { final ObjectProduct dimension; try { dimension = this.getDimensionFromExpression(expression); - } catch (final IllegalArgumentException e) { + } catch (final IllegalArgumentException | NoSuchElementException e) { System.err.printf("Parsing error on line %d:%n", lineCounter); throw e; } @@ -1444,7 +1444,8 @@ public final class UnitDatabase { final UnitPrefix prefix; try { prefix = this.getPrefixFromExpression(expression); - } catch (final IllegalArgumentException e) { + } catch (final IllegalArgumentException + | NoSuchElementException e) { System.err.printf("Parsing error on line %d:%n", lineCounter); throw e; } @@ -1454,7 +1455,8 @@ public final class UnitDatabase { final Unit unit; try { unit = this.getUnitFromExpression(expression); - } catch (final IllegalArgumentException e) { + } catch (final IllegalArgumentException + | NoSuchElementException e) { System.err.printf("Parsing error on line %d:%n", lineCounter); throw e; } @@ -1582,8 +1584,15 @@ public final class UnitDatabase { } return base.toExponent(exponent); + } else { + final ObjectProduct dimension = this.dimensions + .get(name); + if (dimension == null) + throw new NoSuchElementException( + "No dimension with name \"" + name + "\"."); + else + return dimension; } - return this.dimensions.get(name); } /** @@ -1696,7 +1705,12 @@ public final class UnitDatabase { try { return UnitPrefix.valueOf(Double.parseDouble(name)); } catch (final NumberFormatException e) { - return this.prefixes.get(name); + final UnitPrefix prefix = this.prefixes.get(name); + if (prefix == null) + throw new NoSuchElementException( + "No prefix with name \"" + name + "\"."); + else + return prefix; } } diff --git a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java index 7612fc5..ed3b6b5 100644 --- a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java +++ b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java @@ -72,6 +72,23 @@ class UnitDatabaseTest { private static final UnitPrefix AB = UnitPrefix.valueOf(7); private static final UnitPrefix BC = UnitPrefix.valueOf(11); + /** + * Loads the dimensionfile at src/test/resources/[path] to the database + * {@code loadTo}. + * + * @param loadTo database to load to + * @param path path of file to load + * @since 2021-10-04 + */ + private static void loadDimensionFile(UnitDatabase loadTo, String path) { + try (final InputStream testFile = UnitDatabaseTest.class + .getResourceAsStream(path)) { + loadTo.loadDimensionsFromStream(testFile); + } catch (final IOException e) { + fail(e.getClass() + " occurred upon loading file \"" + path + "\"."); + } + } + /** * Loads the unitfile at src/test/resources/[path] to the database * {@code loadTo}. @@ -140,6 +157,27 @@ class UnitDatabaseTest { assertThrows(IllegalStateException.class, () -> keySet.toArray()); } + /** + * A bunch of tests for invalid dimension files + * + * @param num which file to test + * @since 2021-10-04 + */ + @ParameterizedTest + @ValueSource(ints = { 1, 2, 3 }) + public void testLoadingInvalidDimensionFile(int num) { + final UnitDatabase 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", + num); + final RuntimeException e = assertThrows(RuntimeException.class, + () -> loadDimensionFile(database, filename)); + assertTrue(e instanceof IllegalArgumentException + || e instanceof NoSuchElementException); + } + /** * A bunch of tests for invalid unit files * @@ -148,12 +186,32 @@ class UnitDatabaseTest { */ @ParameterizedTest @ValueSource(ints = { 1, 2, 3, 4, 5 }) - public void testLoadingInvalidFile(int num) { + public void testLoadingInvalidUnitFile(int num) { final UnitDatabase database = new UnitDatabase(); final String filename = String.format("/test-unitsfile-invalid%d.txt", num); - assertThrows(IllegalArgumentException.class, + final RuntimeException e = assertThrows(RuntimeException.class, () -> loadUnitsFile(database, filename)); + assertTrue(e instanceof IllegalArgumentException + || e instanceof NoSuchElementException); + } + + /** + * Tests loading a valid dimension-file with some derived dimensions. + * + * @since 2021-10-04 + */ + @Test + public void testLoadingValidDimensions() { + final UnitDatabase database = new UnitDatabase(); + database.addDimension("LENGTH", Metric.Dimensions.LENGTH); + database.addDimension("MASS", Metric.Dimensions.MASS); + database.addDimension("TIME", Metric.Dimensions.TIME); + + loadDimensionFile(database, "/test-dimensionfile-valid1.txt"); + assertEquals(Metric.Dimensions.ENERGY, database.getDimension("ENERGY")); + assertEquals(Metric.Dimensions.POWER, database.getDimension("POWER")); + } /** @@ -207,6 +265,12 @@ class UnitDatabaseTest { final Unit actual3 = database.getUnit("test3"); assertEquals(expected3, actual3); + final UnitValue expected4 = UnitValue.of(U, 1); + final UnitValue actual4 = database + .evaluateUnitExpression("-5 * U + -3 * U + 12 * U - 3 * U") + .asUnitValue(); + assertEquals(expected4, actual4); + assertTrue(System.err.toString().length() > 0); } diff --git a/src/test/resources/test-dimensionfile-invalid1.txt b/src/test/resources/test-dimensionfile-invalid1.txt new file mode 100644 index 0000000..ff9ccd8 --- /dev/null +++ b/src/test/resources/test-dimensionfile-invalid1.txt @@ -0,0 +1,3 @@ +LENGTH +MASS +TIME diff --git a/src/test/resources/test-dimensionfile-invalid2.txt b/src/test/resources/test-dimensionfile-invalid2.txt new file mode 100644 index 0000000..2818cfc --- /dev/null +++ b/src/test/resources/test-dimensionfile-invalid2.txt @@ -0,0 +1 @@ +NONEXISTENT ! diff --git a/src/test/resources/test-dimensionfile-invalid3.txt b/src/test/resources/test-dimensionfile-invalid3.txt new file mode 100644 index 0000000..a16f941 --- /dev/null +++ b/src/test/resources/test-dimensionfile-invalid3.txt @@ -0,0 +1,5 @@ +LENGTH ! +MASS ! +TIME ! + +BAD LENGTH +-+ TIME \ No newline at end of file diff --git a/src/test/resources/test-dimensionfile-valid1.txt b/src/test/resources/test-dimensionfile-valid1.txt new file mode 100644 index 0000000..fc6a426 --- /dev/null +++ b/src/test/resources/test-dimensionfile-valid1.txt @@ -0,0 +1,12 @@ +LENGTH ! +MASS ! +TIME ! + +ENERGY MASS * LENGTH^2 / TIME^2 +POWER ENERGY / TIME + +# doesn't work, but would require major changes to fix properly +# for now, just don't use brackets in dimension expressions +# (note that the unit/prefix expressions use a complete hack +# to enable this, one that doesn't work for dimensions) +# POWER MASS * (LENGTH / TIME)^2 / TIME \ No newline at end of file -- cgit v1.2.3 From b4fd2b39e85e2a086e65555ad7c45244bec8ae37 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Thu, 7 Oct 2021 16:21:00 -0500 Subject: Added tests for getUnit and the prefixed unit map Also fixed a bug where a prefixed unit map with units but no prefixes would appear empty --- src/main/java/sevenUnits/unit/UnitDatabase.java | 12 +- .../utils/ConditionalExistenceCollections.java | 2 +- .../java/sevenUnits/unit/UnitDatabaseTest.java | 140 ++++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) (limited to 'src/main/java/sevenUnits') diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index 7c72570..d3c65b2 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -243,7 +243,8 @@ public final class UnitDatabase { return false; else { if (this.prefixNames.isEmpty()) - return this.unitNamePosition >= this.unitNames.size() - 1; + return this.prefixCoordinates.isEmpty() + && this.unitNamePosition < this.unitNames.size(); else return true; } @@ -557,7 +558,8 @@ public final class UnitDatabase { return false; else { if (this.prefixNames.isEmpty()) - return this.unitNamePosition >= this.unitNames.size() - 1; + return this.prefixCoordinates.isEmpty() + && this.unitNamePosition < this.unitNames.size(); else return true; } @@ -1038,7 +1040,7 @@ public final class UnitDatabase { @Override public String toString() { if (this.units.isEmpty() || this.prefixes.isEmpty()) - return super.toString(); + return new HashMap<>(this).toString(); else return String.format( "Infinite map of name-unit entries created from units %s and prefixes %s", @@ -1645,7 +1647,7 @@ public final class UnitDatabase { * @since 2019-03-22 * @since v0.2.0 */ - private LinearUnit getLinearUnit(final String name) { + LinearUnit getLinearUnit(final String name) { // see if I am using a function-unit like tempC(100) Objects.requireNonNull(name, "name may not be null"); if (name.contains("(") && name.contains(")")) { @@ -1680,7 +1682,7 @@ public final class UnitDatabase { * @return {@code LinearUnitValue} instance * @since 2020-08-04 */ - private LinearUnitValue getLinearUnitValue(final String name) { + LinearUnitValue getLinearUnitValue(final String name) { try { // try to parse it as a number - otherwise it is not a number! final BigDecimal number = new BigDecimal(name); diff --git a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java index 2adb579..bee4dd1 100644 --- a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java +++ b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java @@ -283,7 +283,7 @@ public final class ConditionalExistenceCollections { @Override public Set keySet() { - return conditionalExistenceSet(super.keySet(), + return conditionalExistenceSet(this.map.keySet(), k -> this.entryExistenceCondition.test(this.getEntry(k))); } diff --git a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java index ed3b6b5..90c18e6 100644 --- a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java +++ b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java @@ -25,11 +25,13 @@ import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; +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 org.junit.jupiter.api.Test; @@ -47,21 +49,70 @@ import sevenUnits.utils.UncertainDouble; * @since v0.2.0 */ class UnitDatabaseTest { + private static final class SimpleEntry implements Map.Entry { + private final K key; + + private V value; + + /** + * + * @since 2021-10-07 + */ + public SimpleEntry(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof Map.Entry)) + return false; + final Map.Entry other = (Map.Entry) obj; + return Objects.equals(this.key, other.getKey()) + && Objects.equals(this.value, other.getValue()); + } + + @Override + public K getKey() { + return this.key; + } + + @Override + public V getValue() { + return this.value; + } + + @Override + public int hashCode() { + return (this.key == null ? 0 : this.key.hashCode()) + ^ (this.value == null ? 0 : this.value.hashCode()); + } + + @Override + public V setValue(V value) { + final V oldValue = this.value; + this.value = value; + return oldValue; + } + } + // some linear units and one nonlinear private static final Unit U = Metric.METRE; private static final Unit V = Metric.KILOGRAM; - private static final Unit W = Metric.SECOND; + private static final Unit W = Metric.SECOND; // used for testing expressions // J = U^2 * V / W^2 private static final LinearUnit J = Metric.KILOGRAM .times(Metric.METRE.toExponent(2)) .dividedBy(Metric.SECOND.toExponent(2)); + private static final LinearUnit K = Metric.KELVIN; private static final Unit NONLINEAR = Unit.fromConversionFunctions( Metric.METRE.getBase(), o -> o + 1, o -> o - 1); - // make the prefix values prime so I can tell which multiplications were made private static final UnitPrefix A = UnitPrefix.valueOf(2) .withName(NameSymbol.ofName("A")); @@ -70,8 +121,23 @@ class UnitDatabaseTest { private static final UnitPrefix C = UnitPrefix.valueOf(5) .withName(NameSymbol.ofName("C")); private static final UnitPrefix AB = UnitPrefix.valueOf(7); + private static final UnitPrefix BC = UnitPrefix.valueOf(11); + /** + * Gets a map entry. + * + * @param type of key + * @param type of value + * @param key key in entry + * @param value value in entry + * @return entry + * @since 2021-10-07 + */ + private static Map.Entry entry(K key, V value) { + return new SimpleEntry<>(key, value); + } + /** * Loads the dimensionfile at src/test/resources/[path] to the database * {@code loadTo}. @@ -131,6 +197,39 @@ class UnitDatabaseTest { assertEquals(expected, actual); } + /** + * Test for {@link UnitDatabase#getUnit}, {@link UnitDatabase#getLinearUnit} + * and {@link UnitDatabase#getLinearUnitValue}. + * + * @since 2021-10-07 + */ + @Test + public void testGetUnit() { + final UnitDatabase database = new UnitDatabase(); + + database.addUnit("m", Metric.METRE); + database.addUnit("meter", Metric.METRE); + database.addUnit("metre", Metric.METRE); + database.addUnit("badname", Metric.METRE); + database.addUnit("K", Metric.KELVIN); + database.addUnit("degC", Metric.CELSIUS); + + // ensure getUnit returns units, regardless of whether the name is one of + // the unit's names + assertEquals(Metric.METRE, database.getUnit("m")); + assertEquals(Metric.METRE, database.getUnit("metre")); + assertEquals(Metric.METRE, database.getUnit("meter")); + assertEquals(Metric.METRE, database.getUnit("badname")); + assertThrows(NoSuchElementException.class, + () -> database.getUnit("blabla")); + + assertEquals(Metric.KELVIN, database.getLinearUnit("K")); + assertThrows(IllegalArgumentException.class, + () -> database.getLinearUnit("degC")); + assertEquals(Metric.KELVIN.times(373.15), + database.getLinearUnit("degC(100)")); + } + /** * Confirms that operations that shouldn't function for infinite databases * throw an {@code IllegalStateException}. @@ -274,6 +373,43 @@ class UnitDatabaseTest { assertTrue(System.err.toString().length() > 0); } + /** + * Tests the iterator of the prefixless unit map. These tests are simple, as + * the unit map iterator is simple. + * + * @since 2021-10-07 + */ + @Test + public void testPrefixedUnitMapIterator() { + final UnitDatabase database1 = new UnitDatabase(); + + database1.addUnit("U", U); + database1.addUnit("V", V); + database1.addUnit("W", W); + + final Map map1 = database1.unitMap(); + final Iterator keyIterator1 = map1.keySet().iterator(); + final Iterator> entryIterator1 = map1.entrySet() + .iterator(); + + final Set expectedKeys = Set.of("U", "V", "W"); + final Set actualKeys = new HashSet<>(); + while (keyIterator1.hasNext()) { + actualKeys.add(keyIterator1.next()); + } + assertEquals(expectedKeys, actualKeys); + assertEquals(expectedKeys, map1.keySet()); + + final Set> expectedEntries = Set.of(entry("U", U), + entry("V", V), entry("W", W)); + final Set> actualEntries = new HashSet<>(); + while (entryIterator1.hasNext()) { + actualEntries.add(entryIterator1.next()); + } + assertEquals(expectedEntries, actualEntries); + assertEquals(expectedEntries, map1.entrySet()); + } + /** * Test that prefixes correctly apply to units. * -- cgit v1.2.3 From f422742fb7bf45eb0ce6577bd2f9f7e22b739a6b Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Tue, 2 Nov 2021 16:49:10 -0500 Subject: Added a test for UnitDatabase.isRemovableDuplicate --- src/main/java/sevenUnits/unit/UnitDatabase.java | 2 +- .../java/sevenUnits/unit/UnitDatabaseTest.java | 44 ++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) (limited to 'src/main/java/sevenUnits') diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index d3c65b2..18ac619 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -1163,7 +1163,7 @@ public final class UnitDatabase { * @return true if entry represents a removable duplicate entry of unitMap. * @since 2021-05-22 */ - private static boolean isRemovableDuplicate(Map unitMap, + static boolean isRemovableDuplicate(Map unitMap, Entry entry) { for (final Entry e : unitMap.entrySet()) { final String name = e.getKey(); diff --git a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java index 90c18e6..033d763 100644 --- a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java +++ b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java @@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -195,6 +196,10 @@ class UnitDatabaseTest { final LinearUnitValue actual = database .evaluateUnitExpression("J + (2 * 3) J + (20 / 4) J"); assertEquals(expected, actual); + + // check that negation works properly + assertEquals(2, + database.evaluateUnitExpression("J - -1 * J").getValueExact()); } /** @@ -491,6 +496,45 @@ class UnitDatabaseTest { assertThrows(NoSuchElementException.class, () -> database.getUnit("Z")); } + @Test + public void testRemovableDuplicates() { + final Map unitMap = new HashMap<>(); + unitMap.put("meter", Metric.METRE); + unitMap.put("metre", Metric.METRE); + unitMap.put("m", Metric.METRE); + unitMap.put("second", Metric.SECOND); + + assertTrue(UnitDatabase.isRemovableDuplicate(unitMap, + entry("m", Metric.METRE))); + assertTrue(UnitDatabase.isRemovableDuplicate(unitMap, + entry("meter", Metric.METRE))); + assertFalse(UnitDatabase.isRemovableDuplicate(unitMap, + entry("metre", Metric.METRE))); + assertFalse(UnitDatabase.isRemovableDuplicate(unitMap, + entry("second", Metric.SECOND))); + } + + @Test + public void testToString() { + final UnitDatabase database = new UnitDatabase(); + + database.addUnit("J", J); + database.addUnit("K", J); + + database.addPrefix("A", A); + database.addPrefix("B", B); + database.addPrefix("C", C); + + if ("Unit Database with 1 units, 3 unit prefixes and 0 dimensions" + .equals(database.toString())) { + fail("Database counts by number of units, not number of unit names."); + } + + assertEquals( + "Unit Database with 2 units, 3 unit prefixes and 0 dimensions", + database.toString()); + } + /** * Test that unit expressions return the correct value. * -- cgit v1.2.3 From 16dfc3d7c7813fa343f3f3502bf5a2fea4a252cc Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Thu, 4 Nov 2021 16:01:24 -0500 Subject: Added a bunch of tests related to unit values --- src/main/java/sevenUnits/unit/BritishImperial.java | 39 +++--- src/main/java/sevenUnits/unit/LinearUnitValue.java | 5 +- src/main/java/sevenUnits/unit/UnitValue.java | 3 +- src/main/java/sevenUnits/utils/ObjectProduct.java | 135 ++++++++++----------- .../java/sevenUnits/unit/UnitDatabaseTest.java | 6 + src/test/java/sevenUnits/unit/UnitTest.java | 86 ++++++++++++- 6 files changed, 182 insertions(+), 92 deletions(-) (limited to 'src/main/java/sevenUnits') diff --git a/src/main/java/sevenUnits/unit/BritishImperial.java b/src/main/java/sevenUnits/unit/BritishImperial.java index 81a3f2a..743beeb 100644 --- a/src/main/java/sevenUnits/unit/BritishImperial.java +++ b/src/main/java/sevenUnits/unit/BritishImperial.java @@ -37,7 +37,7 @@ public final class BritishImperial { public static final LinearUnit ROOD = Length.ROD.times(Length.FURLONG); public static final LinearUnit ACRE = Length.FURLONG.times(Length.CHAIN); } - + /** * Imperial units that measure length * @@ -46,7 +46,8 @@ public final class BritishImperial { */ public static final class Length { /** - * According to the International Yard and Pound of 1959, a yard is defined as exactly 0.9144 metres. + * According to the International Yard and Pound of 1959, a yard is + * defined as exactly 0.9144 metres. */ public static final LinearUnit YARD = Metric.METRE.times(0.9144); public static final LinearUnit FOOT = YARD.dividedBy(3); @@ -56,15 +57,15 @@ public final class BritishImperial { public static final LinearUnit FURLONG = CHAIN.times(10); public static final LinearUnit MILE = FURLONG.times(8); public static final LinearUnit LEAGUE = MILE.times(3); - + public static final LinearUnit NAUTICAL_MILE = Metric.METRE.times(1852); public static final LinearUnit CABLE = NAUTICAL_MILE.dividedBy(10); public static final LinearUnit FATHOM = CABLE.dividedBy(100); - + public static final LinearUnit ROD = YARD.times(5.5); public static final LinearUnit LINK = ROD.dividedBy(25); } - + /** * British Imperial units that measure mass. * @@ -82,7 +83,7 @@ public final class BritishImperial { public static final LinearUnit LONG_TON = HUNDREDWEIGHT.times(20); public static final LinearUnit SLUG = Metric.KILOGRAM.times(14.59390294); } - + /** * British Imperial units that measure volume * @@ -90,27 +91,33 @@ public final class BritishImperial { * @since 2019-11-08 */ public static final class Volume { - public static final LinearUnit FLUID_OUNCE = Metric.LITRE.withPrefix(Metric.MILLI).times(28.4130625); + public static final LinearUnit FLUID_OUNCE = Metric.LITRE + .withPrefix(Metric.MILLI).times(28.4130625); public static final LinearUnit GILL = FLUID_OUNCE.times(5); public static final LinearUnit PINT = FLUID_OUNCE.times(20); public static final LinearUnit QUART = PINT.times(2); public static final LinearUnit GALLON = QUART.times(4); public static final LinearUnit PECK = GALLON.times(2); public static final LinearUnit BUSHEL = PECK.times(4); - + public static final LinearUnit CUBIC_INCH = Length.INCH.toExponent(3); public static final LinearUnit CUBIC_FOOT = Length.FOOT.toExponent(3); public static final LinearUnit CUBIC_YARD = Length.YARD.toExponent(3); public static final LinearUnit ACRE_FOOT = Area.ACRE.times(Length.FOOT); } - - public static final LinearUnit OUNCE_FORCE = Mass.OUNCE.times(Metric.Constants.EARTH_GRAVITY); - public static final LinearUnit POUND_FORCE = Mass.POUND.times(Metric.Constants.EARTH_GRAVITY); - - public static final LinearUnit BRITISH_THERMAL_UNIT = Metric.JOULE.times(1055.06); + + public static final LinearUnit OUNCE_FORCE = Mass.OUNCE + .times(Metric.Constants.EARTH_GRAVITY); + public static final LinearUnit POUND_FORCE = Mass.POUND + .times(Metric.Constants.EARTH_GRAVITY); + + public static final LinearUnit BRITISH_THERMAL_UNIT = Metric.JOULE + .times(1055.06); public static final LinearUnit CALORIE = Metric.JOULE.times(4.184); public static final LinearUnit KILOCALORIE = Metric.JOULE.times(4184); - - public static final Unit FAHRENHEIT = Unit.fromConversionFunctions(Metric.KELVIN.getBase(), - tempK -> tempK * 1.8 - 459.67, tempF -> (tempF + 459.67) / 1.8); + + public static final Unit FAHRENHEIT = Unit + .fromConversionFunctions(Metric.KELVIN.getBase(), + tempK -> tempK * 1.8 - 459.67, tempF -> (tempF + 459.67) / 1.8) + .withName(NameSymbol.of("degrees Fahrenheit", "\u00B0F")); } diff --git a/src/main/java/sevenUnits/unit/LinearUnitValue.java b/src/main/java/sevenUnits/unit/LinearUnitValue.java index 2219ffd..a50e1f5 100644 --- a/src/main/java/sevenUnits/unit/LinearUnitValue.java +++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java @@ -332,9 +332,10 @@ public final class LinearUnitValue { + (showUncertainty ? ")" : ""); // create string - if (primaryName.isEmpty() && symbol.isEmpty()) + if (chosenName == null) return String.format("%s unnamed unit (= %s %s)", valueString, - baseValueString, this.unit.getBase()); + baseValueString, this.unit.getBase() + .toString(unit -> unit.getSymbol().orElseThrow())); else return String.format("%s %s", valueString, chosenName); } diff --git a/src/main/java/sevenUnits/unit/UnitValue.java b/src/main/java/sevenUnits/unit/UnitValue.java index 2fedd81..f6d18f8 100644 --- a/src/main/java/sevenUnits/unit/UnitValue.java +++ b/src/main/java/sevenUnits/unit/UnitValue.java @@ -163,7 +163,8 @@ public final class UnitValue { if (primaryName.isEmpty() && symbol.isEmpty()) { final double baseValue = this.getUnit().convertToBase(this.getValue()); return String.format("%s unnamed unit (= %s %s)", this.getValue(), - baseValue, this.getUnit().getBase()); + baseValue, this.getUnit().getBase() + .toString(unit -> unit.getSymbol().orElseThrow())); } else { final String unitName = symbol.orElse(primaryName.get()); return this.getValue() + " " + unitName; diff --git a/src/main/java/sevenUnits/utils/ObjectProduct.java b/src/main/java/sevenUnits/utils/ObjectProduct.java index 1dacb7d..5b1b739 100644 --- a/src/main/java/sevenUnits/utils/ObjectProduct.java +++ b/src/main/java/sevenUnits/utils/ObjectProduct.java @@ -27,8 +27,8 @@ import java.util.Set; import java.util.function.Function; /** - * An immutable product of multiple objects of a type, such as base units. The objects can be multiplied and - * exponentiated. + * An immutable product of multiple objects of a type, such as base units. The + * objects can be multiplied and exponentiated. * * @author Adrien Hopkins * @since 2019-10-16 @@ -37,38 +37,35 @@ public final class ObjectProduct { /** * Returns an empty ObjectProduct of a certain type * - * @param - * type of objects that can be multiplied + * @param type of objects that can be multiplied * @return empty product * @since 2019-10-16 */ public static final ObjectProduct empty() { return new ObjectProduct<>(new HashMap<>()); } - + /** * Gets an {@code ObjectProduct} from an object-to-integer mapping * - * @param - * type of object in product - * @param map - * map mapping objects to exponents + * @param type of object in product + * @param map map mapping objects to exponents * @return object product * @since 2019-10-16 */ - public static final ObjectProduct fromExponentMapping(final Map map) { + public static final ObjectProduct fromExponentMapping( + final Map map) { return new ObjectProduct<>(new HashMap<>(map)); } - + /** - * Gets an ObjectProduct that has one of the inputted argument, and nothing else. + * Gets an ObjectProduct that has one of the inputted argument, and nothing + * else. * - * @param object - * object that will be in the product + * @param object object that will be in the product * @return product * @since 2019-10-16 - * @throws NullPointerException - * if object is null + * @throws NullPointerException if object is null */ public static final ObjectProduct oneOf(final T object) { Objects.requireNonNull(object, "object must not be null."); @@ -76,35 +73,34 @@ public final class ObjectProduct { map.put(object, 1); return new ObjectProduct<>(map); } - + /** - * The objects that make up the product, mapped to their exponents. This map treats zero as null, and is immutable. + * The objects that make up the product, mapped to their exponents. This map + * treats zero as null, and is immutable. * * @since 2019-10-16 */ final Map exponents; - + /** * Creates the {@code ObjectProduct}. * - * @param exponents - * objects that make up this product + * @param exponents objects that make up this product * @since 2019-10-16 */ private ObjectProduct(final Map exponents) { - this.exponents = Collections.unmodifiableMap(ConditionalExistenceCollections.conditionalExistenceMap(exponents, - e -> !Integer.valueOf(0).equals(e.getValue()))); + this.exponents = Collections.unmodifiableMap( + ConditionalExistenceCollections.conditionalExistenceMap(exponents, + e -> !Integer.valueOf(0).equals(e.getValue()))); } - + /** * Calculates the quotient of two products * - * @param other - * other product + * @param other other product * @return quotient of two products * @since 2019-10-16 - * @throws NullPointerException - * if other is null + * @throws NullPointerException if other is null */ public ObjectProduct dividedBy(final ObjectProduct other) { Objects.requireNonNull(other, "other must not be null."); @@ -112,17 +108,17 @@ public final class ObjectProduct { final Set objects = new HashSet<>(); objects.addAll(this.getBaseSet()); objects.addAll(other.getBaseSet()); - + // get a list of all exponents final Map map = new HashMap<>(objects.size()); for (final T key : objects) { map.put(key, this.getExponent(key) - other.getExponent(key)); } - + // create the product return new ObjectProduct<>(map); } - + // this method relies on the use of ZeroIsNullMap @Override public boolean equals(final Object obj) { @@ -133,7 +129,7 @@ public final class ObjectProduct { final ObjectProduct other = (ObjectProduct) obj; return Objects.equals(this.exponents, other.exponents); } - + /** * @return immutable map mapping objects to exponents * @since 2019-10-16 @@ -141,30 +137,31 @@ public final class ObjectProduct { public Map exponentMap() { return this.exponents; } - + /** - * @return a set of all of the base objects with non-zero exponents that make up this dimension. + * @return a set of all of the base objects with non-zero exponents that make + * up this dimension. * @since 2018-12-12 * @since v0.1.0 */ public final Set getBaseSet() { final Set dimensions = new HashSet<>(); - - // add all dimensions with a nonzero exponent - zero exponents shouldn't be there in the first place + + // add all dimensions with a nonzero exponent - zero exponents shouldn't + // be there in the first place for (final T dimension : this.exponents.keySet()) { if (!this.exponents.get(dimension).equals(0)) { dimensions.add(dimension); } } - + return dimensions; } - + /** * Gets the exponent for a specific dimension. * - * @param dimension - * dimension to check + * @param dimension dimension to check * @return exponent for that dimension * @since 2018-12-12 * @since v0.1.0 @@ -172,14 +169,15 @@ public final class ObjectProduct { public int getExponent(final T dimension) { return this.exponents.getOrDefault(dimension, 0); } - + @Override public int hashCode() { return Objects.hash(this.exponents); } - + /** - * @return true if this product is a single object, i.e. it has one exponent of one and no other nonzero exponents + * @return true if this product is a single object, i.e. it has one exponent + * of one and no other nonzero exponents * @since 2019-10-16 */ public boolean isSingleObject() { @@ -194,16 +192,14 @@ public final class ObjectProduct { } return oneCount == 1 && !twoOrMore; } - + /** * Multiplies this product by another * - * @param other - * other product + * @param other other product * @return product of two products * @since 2019-10-16 - * @throws NullPointerException - * if other is null + * @throws NullPointerException if other is null */ public ObjectProduct times(final ObjectProduct other) { Objects.requireNonNull(other, "other must not be null."); @@ -211,22 +207,21 @@ public final class ObjectProduct { final Set objects = new HashSet<>(); objects.addAll(this.getBaseSet()); objects.addAll(other.getBaseSet()); - + // get a list of all exponents final Map map = new HashMap<>(objects.size()); for (final T key : objects) { map.put(key, this.getExponent(key) + other.getExponent(key)); } - + // create the product return new ObjectProduct<>(map); } - + /** * Returns this product, but to an exponent * - * @param exponent - * exponent + * @param exponent exponent * @return result of exponentiation * @since 2019-10-16 */ @@ -237,11 +232,12 @@ public final class ObjectProduct { } return new ObjectProduct<>(map); } - + /** - * Converts this product to a string using the objects' {@link Object#toString()} method. If objects have a long - * toString representation, it is recommended to use {@link #toString(Function)} instead to shorten the returned - * string. + * Converts this product to a string using the objects' + * {@link Object#toString()} method. If objects have a long toString + * representation, it is recommended to use {@link #toString(Function)} + * instead to shorten the returned string. * *

* {@inheritDoc} @@ -250,35 +246,38 @@ public final class ObjectProduct { public String toString() { return this.toString(Object::toString); } - + /** - * Converts this product to a string. The objects that make up this product are represented by - * {@code objectToString} + * Converts this product to a string. The objects that make up this product + * are represented by {@code objectToString} * - * @param objectToString - * function to convert objects to strings + * @param objectToString function to convert objects to strings * @return string representation of product * @since 2019-10-16 */ public String toString(final Function objectToString) { final List positiveStringComponents = new ArrayList<>(); final List negativeStringComponents = new ArrayList<>(); - + // for each base object that makes up this object, add it and its exponent for (final T object : this.getBaseSet()) { final int exponent = this.exponents.get(object); - if (exponent > 0) { - positiveStringComponents.add(String.format("%s^%d", objectToString.apply(object), exponent)); + if (exponent > 1) { + positiveStringComponents.add(String.format("%s^%d", + objectToString.apply(object), exponent)); + } else if (exponent == 1) { + positiveStringComponents.add(objectToString.apply(object)); } else if (exponent < 0) { - negativeStringComponents.add(String.format("%s^%d", objectToString.apply(object), -exponent)); + negativeStringComponents.add(String.format("%s^%d", + objectToString.apply(object), -exponent)); } } - + final String positiveString = positiveStringComponents.isEmpty() ? "1" : String.join(" * ", positiveStringComponents); final String negativeString = negativeStringComponents.isEmpty() ? "" : " / " + String.join(" * ", negativeStringComponents); - + return positiveString + negativeString; } } diff --git a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java index 033d763..2276d7c 100644 --- a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java +++ b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java @@ -568,6 +568,12 @@ class UnitDatabaseTest { final Unit actual2 = database.getUnitFromExpression("2 fj + 6 ej"); assertEquals(expected2, actual2); + + // test incorrect expressions + assertThrows(IllegalArgumentException.class, + () -> database.getUnitFromExpression("U + V")); + assertThrows(IllegalArgumentException.class, + () -> database.getUnitFromExpression("U - V")); } /** diff --git a/src/test/java/sevenUnits/unit/UnitTest.java b/src/test/java/sevenUnits/unit/UnitTest.java index e495338..bb2e6a4 100644 --- a/src/test/java/sevenUnits/unit/UnitTest.java +++ b/src/test/java/sevenUnits/unit/UnitTest.java @@ -27,6 +27,7 @@ import java.util.concurrent.ThreadLocalRandom; import org.junit.jupiter.api.Test; import sevenUnits.utils.DecimalComparison; +import sevenUnits.utils.UncertainDouble; /** * Testing the various Unit classes. This is NOT part of this program's public @@ -53,8 +54,10 @@ class UnitTest { // test with LinearUnitValue final LinearUnitValue value1 = LinearUnitValue.getExact(Metric.METRE, 15); final LinearUnitValue value2 = LinearUnitValue.getExact(foot, 120); - final LinearUnitValue value3 = LinearUnitValue.getExact(Metric.METRE, 0.5); - final LinearUnitValue value4 = LinearUnitValue.getExact(Metric.KILOGRAM, 60); + final LinearUnitValue value3 = LinearUnitValue.getExact(Metric.METRE, + 0.5); + final LinearUnitValue value4 = LinearUnitValue.getExact(Metric.KILOGRAM, + 60); // make sure addition is done correctly assertEquals(51.576, value1.plus(value2).getValueExact(), 0.001); @@ -71,6 +74,7 @@ class UnitTest { // make sure errors happen when they should assertThrows(IllegalArgumentException.class, () -> value1.plus(value4)); + assertThrows(IllegalArgumentException.class, () -> value1.minus(value4)); } @Test @@ -122,7 +126,8 @@ class UnitTest { public void testMultiplicationAndDivision() { // test unit-times-unit multiplication final LinearUnit generatedJoule = Metric.KILOGRAM - .times(Metric.METRE.toExponent(2)).dividedBy(Metric.SECOND.toExponent(2)); + .times(Metric.METRE.toExponent(2)) + .dividedBy(Metric.SECOND.toExponent(2)); final LinearUnit actualJoule = Metric.JOULE; assertEquals(generatedJoule, actualJoule); @@ -132,16 +137,87 @@ class UnitTest { final LinearUnit hour = Metric.SECOND.times(3600); final LinearUnit generatedKPH = kilometre.dividedBy(hour); - final LinearUnit actualKPH = Metric.METRE.dividedBy(Metric.SECOND).dividedBy(3.6); + final LinearUnit actualKPH = Metric.METRE.dividedBy(Metric.SECOND) + .dividedBy(3.6); assertEquals(generatedKPH, actualKPH); } @Test public void testPrefixes() { - final LinearUnit generatedKilometre = Metric.METRE.withPrefix(Metric.KILO); + final LinearUnit generatedKilometre = Metric.METRE + .withPrefix(Metric.KILO); final LinearUnit actualKilometre = Metric.METRE.times(1000); assertEquals(generatedKilometre, actualKilometre); } + + /** + * Tests converting an uncertain LinearUnitValue to a string. + * + * @since 2021-11-04 + */ + @Test + public void testValueToString1() { + final LinearUnitValue value = LinearUnitValue.of(Metric.METRE, + UncertainDouble.of(10, 0.24)); + + assertEquals("(10.0 ± 0.2) m", value.toString()); + assertEquals("(10.0 ± 0.2) m", value.toString(true)); + assertEquals("10.0 m", value.toString(false)); + } + + /** + * Tests converting a certain LinearUnitValue to a string. + * + * @since 2021-11-04 + */ + @Test + public void testValueToString2() { + final LinearUnitValue value = LinearUnitValue.of(Metric.METRE, + UncertainDouble.of(10, 0)); + + assertEquals("10.0 m", value.toString()); + assertEquals("(10.0 ± 0.0) m", value.toString(true)); + assertEquals("10.0 m", value.toString(false)); + } + + /** + * Tests converting an unnamed LinearUnitValue to a string. + * + * @since 2021-11-04 + */ + @Test + public void testValueToString3() { + final LinearUnitValue value = LinearUnitValue.of( + Metric.METRE.withName(NameSymbol.EMPTY), + UncertainDouble.of(10, 0.24)); + + assertEquals("10.0 unnamed unit (= 10.0 m)", value.toString(false)); + } + + /** + * Tests converting a named UnitValue to a string. + * + * @since 2021-11-04 + */ + @Test + public void testValueToString4() { + final UnitValue value = UnitValue.of(BritishImperial.FAHRENHEIT, 80); + + assertEquals("80.0 \u00B0F", value.toString()); + } + + /** + * Tests converting an unnamed UnitValue to a string. + * + * @since 2021-11-04 + */ + @Test + public void testValueToString5() { + final UnitValue value = UnitValue + .of(USCustomary.FAHRENHEIT.withName(NameSymbol.EMPTY), 50); + + assertEquals("50.0 unnamed unit (= 283.15 K)", value.toString()); + } } -- cgit v1.2.3 From 8ea77520ce58e948eeffc4c2e8c25c69e88ed00a Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Mon, 29 Nov 2021 17:55:35 -0500 Subject: Added a full suite of tests for the UncertainDouble --- .../java/sevenUnits/utils/UncertainDouble.java | 15 ++-- .../java/sevenUnits/utils/UncertainDoubleTest.java | 90 ++++++++++++++++++++++ 2 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 src/test/java/sevenUnits/utils/UncertainDoubleTest.java (limited to 'src/main/java/sevenUnits') diff --git a/src/main/java/sevenUnits/utils/UncertainDouble.java b/src/main/java/sevenUnits/utils/UncertainDouble.java index 8fe4b31..fe41104 100644 --- a/src/main/java/sevenUnits/utils/UncertainDouble.java +++ b/src/main/java/sevenUnits/utils/UncertainDouble.java @@ -36,13 +36,14 @@ public final class UncertainDouble implements Comparable { */ public static final UncertainDouble ZERO = UncertainDouble.of(0, 0); + static final String NUMBER_REGEX = "(\\d+(?:[\\.,]\\d+))"; + /** * A regular expression that can recognize toString forms */ - private static final Pattern TO_STRING = Pattern - .compile("([a-zA-Z_0-9\\.\\,]+)" // a number - // optional "± [number]" - + "(?:\\s*(?:±|\\+-)\\s*([a-zA-Z_0-9\\.\\,]+))?"); + static final Pattern TO_STRING = Pattern.compile(NUMBER_REGEX + // optional "± [number]" + + "(?:\\s*(?:±|\\+-)\\s*" + NUMBER_REGEX + ")?"); /** * Parses a string in the form of {@link UncertainDouble#toString(boolean)} @@ -60,10 +61,14 @@ public final class UncertainDouble implements Comparable { Objects.requireNonNull(s, "s may not be null"); final Matcher matcher = TO_STRING.matcher(s); + if (!matcher.matches()) + throw new IllegalArgumentException( + "Could not parse stirng \"" + s + "\"."); + double value, uncertainty; try { value = Double.parseDouble(matcher.group(1)); - } catch (IllegalStateException | NumberFormatException e) { + } catch (final NumberFormatException e) { throw new IllegalArgumentException( "String " + s + " not in correct format."); } diff --git a/src/test/java/sevenUnits/utils/UncertainDoubleTest.java b/src/test/java/sevenUnits/utils/UncertainDoubleTest.java new file mode 100644 index 0000000..c891f20 --- /dev/null +++ b/src/test/java/sevenUnits/utils/UncertainDoubleTest.java @@ -0,0 +1,90 @@ +/** + * Copyright (C) 2021 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package sevenUnits.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static sevenUnits.utils.UncertainDouble.fromString; +import static sevenUnits.utils.UncertainDouble.of; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +/** + * Tests for the UncertainDouble + * + * @author Adrien Hopkins + * @since 2021-11-29 + */ +class UncertainDoubleTest { + @Test + final void testCompareTo() { + assertTrue(of(2.0, 0.5).compareTo(of(2.0, 0.1)) == 0); + assertTrue(of(2.0, 0.5).compareTo(of(1.0, 0.1)) > 0); + assertTrue(of(2.0, 0.5).compareTo(of(3.0, 0.1)) < 0); + } + + @Test + final void testExactOperations() { + final UncertainDouble x = UncertainDouble.of(Math.PI, 0.1); + + // slightly different because roundoff errors + final UncertainDouble x1 = UncertainDouble.of(Math.PI + Math.E - Math.E, + 0.1); + final UncertainDouble x2 = UncertainDouble.of(Math.PI * Math.E / Math.E, + 0.1); + + // get results + final UncertainDouble result1 = x.plusExact(Math.E).minusExact(Math.E); + final UncertainDouble result2 = x.timesExact(Math.E) + .dividedByExact(Math.E); + + // test that these operations work & don't change uncertainty + assertEquals(x1, result1); + assertTrue(x.equivalent(result1)); + assertEquals(x2, result2); + assertTrue(x.equivalent(result2)); + + // exponents are different + assertEquals(Math.pow(Math.PI, Math.E), + x.toExponentExact(Math.E).value()); + } + + @Test + final void testFromString() { + // valid strings + assertEquals(of(2.0, 0.5), fromString("2.0 ± 0.5")); + assertEquals(of(2.0, 0.5), fromString("2.0 +- 0.5")); + assertEquals(of(2.0, 0.0), fromString("2.0")); + + // invalid strings + for (final String s : List.of("2.A", "A", "2.0 ± .", "± 3.5")) { + assertThrows(IllegalArgumentException.class, () -> fromString(s)); + } + + // back and forth + assertEquals("2.0 ± 0.5", of(2.0, 0.5).toString()); + assertEquals("2.0", of(2.0, 0).toString()); + } + + @Test + final void testHashCode() { + assertEquals(of(2.0, 0.5).hashCode(), fromString("2.0 ± 0.5").hashCode()); + } +} -- cgit v1.2.3