All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tech.units.tck.util.TestUtils Maven / Gradle / Ivy

There is a newer version: 2.2
Show newest version
/*
 * Units of Measurement TCK
 * Copyright © 2005-2019, Jean-Marie Dautelle, Werner Keil, Otavio Santana.
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
 *    and the following disclaimer in the documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of JSR-385 nor the names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package tech.units.tck.util;

import static java.lang.reflect.Modifier.PUBLIC;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo;
import static org.reflections.ReflectionUtils.getAllMethods;
import static org.reflections.ReflectionUtils.withModifier;
import static org.reflections.ReflectionUtils.withName;
import static org.reflections.ReflectionUtils.withParametersCount;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.Set;

import org.mutabilitydetector.unittesting.AllowedReason;
import org.mutabilitydetector.unittesting.MutabilityAssert;
import org.mutabilitydetector.unittesting.MutabilityMatchers;
import org.testng.Assert;

import tech.units.tck.TCKValidationException;

import javax.inject.Singleton;
import javax.measure.*;
import javax.measure.spi.*;

/**
 * Test utilities used in the JSR 385 TCK.
 *
 * @author Werner Keil
 * @version 1.4, July 7, 2019
 * @since 1.0
 */
@Singleton
public class TestUtils {

    /**
     * Name of the system property to pass the desired profile
     */
    public static final String SYS_PROPERTY_PROFILE = "tech.units.tck.profile";

    /**
     * Name of the system property to override the default output directory
     */
    public static final String SYS_PROPERTY_OUTPUT_DIR = "tech.units.tck.outputDir";

    /**
     * Name of the system property to override the default report file
     */
    public static final String SYS_PROPERTY_REPORT_FILE = "tech.units.tck.reportFile";

    /**
     * Name of the system property to set the verbose flag
     */
    public static final String SYS_PROPERTY_VERBOSE = "tech.units.tck.verbose";

    private static final StringBuilder warnings = new StringBuilder();

    /**
     * This class should not be instantiated
     */
    private TestUtils() {
    }

    static Number createNumberWithPrecision(QuantityFactory f, int precision) {
        if (precision == 0) {
            precision = new Random().nextInt(100);
        }
        StringBuilder b = new StringBuilder(precision + 1);
        for (int i = 0; i < precision; i++) {
            b.append(String.valueOf(i % 10));
        }
        return new Double(b.toString());
    }

    static Number createNumberWithScale(QuantityFactory f, int scale) {
        StringBuilder b = new StringBuilder(scale + 2);
        b.append("9.");
        for (int i = 0; i < scale; i++) {
            b.append(String.valueOf(i % 10));
        }
        return new Double(b.toString());
    }

    /**
     * Tests the given object being (effectively) serializable by serializing it.
     *
     * @param section
     *            the section of the spec under test
     * @param type
     *            the type to be checked.
     * @throws TCKValidationException
     *             if the test fails.
     */
    public static void testSerializable(String section, Class type) {
        if (!Serializable.class.isAssignableFrom(type)) {
            throw new TCKValidationException(section + ": Class must be serializable: " + type.getName());
        }
    }

    /**
     * Tests the given class being serializable.
     *
     * @param section
     *            the section of the spec under test
     * @param type
     *            the type to be checked.
     * @throws TCKValidationException
     *             if test fails.
     */
    public static void testImmutable(String section, Class type) {
        try {
            MutabilityAssert.assertInstancesOf(type, MutabilityMatchers.areImmutable(),
                    AllowedReason.provided(Dimension.class, Quantity.class, Unit.class, UnitConverter.class).areAlsoImmutable(),
                    AllowedReason.allowingForSubclassing(), AllowedReason.allowingNonFinalFields());
        } catch (Exception e) {
            throw new TCKValidationException(section + ": Class is not immutable: " + type.getName(), e);
        }
    }

    /**
     * Tests the given object being (effectively) serializable by serializing it.
     *
     * @param section
     *            the section of the spec under test
     * @param o
     *            the object to be checked.
     * @throws TCKValidationException
     *             if test fails.
     */
    public static void testSerializable(String section, Object o) {
        if (!(o instanceof Serializable)) {
            throw new TCKValidationException(section + ": Class must be serializable: " + o.getClass().getName());
        }
        try (ObjectOutputStream oos = new ObjectOutputStream(new ByteArrayOutputStream())) {
            oos.writeObject(o);
        } catch (Exception e) {
            throw new TCKValidationException("Class must be serializable, but serialization failed: " + o.getClass().getName(), e);
        }
    }

    /**
     * Tests the given class implements a given interface.
     *
     * @param section
     *            the section of the spec under test
     * @param type
     *            the type to be checked.
     * @param iface
     *            the interface to be checked for.
     * Triggers Assert#fail
     *             if test fails.
     */
    public static void testImplementsInterface(String section, Class type, Class iface) {
        for (Class ifa : type.getInterfaces()) {
            if (ifa.equals(iface)) {
                return;
            }
        }
        Assert.fail(section + ": Class must implement " + iface.getName() + ", but does not: " + type.getName());
    }

    /**
     * Tests if the given type is comparable.
     * 
     * @param section
     *            the section of the spec under test
     * @param type
     *            the type to be checked.
     */
    public static void testComparable(String section, Class type) {
        testImplementsInterface(section, type, Comparable.class);
    }

    /**
     * 
     * @param section the section of the specification
     * @param type the type to be checked.
     * @param returnType the expected return type
     * @param name the name of the method
     * @param paramTypes the types of parameters if available
     * @deprecated use the simplified version on top of Reflections.org where possible
     */
    public static void testHasPublicMethod(String section, Class type, Class returnType, String name, Class... paramTypes) {
        Class current = type;
        while (current != null) {
            for (Method m : current.getDeclaredMethods()) {
                if (returnType.equals(returnType) && m.getName().equals(name) && ((m.getModifiers() & PUBLIC) != 0)
                        && Arrays.equals(m.getParameterTypes(), paramTypes)) {
                    return;
                }
            }
            current = current.getSuperclass();
        }
        throw new TCKValidationException(section + ": Class must implement method " + name + '(' + Arrays.toString(paramTypes) + "): "
                + returnType.getName() + ", but does not: " + type.getName());
    }

    @SuppressWarnings("rawtypes")
	private static final List PRIMITIVE_CLASSES = Collections
            .unmodifiableList(Arrays.asList(new Class[] { Object.class, Number.class, Enum.class }));

    /**
     * 
     * @param section the section of the specification
     * @param type the data type
     * @param trySuperclassFirst if tht super class if available should be tested first
     * @param returnType the expected return type
     * @param name the name of the method
     * @param paramTypes types of parameters
     */
    public static void testHasPublicMethod(String section, Class type, boolean trySuperclassFirst, Class returnType, String name,
            Class... paramTypes) {
        if (trySuperclassFirst && type.getSuperclass() != null) {
            if (PRIMITIVE_CLASSES.contains(type.getSuperclass())) {
                testHasPublicMethod(section, type, returnType, name, paramTypes);
            } else {
                testHasPublicMethod(section, type.getSuperclass(), returnType, name, paramTypes);
            }
        } else {
            testHasPublicMethod(section, type, returnType, name, paramTypes);
        }
    }

    /**
     * Tests if the given type has a public method with the given signature.
     * 
     * @param section
     *            the section of the spec under test
     * @param type
     *            the type to be checked.
     * @param name
     *            the method name
     * @param hasParameters
     *            the method has parameters.
     * @throws TCKValidationException
     *             if test fails.
     */
    @SuppressWarnings({ "unchecked" })
    public static void testHasPublicMethod(String section, Class type, String name, boolean hasParameters) {
        Set getters;
        if (hasParameters) {
            getters = getAllMethods(type, withModifier(PUBLIC), withName(name));
        } else {
            getters = getAllMethods(type, withModifier(PUBLIC), withName(name), withParametersCount(0));
        }
        assertThat(getters.size(), greaterThanOrEqualTo(1)); // interface plus
        // at least one implementation
    }

    /**
     * @param section the section of the specification
     * @param type the data type
     * @param name the name of the method
     */
    public static void testHasPublicMethod(String section, Class type, String name) {
        testHasPublicMethod(section, type, name, false);
    }

    /**
     * Tests if the given type has a public static method with the given signature.
     * 
     * @param section
     *            the section of the spec under test
     * @param type
     *            the type to be checked.
     * @param returnType
     *            the method return type.
     * @param name
     *            the method name
     * @param paramTypes
     *            the parameter types.
     * @throws TCKValidationException
     *             if test fails.
     */
    @SuppressWarnings("rawtypes")
    static void testHasPublicStaticMethod(String section, Class type, Class returnType, String name, Class... paramTypes) {
        Class current = type;
        while (current != null) {
            for (Method m : current.getDeclaredMethods()) {
                if (returnType.equals(returnType) && m.getName().equals(name) && ((m.getModifiers() & PUBLIC) != 0)
                        && ((m.getModifiers() & Modifier.STATIC) != 0) && Arrays.equals(m.getParameterTypes(), paramTypes)) {
                    return;
                }
            }
            current = current.getSuperclass();
        }
        throw new TCKValidationException(section + ": Class must implement method " + name + '(' + Arrays.toString(paramTypes) + "): "
                + returnType.getName() + ", but does not: " + type.getName());
    }

    /**
     * Tests if the given type has not a public method with the given signature.
     * 
     * @param section
     *            the section of the spec under test
     * @param type
     *            the type to be checked.
     * @param returnType
     *            the method return type.
     * @param name
     *            the method name
     * @param paramTypes
     *            the parameter types.
     * @throws TCKValidationException
     *             if test fails.
     */
    @SuppressWarnings("rawtypes")
	public static void testHasNotPublicMethod(String section, Class type, Class returnType, String name, Class... paramTypes) {
        Class current = type;
        while (current != null) {
            for (Method m : current.getDeclaredMethods()) {
                if (returnType.equals(returnType) && m.getName().equals(name) && Arrays.equals(m.getParameterTypes(), paramTypes)) {
                    throw new TCKValidationException(section + ": Class must NOT implement method " + name + '(' + Arrays.toString(paramTypes) + "): "
                            + returnType.getName() + ", but does: " + type.getName());
                }
            }
            current = current.getSuperclass();
        }
    }

    /**
     * Checks the returned value, when calling a given method.
     * 
     * @param section
     *            the section of the spec under test
     * @param value
     *            the expected value
     * @param methodName
     *            the target method name
     * @param instance
     *            the instance to call
     * @throws NoSuchMethodException if no method with the given name exists.
     * @throws SecurityException if a security problem occurs.
     * @throws IllegalAccessException if the method may not be called, e.g. due to security constraints.
     * @throws IllegalArgumentException if a wrong or inappropriate argument was provided.
     * @throws InvocationTargetException if an exception thrown by an invoked method or constructor.
     * @throws TCKValidationException
     *             if test fails.
     */
    public static void assertValue(String section, Object value, String methodName, Object instance)
            throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        Method m = instance.getClass().getDeclaredMethod(methodName);
        Assert.assertEquals(value, m.invoke(instance), section + ": " + m.getName() + '(' + instance + ") returned invalid value:");
    }

    static boolean testHasPublicStaticMethodOpt(String section, @SuppressWarnings("rawtypes") Class type, @SuppressWarnings("rawtypes") Class returnType, String methodName, @SuppressWarnings("rawtypes") Class... paramTypes) {
        try {
            testHasPublicStaticMethod(section, type, returnType, methodName, paramTypes);
            return true;
        } catch (Exception e) {
            warnings.append(section).append(": Recommendation failed: Missing method [public static ").append(methodName).append('(')
                    .append(Arrays.toString(paramTypes)).append("):").append(returnType.getName()).append("] on: ").append(type.getName())
                    .append("\n");
            return false;
        }
    }

    /**
     * Test for immutability (optional recommendation), writes a warning if not given.
     * 
     * @param section
     *            the section of the spec under test
     * @param type
     *            the type to be checked.
     * @return true, if the instance is probably immutable.
     */
    public static boolean testImmutableOpt(String section, @SuppressWarnings("rawtypes") Class type) {
        try {
            testImmutable(section, type);
            return true;
        } catch (Exception e) {
            warnings.append(section).append(": Recommendation failed: Class should be immutable: ").append(type.getName()).append(", details: ")
                    .append(e.getMessage()).append("\n");
            return false;
        }
    }

    /**
     * Test for serializable (optional recommendation), writes a warning if not given.
     * 
     * @param section
     *            the section of the spec under test
     * @param type
     *            the type to be checked.
     * @return true, if the type is probably serializable.
     */
    public static boolean testSerializableOpt(String section, @SuppressWarnings("rawtypes") Class type) {
        try {
            testSerializable(section, type);
            return true;
        } catch (Exception e) {
            warnings.append(section).append(": Recommendation failed: Class should be serializable: ").append(type.getName()).append(", details: ")
                    .append(e.getMessage()).append("\n");
            return false;
        }
    }

    /**
     * Test for serializable (optional recommendation), writes a warning if not given.
     * 
     * @param section
     *            the section of the spec under test
     * @param instance
     *            the object to be checked.
     * @return true, if the instance is probably serializable.
     */
    public static boolean testSerializableOpt(String section, Object instance) {
        try {
            testSerializable(section, instance);
            return true;
        } catch (Exception e) {
            warnings.append(section).append(": Recommendation failed: Class is serializable, but serialization failed: ")
                    .append(instance.getClass().getName()).append("\n");
            return false;
        }
    }

    static void resetWarnings() {
        warnings.setLength(0);
    }

    static String getWarnings() {
        return warnings.toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy