diff --git a/pom.xml b/pom.xml index d07ee66..7dd76ec 100644 --- a/pom.xml +++ b/pom.xml @@ -101,6 +101,13 @@ 1.10.17 test + + org.esa.snap + snap-runtime + ${snap.test.version} + test-jar + test + @@ -220,6 +227,19 @@ test test-jar + + org.esa.snap + snap-ndvi + ${project.version} + test + + + org.esa.snap + snap-ndvi + ${project.version} + test + test-jar + @@ -523,6 +543,19 @@ test test-jar + + org.esa.s2tbx + s2tbx-radiometric-indices + ${s2tbx.test.version} + test + + + org.esa.s2tbx + s2tbx-radiometric-indices + ${s2tbx.test.version} + test + test-jar + diff --git a/src/test/java/org/esa/snap/dataio/ProductReaderAcceptanceTest.java b/src/test/java/org/esa/snap/dataio/ProductReaderAcceptanceTest.java index 4d4aa34..4e9f3bb 100644 --- a/src/test/java/org/esa/snap/dataio/ProductReaderAcceptanceTest.java +++ b/src/test/java/org/esa/snap/dataio/ProductReaderAcceptanceTest.java @@ -72,7 +72,7 @@ public class ProductReaderAcceptanceTest { private static final String PROPERTYNAME_FAIL_ON_MISSING_DATA = "snap.reader.tests.failOnMissingData"; private static final String PROPERTYNAME_FAIL_ON_INTENDED = "snap.reader.tests.failOnMultipleIntendedReaders"; private static final String PROPERTYNAME_LOG_FILE_PATH = "snap.reader.tests.log.file"; - private static final String PROPERTYNAME_CASS_NAME = "snap.reader.tests.class.name"; + private static final String PROPERTYNAME_CLASS_NAME = "snap.reader.tests.class.name"; private static final boolean FAIL_ON_MISSING_DATA = Boolean.parseBoolean(System.getProperty(PROPERTYNAME_FAIL_ON_MISSING_DATA, "true")); private static final String INDENT = "\t"; private static final ProductList testProductList = new ProductList(); @@ -471,7 +471,7 @@ private static void loadProductReaderTestDefinitions() throws IOException { final Iterable readerPlugIns = SystemUtils.loadServices(ProductReaderPlugIn.class); testDefinitionList = new TestDefinitionList(); - final String className = System.getProperty(PROPERTYNAME_CASS_NAME); + final String className = System.getProperty(PROPERTYNAME_CLASS_NAME); for (ProductReaderPlugIn readerPlugIn : readerPlugIns) { final Class readerPlugInClass = readerPlugIn.getClass(); diff --git a/src/test/java/org/esa/snap/dataio/RunProductReaderTest.java b/src/test/java/org/esa/snap/dataio/RunProductReaderTest.java new file mode 100644 index 0000000..18eab8c --- /dev/null +++ b/src/test/java/org/esa/snap/dataio/RunProductReaderTest.java @@ -0,0 +1,305 @@ +package org.esa.snap.dataio; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.esa.s2tbx.dataio.gdal.GDALLibraryInstaller; +import org.esa.snap.core.dataio.ProductReader; +import org.esa.snap.core.dataio.ProductReaderPlugIn; +import org.esa.snap.core.datamodel.Product; +import org.esa.snap.core.util.SystemUtils; +import org.esa.snap.dataio.netcdf.NetCdfActivator; +import org.esa.snap.jp2.reader.OpenJPEGLibraryInstaller; +import org.esa.snap.runtime.LogUtils4Tests; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.rules.ErrorCollector; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.junit.Assert.fail; + +public class RunProductReaderTest { + + private static final Logger logger = Logger.getLogger(RunProductReaderTest.class.getName()); + + private static final String PROPERTY_NAME_FAIL_ON_MISSING_DATA = "snap.reader.tests.failOnMissingData"; + private static final String PROPERTY_NAME_DATA_DIR = "snap.reader.tests.data.dir"; + private static final String PROPERTY_NAME_CASS_NAME = "snap.reader.tests.class.name"; + private static final String PROPERTY_NAME_PRODUCT_IDS = "snap.reader.tests.product.ids"; + + private static List testDefinitionList; + private static File dataRootDir; + private static boolean isFailOnMissingData; + + private ErrorCollector errorCollector = new ErrorCollector(); + + public RunProductReaderTest() { + } + + @BeforeClass + public static void initialize() throws Exception { + LogUtils4Tests.initLogger(); + + String dataDirPropertyValue = System.getProperty(PROPERTY_NAME_DATA_DIR); + if (dataDirPropertyValue == null) { + fail("The test data property '"+PROPERTY_NAME_DATA_DIR+"' representing the folder which contains the source products is not set."); + } + + String readerPlugInClassNameToTest = System.getProperty(PROPERTY_NAME_CASS_NAME); + String productIdsToTest = System.getProperty(PROPERTY_NAME_PRODUCT_IDS); + String failOnMissingData = System.getProperty(PROPERTY_NAME_FAIL_ON_MISSING_DATA); + + if (logger.isLoggable(Level.FINE)) { + StringBuilder message = new StringBuilder(); + message.append("Input test parameters: data folder path: ") + .append(dataDirPropertyValue) + .append(", reader plugin class: ") + .append(readerPlugInClassNameToTest) + .append(", product ids: ") + .append(productIdsToTest) + .append(", fail on missing data: ") + .append(failOnMissingData) + .append("."); + logger.log(Level.FINE, message.toString()); + } + + dataRootDir = new File(dataDirPropertyValue); + if (!dataRootDir.exists()) { + fail("The path '" + dataDirPropertyValue+"' representing the folder which contains the source products does not exist."); + } + if (!dataRootDir.isDirectory()) { + fail("The path '" + dataDirPropertyValue+"' exists and it does not denote a folder."); + } + + isFailOnMissingData = true; + if (failOnMissingData != null) { + isFailOnMissingData = Boolean.parseBoolean(failOnMissingData); + } + + SystemUtils.init3rdPartyLibs(RunProductReaderTest.class); + + if (!isFailOnMissingData) { + logger.warning("Tests will not fail if test data is missing!"); + } + + testDefinitionList = loadProductReaderTestDefinitions(readerPlugInClassNameToTest, productIdsToTest); + + OpenJPEGLibraryInstaller.install(); + GDALLibraryInstaller.install(); + NetCdfActivator.activate(); + } + + /** + * The required parameters to run the method: + * -Dsnap.reader.tests.data.dir=\\CV-DEV-SRV01\Satellite_Imagery\TestingJUnitFiles + * + * Optional parameters: + * -Dsnap.reader.tests.class.name=org.esa.s2tbx.dataio.muscate.MuscateProductReaderPlugin + * -Dsnap.reader.tests.product.ids=MUSCATE-Zip-31TFK,MUSCATE-v18,MUSCATE-v20 + * -Dsnap.reader.tests.failOnMissingData=false + * + * @throws Exception + */ + @Test + public void testReadIntendedProductContent() throws IOException { + logger.info("Test product content for " + testDefinitionList.size() + " reader plugins."); + + for (int k=0; k 0) { + logger.info(""); // write an empty line + } + final List intendedProductIds = testDefinition.getDecodableProductIds(); + String readerPluginClassName = testDefinition.getProductReaderPlugin().getClass().getSimpleName(); + + logger.info("Test products content: reader plugin class: " + readerPluginClassName+ ", product count: " + intendedProductIds.size()+"."); + + for (String productId : intendedProductIds) { + TestProduct foundTestProduct = null; + for (TestProduct testProduct : testDefinition.getAllProducts()) { + if (testProduct.getId().equalsIgnoreCase(productId)) { + foundTestProduct = testProduct; + break; + } + } + if (foundTestProduct == null) { + String message = "The test product with id '"+ productId+"' does not exist."; + if (isFailOnMissingData) { + fail(message); + } else { + logger.info(message); + } + } else if (foundTestProduct.exists()) { + logger.info("Start testing the product content: reader plugin class: " + readerPluginClassName+ ", product id: " + foundTestProduct.getId()+", product relative path: " + foundTestProduct.getRelativePath()+"."); + + long startTime = System.currentTimeMillis(); + + final File testProductFile = new File(dataRootDir, foundTestProduct.getRelativePath()); + + long startTimeReadProduct = System.currentTimeMillis(); + + final ProductReader productReader = testDefinition.getProductReaderPlugin().createReaderInstance(); + final Product product = productReader.readProductNodes(testProductFile, null); + if (product == null) { + String message = "The product can not be read from file '" + testProductFile.getAbsolutePath()+"' does not exist."; + if (isFailOnMissingData) { + fail(message); + } else { + logger.log(Level.SEVERE, message); + } + } else { + try { + if (logger.isLoggable(Level.FINE)) { + double elapsedSecondsReadProduct = (System.currentTimeMillis() - startTimeReadProduct) / 1000.0d; + StringBuilder message = new StringBuilder(); + message.append("Finish reading the product id: ") + .append(foundTestProduct.getId()) + .append(", relative path: ") + .append(foundTestProduct.getRelativePath()) + .append(", reader class: ") + .append(productReader.getClass().getName()) + .append(", size: ") + .append(product.getSceneRasterWidth()) + .append("x") + .append(product.getSceneRasterHeight()) + .append(", elapsed time: ") + .append(elapsedSecondsReadProduct) + .append(" seconds."); + logger.log(Level.FINE, message.toString()); + } + + assertExpectedContent(testDefinition, foundTestProduct, product); + } catch (Throwable t) { + errorCollector.addError(new Throwable("[" + productId + "] " + t.getMessage(), t)); + } finally { + product.dispose(); + } + + if (logger.isLoggable(Level.FINE)) { + double elapsedSeconds = (System.currentTimeMillis() - startTime) / 1000.0d; + StringBuilder message = new StringBuilder(); + message.append("Finish testing the product content id: ") + .append(foundTestProduct.getId()) + .append(", relative path: ") + .append(foundTestProduct.getRelativePath()) + .append(", elapsed time: ") + .append(elapsedSeconds) + .append(" seconds."); + logger.log(Level.FINE, message.toString()); + } + } + } else { + logger.info("The test product with id '"+ foundTestProduct.getId()+"' is missing from the disc."); + } + } + } + } + + private static void assertExpectedContent(TestDefinition testDefinition, TestProduct testProduct, Product product) { + final ExpectedContent expectedContent = testDefinition.getExpectedContent(testProduct.getId()); + if (expectedContent == null) { + String message = "No expected content for product id '" + testProduct.getId()+"'."; + if (isFailOnMissingData) { + fail(message); + } else { + logger.log(Level.SEVERE, message); + } + } else { + long startTime = System.currentTimeMillis(); + + final ContentAssert contentAssert = new ContentAssert(expectedContent, testProduct.getId(), product); + contentAssert.assertProductContent(); + + if (logger.isLoggable(Level.FINE)) { + double elapsedSeconds = (System.currentTimeMillis() - startTime) / 1000.0d; + StringBuilder message = new StringBuilder(); + message.append("Finish testing expected content: product id: ") + .append(testProduct.getId()) + .append(", relative path: ") + .append(testProduct.getRelativePath()) + .append(", elapsed time: ") + .append(elapsedSeconds) + .append(" seconds."); + logger.log(Level.FINE, message.toString()); + } + } + } + + private static List loadProductReaderTestDefinitions(String readerPlugInClassNameToTest, String productIdsToTest) throws IOException { + final ObjectMapper mapper = new ObjectMapper(); + final Iterable readerPlugIns = SystemUtils.loadServices(ProductReaderPlugIn.class); + + String[] productIds = null; + if (productIdsToTest != null) { + productIds = productIdsToTest.split(","); + if (productIds.length == 0) { + throw new IllegalArgumentException("The product ids array is empty."); + } else { + for (int k=0; k testDefinitionList = new ArrayList<>(); + for (ProductReaderPlugIn readerPlugIn : readerPlugIns) { + final Class readerPlugInClass = readerPlugIn.getClass(); + if (readerPlugInClassNameToTest != null && !readerPlugInClass.getName().startsWith(readerPlugInClassNameToTest)) { + continue; + } + + final String path = readerPlugInClass.getName().replace(".", "/"); + final String dataResourceName = "/" + path + "-data.json"; + final URL dataResource = readerPlugInClass.getResource(dataResourceName); + if (dataResource == null) { + logger.warning("The reader plugin class '"+readerPlugInClass.getSimpleName() + "' does not define test data."); + continue; + } + + final ProductList productList = mapper.readValue(dataResource, ProductList.class); + + final TestDefinition testDefinition = new TestDefinition(); + testDefinition.setProductReaderPlugin(readerPlugIn); + for (TestProduct testProduct : productList.getAll()) { + boolean canTestProduct; + if (productIds == null) { + canTestProduct = true; + } else { + canTestProduct = false; + for (int k=0; k gptGraphParameters) throws Exception { @@ -327,34 +358,34 @@ public void write(int b) { } } - private static void assertExpectedContent(Product product, ExpectedContent expectedContent, String productId) throws IOException { - ContentAssert contentAssert = new ContentAssert(expectedContent, productId, product); - contentAssert.assertProductContent(); - } + private static void assertExpectedContent(File testFile, InputGraphData inputGraphData, String outputNameValue, Product product, ExpectedContent expectedContent) { + StringBuilder assertMessagePrefix = new StringBuilder(); + assertMessagePrefix.append("Test file '") + .append(testFile.getName()) + .append("', id '") + .append(inputGraphData.getId()) + .append("', output name '") + .append(outputNameValue) + .append("': "); - private static void initLogger() throws Exception { - // Suppress ugly (and harmless) JAI error messages saying that a JAI is going to continue in pure Java mode. - System.setProperty("com.sun.media.jai.disableMediaLib", "true"); // disable native libraries for JAI - - logger = Logger.getLogger(ProductReaderAcceptanceTest.class.getSimpleName()); - removeRootLogHandler(); - ConsoleHandler consoleHandler = new ConsoleHandler(); - consoleHandler.setFormatter(new CustomLogFormatter()); - logger.addHandler(consoleHandler); - String logFilePath = System.getProperty(PROPERTY_NAME_LOG_FILE_PATH); - if (logFilePath != null) { - File logFile = new File(logFilePath); - FileOutputStream fos = new FileOutputStream(logFile); - StreamHandler streamHandler = new StreamHandler(fos, new CustomLogFormatter()); - logger.addHandler(streamHandler); - } - } + long startTime = System.currentTimeMillis(); + + ContentAssert contentAssert = new ContentAssert(expectedContent, assertMessagePrefix.toString(), product); + contentAssert.assertProductContent(); - private static void removeRootLogHandler() { - Logger rootLogger = LogManager.getLogManager().getLogger(""); - Handler[] handlers = rootLogger.getHandlers(); - for (Handler handler : handlers) { - rootLogger.removeHandler(handler); + if (logger.isLoggable(Level.FINE)) { + double elapsedSeconds = (System.currentTimeMillis() - startTime) / 1000.0d; + StringBuilder message = new StringBuilder(); + message.append("Finish testing expected content: file: ") + .append(testFile.getName()) + .append(", id: ") + .append(inputGraphData.getId()) + .append(", output name: ") + .append(outputNameValue) + .append(", elapsed time: ") + .append(elapsedSeconds) + .append(" seconds."); + logger.log(Level.FINE, message.toString()); } } @@ -378,29 +409,6 @@ private static void validateFileOnDisk(File fileToValidate) throws FileNotFoundE } } - private static class CustomLogFormatter extends Formatter { - - private final static String LINE_SEPARATOR = System.getProperty("line.separator", "\r\n"); - - @Override - public synchronized String format(LogRecord record) { - StringBuilder sb = new StringBuilder(); - String message = formatMessage(record); - sb.append(record.getLevel().getName()); - sb.append(": "); - sb.append(message); - sb.append(LINE_SEPARATOR); - if (record.getThrown() != null) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - record.getThrown().printStackTrace(pw); - pw.close(); - sb.append(sw.toString()); - } - return sb.toString(); - } - } - private static class InputGraphData { private String id;