1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package platform.test.screenshot.report 18 19 import android.graphics.Bitmap 20 import java.nio.file.Files 21 import java.nio.file.Path 22 import java.nio.file.Paths 23 import kotlin.io.path.outputStream 24 import kotlin.io.path.writeText 25 import platform.test.screenshot.GoldenPathManager 26 import platform.test.screenshot.proto.ScreenshotResultProto 27 28 /** 29 * Writes the bitmap diff results on the local development machine. 30 * 31 * Useful during development of deviceless tests. 32 * 33 * TODO(b/322324387) Cleanup code - this is copied without modification from 34 * http://shortn/_nZmmj8v5zv for reviewability. 35 */ 36 internal class DevicelessDevMachineExportStrategy( 37 private val goldenPathManager: GoldenPathManager, 38 ) : DiffResultExportStrategy { 39 private val imageExtension = ".png" 40 41 override fun reportResult( 42 testIdentifier: String, 43 goldenIdentifier: String, 44 actual: Bitmap, 45 status: ScreenshotResultProto.DiffResult.Status, 46 comparisonStatistics: ScreenshotResultProto.DiffResult.ComparisonStatistics?, 47 expected: Bitmap?, 48 diff: Bitmap? 49 ) { 50 val localDir = Paths.get("/tmp/screenshots") 51 val actualDir = localDir.resolve("actual") 52 val expectedDir = localDir.resolve("expected") 53 val diffDir = localDir.resolve("diff") 54 val reportDir = localDir.resolve("report") 55 56 val androidBuildTopDir = System.getenv("ANDROID_BUILD_TOP") 57 val androidBuildTop = 58 if (androidBuildTopDir != null) { 59 Paths.get(androidBuildTopDir) 60 } else { 61 null 62 } 63 val assetsDir = androidBuildTop?.resolve(goldenPathManager.assetsPathRelativeToBuildRoot) 64 val imagePath = goldenPathManager.goldenImageIdentifierResolver(goldenIdentifier) 65 val actualImagePath = actualDir.resolve(imagePath) 66 val expectedImagePath = expectedDir.resolve(imagePath) 67 val diffImagePath = diffDir.resolve(imagePath) 68 69 actual.writeTo(actualImagePath) 70 if (assetsDir != null) { 71 actual.writeTo(assetsDir.resolve(imagePath)) 72 } 73 expected?.writeTo(expectedImagePath) 74 diff?.writeTo(diffImagePath) 75 76 check(imagePath.endsWith(imageExtension)) 77 78 val reportPath = 79 reportDir.resolve( 80 imagePath.substring(0, imagePath.length - imageExtension.length) + ".html" 81 ) 82 83 println("file://$reportPath") 84 Files.createDirectories(reportPath.parent) 85 86 fun html(bitmap: Bitmap?, image: Path, name: String, alt: String): String { 87 return if (bitmap == null) { 88 "" 89 } else { 90 """ 91 <p> 92 <h2><a href="file://$image">$name</a></h2> 93 <img src="$image" alt="$alt"/> 94 </p> 95 """ 96 .trimIndent() 97 } 98 } 99 100 reportPath.writeText( 101 """ 102 <!DOCTYPE html> 103 <meta charset="utf-8"> 104 <title>$imagePath</title> 105 <p><h1>$testIdentifier</h1></p> 106 ${html(expected, expectedImagePath, "Expected", "Golden")} 107 ${html(actual, actualImagePath, "Actual", "Actual")} 108 ${html(diff, diffImagePath, "Diff", "Diff")} 109 """ 110 .trimIndent() 111 ) 112 } 113 114 private fun Bitmap.writeTo(path: Path) { 115 // Make sure we either create a new file or overwrite an existing one. 116 check(!Files.exists(path) || Files.isRegularFile(path)) 117 118 // Make sure the parent directory exists. 119 Files.createDirectories(path.parent) 120 121 // Write the Bitmap to the given file. 122 path.outputStream().use { stream -> 123 this@writeTo.compress(Bitmap.CompressFormat.PNG, 0, stream) 124 } 125 } 126 } 127