This is an automated email from the ASF dual-hosted git repository. aherbert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-numbers.git
commit 778f94b5a4c93d378d549ed0dfd0a8147264f042 Author: aherbert <[email protected]> AuthorDate: Tue Dec 10 12:55:49 2019 +0000 Improved CReferenceTest to allow processing of flagged reference data. --- .../commons/numbers/complex/CReferenceTest.java | 122 +++++++++++++++------ .../apache/commons/numbers/complex/TestUtils.java | 83 ++++++++++---- 2 files changed, 150 insertions(+), 55 deletions(-) diff --git a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/CReferenceTest.java b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/CReferenceTest.java index 57822b8..d122235 100644 --- a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/CReferenceTest.java +++ b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/CReferenceTest.java @@ -17,6 +17,7 @@ package org.apache.commons.numbers.complex; +import org.apache.commons.numbers.complex.TestUtils.TestDataFlagOption; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -37,14 +38,33 @@ import java.util.function.UnaryOperator; */ public class CReferenceTest { /** - * The maximum units of least precision allowed between values. + * The maximum units of least precision (ULPs) allowed between values. + * This is a global setting used to override individual test settings for ULPs as follows: * - * <p>In the normal use case this is set to zero and ignored. - * It can be set to a non-zero value and it overrides the ulps values used in - * each test if greater in magnitude. This can be used to output a report - * of the ULPS between Complex and the reference data. + * <ul> + * <li>In the normal use case this is set to zero and ignored. + * <li>If the sign matches the setting of the test then the larger magnitude is used. + * <li>If the global setting is negative and the test setting is positive then it overrides + * the individual test setting for reporting purposes. + * <li>If the global setting is positive and the test setting is negative then the test + * setting takes precedence. + * </ul> + * + * <p>During testing if the difference between an expected and actual result is greater in + * magnitude than the current ULPS then this is considered an error. If the maximum ULPs is + * positive then an assertion error is raised. If negative then the error is printed to + * System out. This allows reporting of large deviations between the library and the + * reference data. + * + * <p>In a standard use-case all tests will have a configured positive maximum ULPs to + * pass the current test data. The global setting can be set to a negative value to allow + * reporting of errors larger in magnitude to the console. + * + * <p>Setting the global maximum ULPs to negative has the second effect of loading all + * data that has been flagged in data files. Otherwise this data is ignored by testing and + * printed to System out. */ - private static final long MAX_ULPS = 0; + private static long globalMaxUlps = 0; /** * Assert the two numbers are equal within the provided units of least precision. @@ -81,16 +101,17 @@ public class CReferenceTest { * {@code maxUlps - 1}. * * @param c Input number. + * @param name the operation name * @param operation the operation * @param expected Expected result. * @param maxUlps the maximum units of least precision between the two values */ private static void assertComplex(Complex c, - UnaryOperator<Complex> operation, + String name, UnaryOperator<Complex> operation, Complex expected, long maxUlps) { final Complex z = operation.apply(c); - assertEquals(() -> c + ": real", expected.real(), z.real(), maxUlps); - assertEquals(() -> c + ": imaginary", expected.imag(), z.imag(), maxUlps); + assertEquals(() -> c + "." + name + "(): real", expected.real(), z.real(), maxUlps); + assertEquals(() -> c + "." + name + "(): imaginary", expected.imag(), z.imag(), maxUlps); } /** @@ -102,51 +123,68 @@ public class CReferenceTest { * * @param c1 First number. * @param c2 Second number. + * @param name the operation name * @param operation the operation * @param expected Expected real part. * @param maxUlps the maximum units of least precision between the two values */ private static void assertComplex(Complex c1, Complex c2, - BiFunction<Complex, Complex, Complex> operation, + String name, BiFunction<Complex, Complex, Complex> operation, Complex expected, long maxUlps) { final Complex z = operation.apply(c1, c2); - assertEquals(() -> c1 + " op " + c2 + ": real", expected.real(), z.real(), maxUlps); - assertEquals(() -> c1 + " op " + c2 + ": imaginary", expected.imag(), z.imag(), maxUlps); + assertEquals(() -> c1 + "." + name + c2 + ": real", expected.real(), z.real(), maxUlps); + assertEquals(() -> c1 + "." + name + c2 + ": imaginary", expected.imag(), z.imag(), maxUlps); } /** * Assert the operation using the data loaded from test resources. * - * @param testData Test data resource name. + * @param name the operation name * @param operation the operation * @param maxUlps the maximum units of least precision between the two values */ - private static void assertOperation(String testData, + private static void assertOperation(String name, UnaryOperator<Complex> operation, long maxUlps) { - final List<Complex[]> data = TestUtils.loadTestData(testData); + final List<Complex[]> data = loadTestData(name); final long ulps = getTestUlps(maxUlps); for (final Complex[] pair : data) { - assertComplex(pair[0], operation, pair[1], ulps); + assertComplex(pair[0], name, operation, pair[1], ulps); } } /** * Assert the operation using the data loaded from test resources. * - * @param testData Test data resource name. + * @param name the operation name * @param operation the operation * @param maxUlps the maximum units of least precision between the two values */ - private static void assertBiOperation(String testData, + private static void assertBiOperation(String name, BiFunction<Complex, Complex, Complex> operation, long maxUlps) { - final List<Complex[]> data = TestUtils.loadTestData(testData); + final List<Complex[]> data = loadTestData(name); final long ulps = getTestUlps(maxUlps); for (final Complex[] triple : data) { - assertComplex(triple[0], triple[1], operation, triple[2], ulps); + assertComplex(triple[0], triple[1], name, operation, triple[2], ulps); } } /** + * Assert the operation using the data loaded from test resources. + * + * @param testData Test data resource name. + * @return the list + */ + private static List<Complex[]> loadTestData(String name) { + final String testData = "data/" + name + ".txt"; + final TestDataFlagOption option = globalMaxUlps < 1 ? + TestDataFlagOption.LOAD : TestDataFlagOption.IGNORE; + return TestUtils.loadTestData(testData, option, + // CHECKSTYLE: stop Regex + s -> System.out.println(name + " IGNORED: " + s)); + // CHECKSTYLE: resume Regex + } + + /** * Gets the test ulps. This uses the input value of the global setting if that is greater * in magnitude. * @@ -154,78 +192,90 @@ public class CReferenceTest { * @return the test ulps */ private static long getTestUlps(long ulps) { - final long max = Math.max(Math.abs(ulps), Math.abs(MAX_ULPS)); - // If either are negative then choose negative for debugging output - return (ulps | MAX_ULPS) < 0 ? -max : max; + // If sign matches use the larger magnitude. + // xor the sign bytes will be negative if the sign does not match + if ((globalMaxUlps ^ ulps) >= 0) { + final long max = Math.max(Math.abs(globalMaxUlps), Math.abs(ulps)); + // restore sign + return ulps < 0 ? -max : max; + } + // If the global setting is negative and the test setting is positive then it overrides + // the individual test setting for reporting purposes. + if (globalMaxUlps < 0) { + return globalMaxUlps; + } + // If the global setting is positive and the test setting is negative then the test + // setting takes precedence. + return ulps; } @Test public void testAcos() { - assertOperation("data/acos.txt", Complex::acos, 36); + assertOperation("acos", Complex::acos, 36); } @Test public void testAcosh() { - assertOperation("data/acosh.txt", Complex::acosh, 36); + assertOperation("acosh", Complex::acosh, 36); } @Test public void testAsinh() { // Odd function: negative real cases defined by positive real cases - assertOperation("data/asinh.txt", Complex::asinh, 2); + assertOperation("asinh", Complex::asinh, 2); } @Test public void testAtanh() { // Odd function: negative real cases defined by positive real cases - assertOperation("data/atanh.txt", Complex::atanh, 26); + assertOperation("atanh", Complex::atanh, 26); } @Test public void testCosh() { // Even function: negative real cases defined by positive real cases - assertOperation("data/cosh.txt", Complex::cosh, 2); + assertOperation("cosh", Complex::cosh, 2); } @Test public void testSinh() { // Odd function: negative real cases defined by positive real cases - assertOperation("data/sinh.txt", Complex::sinh, 2); + assertOperation("sinh", Complex::sinh, 2); } @Test public void testTanh() { // Odd function: negative real cases defined by positive real cases - assertOperation("data/tanh.txt", Complex::tanh, 34); + assertOperation("tanh", Complex::tanh, 34); } @Test public void testExp() { - assertOperation("data/exp.txt", Complex::exp, 2); + assertOperation("exp", Complex::exp, 2); } @Test public void testLog() { - assertOperation("data/log.txt", Complex::log, 3); + assertOperation("log", Complex::log, 3); } @Test public void testSqrt() { - assertOperation("data/sqrt.txt", Complex::sqrt, 1); + assertOperation("sqrt", Complex::sqrt, 1); } @Test public void testMultiply() { - assertBiOperation("data/multiply.txt", Complex::multiply, 0); + assertBiOperation("multiply", Complex::multiply, 0); } @Test public void testDivide() { - assertBiOperation("data/divide.txt", Complex::divide, 0); + assertBiOperation("divide", Complex::divide, 0); } @Test public void testPowComplex() { - assertBiOperation("data/pow.txt", Complex::pow, 17); + assertBiOperation("pow", Complex::pow, 17); } } diff --git a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/TestUtils.java b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/TestUtils.java index c1cc4a6..eb85e68 100644 --- a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/TestUtils.java +++ b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/TestUtils.java @@ -26,6 +26,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import org.apache.commons.numbers.core.Precision; @@ -35,6 +36,18 @@ import org.junit.jupiter.api.Assertions; * Test utilities. TODO: Cleanup (remove unused and obsolete methods). */ public final class TestUtils { + + /** + * The option for how to process test data lines flagged (prefixed) + * with the {@code ;} character + */ + public enum TestDataFlagOption { + /** Ignore the line. */ + IGNORE, + /** Load the data by stripping the {@code ;} character. */ + LOAD + } + /** * Collection of static methods used in math unit tests. */ @@ -288,42 +301,40 @@ public final class TestUtils { /** * Load test data from resources. * - * <p>This method can be used to load input complex numbers and the expected - * result after applying a function. + * <p>This method can be used to load input complex numbers and the expected result + * after applying a function. * - * <p>Data is assumed to be a resource available to the class loader. The data - * should be space delimited doubles. Each pair of doubles on a line is - * converted to a Complex. For example the following represents the numbers (0.5 - * - 0 i) and (1.5 + 2 i): + * <p>Data is assumed to be a resource available to the class loader. The data should + * be space delimited doubles. Each pair of doubles on a line is converted to a + * Complex. For example the following represents the numbers (0.5 - 0 i) and (1.5 + 2 + * i): * * <pre> * 0.5 -0.0 1.5 2 * </pre> * - * <p>An unmatched double not part of a pair on a line will raise an - * AssertionError. + * <p>An unmatched double not part of a pair on a line will raise an AssertionError. * * <p>Lines starting with the {@code #} character are ignored. * - * <p>Lines starting with the {@code ;} character are ignored but printed. This can be used - * to disable tests in the data file. + * <p>Lines starting with the {@code ;} character are processed using the provided + * flag option. This character can be used to disable tests in the data file. + * + * <p>The flagged data will be passed to the consumer. * * @param name the resource name + * @param option the option controlling processing of flagged data + * @param flaggedDataConsumer the flagged data consumer (can be null) * @return the list */ - public static List<Complex[]> loadTestData(String name) { + public static List<Complex[]> loadTestData(String name, TestDataFlagOption option, + Consumer<String> flaggedDataConsumer) { final List<Complex[]> data = new ArrayList<>(); try (BufferedReader input = new BufferedReader( new InputStreamReader(Thread.currentThread().getContextClassLoader().getResourceAsStream(name)))) { for (String line = input.readLine(); line != null; line = input.readLine()) { - // Skip comments and empty lines - if (line.length() == 0 || line.charAt(0) == '#') { - continue; - } - if (line.charAt(0) == ';') { - // CHECKSTYLE: stop all - System.out.println(line); - // CHECKSTYLE: resume + line = preprocessTestData(line, option, flaggedDataConsumer); + if (line == null) { continue; } final String[] parts = line.split(" "); @@ -343,4 +354,38 @@ public final class TestUtils { } return data; } + + /** + * Pre-process the next line of data from the input. + * Returns null when the line should be ignored. + * + * @param input the input + * @param option the option controlling processing of flagged data + * @param flaggedDataConsumer the flagged data consumer (can be null) + * @return the line of data (or null) + * @throws IOException Signals that an I/O exception has occurred. + */ + private static String preprocessTestData(String line, TestDataFlagOption option, + Consumer<String> flaggedDataConsumer) { + // Skip comments and empty lines + if (line.length() == 0 || line.charAt(0) == '#') { + return null; + } + if (line.charAt(0) == ';') { + switch (option) { + case LOAD: + // Strip the leading character + line = line.substring(1); + break; + case IGNORE: + default: + if (flaggedDataConsumer != null) { + flaggedDataConsumer.accept(line.substring(1)); + } + // Ignore the line + line = null; + } + } + return line; + } }
