Skip to content

Commit

Permalink
✅ Add tests for metadata import and export
Browse files Browse the repository at this point in the history
  • Loading branch information
Poeschl committed Sep 9, 2024
1 parent 0b4bdfa commit 7fedf56
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ class MapImportExportService {
private val LOGGER = LoggerFactory.getLogger(MapImportExportService::class.java)

private const val XMP_URI = "https://github.com/Poeschl/RoboRush"
private const val XMP_MAP_SOLAR_CHARGE_RATE_KEY = "SolarChargeRate"
private const val XMP_MAP_MAX_ROBOT_FUEL_KEY = "MaxRobotFuel"
private const val XMP_PREFIX = "rr:"
private const val XMP_MAP_SOLAR_CHARGE_RATE_KEY = "${XMP_PREFIX}solarChargeRate"
private const val XMP_MAP_MAX_ROBOT_FUEL_KEY = "${XMP_PREFIX}maxRobotFuel"
}

fun exportMap(map: Map): ByteArray {
Expand Down Expand Up @@ -205,6 +206,7 @@ class MapImportExportService {

return ByteArrayOutputStream().use {
val pngParams = PngImagingParameters()
pngParams.isForceTrueColor = true
pngParams.xmpXml = xmpString
PngWriter().writeImage(imageInput, it, pngParams, null)
return@use it.toByteArray()
Expand All @@ -214,16 +216,20 @@ class MapImportExportService {
} catch (ex: SAXException) {
LOGGER.warn("Couldn't write metadata!", ex)
}

// In case of error write image without metadata
return ByteArrayOutputStream().use {
PngWriter().writeImage(imageInput, it, null, null)
val pngParams = PngImagingParameters()
pngParams.isForceTrueColor = true
PngWriter().writeImage(imageInput, it, pngParams, null)
return@use it.toByteArray()
}
}

private fun getMapMetadata(imageInput: InputStream): MapMetadata? {
val xmpString = Imaging.getXmpXml(imageInput.readAllBytes())
if (xmpString != null && xmpString.isNotEmpty() && xmpString.contains(XMP_URI)) {
val xmpMetadata = XMPParser.parseXMP(StreamSource(xmpString.byteInputStream()))
val xmpMetadata = XMPParser.parseXMP(StreamSource(xmpString.byteInputStream().buffered()))

val solarChargeProp = Optional.ofNullable(xmpMetadata.getProperty(XMP_URI, XMP_MAP_SOLAR_CHARGE_RATE_KEY)).getOrNull()
val maxRobotFuelProp = Optional.ofNullable(xmpMetadata.getProperty(XMP_URI, XMP_MAP_MAX_ROBOT_FUEL_KEY)).getOrNull()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package xyz.poeschl.roborush.service

import org.apache.commons.imaging.Imaging
import org.apache.commons.imaging.formats.png.PngImagingParameters
import org.apache.commons.imaging.formats.png.PngWriter
import org.apache.xmlgraphics.xmp.XMPParser
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import org.springframework.core.io.ByteArrayResource
import org.springframework.core.io.ClassPathResource
import xyz.poeschl.roborush.exceptions.NoStartingPosition
import xyz.poeschl.roborush.exceptions.NoTargetPosition
Expand All @@ -19,10 +24,14 @@ import xyz.poeschl.roborush.test.utils.builder.GameLogicBuilder.Companion.`$Map`
import xyz.poeschl.roborush.test.utils.builder.GameLogicBuilder.Companion.`$Position`
import xyz.poeschl.roborush.test.utils.builder.GameLogicBuilder.Companion.`$Size`
import xyz.poeschl.roborush.test.utils.builder.GameLogicBuilder.Companion.`$Tile`
import xyz.poeschl.roborush.test.utils.builder.NativeTypes.Companion.`$Double`
import xyz.poeschl.roborush.test.utils.builder.NativeTypes.Companion.`$Int`
import xyz.poeschl.roborush.test.utils.builder.NativeTypes.Companion.`$String`
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.util.stream.Stream
import javax.imageio.ImageIO
import javax.xml.transform.stream.StreamSource

class MapImportExportServiceTest {

Expand All @@ -34,6 +43,17 @@ class MapImportExportServiceTest {
Arguments.of(TileType.FUEL_TILE, 1, Color(1, 1, 255)),
Arguments.of(TileType.DEFAULT_TILE, 100, Color(100, 100, 100))
)

private val XMP_META_TEMPLATE = """
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?><x:xmpmeta xmlns:x="adobe:ns:meta/">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description xmlns:rr="https://github.com/Poeschl/RoboRush" rdf:about="">
<rr:solarChargeRate>%s</rr:solarChargeRate>
<rr:maxRobotFuel>%s</rr:maxRobotFuel>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
""".trimIndent()
}

private val importExportService = MapImportExportService()
Expand Down Expand Up @@ -75,6 +95,30 @@ class MapImportExportServiceTest {
assertThat(Color.fromColorInt(resultImage.getRGB(0, 0))).isEqualTo(expectedColor)
}

@Test
fun exportMap_metadata() {
// WHEN
val expectedMaxRobotFuel = a(`$Int`(10, 500))
val expectedSolarChargeRate = a(`$Double`(.1, 1.0))
val map = a(
`$Map`()
.withMaxRobotFuel(expectedMaxRobotFuel)
.withSolarChargeRate(expectedSolarChargeRate)
)
map.addTile(a(`$Tile`()))

// THEN
val byteResult = importExportService.exportMap(map)

// VERIFY
val xmpMetadataString = Imaging.getXmpXml(byteResult)
val xmpMetadata = XMPParser.parseXMP(StreamSource(xmpMetadataString.byteInputStream().buffered()))
assertThat(xmpMetadata.getProperty("https://github.com/Poeschl/RoboRush", "rr:solarChargeRate").value)
.isEqualTo(expectedSolarChargeRate.toString())
assertThat(xmpMetadata.getProperty("https://github.com/Poeschl/RoboRush", "rr:maxRobotFuel").value)
.isEqualTo(expectedMaxRobotFuel.toString())
}

@Test
fun importMap_correct() {
// WHEN
Expand Down Expand Up @@ -196,4 +240,33 @@ class MapImportExportServiceTest {
)
)
}

@Test
fun importMap_readMetadata() {
// WHEN
val inputImage = ClassPathResource("/maps/with-fuel.png")
val expectedMaxRobotFuel = a(`$Int`(10, 500))
val expectedSolarChargeRate = a(`$Double`(.1, 1.0))
val xmpString = XMP_META_TEMPLATE.format(expectedSolarChargeRate, expectedMaxRobotFuel)
val name = a(`$String`("name"))

// Put metadata in image, since the png optimizer removes it from the file
val imageWithMetadata = ByteArrayResource(
ByteArrayOutputStream().use {
val pngParams = PngImagingParameters()
pngParams.isForceTrueColor = true
pngParams.xmpXml = xmpString
PngWriter().writeImage(ImageIO.read(inputImage.inputStream.buffered()), it, pngParams, null)
return@use it.toByteArray()
}
)

// THEN
val genResult = importExportService.importMap(name, imageWithMetadata)

// VERIFY
assertThat(genResult.errors).isEmpty()
assertThat(genResult.map.solarChargeRate).isEqualTo(expectedSolarChargeRate)
assertThat(genResult.map.maxRobotFuel).isEqualTo(expectedMaxRobotFuel)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ class GameLogicBuilder {
fun `$Tile`() = TileBuilder()
.withId(null)
.withPosition(a(`$Position`()))
.withHeight(a(`$Int`()))
.withHeight(a(`$Int`(1, 254)))
.withType(TileType.DEFAULT_TILE)

fun `$Size`() = SizeBuilder()
.withHeight(a(`$Int`()))
.withWidth(a(`$Int`()))
.withHeight(a(`$Int`(1, 512)))
.withWidth(a(`$Int`(1, 512)))

fun `$Position`() = PositionBuilder()
.withX(a(`$Int`(0, Int.MAX_VALUE)))
Expand Down

0 comments on commit 7fedf56

Please sign in to comment.