1 /* 2 * 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.content.Context 20 import android.graphics.BitmapFactory 21 import android.graphics.Color 22 import androidx.test.ext.junit.runners.AndroidJUnit4 23 import androidx.test.platform.app.InstrumentationRegistry 24 import com.google.common.truth.Truth.assertThat 25 import java.io.File 26 import org.junit.After 27 import org.junit.Test 28 import org.junit.runner.RunWith 29 import platform.test.screenshot.GoldenPathManager 30 import platform.test.screenshot.PathConfig 31 import platform.test.screenshot.PathElementNoContext 32 import platform.test.screenshot.getDeviceOutputDirectory 33 import platform.test.screenshot.proto.ScreenshotResultProto.DiffResult 34 import platform.test.screenshot.report.OutputFileType.IMAGE_ACTUAL 35 import platform.test.screenshot.report.OutputFileType.IMAGE_DIFF 36 import platform.test.screenshot.report.OutputFileType.IMAGE_EXPECTED 37 import platform.test.screenshot.report.OutputFileType.RESULT_BIN_PROTO 38 import platform.test.screenshot.report.OutputFileType.RESULT_PROTO 39 import platform.test.screenshot.utils.createBitmap 40 41 @RunWith(AndroidJUnit4::class) 42 class ScubaExportStrategyTest { 43 44 private val testId = "test-id" 45 private val goldenId = "golden-id" 46 47 // Helper to avoid repeating constant " testId, goldenId" getPathOnDeviceFornull48 private fun ExportToScubaStrategy.getPathOnDeviceFor(fileType: OutputFileType) = 49 getPathOnDeviceFor(fileType, goldenId, testId) 50 51 private val context: Context 52 get() = InstrumentationRegistry.getInstrumentation().context 53 54 @After 55 fun cleanupFiles() { 56 // Make sure to clean-up the files, since all tests us the same testId/goldenId 57 File(getDeviceOutputDirectory(context)).deleteRecursively() 58 } 59 60 @Test getPathOnDeviceFor_emptyPathConfig_filenameIncludesPathElementnull61 fun getPathOnDeviceFor_emptyPathConfig_filenameIncludesPathElement() { 62 val subject = ExportToScubaStrategy(GoldenPathManager(context, pathConfig = PathConfig())) 63 64 assertThat(subject.getPathOnDeviceFor(IMAGE_ACTUAL).name) 65 .isEqualTo("test-id_actual_golden-id.png") 66 assertThat(subject.getPathOnDeviceFor(IMAGE_EXPECTED).name) 67 .isEqualTo("test-id_expected_golden-id.png") 68 assertThat(subject.getPathOnDeviceFor(IMAGE_DIFF).name) 69 .isEqualTo("test-id_diff_golden-id.png") 70 assertThat(subject.getPathOnDeviceFor(RESULT_PROTO).name) 71 .isEqualTo("test-id_golden-id_goldResult.textproto") 72 assertThat(subject.getPathOnDeviceFor(RESULT_BIN_PROTO).name) 73 .isEqualTo("test-id_golden-id_goldResult.pb") 74 } 75 76 @Test getPathOnDeviceFor_directoryElement_filenameIncludesPathElementnull77 fun getPathOnDeviceFor_directoryElement_filenameIncludesPathElement() { 78 val pathConfig = PathConfig(PathElementNoContext("foo", isDir = true) { "bar" }) 79 val subject = ExportToScubaStrategy(GoldenPathManager(context, pathConfig = pathConfig)) 80 81 assertThat(subject.getPathOnDeviceFor(IMAGE_ACTUAL).name) 82 .isEqualTo("test-id_actual_bar_golden-id.png") 83 assertThat(subject.getPathOnDeviceFor(IMAGE_EXPECTED).name) 84 .isEqualTo("test-id_expected_bar_golden-id.png") 85 assertThat(subject.getPathOnDeviceFor(IMAGE_DIFF).name) 86 .isEqualTo("test-id_diff_bar_golden-id.png") 87 assertThat(subject.getPathOnDeviceFor(RESULT_PROTO).name) 88 .isEqualTo("test-id_bar_golden-id_goldResult.textproto") 89 assertThat(subject.getPathOnDeviceFor(RESULT_BIN_PROTO).name) 90 .isEqualTo("test-id_bar_golden-id_goldResult.pb") 91 } 92 93 @Test getPathOnDeviceFor_nonDirectoryElement_filenameIncludesPathElementnull94 fun getPathOnDeviceFor_nonDirectoryElement_filenameIncludesPathElement() { 95 val pathConfig = PathConfig(PathElementNoContext("foo", isDir = false) { "bar" }) 96 val subject = ExportToScubaStrategy(GoldenPathManager(context, pathConfig = pathConfig)) 97 98 assertThat(subject.getPathOnDeviceFor(IMAGE_ACTUAL).name) 99 .isEqualTo("test-id_actual_bar_golden-id.png") 100 assertThat(subject.getPathOnDeviceFor(IMAGE_EXPECTED).name) 101 .isEqualTo("test-id_expected_bar_golden-id.png") 102 assertThat(subject.getPathOnDeviceFor(IMAGE_DIFF).name) 103 .isEqualTo("test-id_diff_bar_golden-id.png") 104 assertThat(subject.getPathOnDeviceFor(RESULT_PROTO).name) 105 .isEqualTo("test-id_bar_golden-id_goldResult.textproto") 106 assertThat(subject.getPathOnDeviceFor(RESULT_BIN_PROTO).name) 107 .isEqualTo("test-id_bar_golden-id_goldResult.pb") 108 } 109 110 @Test getPathOnDeviceFor_multiplePathConfigs_filenameIncludesPathElementnull111 fun getPathOnDeviceFor_multiplePathConfigs_filenameIncludesPathElement() { 112 val pathConfig = 113 PathConfig( 114 PathElementNoContext("foo", isDir = true) { "bar" }, 115 PathElementNoContext("one", isDir = false) { "two" } 116 ) 117 val subject = ExportToScubaStrategy(GoldenPathManager(context, pathConfig = pathConfig)) 118 119 assertThat(subject.getPathOnDeviceFor(IMAGE_ACTUAL).name) 120 .isEqualTo("test-id_actual_bar_two_golden-id.png") 121 assertThat(subject.getPathOnDeviceFor(IMAGE_EXPECTED).name) 122 .isEqualTo("test-id_expected_bar_two_golden-id.png") 123 assertThat(subject.getPathOnDeviceFor(IMAGE_DIFF).name) 124 .isEqualTo("test-id_diff_bar_two_golden-id.png") 125 assertThat(subject.getPathOnDeviceFor(RESULT_PROTO).name) 126 .isEqualTo("test-id_bar_two_golden-id_goldResult.textproto") 127 assertThat(subject.getPathOnDeviceFor(RESULT_BIN_PROTO).name) 128 .isEqualTo("test-id_bar_two_golden-id_goldResult.pb") 129 } 130 131 @Test reportResult_withoutOptionalArgs_writesActualAndProtonull132 fun reportResult_withoutOptionalArgs_writesActualAndProto() { 133 val subject = ExportToScubaStrategy(GoldenPathManager(context)) 134 135 subject.reportResult( 136 testId, 137 goldenId, 138 status = DiffResult.Status.PASSED, 139 actual = createBitmap(Color.GREEN), 140 ) 141 142 assertThat(subject.getPathOnDeviceFor(IMAGE_ACTUAL).exists()).isTrue() 143 assertThat(subject.getPathOnDeviceFor(RESULT_BIN_PROTO).exists()).isTrue() 144 assertThat(subject.getPathOnDeviceFor(RESULT_PROTO).exists()).isTrue() 145 assertThat(subject.getPathOnDeviceFor(IMAGE_DIFF).exists()).isFalse() 146 assertThat(subject.getPathOnDeviceFor(IMAGE_EXPECTED).exists()).isFalse() 147 } 148 149 @Test reportResult_withDiff_writesDiffnull150 fun reportResult_withDiff_writesDiff() { 151 val subject = ExportToScubaStrategy(GoldenPathManager(context)) 152 153 subject.reportResult( 154 testId, 155 goldenId, 156 status = DiffResult.Status.PASSED, 157 actual = createBitmap(Color.GREEN), 158 diff = createBitmap(Color.YELLOW), 159 ) 160 161 assertThat(subject.getPathOnDeviceFor(IMAGE_ACTUAL).exists()).isTrue() 162 assertThat(subject.getPathOnDeviceFor(RESULT_BIN_PROTO).exists()).isTrue() 163 assertThat(subject.getPathOnDeviceFor(RESULT_PROTO).exists()).isTrue() 164 assertThat(subject.getPathOnDeviceFor(IMAGE_DIFF).exists()).isTrue() 165 assertThat(subject.getPathOnDeviceFor(IMAGE_EXPECTED).exists()).isFalse() 166 } 167 168 @Test reportResult_withExpected_writesExpectednull169 fun reportResult_withExpected_writesExpected() { 170 val subject = ExportToScubaStrategy(GoldenPathManager(context)) 171 172 subject.reportResult( 173 testId, 174 goldenId, 175 status = DiffResult.Status.PASSED, 176 actual = createBitmap(Color.GREEN), 177 expected = createBitmap(Color.RED), 178 ) 179 180 assertThat(subject.getPathOnDeviceFor(IMAGE_ACTUAL).exists()).isTrue() 181 assertThat(subject.getPathOnDeviceFor(RESULT_BIN_PROTO).exists()).isTrue() 182 assertThat(subject.getPathOnDeviceFor(RESULT_PROTO).exists()).isTrue() 183 assertThat(subject.getPathOnDeviceFor(IMAGE_DIFF).exists()).isFalse() 184 assertThat(subject.getPathOnDeviceFor(IMAGE_EXPECTED).exists()).isTrue() 185 } 186 187 @Test reportResult_passed_writesStatusnull188 fun reportResult_passed_writesStatus() { 189 val subject = ExportToScubaStrategy(GoldenPathManager(context)) 190 191 subject.reportResult( 192 testId, 193 goldenId, 194 status = DiffResult.Status.PASSED, 195 actual = createBitmap(Color.GREEN), 196 ) 197 198 assertThat(subject.getPathOnDeviceFor(RESULT_PROTO).readText()).contains("PASSED") 199 } 200 201 @Test reportResult_failed_writesStatusnull202 fun reportResult_failed_writesStatus() { 203 val subject = ExportToScubaStrategy(GoldenPathManager(context)) 204 205 subject.reportResult( 206 testId, 207 goldenId, 208 status = DiffResult.Status.FAILED, 209 actual = createBitmap(Color.GREEN), 210 ) 211 212 assertThat(subject.getPathOnDeviceFor(RESULT_PROTO).readText()).contains("FAILED") 213 } 214 215 @Test reportResult_writesCorrectImageContentsnull216 fun reportResult_writesCorrectImageContents() { 217 val subject = ExportToScubaStrategy(GoldenPathManager(context)) 218 219 subject.reportResult( 220 testId, 221 goldenId, 222 status = DiffResult.Status.PASSED, 223 actual = createBitmap(Color.RED), 224 diff = createBitmap(Color.GREEN), 225 expected = createBitmap(Color.BLUE), 226 ) 227 228 fun extractColorSample(fileType: OutputFileType): Color { 229 val path = subject.getPathOnDeviceFor(fileType).absolutePath 230 return BitmapFactory.decodeFile(path).getColor(0, 0) 231 } 232 233 assertThat(extractColorSample(IMAGE_ACTUAL)).isEqualTo(Color.valueOf(Color.RED)) 234 assertThat(extractColorSample(IMAGE_DIFF)).isEqualTo(Color.valueOf(Color.GREEN)) 235 assertThat(extractColorSample(IMAGE_EXPECTED)).isEqualTo(Color.valueOf(Color.BLUE)) 236 } 237 238 @Test reportResult_duplicateCall_doesNotOverwriteFilenull239 fun reportResult_duplicateCall_doesNotOverwriteFile() { 240 // Unsure why this is a desired behavior, but there was an explicit test for this before 241 // the refactoring, hence carrying it forward. 242 val subject = ExportToScubaStrategy(GoldenPathManager(context)) 243 244 subject.reportResult( 245 testId, 246 goldenId, 247 status = DiffResult.Status.PASSED, 248 actual = createBitmap(Color.RED), 249 diff = createBitmap(Color.GREEN), 250 expected = createBitmap(Color.BLUE), 251 ) 252 253 subject.reportResult( 254 testId, 255 goldenId, 256 status = DiffResult.Status.FAILED, 257 actual = createBitmap(Color.BLACK), 258 diff = createBitmap(Color.BLACK), 259 expected = createBitmap(Color.BLACK), 260 ) 261 262 fun extractColorSample(fileType: OutputFileType): Color { 263 val path = subject.getPathOnDeviceFor(fileType).absolutePath 264 return BitmapFactory.decodeFile(path).getColor(0, 0) 265 } 266 267 assertThat(subject.getPathOnDeviceFor(RESULT_PROTO).readText()).contains("PASSED") 268 assertThat(extractColorSample(IMAGE_ACTUAL)).isEqualTo(Color.valueOf(Color.RED)) 269 assertThat(extractColorSample(IMAGE_DIFF)).isEqualTo(Color.valueOf(Color.GREEN)) 270 assertThat(extractColorSample(IMAGE_EXPECTED)).isEqualTo(Color.valueOf(Color.BLUE)) 271 } 272 } 273