From 0826abb2d6f40232a94073d7af3e6ff1ff280983 Mon Sep 17 00:00:00 2001 From: "taylor.smock" Date: Thu, 12 Sep 2024 10:37:19 +0000 Subject: [PATCH] Fix failing build due to putting unit test for renderer in unit instead of functional This was done by fixing a TODO comment ("move to separate class ImageTestUtils") in the MapCSSRendererTest. git-svn-id: https://josm.openstreetmap.de/svn/trunk@19221 0c6e7542-c601-0410-84e7-c038aed88b3b --- .../josm/gui/mappaint/MapCSSRendererTest.java | 116 +----------- .../josm/tools/ImageProviderTest.java | 2 +- .../paint/StyledTiledMapRendererTest.java | 29 +-- .../josm/testutils/ImageTestUtils.java | 165 ++++++++++++++++++ .../josm/testutils/annotations/Plugins.java | 1 + 5 files changed, 175 insertions(+), 138 deletions(-) create mode 100644 test/unit/org/openstreetmap/josm/testutils/ImageTestUtils.java diff --git a/test/functional/org/openstreetmap/josm/gui/mappaint/MapCSSRendererTest.java b/test/functional/org/openstreetmap/josm/gui/mappaint/MapCSSRendererTest.java index f9741d6cbf2..8e4dca66792 100644 --- a/test/functional/org/openstreetmap/josm/gui/mappaint/MapCSSRendererTest.java +++ b/test/functional/org/openstreetmap/josm/gui/mappaint/MapCSSRendererTest.java @@ -1,26 +1,21 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.mappaint; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.openstreetmap.josm.testutils.ImageTestUtils.assertImageEquals; -import java.awt.Color; import java.awt.GraphicsEnvironment; -import java.awt.Point; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Paths; -import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -39,7 +34,6 @@ import org.openstreetmap.josm.io.OsmReader; import org.openstreetmap.josm.testutils.annotations.BasicPreferences; import org.openstreetmap.josm.testutils.annotations.Projection; -import org.openstreetmap.josm.tools.ColorHelper; import org.openstreetmap.josm.tools.Utils; /** @@ -58,9 +52,6 @@ public class MapCSSRendererTest { private static final Bounds AREA_DEFAULT = new Bounds(0, 0, 1, 1); private static final int IMAGE_SIZE = 256; - // development flag - set to true in order to update all reference images - private static final boolean UPDATE_ALL = false; - /** * The different configurations of this test. * @@ -201,111 +192,6 @@ void testRender(TestConfig testConfig, String ignored) throws Exception { }); } - /** - * Compares the reference image file with the actual images given as {@link BufferedImage}. - * @param testIdentifier a test identifier for error messages - * @param referenceImageFile the reference image file to be read using {@link ImageIO#read(File)} - * @param image the actual image - * @param thresholdPixels maximum number of differing pixels - * @param thresholdTotalColorDiff maximum sum of color value differences - * @param diffImageConsumer a consumer for a rendered image highlighting the differing pixels, may be null - * @throws IOException in case of I/O error - */ - public static void assertImageEquals( - String testIdentifier, File referenceImageFile, BufferedImage image, - int thresholdPixels, int thresholdTotalColorDiff, Consumer diffImageConsumer) throws IOException { - - // TODO move to separate class ImageTestUtils - if (UPDATE_ALL) { - ImageIO.write(image, "png", referenceImageFile); - return; - } - final BufferedImage reference = ImageIO.read(referenceImageFile); - assertImageEquals(testIdentifier, reference, image, thresholdPixels, thresholdTotalColorDiff, diffImageConsumer); - } - - /** - * Compares the reference image file with the actual images given as {@link BufferedImage}. - * @param testIdentifier a test identifier for error messages - * @param reference the reference image - * @param image the actual image - * @param thresholdPixels maximum number of differing pixels - * @param thresholdTotalColorDiff maximum sum of color value differences - * @param diffImageConsumer a consumer for a rendered image highlighting the differing pixels, may be null - */ - public static void assertImageEquals(String testIdentifier, BufferedImage reference, BufferedImage image, - int thresholdPixels, int thresholdTotalColorDiff, Consumer diffImageConsumer) { - assertEquals(reference.getWidth(), image.getWidth()); - assertEquals(reference.getHeight(), image.getHeight()); - - StringBuilder differences = new StringBuilder(); - ArrayList differencePoints = new ArrayList<>(); - int colorDiffSum = 0; - - for (int y = 0; y < reference.getHeight(); y++) { - for (int x = 0; x < reference.getWidth(); x++) { - int expected = reference.getRGB(x, y); - int result = image.getRGB(x, y); - int expectedAlpha = expected >> 24; - boolean colorsAreSame = expectedAlpha == 0 ? result >> 24 == 0 : expected == result; - if (!colorsAreSame) { - Color expectedColor = new Color(expected, true); - Color resultColor = new Color(result, true); - int colorDiff = Math.abs(expectedColor.getRed() - resultColor.getRed()) - + Math.abs(expectedColor.getGreen() - resultColor.getGreen()) - + Math.abs(expectedColor.getBlue() - resultColor.getBlue()); - int alphaDiff = Math.abs(expectedColor.getAlpha() - resultColor.getAlpha()); - // Ignore small alpha differences due to Java versions, rendering libraries and so on - if (alphaDiff <= 20) { - alphaDiff = 0; - } - // Ignore small color differences for the same reasons, but also completely for almost-transparent pixels - if (colorDiff <= 15 || resultColor.getAlpha() <= 20) { - colorDiff = 0; - } - if (colorDiff + alphaDiff > 0) { - differencePoints.add(new Point(x, y)); - if (differences.length() < 2000) { - differences.append("\nDifference at ") - .append(x) - .append(",") - .append(y) - .append(": Expected ") - .append(ColorHelper.color2html(expectedColor)) - .append(" but got ") - .append(ColorHelper.color2html(resultColor)) - .append(" (color diff is ") - .append(colorDiff) - .append(", alpha diff is ") - .append(alphaDiff) - .append(")"); - } - } - colorDiffSum += colorDiff + alphaDiff; - } - } - } - - if (differencePoints.size() > thresholdPixels || colorDiffSum > thresholdTotalColorDiff) { - // Add a nice image that highlights the differences: - BufferedImage diffImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); - for (Point p : differencePoints) { - diffImage.setRGB(p.x, p.y, 0xffff0000); - } - if (diffImageConsumer != null) { - diffImageConsumer.accept(diffImage); - } - - if (differencePoints.size() > thresholdPixels) { - fail(MessageFormat.format("Images for test {0} differ at {1} points, threshold is {2}: {3}", - testIdentifier, differencePoints.size(), thresholdPixels, differences.toString())); - } else { - fail(MessageFormat.format("Images for test {0} differ too much in color, value is {1}, permitted threshold is {2}: {3}", - testIdentifier, colorDiffSum, thresholdTotalColorDiff, differences.toString())); - } - } - } - private void loadPrimitiveStyle(OsmPrimitive n) { n.setHighlighted(n.isKeyTrue("highlight")); if (n.isKeyTrue("disabled")) { diff --git a/test/functional/org/openstreetmap/josm/tools/ImageProviderTest.java b/test/functional/org/openstreetmap/josm/tools/ImageProviderTest.java index a5b1d7df4a3..4ad941f8860 100644 --- a/test/functional/org/openstreetmap/josm/tools/ImageProviderTest.java +++ b/test/functional/org/openstreetmap/josm/tools/ImageProviderTest.java @@ -5,7 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.openstreetmap.josm.gui.mappaint.MapCSSRendererTest.assertImageEquals; +import static org.openstreetmap.josm.testutils.ImageTestUtils.assertImageEquals; import java.awt.Dimension; import java.awt.Image; diff --git a/test/unit/org/openstreetmap/josm/data/osm/visitor/paint/StyledTiledMapRendererTest.java b/test/unit/org/openstreetmap/josm/data/osm/visitor/paint/StyledTiledMapRendererTest.java index fe1015a76ae..8407ac0a249 100644 --- a/test/unit/org/openstreetmap/josm/data/osm/visitor/paint/StyledTiledMapRendererTest.java +++ b/test/unit/org/openstreetmap/josm/data/osm/visitor/paint/StyledTiledMapRendererTest.java @@ -1,14 +1,11 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.data.osm.visitor.paint; -import static org.openstreetmap.josm.gui.mappaint.MapCSSRendererTest.assertImageEquals; +import static org.openstreetmap.josm.testutils.ImageTestUtils.assertImageEquals; +import static org.openstreetmap.josm.testutils.ImageTestUtils.writeDebugImages; import java.awt.Graphics2D; import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; import java.nio.file.Paths; import java.util.Arrays; import java.util.Map; @@ -20,8 +17,6 @@ import java.util.stream.IntStream; import java.util.stream.Stream; -import javax.imageio.ImageIO; - import org.apache.commons.jcs3.access.CacheAccess; import org.awaitility.Awaitility; import org.junit.jupiter.params.ParameterizedTest; @@ -72,7 +67,7 @@ static Stream testRender() { .flatMap(generateTests); } - @ParameterizedTest(name = "{0} - {2}") + @ParameterizedTest(name = "{2} - {0}") @MethodSource void testRender(String testIdentifier, final Supplier dataSetSupplier, final TileZXY tile) throws InterruptedException, ExecutionException { @@ -117,20 +112,10 @@ public int getHeight() { null, 0, image.getWidth())).anyMatch(i -> i != 0); }).collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().image())); try { - assertImageEquals(testIdentifier, oldRenderStyle, newRenderStyle, 0, 0, diff -> { - try { - if (!Files.isDirectory(Paths.get(TestUtils.getTestDataRoot(), "output"))) { - Files.createDirectories(Paths.get(TestUtils.getTestDataRoot(), "output")); - } - final String basename = TestUtils.getTestDataRoot() + "output/" + - testIdentifier + ' ' + tile.zoom() + '-' + tile.x() + '-' + tile.y(); - ImageIO.write(diff, "png", new File(basename + "-diff.png")); - ImageIO.write(newRenderStyle, "png", new File(basename + "-new.png")); - ImageIO.write(oldRenderStyle, "png", new File(basename + "-old.png")); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }); + assertImageEquals(testIdentifier, oldRenderStyle, newRenderStyle, 0, 0, diff -> + writeDebugImages(Paths.get(TestUtils.getTestDataRoot(), "output"), + testIdentifier + ' ' + tile.zoom() + '-' + tile.x() + '-' + tile.y(), diff, oldRenderStyle, newRenderStyle) + ); } finally { cache.clear(); } diff --git a/test/unit/org/openstreetmap/josm/testutils/ImageTestUtils.java b/test/unit/org/openstreetmap/josm/testutils/ImageTestUtils.java new file mode 100644 index 00000000000..2fd05605a12 --- /dev/null +++ b/test/unit/org/openstreetmap/josm/testutils/ImageTestUtils.java @@ -0,0 +1,165 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.testutils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.awt.Color; +import java.awt.Point; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.function.Consumer; + +import javax.imageio.ImageIO; + +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import org.openstreetmap.josm.tools.ColorHelper; + +/** + * Test helper class for images + */ +public final class ImageTestUtils { + // development flag - set to true in order to update all reference images + private static final boolean UPDATE_ALL = false; + + private ImageTestUtils() { + /* Hide constructor */ + } + + /** + * Compares the reference image file with the actual images given as {@link BufferedImage}. + * @param testIdentifier a test identifier for error messages + * @param referenceImageFile the reference image file to be read using {@link ImageIO#read(File)} + * @param image the actual image + * @param thresholdPixels maximum number of differing pixels + * @param thresholdTotalColorDiff maximum sum of color value differences + * @param diffImageConsumer a consumer for a rendered image highlighting the differing pixels, may be null + * @throws IOException in case of I/O error + */ + public static void assertImageEquals( + String testIdentifier, File referenceImageFile, BufferedImage image, + int thresholdPixels, int thresholdTotalColorDiff, Consumer diffImageConsumer) throws IOException { + + if (UPDATE_ALL) { + ImageIO.write(image, "png", referenceImageFile); + return; + } + final BufferedImage reference = ImageIO.read(referenceImageFile); + assertImageEquals(testIdentifier, reference, image, thresholdPixels, thresholdTotalColorDiff, diffImageConsumer); + } + + /** + * Compares the reference image file with the actual images given as {@link BufferedImage}. + * @param testIdentifier a test identifier for error messages + * @param reference the reference image + * @param image the actual image + * @param thresholdPixels maximum number of differing pixels + * @param thresholdTotalColorDiff maximum sum of color value differences + * @param diffImageConsumer a consumer for a rendered image highlighting the differing pixels, may be null + */ + public static void assertImageEquals(String testIdentifier, BufferedImage reference, BufferedImage image, + int thresholdPixels, int thresholdTotalColorDiff, Consumer diffImageConsumer) { + assertEquals(reference.getWidth(), image.getWidth()); + assertEquals(reference.getHeight(), image.getHeight()); + + StringBuilder differences = new StringBuilder(); + ArrayList differencePoints = new ArrayList<>(); + int colorDiffSum = 0; + + for (int y = 0; y < reference.getHeight(); y++) { + for (int x = 0; x < reference.getWidth(); x++) { + int expected = reference.getRGB(x, y); + int result = image.getRGB(x, y); + int expectedAlpha = expected >> 24; + boolean colorsAreSame = expectedAlpha == 0 ? result >> 24 == 0 : expected == result; + if (!colorsAreSame) { + Color expectedColor = new Color(expected, true); + Color resultColor = new Color(result, true); + int colorDiff = Math.abs(expectedColor.getRed() - resultColor.getRed()) + + Math.abs(expectedColor.getGreen() - resultColor.getGreen()) + + Math.abs(expectedColor.getBlue() - resultColor.getBlue()); + int alphaDiff = Math.abs(expectedColor.getAlpha() - resultColor.getAlpha()); + // Ignore small alpha differences due to Java versions, rendering libraries and so on + if (alphaDiff <= 20) { + alphaDiff = 0; + } + // Ignore small color differences for the same reasons, but also completely for almost-transparent pixels + if (colorDiff <= 15 || resultColor.getAlpha() <= 20) { + colorDiff = 0; + } + if (colorDiff + alphaDiff > 0) { + differencePoints.add(new Point(x, y)); + if (differences.length() < 2000) { + differences.append("\nDifference at ") + .append(x) + .append(",") + .append(y) + .append(": Expected ") + .append(ColorHelper.color2html(expectedColor)) + .append(" but got ") + .append(ColorHelper.color2html(resultColor)) + .append(" (color diff is ") + .append(colorDiff) + .append(", alpha diff is ") + .append(alphaDiff) + .append(")"); + } + } + colorDiffSum += colorDiff + alphaDiff; + } + } + } + + if (differencePoints.size() > thresholdPixels || colorDiffSum > thresholdTotalColorDiff) { + // Add a nice image that highlights the differences: + BufferedImage diffImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); + for (Point p : differencePoints) { + diffImage.setRGB(p.x, p.y, 0xffff0000); + } + if (diffImageConsumer != null) { + diffImageConsumer.accept(diffImage); + } + + if (differencePoints.size() > thresholdPixels) { + fail(MessageFormat.format("Images for test {0} differ at {1} points, threshold is {2}: {3}", + testIdentifier, differencePoints.size(), thresholdPixels, differences.toString())); + } else { + fail(MessageFormat.format("Images for test {0} differ too much in color, value is {1}, permitted threshold is {2}: {3}", + testIdentifier, colorDiffSum, thresholdTotalColorDiff, differences.toString())); + } + } + } + + /** + * Write debug images to a directory + * @param directory The directory to put the debug images in + * @param filePrefix The file prefix for the images (e.g. test name) + * @param diff The difference between the expected image and the actual image + * @param oldImage The expected image + * @param newImage The actual image + */ + public static void writeDebugImages(@Nonnull Path directory, @Nonnull String filePrefix, @Nonnull BufferedImage diff, + @Nullable BufferedImage oldImage, @Nullable BufferedImage newImage) { + if (!UPDATE_ALL) { + return; + } + try { + if (!Files.isDirectory(directory)) { + Files.createDirectories(directory); + } + final String basename = directory.resolve(filePrefix).toString(); + ImageIO.write(diff, "png", new File(basename + "-diff.png")); + if (newImage != null) ImageIO.write(newImage, "png", new File(basename + "-new.png")); + if (oldImage != null) ImageIO.write(oldImage, "png", new File(basename + "-old.png")); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/Plugins.java b/test/unit/org/openstreetmap/josm/testutils/annotations/Plugins.java index ee73fdc2b97..9b842ff0391 100644 --- a/test/unit/org/openstreetmap/josm/testutils/annotations/Plugins.java +++ b/test/unit/org/openstreetmap/josm/testutils/annotations/Plugins.java @@ -33,6 +33,7 @@ */ class PluginExtension implements AfterEachCallback { + @SuppressWarnings("unchecked") @Override public void afterEach(ExtensionContext context) throws Exception { // We want to clean up as much as possible using "standard" methods