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