blob: fc8365a8d15d3902db6e0c67bec02dada1ee9843 [file] [log] [blame]
/*
* 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.internal.netcdf;
import java.util.Date;
import java.util.Map;
import java.util.EnumMap;
import java.util.Iterator;
import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.internal.storage.AbstractResource;
import org.apache.sis.internal.netcdf.ucar.DecoderWrapper;
import org.apache.sis.setup.GeometryLibrary;
import org.opengis.test.dataset.TestData;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.NetcdfFile;
import org.junit.AfterClass;
import static org.junit.Assert.*;
/**
* Base class of netCDF tests. The base class uses the UCAR decoder, which is taken as a reference implementation.
* Subclasses testing Apache SIS implementation needs to override the {@link #createDecoder(TestData)}.
*
* <p>This class is <strong>not</strong> thread safe - do not run subclasses in parallel.</p>
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.0
* @since 0.3
* @module
*/
public abstract strictfp class TestCase extends org.apache.sis.test.TestCase {
/**
* The {@code searchPath} argument value to be given to the {@link Decoder#setSearchPath(String[])}
* method when the decoder shall search only in global attributes.
*/
private static final String[] GLOBAL = new String[1];
/**
* The decoders cached by {@link #selectDataset(TestData)}.
* The map is non-empty only during the execution of a single test class.
*
* @see #closeAllDecoders()
*/
private static final Map<TestData,Decoder> DECODERS = new EnumMap<>(TestData.class);
/**
* The decoder to test, which is set by {@link #selectDataset(TestData)}.
* This field must be set before any {@code assert} method is invoked.
*/
private Decoder decoder;
/**
* Creates a new test case.
*/
protected TestCase() {
}
/**
* Creates a netCDF reader from the UCAR library for the specified data set.
* We use the UCAR library as a reference implementation for the tests.
*
* @param file the dataset as one of the {@code NETCDF_*} constants.
* @return the decoder for the specified dataset.
* @throws IOException if an I/O error occurred while opening the file.
*/
protected static NetcdfFile createUCAR(final TestData file) throws IOException {
/*
* Binary netCDF files need to be read either from a file, or from a byte array in memory.
* Reading from a file is not possible if the test file is in geoapi-conformance JAR file.
* But since those test files are less than 15 kilobytes, loading them in memory is okay.
*/
String location = file.location().toString();
location = location.substring(location.lastIndexOf('/') + 1);
return NetcdfFile.openInMemory(location, file.content());
}
/**
* Invoked when a new {@link Decoder} instance needs to be created for the specified dataset.
* The {@code file} parameter can be one of the following values:
*
* <ul>
* <li>{@link TestData#NETCDF_2D_GEOGRAPHIC} — uses a geographic CRS for global data over the world.</li>
* <li>{@link TestData#NETCDF_4D_PROJECTED} — uses a projected CRS with elevation and time.</li>
* </ul>
*
* Default implementation opens the file with UCAR netCDF library and wraps the UCAR object in {@link DecoderWrapper}.
* We proceeded that way because we use UCAR library as the reference implementation.
* Subclasses override this method for testing with Apache SIS implementation.
*
* @param file the dataset as one of the above-cited constants.
* @return the decoder for the specified dataset.
* @throws IOException if an I/O error occurred while opening the file.
* @throws DataStoreException if a logical error occurred.
*/
protected Decoder createDecoder(final TestData file) throws IOException, DataStoreException {
return new DecoderWrapper(new NetcdfDataset(createUCAR(file)), GeometryLibrary.JAVA2D, new AbstractResource(null));
}
/**
* Selects the dataset to use for the tests. If a decoder for the given name has already been
* opened, then this method returns that decoder. Otherwise a new decoder is created by a call
* to {@link #createDecoder(TestData)}, then cached.
*
* <p>The {@linkplain Decoder#setSearchPath(String[]) search path} of the returned decoder
* is initialized to the global attributes only.</p>
*
* @param name the file as one of the constants enumerated in the {@link #createDecoder(TestData)} method.
* @return the decoder for the given name.
* @throws IOException if an I/O error occurred while opening the file.
* @throws DataStoreException if a logical error occurred.
*/
protected final Decoder selectDataset(final TestData name) throws IOException, DataStoreException {
synchronized (DECODERS) { // Paranoiac safety, but should not be used in multi-threads environment.
decoder = DECODERS.get(name);
if (decoder == null) {
decoder = createDecoder(name);
assertNotNull(decoder);
assertNull(DECODERS.put(name, decoder));
}
decoder.setSearchPath(GLOBAL);
return decoder; // Reminder: Decoder instances are not thread-safe.
}
}
/**
* Returns the decoder being tested.
*/
final Decoder decoder() {
return decoder;
}
/**
* Invoked after all tests in a class have been executed.
* This method closes all netCDF files.
*
* @throws IOException if an error occurred while closing a file.
*/
@AfterClass
public static void closeAllDecoders() throws IOException {
Throwable failure = null;
synchronized (DECODERS) { // Paranoiac safety.
final Iterator<Decoder> it = DECODERS.values().iterator();
while (it.hasNext()) {
final Decoder decoder = it.next();
try {
decoder.close();
} catch (Throwable e) {
if (failure == null) {
failure = e;
} else {
failure.addSuppressed(e);
}
}
it.remove();
}
assertTrue(DECODERS.isEmpty());
}
/*
* If we failed to close a file, propagates the error
* only after we have closed all other files.
*/
if (failure != null) {
if (failure instanceof IOException) {
throw (IOException) failure;
}
if (failure instanceof RuntimeException) {
throw (RuntimeException) failure;
}
if (failure instanceof Error) {
throw (Error) failure;
}
throw new UndeclaredThrowableException(failure);
}
}
/**
* Asserts that the textual value of the named attribute is equals to the expected value.
* The {@link #selectDataset(TestData)} method must be invoked at least once before this method.
*
* @param expected the expected attribute value.
* @param attributeName the name of the attribute to test.
* @throws IOException if an error occurred while reading the netCDF file.
*/
protected final void assertAttributeEquals(final String expected, final String attributeName) throws IOException {
assertEquals(attributeName, expected, decoder.stringValue(attributeName));
}
/**
* Asserts that the numeric value of the named attribute is equals to the expected value.
* The {@link #selectDataset(TestData)} method must be invoked at least once before this method.
*
* @param expected the expected attribute value.
* @param attributeName the name of the attribute to test.
* @throws IOException if an error occurred while reading the netCDF file.
*/
protected final void assertAttributeEquals(final Number expected, final String attributeName) throws IOException {
assertEquals(attributeName, expected, decoder.numericValue(attributeName));
}
/**
* Asserts that the temporal value of the named attribute is equals to the expected value.
* The {@link #selectDataset(TestData)} method must be invoked at least once before this method.
*
* @param expected the expected attribute value.
* @param attributeName the name of the attribute to test.
* @throws IOException if an error occurred while reading the netCDF file.
*/
protected final void assertAttributeEquals(final Date expected, final String attributeName) throws IOException {
assertEquals(attributeName, expected, decoder.dateValue(attributeName));
}
}