001/*
002 *    GeoAPI - Java interfaces for OGC/ISO standards
003 *    http://www.geoapi.org
004 *
005 *    Copyright (C) 2008-2019 Open Geospatial Consortium, Inc.
006 *    All Rights Reserved. http://www.opengeospatial.org/ogc/legal
007 *
008 *    Permission to use, copy, and modify this software and its documentation, with
009 *    or without modification, for any purpose and without fee or royalty is hereby
010 *    granted, provided that you include the following on ALL copies of the software
011 *    and documentation or portions thereof, including modifications, that you make:
012 *
013 *    1. The full text of this NOTICE in a location viewable to users of the
014 *       redistributed or derivative work.
015 *    2. Notice of any changes or modifications to the OGC files, including the
016 *       date changes were made.
017 *
018 *    THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE
019 *    NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
020 *    TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT
021 *    THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY
022 *    PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
023 *
024 *    COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR
025 *    CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION.
026 *
027 *    The name and trademarks of copyright holders may NOT be used in advertising or
028 *    publicity pertaining to the software without specific, written prior permission.
029 *    Title to copyright in this software and any associated documentation will at all
030 *    times remain with copyright holders.
031 */
032package org.opengis.test;
033
034import java.util.Map;
035import java.util.HashMap;
036import java.util.List;
037import java.util.Arrays;
038import java.util.ArrayList;
039import java.util.Objects;
040import java.util.ServiceLoader;
041import java.util.ServiceConfigurationError;
042import java.util.logging.Level;
043import java.util.logging.Logger;
044import java.util.logging.LogRecord;
045
046import org.junit.Rule;
047import org.junit.rules.TestWatcher;
048import org.junit.runner.Description;
049
050import org.opengis.util.Factory;
051
052
053/**
054 * Base class of all GeoAPI tests. All concrete subclasses need at least one {@linkplain Factory
055 * factory} for instantiating the objects to test. The factories must be specified at subclasses
056 * construction time either directly by the implementor, or indirectly by calls to the
057 * {@link #factories(Class[])} method.
058 *
059 * @author  Martin Desruisseaux (Geomatys)
060 * @version 3.1
061 * @since   2.2
062 *
063 * @see TestSuite
064 */
065public strictfp abstract class TestCase {
066    /**
067     * An empty array of factories, as a convenience for
068     * {@linkplain #TestCase() no-argument constructors}.
069     */
070    private static final Factory[] NO_FACTORY = new Factory[0];
071
072    /**
073     * The factories specified explicitely by the implementors, or the {@link ServiceLoader}
074     * to use for loading those factories.
075     *
076     * <p>Accesses to this field must be synchronized on itself.</p>
077     *
078     * @see TestSuite#setFactories(Class, Factory[])
079     */
080    static final Map<Class<? extends Factory>, Iterable<? extends Factory>> FACTORIES = new HashMap<>();
081
082    /**
083     * The service loader to use for loading {@link FactoryFilter}.
084     *
085     * <p>Accesses to this field must be synchronized on itself. If both {@code FACTORIES} and
086     * {@code FACTORY_FILTER} are synchronized, then {@code FACTORIES} must be synchronized first.</p>
087     */
088    private static ServiceLoader<FactoryFilter> factoryFilter;
089
090    /**
091     * The service loader to use for loading {@link ImplementationDetails}.
092     *
093     * <p>Accesses to this field must be synchronized on itself. If both {@code FACTORIES}
094     * and {@code IMPLEMENTATION_DETAILS} are synchronized, then {@code FACTORIES} must be
095     * synchronized first.</p>
096     */
097    private static ServiceLoader<ImplementationDetails> implementationDetails;
098
099    /**
100     * The class loader to use for searching implementations, or {@code null} for the default.
101     */
102    private static ClassLoader classLoader;
103
104    /**
105     * Sets the class loader to use for loading implementations. A {@code null} value restores
106     * the default {@linkplain Thread#getContextClassLoader() context class loader}.
107     *
108     * @param loader  the class loader to use, or {@code null} for the default.
109     */
110    static void setClassLoader(final ClassLoader loader) {
111        synchronized (FACTORIES) {
112            if (loader != classLoader) {
113                classLoader = loader;
114                factoryFilter = null;
115                implementationDetails = null;
116            }
117        }
118    }
119
120    /**
121     * Creates a service loader for the given type. This method must be invoked from a block
122     * synchronized on {@link FACTORIES}.
123     */
124    private static <T> ServiceLoader<T> load(final Class<T> service) {
125        return (classLoader == null) ? ServiceLoader.load(service)
126                : ServiceLoader.load(service, classLoader);
127    }
128
129    /**
130     * Returns the current {@link #factoryFilter} instance, creating a new one if needed.
131     */
132    static ServiceLoader<FactoryFilter> getFactoryFilter() {
133        synchronized (FACTORIES) {
134            if (factoryFilter == null) {
135                factoryFilter = load(FactoryFilter.class);
136            }
137            return factoryFilter;
138        }
139    }
140
141    /**
142     * Returns the current {@link #implementationDetails} instance, creating a new one if needed.
143     */
144    static ServiceLoader<ImplementationDetails> getImplementationDetails() {
145        synchronized (FACTORIES) {
146            if (implementationDetails == null) {
147                implementationDetails = load(ImplementationDetails.class);
148            }
149            return implementationDetails;
150        }
151    }
152
153    /**
154     * The test listeners. We intentionally copy the full array every time a listener is
155     * added or removed. We do not clone the array used by the {@link #listener} field,
156     * so it is important that any array instance is never modified after creation.
157     *
158     * @see #addTestListener(TestListener)
159     * @see #removeTestListener(TestListener)
160     * @see #getTestListeners()
161     */
162    private static TestListener[] listeners = new TestListener[0];
163
164    /**
165     * Returns all currently registered test listeners, or an empty array if none.
166     * This method returns directly the internal array, so it is important to never modify it.
167     * This method is for internal usage by the {@link #listener} field only.
168     */
169    @SuppressWarnings("ReturnOfCollectionOrArrayField")
170    static synchronized TestListener[] getTestListeners() {
171        return listeners;
172    }
173
174    /**
175     * A JUnit {@linkplain Rule rule} for listening to test execution events. This rule forwards
176     * events to all {@linkplain TestSuite#addTestListener(TestListener) registered listeners}.
177     *
178     * <p>This field is public because JUnit requires us to do so, but should be considered as
179     * an implementation details (it should have been a private field).</p>
180     *
181     * @since 3.1
182     */
183    @Rule
184    public final TestWatcher listener = new TestWatcher() {
185        /**
186         * A snapshot of the test listeners. We make this snapshot at rule creation time
187         * in order to be sure that the same set of listeners is notified for all phases
188         * of the test method being run.
189         */
190        private final TestListener[] listeners = getTestListeners();
191
192        /**
193         * Invoked when a test is about to start.
194         */
195        @Override
196        protected void starting(final Description description) {
197            final TestEvent event = new TestEvent(TestCase.this, description);
198            for (final TestListener listener : listeners) {
199                listener.starting(event);
200            }
201        }
202
203        /**
204         * Invoked when a test succeeds.
205         */
206        @Override
207        protected void succeeded(final Description description) {
208            final TestEvent event = new TestEvent(TestCase.this, description);
209            for (final TestListener listener : listeners) {
210                listener.succeeded(event);
211            }
212        }
213
214        /**
215         * Invoked when a test fails. If the failure occurred in an optional part of
216         * the set, logs an information message for helping the developer to disable
217         * that test if he wish.
218         */
219        @Override
220        protected void failed(final Throwable exception, final Description description) {
221            final TestEvent event = new TestEvent(TestCase.this, description);
222            final Configuration.Key<Boolean> tip = configurationTip;
223            if (tip != null) {
224                event.configurationTip = tip;
225                final Logger logger = Logger.getLogger("org.opengis.test");
226                final LogRecord record = new LogRecord(Level.INFO, "A test failure occurred while "
227                        + "testing an optional feature. To skip that part of the test, set the '"
228                        + tip.name() + "' boolean field to false or specify that value in the "
229                        + "Configuration map.");
230                record.setLoggerName(logger.getName());
231                record.setSourceClassName(event.getClassName());
232                record.setSourceMethodName(event.getMethodName());
233                logger.log(record);
234            }
235            for (final TestListener listener : listeners) {
236                listener.failed(event, exception);
237            }
238        }
239
240        /**
241         * Invoked when a test method finishes (whether passing or failing)
242         */
243        @Override
244        protected void finished(final Description description) {
245            final TestEvent event = new TestEvent(TestCase.this, description);
246            for (final TestListener listener : listeners) {
247                listener.finished(event);
248            }
249        }
250    };
251
252    /**
253     * The factories used by the test case to execute, or an empty array if none.
254     * This array is given at construction time and is not cloned.
255     */
256    private final Factory[] factories;
257
258    /**
259     * Provider of units of measurement (degree, metre, second, <i>etc</i>), never {@code null}.
260     * The {@link Units#degree()}, {@link Units#metre() metre()} and other methods shall return
261     * {@link javax.measure.Unit} instances compatible with the units created by the {@link Factory}
262     * instances to be tested. Those {@code Unit<?>} instances depend on the Unit of Measurement (JSR-373)
263     * implementation used by the factories.
264     * If no units were {@linkplain org.opengis.test.Configuration.Key#units explicitely specified},
265     * then the {@linkplain Units#getDefault() default units} are used.
266     *
267     * @since 3.1
268     */
269    protected final Units units;
270
271    /**
272     * The set of {@link Validator} instances to use for verifying objects conformance (never {@code null}).
273     * If no validators were {@linkplain org.opengis.test.Configuration.Key#validators explicitely specified},
274     * then the {@linkplain Validators#DEFAULT default validators} are used.
275     *
276     * @since 3.1
277     */
278    protected final ValidatorContainer validators;
279
280    /**
281     * A tip set by subclasses during the execution of some optional tests.
282     * In case of optional test failure, if this field is non-null, then a message will be logged at the
283     * {@link java.util.logging.Level#INFO} for giving some tips to the developer about how he can disable the test.
284     *
285     * <p><b>Example</b></p>
286     * <blockquote><pre>&#64;Test
287     *public void myTest() {
288     *    if (isDerivativeSupported) {
289     *        configurationTip = Configuration.Key.isDerivativeSupported;
290     *        // Do some tests the require support of math transform derivatives.
291     *    }
292     *    configurationTip = null;
293     *}</pre></blockquote>
294     *
295     * @since 3.1
296     */
297    protected transient Configuration.Key<Boolean> configurationTip;
298
299    /**
300     * Creates a new test without factory. This constructor is provided for subclasses
301     * that instantiate their test object directly, without using any factory.
302     */
303    protected TestCase() {
304       this(NO_FACTORY);
305    }
306
307    /**
308     * Creates a new test which will use the given factories to execute.
309     *
310     * @param factories  the factories to be used by the test. Those factories will be given to
311     *        {@link ImplementationDetails#configuration(Factory[])} in order to decide which
312     *        {@linkplain #validators} to use.
313     *
314     * @since 3.1
315     */
316    protected TestCase(final Factory... factories) {
317        Objects.requireNonNull(factories, "Given 'factories' array can not be null.");
318        this.factories = factories;
319        Units units = null;
320        ValidatorContainer validators = null;
321        final ServiceLoader<ImplementationDetails> services = getImplementationDetails();
322        synchronized (services) {
323            for (final ImplementationDetails impl : services) {
324                final Configuration config = impl.configuration(factories);
325                if (config != null) {
326                    if (units == null) {
327                        units = config.get(Configuration.Key.units);
328                    }
329                    if (validators == null) {
330                        validators = config.get(Configuration.Key.validators);
331                    }
332                    if (units != null && validators != null) {
333                        break;          // We got all information will we looking for, no need to continue.
334                    }
335                }
336            }
337        }
338        if (units == null) {
339            units = Units.getDefault();
340        }
341        if (validators == null) {
342            Objects.requireNonNull(validators = Validators.DEFAULT, "Validators.DEFAULT shall not be null.");
343        }
344        this.units = units;
345        this.validators = validators;
346    }
347
348    /**
349     * Returns factory instances for given factory interfaces. Each element in the returned list
350     * is the arguments to give to the subclass constructor. There is typically only one element
351     * in the list, but more elements could be included if many factory implementations are found
352     * for the same interface.
353     *
354     * <p>This method is used by static methods having the {@link org.junit.runners.Parameterized.Parameters}
355     * annotation in subclasses. For example if a subclass constructor expects 3 factories of kind
356     * {@link org.opengis.referencing.crs.CRSFactory}, {@link org.opengis.referencing.cs.CSFactory}
357     * and {@link org.opengis.referencing.datum.DatumFactory} in that order, then that subclass
358     * contains the following method:</p>
359     *
360     * <blockquote><pre>&#64;Parameterized.Parameters
361     *public static List&lt;Factory[]&gt; factories() {
362     *    return factories(CRSFactory.class, CSFactory.class, DatumFactory.class);
363     *}</pre></blockquote>
364     *
365     * Note that the arrays may contain null elements if no factory implementation were found
366     * for a given interface. All GeoAPI test cases use {@link org.junit.Assume} checks in order
367     * to disable any tests that require a missing factory.
368     *
369     * <p>If many factory implementations were found for a given interface, then this method
370     * returns all possible combinations of those factories. For example if two instances
371     * of interface {@code A} are found (named {@code A1} and {@code A2}), and two instances
372     * of interface {@code B} are also found (named {@code B1} and {@code B2}), then this
373     * method returns a list containing:</p>
374     *
375     * <blockquote><pre>{A1, B1}
376     *{A2, B1}
377     *{A1, B2}
378     *{A2, B2}</pre></blockquote>
379     *
380     * The current implementation first checks the factories explicitely specified by calls to the
381     * {@link TestSuite#setFactories(Class, Factory[])} method. In no factories were explicitely
382     * specified, then this method searches the classpath using {@link ServiceLoader}.
383     *
384     * @param  types  the kind of factories to fetch.
385     * @return all combinations of factories of the given kind. Each list element is an array
386     *         having the same length than {@code types}.
387     *
388     * @see org.opengis.test.util.NameTest#factories()
389     * @see org.opengis.test.referencing.ObjectFactoryTest#factories()
390     * @see org.opengis.test.referencing.AuthorityFactoryTest#factories()
391     * @see org.opengis.test.referencing.AffineTransformTest#factories()
392     * @see org.opengis.test.referencing.ParameterizedTransformTest#factories()
393     *
394     * @since 3.1
395     */
396    @SafeVarargs
397    protected static List<Factory[]> factories(final Class<? extends Factory>... types) {
398        return factories(null, types);
399    }
400
401    /**
402     * Returns factory instances for given factory interfaces, excluding the factories filtered
403     * by the given filter. This method performs the same work than {@link #factories(Class[])}
404     * except that the given filter is applied in addition to any filter found on the classpath.
405     *
406     * <p>The main purpose of this method is to get {@link org.opengis.referencing.AuthorityFactory}
407     * instances for a given authority name.</p>
408     *
409     * @param  filter  an optional factory filter to use in addition to any filter declared in
410     *                 the classpath, or {@code null} if none.
411     * @param  types   the kind of factories to fetch.
412     * @return all combinations of factories of the given kind. Each list element is an array
413     *         having the same length than {@code types}.
414     *
415     * @since 3.1
416     */
417    @SafeVarargs
418    protected static List<Factory[]> factories(final FactoryFilter filter, final Class<? extends Factory>... types) {
419        final List<Factory[]> factories = new ArrayList<>(4);
420        try {
421            synchronized (FACTORIES) {
422                if (!factories(filter, types, factories)) {
423                    // The user has invoked TestSuite.setFactories(…), for example inside
424                    // his FactoryFilter.filter(…) method. Let be lenient and try again.
425                    // If the second try fails for the same raison, we will give up.
426                    factories.clear();
427                    if (!factories(filter, types, factories)) {
428                        throw new ServiceConfigurationError("TestSuite.setFactories(…) has been invoked "
429                                + "in the middle of a search for factories.");
430                    }
431                }
432            }
433        } catch (ServiceConfigurationError e) {
434            // JUnit 4.10 eats the exception silently, so we need to log
435            // it in order to allow users to figure out what is going.
436            Logger.getLogger("org.opengis.test").log(Level.WARNING, e.toString(), e);
437            throw e;                                          // To be caught by JUnit.
438        }
439        return factories;
440    }
441
442    /**
443     * Implementation of the above {@code factories} method. The factories are added to
444     * the given list. This method returns {@code true} on success, or {@code false} if
445     * we detected that {@link TestSuite#setFactories(Class, T[])} has been invoked by
446     * some user method while we were iterating. This check is done in an opportunist;
447     * it is not fully reliable.
448     */
449    private static boolean factories(final FactoryFilter filter,
450            final Class<? extends Factory>[] types, final List<Factory[]> factories)
451    {
452        factories.add(new Factory[types.length]);
453        for (int i=0; i<types.length; i++) {
454            final Class<? extends Factory> type = types[i];
455            Iterable<? extends Factory> choices = FACTORIES.get(type);
456            if (choices == null) {
457                choices = load(type);
458                final Iterable<? extends Factory> old = FACTORIES.put(type, choices);
459                if (old != null) {
460                    // TestSuite.setFactories(…) has been invoked,  maybe as a result of user
461                    // class initialization. Restores the user-provided value and declares that
462                    // this operation failed.
463                    FACTORIES.put(type, old);
464                    return false;
465                }
466            }
467            List<Factory[]> toUpdate = factories;
468            for (final Factory factory : choices) {
469                if (filter(type, factory, filter)) {
470                    if (toUpdate == factories) {
471                        toUpdate = Arrays.asList(factories.toArray(new Factory[factories.size()][]));
472                    } else {
473                        for (int j=toUpdate.size(); --j>=0;) {
474                            toUpdate.set(j, toUpdate.get(j).clone());
475                        }
476                        factories.addAll(toUpdate);
477                    }
478                    for (final Factory[] previous : toUpdate) {
479                        previous[i] = factory;
480                    }
481                }
482            }
483            // Check if TestSuite.setFactories(…) has been invoked while we were iterating.
484            // The method may have been invoked by a FactoryFilter.filter(…) method for
485            // example. While not an encouraged practice, we try to be a little bit more
486            // robust than not checking at all.
487            if (FACTORIES.get(type) != choices) {
488                return false;
489            }
490        }
491        return true;
492    }
493
494    /**
495     * Returns {@code true} if the given factory can be tested. This method iterates over all
496     * registered {@link FactoryFilter} and ensures that all of them accept the given factory.
497     */
498    private static <T extends Factory> boolean filter(final Class<T> category, final Factory factory, final FactoryFilter filter) {
499        final T checked = category.cast(factory);
500        if (filter != null && !filter.filter(category, checked)) {
501            return false;
502        }
503        final ServiceLoader<FactoryFilter> services = getFactoryFilter();
504        synchronized (services) {
505            for (final FactoryFilter impl : services) {
506                if (!impl.filter(category, checked)) {
507                    return false;
508                }
509            }
510        }
511        return true;
512    }
513
514    /**
515     * Returns booleans indicating whether the given operations are enabled. By default, every
516     * operations are enabled. However if any {@link ImplementationDetails} instance found on the
517     * classpath returns a {@linkplain ImplementationDetails#configuration configuration} map
518     * having the value {@link Boolean#FALSE} for a given key, then the boolean value corresponding
519     * to that key is set to {@code false}.
520     *
521     * @param  properties  the key for which the flags are wanted.
522     * @return an array of the same length than {@code properties} in which each element at index
523     *         <var>i</var> indicates whether the {@code properties[i]} test should be enabled.
524     *
525     * @since 3.1
526     */
527    @SafeVarargs
528    protected final boolean[] getEnabledFlags(final Configuration.Key<Boolean>... properties) {
529        final boolean[] isEnabled = new boolean[properties.length];
530        Arrays.fill(isEnabled, true);
531        final ServiceLoader<ImplementationDetails> services = getImplementationDetails();
532        synchronized (services) {
533            for (final ImplementationDetails impl : services) {
534                final Configuration config = impl.configuration(factories);
535                if (config != null) {
536                    boolean atLeastOneTestIsEnabled = false;
537                    for (int i=0; i<properties.length; i++) {
538                        if (isEnabled[i]) {
539                            final Boolean value = config.get(properties[i]);
540                            if (value != null && !(isEnabled[i] = value)) {
541                                continue;                       // Leave 'atLeastOneTestIsEnabled' unchanged.
542                            }
543                            atLeastOneTestIsEnabled = true;
544                        }
545                    }
546                    if (!atLeastOneTestIsEnabled) {
547                        break;                                  // No need to continue scanning the classpath.
548                    }
549                }
550            }
551        }
552        return isEnabled;
553    }
554
555    /**
556     * Returns information about the configuration of the test which has been run.
557     * The content of this map depends on the {@code TestCase} subclass and on the
558     * values returned by the {@link ImplementationDetails#configuration(Factory[])}
559     * method for the factories being tested. For a description of the map content,
560     * see any of the following subclasses:
561     *
562     * <ul>
563     *   <li>{@link org.opengis.test.referencing.AffineTransformTest#configuration()}</li>
564     *   <li>{@link org.opengis.test.referencing.ParameterizedTransformTest#configuration()}</li>
565     *   <li>{@link org.opengis.test.referencing.AuthorityFactoryTest#configuration()}</li>
566     *   <li>{@link org.opengis.test.referencing.gigs.AuthorityFactoryTestCase#configuration()}</li>
567     * </ul>
568     *
569     * @return the configuration of the test being run, or an empty map if none.
570     *         This method returns a modifiable map in order to allow subclasses to modify it.
571     *
572     * @see ImplementationDetails#configuration(Factory[])
573     *
574     * @since 3.1
575     */
576    public Configuration configuration() {
577        final Configuration configuration = new Configuration();
578        configuration.put(Configuration.Key.units,      units);
579        configuration.put(Configuration.Key.validators, validators);
580        return configuration;
581    }
582
583    /**
584     * Implementation of the {@link TestSuite#addTestListener(TestListener)} public method.
585     *
586     * @param listener  the listener to add. {@code null} values are silently ignored.
587     *
588     * @deprecated To be replaced by JUnit 5 listener mechanism.
589     */
590    @Deprecated
591    static synchronized void addTestListener(final TestListener listener) {
592        if (listener != null) {
593            final int length = listeners.length;
594            listeners = Arrays.copyOf(listeners, length + 1);
595            listeners[length] = listener;
596        }
597    }
598
599    /**
600     * Implementation of the {@link TestSuite#removeTestListener(TestListener)} public method.
601     *
602     * @param listener  the listener to remove. {@code null} values are silently ignored.
603     *
604     * @deprecated To be replaced by JUnit 5 listener mechanism.
605     */
606    @Deprecated
607    static synchronized void removeTestListener(final TestListener listener) {
608        for (int i=listeners.length; --i>=0;) {
609            if (listeners[i] == listener) {
610                final int length = listeners.length - 1;
611                System.arraycopy(listeners, i, listeners, i+1, length-i);
612                listeners = Arrays.copyOf(listeners, length);
613                break;
614            }
615        }
616    }
617}