Skip to content

Fix an instability in Geotiff unit test#44

Open
alexismanin wants to merge 6 commits into
geoapi-4.0from
fix/geotiff-flaky-test
Open

Fix an instability in Geotiff unit test#44
alexismanin wants to merge 6 commits into
geoapi-4.0from
fix/geotiff-flaky-test

Conversation

@alexismanin
Copy link
Copy Markdown
Contributor

I stumbled upon an error while building SIS with a specific JDK: 23.0.2 Liberica (installed using sdkman -> sdk install java 23.0.2.fx-librca).

The test was failing because it tested the size of a written Geotiff file. The problem is, as the written file uses Deflate compression, there's no guarantee that the produced file will always have the same size, depending on user build environment. The stack trace of the error is :

GeoTiffStoreTest > testWriteTiled() FAILED
    org.opentest4j.AssertionFailedError: array lengths differ, expected: <2334> but was: <2336>
        at app//org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
        at app//org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
        at app//org.junit.jupiter.api.AssertArrayEquals.assertArraysHaveSameLength(AssertArrayEquals.java:428)
        at app//org.junit.jupiter.api.AssertArrayEquals.assertArrayEquals(AssertArrayEquals.java:205)
        at app//org.junit.jupiter.api.AssertArrayEquals.assertArrayEquals(AssertArrayEquals.java:63)
        at app//org.junit.jupiter.api.AssertArrayEquals.assertArrayEquals(AssertArrayEquals.java:59)
        at app//org.junit.jupiter.api.Assertions.assertArrayEquals(Assertions.java:1229)
        at app/org.apache.sis.storage.geotiff/org.apache.sis.storage.geotiff.GeoTiffStoreTest.testWrite(GeoTiffStoreTest.java:190)
        at app/org.apache.sis.storage.geotiff/org.apache.sis.storage.geotiff.GeoTiffStoreTest.testWriteTiled(GeoTiffStoreTest.java:152)

This PR refactors the test to stop comparing file size. Instead, we read back the written file, and check that the decoded coverage is equivalent to the original coverage that was written.

A few side notes about this PR:

  • It contains a commit from another PR I opened here. The commit is the one that upgrades Gradle, to make it compatible with JDK 23. Depending on which PR is merged first, I will rebase the other one to get rid of the duplicated commit.
  • It also add another test for ImageProcessor, to verify that reformat operation is capable of retiling image according to user request.
  • It makes SampleDimension object implement "LenientComparable", which is useful for coverage comparison (which is required by the refactored Geotiff test).

The Geotiff writing test was failing depending on user build environment; The cause is an assertion that expects that a written image has an exact size, despite it being compressed using deflate algorithm (not deterministic).
The fix consists in changing the comparison logic.
We now read back the written file, and verify that the returned coverage is equivalent to the coverage that has been used as test input.
@alexismanin alexismanin requested a review from desruisseaux May 21, 2026 09:26
@desruisseaux desruisseaux self-assigned this May 21, 2026
@desruisseaux desruisseaux added this to the 1.7 milestone May 21, 2026
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @author Alexis Manin (Geomatys)
* @version 1.5
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to be changed to 1.7.

* @return {@code true} if the given object is equal to this sample dimension.
*/
@Override
public boolean equals(final Object object) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern in other classes is to rewrite this method as a final method which redirects to equals(object, ComparisonMode.STRIC). The current implementation would move to the latter method. The intent is to have only one method to override in subclasses (if any), for making sure that users do not override equals(Object) and forget to override equals(Object, ComparisonMode).

if (object == this) {
return true;
}
if (object instanceof SampleDimension) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an issue of this pull request, but I just realized that this line should be if (object.getClass() == getClass()) (for ComparisonMode.EXACT only).

if (other == this) return true;
if (mode.isApproximate() && other instanceof SampleDimension) {
final var otherDim = (SampleDimension) other;
return Utilities.deepEquals(this.transferFunction, otherDim.transferFunction, mode)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The transfer function was not compared in equals(Object) because it was derived from categories. Therefore, comparing transferFunction was considered redundant with comparing categories. If we want the comparison to be tolerant to slight difference in transfer functions, then we need to make Category implements LenientComparable too, otherwise this method will actually behave as if a strict comparison was performed regardless the flexibility of Utilities.deepEquals(this.transferFunction, otherDim.transferFunction, mode).

@Override
public boolean equals(Object other, ComparisonMode mode) {
if (other == this) return true;
if (mode.isApproximate() && other instanceof SampleDimension) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should only test if the mode is STRICT, in which case the test should be done as in the current implementation of equals(Object), and unconditionally uses Utilities.deepEquals in all other cases. The reason is that since we need to make Category implements LenientComparable too, Category could apply whether policy it wants. In particular, it may compare the category name in ComparisonMode.BY_CONTRACT and ignore that name in ComparisonMode.IGNORE_METADATA.

/**
* Verify that given image tiling respects user tiling request, modulo a given restriction.
* The restriction maps Tiff standard requirement for tile size to be multiple of a given factor.
* </br>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a small detail, but the rest of SIS rather uses <p>...</p> instead of <br/> (the slash also appears to be in the wrong side in this line). This is for semantic reason (the same reason why we use <i>, <em>, <var> or <dfn> in different contexts, even if they all appear in italic in the browser): the semantic is that we want to start a new paragraph.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants