Subject: [PATCH] #21931: Add some basic tests
---
Index: test/unit/org/openstreetmap/josm/io/importexport/FileExporterTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/io/importexport/FileExporterTest.java b/test/unit/org/openstreetmap/josm/io/importexport/FileExporterTest.java
new file mode 100644
--- /dev/null	(date 1675979865307)
+++ b/test/unit/org/openstreetmap/josm/io/importexport/FileExporterTest.java	(date 1675979865307)
@@ -0,0 +1,193 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.importexport;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.io.importexport.FileExporter;
+import org.openstreetmap.josm.gui.io.importexport.FileImporter;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.LayerManager;
+import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.io.IllegalDataException;
+import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
+
+/**
+ * A test class for file exporters
+ * @param <E> The file exporter class
+ * @param <I> The <i>original</i> importer class
+ * @param <J> The importer to class to use <i>after</i> we have exported the original data
+ */
+@BasicPreferences
+public interface FileExporterTest<E extends FileExporter, I extends FileImporter, J extends FileImporter> {
+    /**
+     * Get the exporter instance for tests
+     * @return The exporter to test
+     */
+    E getExporter();
+
+    /**
+     * Get a file extension for the exporter
+     * @return The expected file extension
+     */
+    String getExtension();
+
+    /**
+     * Get the original importer instance for tests
+     * @return The importer to use
+     */
+    I getOriginalImporter();
+
+    /**
+     * Get the importer instance for verifying that the data was written correctly
+     */
+    J getSavedDataImporter();
+
+    /**
+     * Verify that the importers have generated the same data
+     */
+    default void verifyData(File original, File saved) throws IOException, IllegalDataException {
+        assertTrue(MainApplication.getLayerManager().getLayers().isEmpty());
+        final LayerManager layerManager = MainApplication.getLayerManager();
+        I originalImporter = getOriginalImporter();
+        assertTrue(originalImporter.acceptFile(original));
+        if (originalImporter.isBatchImporter()) {
+            originalImporter.importData(Collections.singletonList(original), NullProgressMonitor.INSTANCE);
+        } else {
+            originalImporter.importData(original, NullProgressMonitor.INSTANCE);
+        }
+        syncThreads();
+        final List<Layer> originalLayers = new ArrayList<>(layerManager.getLayers());
+        originalLayers.forEach(layerManager::removeLayer);
+        assertFalse(originalLayers.isEmpty());
+
+        assertTrue(MainApplication.getLayerManager().getLayers().isEmpty());
+        J savedImporter = getSavedDataImporter();
+        assertTrue(savedImporter.acceptFile(saved));
+        if (savedImporter.isBatchImporter()) {
+            savedImporter.importData(Collections.singletonList(saved), NullProgressMonitor.INSTANCE);
+        } else {
+            savedImporter.importData(saved, NullProgressMonitor.INSTANCE);
+        }
+        syncThreads();
+        final List<Layer> savedLayers = new ArrayList<>(layerManager.getLayers());
+        savedLayers.forEach(layerManager::removeLayer);
+        assertFalse(savedLayers.isEmpty());
+
+        verifyLayers(originalLayers, savedLayers);
+    }
+
+    /**
+     * Verify that two layer lists are equal
+     * @param original The original layers (from the original data)
+     * @param saved The saved layers (from the exporter)
+     */
+    void verifyLayers(List<Layer> original, List<Layer> saved);
+
+    /**
+     * Load data that the exporter can save
+     * @return The data to save via the exporter
+     */
+    File goodData();
+
+    /**
+     * Data that the exporter should not save
+     * @return The data to try and save via the exporter. Will be loaded via an importer.
+     */
+    File badData();
+
+    /**
+     * Check that the exporter enablement methods work properly
+     */
+    @Test
+    default void testEnabled() {
+        E exporter = getExporter();
+        assertTrue(exporter.isEnabled(), "Exporters default to enabled");
+        exporter.setEnabled(false);
+        assertFalse(exporter.isEnabled(), "Exporters should be able to be enabled/disabled");
+        exporter.setEnabled(true);
+        assertTrue(exporter.isEnabled(), "Exporters should be able to be enabled/disabled");
+    }
+
+    /**
+     * Check that the exporter (at least) sets that it is cancelled
+     */
+    @Test
+    default void testCancelled() {
+        E exporter = getExporter();
+        assertFalse(exporter.isCanceled());
+        exporter.setCanceled(true);
+        assertTrue(exporter.isCanceled());
+    }
+
+    /**
+     * Test whether or not a file is accepted
+     */
+    @Test
+    default void testAcceptFileGood() {
+        assertTrue(getOriginalImporter().acceptFile(goodData()));
+        assertTrue(getOriginalImporter().acceptFile(badData()));
+    }
+
+    /**
+     * Test a file that probably shouldn't be exported to
+     */
+    @Test
+    default void testAcceptFileBad() {
+        E exporter = getExporter();
+        assertFalse(exporter.acceptFile(new File("foo.bazzzz"), null));
+    }
+
+    /**
+     * Test quietly exporting data
+     * @param directory The directory in which we are exporting data
+     * @throws IOException If we cannot read/write files
+     * @throws IllegalDataException If the data was not good
+     */
+    @Test
+    default void testExportDataQuiet(@TempDir Path directory) throws IOException, IllegalDataException {
+        final E exporter = getExporter();
+        final LayerManager layerManager = MainApplication.getLayerManager();
+        getOriginalImporter().importData(goodData(), NullProgressMonitor.INSTANCE);
+        syncThreads();
+        final File testFile = directory.resolve("exportDataQuiet." + getExtension()).toFile();
+        List<Layer> exportableLayers = layerManager.getLayers()
+                .stream().filter(layer -> exporter.acceptFile(testFile, layer)).collect(Collectors.toList());
+        assertEquals(1, exportableLayers.size());
+        exporter.exportDataQuiet(testFile, exportableLayers.get(0));
+        syncThreads();
+        for (Layer layer : new ArrayList<>(layerManager.getLayers())) {
+            layerManager.removeLayer(layer);
+        }
+        verifyData(goodData(), testFile);
+    }
+
+    /**
+     * Test overwrite behavior
+     * @param directory The root directory
+     */
+    @Test
+    void testExportDataOverwrite(@TempDir Path directory);
+
+    /**
+     * Force a thread sync
+     */
+    default void syncThreads() {
+        GuiHelper.runInEDTAndWait(() -> { /* Sync thread */ });
+        assertDoesNotThrow(() -> MainApplication.worker.submit(() -> { /* Sync thread */ }).get());
+    }
+}
Index: test/unit/org/openstreetmap/josm/io/importexport/GpxExporterTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/io/importexport/GpxExporterTest.java b/test/unit/org/openstreetmap/josm/io/importexport/GpxExporterTest.java
new file mode 100644
--- /dev/null	(date 1675980311960)
+++ b/test/unit/org/openstreetmap/josm/io/importexport/GpxExporterTest.java	(date 1675980311960)
@@ -0,0 +1,67 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.importexport;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import junit.framework.AssertionFailedError;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.openstreetmap.josm.TestUtils;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.io.importexport.FileImporter;
+import org.openstreetmap.josm.gui.io.importexport.GpxExporter;
+import org.openstreetmap.josm.gui.io.importexport.GpxImporter;
+import org.openstreetmap.josm.testutils.mockers.ExtendedDialogMocker;
+
+/**
+ * An interface for various import -&gt; export -&gt; import tests
+ */
+public interface GpxExporterTest<I extends FileImporter> extends FileExporterTest<GpxExporter, I, GpxImporter> {
+    @Override
+    default GpxExporter getExporter() {
+        return new GpxExporter();
+    }
+
+    @Override
+    default GpxImporter getSavedDataImporter() {
+        return new GpxImporter();
+    }
+
+    @Override
+    default String getExtension() {
+        return "gpx";
+    }
+
+    @Test
+    @Override
+    default void testExportDataOverwrite(@TempDir Path directory) {
+        TestUtils.assumeWorkingJMockit();
+        // For reference, save == 1, save_as == 2, and cancel == 3
+        final AtomicInteger returnValue = new AtomicInteger(1); // Save
+        new ExtendedDialogMocker() {
+            @Override
+            protected int getMockResult(ExtendedDialog instance) {
+                return returnValue.get();
+            }
+        };
+        final Path path = directory.resolve("exportDataQuiet.gpx");
+        assertDoesNotThrow(() -> Files.write(path, Collections.singletonList("Test file")));
+        assertDoesNotThrow(() -> testExportDataQuiet(directory));
+        assertNotEquals("Test file", assertDoesNotThrow(() -> Files.readAllLines(path)).get(0));
+
+        assertDoesNotThrow(() -> Files.write(path, Collections.singletonList("Test file")));
+        returnValue.set(3); // Cancel
+        // We expect it to fail during re-read
+        assertThrows(IOException.class, () -> testExportDataQuiet(directory));
+        assertEquals("Test file", assertDoesNotThrow(() -> Files.readAllLines(path)).get(0));
+    }
+}
Index: test/unit/org/openstreetmap/josm/io/importexport/NmeaToGpxExporterTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/unit/org/openstreetmap/josm/io/importexport/NmeaToGpxExporterTest.java b/test/unit/org/openstreetmap/josm/io/importexport/NmeaToGpxExporterTest.java
new file mode 100644
--- /dev/null	(date 1675979963232)
+++ b/test/unit/org/openstreetmap/josm/io/importexport/NmeaToGpxExporterTest.java	(date 1675979963232)
@@ -0,0 +1,51 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.importexport;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.openstreetmap.josm.TestUtils;
+import org.openstreetmap.josm.data.gpx.GpxData;
+import org.openstreetmap.josm.gui.io.importexport.NMEAImporter;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.gui.layer.Layer;
+
+/**
+ * A class that tests importing NMEA data and exporting it to GPX
+ */
+class NmeaToGpxExporterTest implements GpxExporterTest<NMEAImporter> {
+    @Override
+    public NMEAImporter getOriginalImporter() {
+        return new NMEAImporter();
+    }
+
+    @Override
+    public void verifyLayers(List<Layer> original, List<Layer> saved) {
+        assertAll(() -> assertEquals(1, original.size()),
+                () -> assertEquals(1, saved.size()),
+                () -> assertTrue(original.get(0) instanceof GpxLayer),
+                () -> assertTrue(saved.get(0) instanceof GpxLayer));
+        final GpxData originalData = ((GpxLayer) original.get(0)).getGpxData();
+        final GpxData savedData = ((GpxLayer) saved.get(0)).getGpxData();
+        assertAll(() -> assertEquals(originalData.getTrackCount(), savedData.getTrackCount()),
+                () -> assertEquals(originalData.getTrackSegsCount(), savedData.getTrackSegsCount()),
+                () -> assertEquals(originalData.getRoutes().size(), savedData.getRoutes().size()),
+                () -> assertEquals(originalData.getWaypoints().size(), savedData.getWaypoints().size()),
+                () -> assertEquals(originalData.getTrackPoints().count(), savedData.getTrackPoints().count()));
+    }
+
+    @Override
+    public File goodData() {
+        return Paths.get(TestUtils.getTestDataRoot(), "sessions", "data.nmea").toFile();
+    }
+
+    @Override
+    public File badData() {
+        return new File(TestUtils.getRegressionDataFile(14924, "input.nmea"));
+    }
+}
