Package org.apache.sis.test

Source Code of org.apache.sis.test.TestUtilities

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.test;

import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.Iterator;
import java.util.Random;
import java.util.concurrent.Callable;
import java.io.PrintWriter;
import java.lang.reflect.UndeclaredThrowableException;
import java.text.Format;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import org.apache.sis.util.Static;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.collection.TreeTable;
import org.apache.sis.util.collection.TableColumn;
import org.apache.sis.util.collection.TreeTableFormat;
import org.apache.sis.internal.util.X364;

import static org.junit.Assert.*;


/**
* Miscellaneous utility methods for test cases.
*
* @author  Martin Desruisseaux (Geomatys)
* @since   0.3 (derived from geotk-3.16)
* @version 0.4
* @module
*/
public final strictfp class TestUtilities extends Static {
    /**
     * Width of the separator to print to {@link TestCase#out}, in number of characters.
     */
    private static final int SEPARATOR_WIDTH = 80;

    /**
     * Maximal time that {@code waitFoo()} methods can wait, in milliseconds.
     *
     * @see #waitForBlockedState(Thread)
     * @see #waitForGarbageCollection(Callable)
     */
    private static final int MAXIMAL_WAIT_TIME = 1000;

    /**
     * Date parser and formatter using the {@code "yyyy-MM-dd HH:mm:ss"} pattern
     * and UTC time zone.
     */
    private static final DateFormat dateFormat;
    static {
        dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CANADA);
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
        dateFormat.setLenient(false);
    };

    /**
     * The {@link TreeTableFormat} to use for unlocalized string representations.
     * Created when first needed.
     */
    private static Format tableFormat;

    /**
     * The thread group for every threads created for testing purpose.
     */
    public static final ThreadGroup THREADS = new ThreadGroup("SIS-Tests");

    /**
     * Do not allow instantiation of this class.
     */
    private TestUtilities() {
    }

    /**
     * Prints and clear the current content of {@link TestCase#out}, regardless of whether
     * {@link TestCase#verbose} is {@code true} or {@code false}. This method should rarely
     * be needed.
     *
     * @since 0.4
     */
    public static void forceFlushOutput() {
        TestCase.flushOutput();
    }

    /**
     * If verbose output are enabled, prints the given title to {@link TestCase#out} in a box.
     * This method is invoked for writing a clear visual separator between the verbose output
     * of different test cases. This method does nothing if verbose output is not enabled,
     * because only the output of failed tests should be printed in such case.
     *
     * @param title The title to write.
     */
    public static void printSeparator(final String title) {
        if (TestCase.verbose) {
            final PrintWriter out = TestCase.out;
            final boolean isAnsiSupported = X364.isAnsiSupported();
            if (isAnsiSupported) {
                out.print(X364.FOREGROUND_CYAN.sequence());
            }
            out.print('╒');
            for (int i=0; i<SEPARATOR_WIDTH-2; i++) {
                out.print('═');
            }
            out.println('╕');
            out.print("│ ");
            out.print(title);
            for (int i=title.codePointCount(0, title.length()); i<SEPARATOR_WIDTH-3; i++) {
                out.print(' ');
            }
            out.println('│');
            out.print('└');
            for (int i=0; i<SEPARATOR_WIDTH-2; i++) {
                out.print('─');
            }
            out.println('┘');
            if (isAnsiSupported) {
                out.print(X364.FOREGROUND_DEFAULT.sequence());
            }
        }
    }

    /**
     * Returns a new random number generator with a random seed.
     * If the test succeed, nothing else will happen. But if the test fails, then the seed value will
     * be logged to the {@link TestCase#out} stream in order to allow the developer to reproduce the
     * test failure.
     *
     * <p>This method shall be invoked only in the body of a test method - the random number generator
     * is not valid anymore after the test finished.</p>
     *
     * <p>This method doesn't need to be used in every cases. For example test cases using
     * {@link Random#nextGaussian()} should create their own random numbers generator with
     * the {@link Random#Random(long)} constructor instead
     * (see {@link org.apache.sis.math.StatisticsTest} for more explanation).
     * Or test cases that are mostly insensitive to the exact sequence of numbers
     * can use the {@link Random#Random()} constructor instead.</p>
     *
     * <p>This method is rather for testing relatively complex code which are likely to behave
     * differently depending on the exact sequence of numbers. We want to use random sequence
     * of numbers in order to test the code in a wider range of scenarios. However in case of
     * test failure, we need to know the <cite>seed</cite> which has been used in order to allow
     * the developer to reproduce the test with the exact same sequence of numbers.
     * Using this method, the seed can be retrieved in the messages sent to the output stream.</p>
     *
     * @return A new random number generator initialized with a random seed.
     */
    public static Random createRandomNumberGenerator() {
        long seed;
        do seed = StrictMath.round(StrictMath.random() * (1L << 48));
        while (seed == 0); // 0 is a sentinal value for "no generator".
        TestCase.randomSeed = seed;
        return new Random(seed);
    }

    /**
     * Parses the date for the given string using the {@code "yyyy-MM-dd HH:mm:ss"} pattern
     * in UTC timezone.
     *
     * @param  date The date as a {@link String}.
     * @return The date as a {@link Date}.
     */
    public static Date date(final String date) {
        ArgumentChecks.ensureNonNull("date", date);
        try {
            synchronized (dateFormat) {
                return dateFormat.parse(date);
            }
        } catch (ParseException e) {
            throw new AssertionError(e);
        }
    }

    /**
     * Formats the given date using the {@code "yyyy-MM-dd HH:mm:ss"} pattern in UTC timezone.
     *
     * @param  date The date to format.
     * @return The date as a {@link String}.
     */
    public static String format(final Date date) {
        ArgumentChecks.ensureNonNull("date", date);
        synchronized (dateFormat) {
            return dateFormat.format(date);
        }
    }

    /**
     * Formats the given value using the given formatter, and parses the text back to its value.
     * If the parsed value is not equal to the original one, an {@link AssertionError} is thrown.
     *
     * @param  formatter The formatter to use for formatting and parsing.
     * @param  value The value to format.
     * @return The formatted value.
     */
    public static String formatAndParse(final Format formatter, final Object value) {
        final String text = formatter.format(value);
        final Object parsed;
        try {
            parsed = formatter.parseObject(text);
        } catch (ParseException e) {
            throw new AssertionError(e);
        }
        assertEquals("Parsed text not equal to the original value", value, parsed);
        return text;
    }

    /**
     * Returns a unlocalized string representation of {@code NAME} and {@code VALUE} columns of the given tree table.
     * Dates and times, if any, will be formatted using the {@code "yyyy-MM-dd HH:mm:ss"} pattern in UTC timezone.
     * This method is used mostly as a convenient way to verify the content of an ISO 19115 metadata object.
     *
     * @param  table The table for which to get a string representation.
     * @return A unlocalized string representation of the given tree table.
     */
    public static String formatNameAndValue(final TreeTable table) {
        synchronized (TestUtilities.class) {
            if (tableFormat == null) {
                final TreeTableFormat f = new TreeTableFormat(null, null);
                f.setColumns(TableColumn.NAME, TableColumn.VALUE);
                tableFormat = f;
            }
            return tableFormat.format(table);
        }
    }

    /**
     * Returns the tree structure of the given string representation, without the localized text.
     * For example given the following string:
     *
     * {@preformat text
     *   Citation
     *     ├─Title…………………………………………………… Some title
     *     └─Cited responsible party
     *         └─Individual name……………… Some person of contact
     * }
     *
     * this method returns an array containing the following elements:
     *
     * {@preformat text
     *   "",
     *   "  ├─",
     *   "  └─",
     *   "      └─"
     * }
     *
     * This method is used for comparing two tree having string representation in different locales.
     * In such case, we can not compare the actual text content. The best we can do is to compare
     * the tree structure.
     *
     * @param  tree The string representation of a tree.
     * @return The structure of the given tree, without text.
     */
    public static CharSequence[] toTreeStructure(final CharSequence tree) {
        final CharSequence[] lines = CharSequences.split(tree, '\n');
        for (int i=0; i<lines.length; i++) {
            final CharSequence line = lines[i];
            final int length = line.length();
            for (int j=0; j<length;) {
                final int c = Character.codePointAt(line, j);
                if (Character.isLetterOrDigit(c)) {
                    lines[i] = line.subSequence(0, j);
                    break;
                }
                j += Character.charCount(c);
            }
        }
        return lines;
    }

    /**
     * Returns the single element from the given array. If the given array is null or
     * does not contains exactly one element, then an {@link AssertionError} is thrown.
     *
     * @param  <E> The type of array elements.
     * @param  array The array from which to get the singleton.
     * @return The singleton element from the array.
     */
    public static <E> E getSingleton(final E[] array) {
        assertNotNull("Null array.", array);
        assertEquals("Not a singleton array.", 1, array.length);
        return array[0];
    }

    /**
     * Returns the single element from the given collection. If the given collection is null
     * or does not contains exactly one element, then an {@link AssertionError} is thrown.
     *
     * @param  <E> The type of collection elements.
     * @param  collection The collection from which to get the singleton.
     * @return The singleton element from the collection.
     */
    public static <E> E getSingleton(final Iterable<? extends E> collection) {
        assertNotNull("Null collection.", collection);
        final Iterator<? extends E> it = collection.iterator();
        assertTrue("The collection is empty.", it.hasNext());
        final E element = it.next();
        assertFalse("The collection has more than one element.", it.hasNext());
        return element;
    }

    /**
     * If the given failure is not null, re-thrown it as an {@link Error} or
     * {@link RuntimeException}. Otherwise do nothing.
     *
     * @param failure The exception to re-thrown if non-null.
     */
    public static void rethrownIfNotNull(final Throwable failure) {
        if (failure != null) {
            if (failure instanceof Error) {
                throw (Error) failure;
            }
            if (failure instanceof RuntimeException) {
                throw (RuntimeException) failure;
            }
            throw new UndeclaredThrowableException(failure);
        }
    }

    /**
     * Waits up to one second for the given thread to reach the
     * {@linkplain java.lang.Thread.State#BLOCKED blocked} or the
     * {@linkplain java.lang.Thread.State#WAITING waiting} state.
     *
     * @param  thread The thread to wait for blocked or waiting state.
     * @throws IllegalThreadStateException If the thread has terminated its execution,
     *         or has not reached the waiting or blocked state before the timeout.
     * @throws InterruptedException If this thread has been interrupted while waiting.
     */
    public static void waitForBlockedState(final Thread thread) throws IllegalThreadStateException, InterruptedException {
        int retry = MAXIMAL_WAIT_TIME / 5; // 5 shall be the same number than in the call to Thread.sleep.
        do {
            Thread.sleep(5);
            switch (thread.getState()) {
                case WAITING:
                case BLOCKED: return;
                case TERMINATED: throw new IllegalThreadStateException("The thread has completed execution.");
            }
        } while (--retry != 0);
        throw new IllegalThreadStateException("The thread is not in a blocked or waiting state.");
    }

    /**
     * Waits up to one second for the garbage collector to do its work. This method can be invoked
     * only if {@link TestConfiguration#allowGarbageCollectorDependentTests()} returns {@code true}.
     *
     * <p>Note that this method does not throw any exception if the given condition has not been
     * reached before the timeout. Instead, it is the caller responsibility to test the return
     * value. This method is designed that way because the caller can usually produce a more
     * accurate error message about which value has not been garbage collected as expected.</p>
     *
     * @param  stopCondition A condition which return {@code true} if this method can stop waiting,
     *         or {@code false} if it needs to ask again for garbage collection.
     * @return {@code true} if the given condition has been meet, or {@code false} if we waited up
     *         to the timeout without meeting the given condition.
     * @throws InterruptedException If this thread has been interrupted while waiting.
     */
    public static boolean waitForGarbageCollection(final Callable<Boolean> stopCondition) throws InterruptedException {
        assertTrue("GC-dependent tests not allowed in this run.", TestConfiguration.allowGarbageCollectorDependentTests());
        int retry = MAXIMAL_WAIT_TIME / 50; // 50 shall be the same number than in the call to Thread.sleep.
        boolean stop;
        do {
            if (--retry == 0) {
                return false;
            }
            Thread.sleep(50);
            System.gc();
            try {
                stop = stopCondition.call();
            } catch (RuntimeException e) {
                throw e;
            } catch (Exception e) {
                throw new AssertionError(e);
            }
        } while (!stop);
        return true;
    }
}
TOP

Related Classes of org.apache.sis.test.TestUtilities

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.