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>@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>@Parameterized.Parameters 361 *public static List<Factory[]> 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}