Skip to content

Commit 74fb882

Browse files
authored
Rn/upload screenshots (#55)
* Adding screenshots upload logic * adding logic to retrieve screenshot files * Adding tests * fix test * small fix * Updating test name * updating test file names * addressing feedback * Changing parsing logic to consider test name and class * batching failed tests together in a list * addressing feedback
1 parent 169cb8e commit 74fb882

File tree

4 files changed

+246
-43
lines changed

4 files changed

+246
-43
lines changed

AndroidXCI/lib/src/main/kotlin/dev/androidx/ci/testRunner/TestExecutionStore.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ internal class TestExecutionStore(
4747
testMatrix: TestMatrix
4848
): List<Step> {
4949
return getTestExecutionSteps(
50-
projectId = testMatrix?.projectId!!,
50+
projectId = testMatrix.projectId!!,
5151
historyId = testMatrix.resultStorage.toolResultsExecution?.historyId!!,
52-
executionId = testMatrix.resultStorage.toolResultsExecution?.executionId!!
52+
executionId = testMatrix.resultStorage.toolResultsExecution.executionId!!
5353
)
5454
}
5555
}

AndroidXCI/lib/src/main/kotlin/dev/androidx/ci/testRunner/TestRunnerService.kt

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,17 @@ interface TestRunnerService {
9292
*/
9393
suspend fun getTestMatrixResults(testMatrix: TestMatrix): List<TestRunResult>?
9494

95+
/**
96+
* Gets the screenshots for the given [testMatrix] and [testIdentifiers].
97+
* The result includes references to images and textproto files
98+
* Returns null when the testMatrix is not complete,
99+
* and emptyMap if there are no screenshots associated with the test (non-screenshot tests)
100+
*/
101+
suspend fun getTestMatrixResultsScreenshots(
102+
testMatrix: TestMatrix,
103+
testIdentifiers: List<TestIdentifier>
104+
): Map<TestIdentifier, List<TestCaseArtifact>>?
105+
95106
companion object {
96107
/**
97108
* Creates an implementation of [TestRunnerService].
@@ -294,6 +305,23 @@ interface TestRunnerService {
294305
/**
295306
* Test case log files produced by the test.
296307
*/
297-
val testCaseLogcats: Map<TestIdentifier, ResultFileResource>
308+
val testCaseArtifacts: Map<TestIdentifier, List<TestCaseArtifact>>
309+
}
310+
311+
data class TestCaseArtifact(
312+
/**
313+
* Test case log files produced by individual tests in the testrun.
314+
*/
315+
val resultFileResource: ResultFileResource,
316+
/**
317+
* Type of files: ex. logcat, png or textproto for screenshot tests
318+
*/
319+
val resourceType: String
320+
) {
321+
companion object ResourceType {
322+
const val LOGCAT = "logcat"
323+
const val PNG = "png"
324+
const val TEXTPROTO = "textproto"
325+
}
298326
}
299327
}

AndroidXCI/lib/src/main/kotlin/dev/androidx/ci/testRunner/TestRunnerServiceImpl.kt

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -174,22 +174,23 @@ internal class TestRunnerServiceImpl internal constructor(
174174
} else if (fileName.endsWith(LOGCAT_FILE_NAME_SUFFIX)) {
175175
val step = steps.flatMap {
176176
it.testExecutionStep?.toolExecution?.toolOutputs ?: emptyList()
177-
}?.find {
177+
}.find {
178178
(it.output?.fileUri == visitor.gcsPath.toString())
179179
}
180180
val runNumber = DeviceRun.create(visitor.fullDeviceId()).runNumber
181181
step?.testCase?.className?.let { className ->
182-
step?.testCase?.name?.let { name ->
182+
step.testCase.name?.let { name ->
183183
TestRunnerService.TestIdentifier(
184184
className,
185185
name,
186186
runNumber
187187
)
188188
}
189189
}?.let { testIdentifier ->
190-
getTestResultFiles(visitor).addTestCaseLogcat(
190+
getTestResultFiles(visitor).addTestCaseArtifact(
191191
testIdentifier,
192-
ResultFileResourceImpl(visitor)
192+
ResultFileResourceImpl(visitor),
193+
"logcat"
193194
)
194195
}
195196
}
@@ -206,13 +207,57 @@ internal class TestRunnerServiceImpl internal constructor(
206207
}
207208
}
208209

210+
private suspend fun findScreenshotFiles(
211+
resultPath: GcsPath,
212+
testIdentifiers: List<TestRunnerService.TestIdentifier>
213+
): Map< TestRunnerService.TestIdentifier, List<TestRunnerService.TestCaseArtifact>> {
214+
val screenshotArtifactsBlobs = mutableMapOf<TestRunnerService.TestIdentifier, MutableList<TestRunnerService.TestCaseArtifact>>()
215+
val screenshotArtifacts: Map<TestRunnerService.TestIdentifier, List<TestRunnerService.TestCaseArtifact>>
216+
val testNames = testIdentifiers.associateBy { testIdentifier ->
217+
"${testIdentifier.className}_${testIdentifier.name}"
218+
}
219+
val testRunNumber = testIdentifiers.first().runNumber
220+
fun BlobVisitor.fullDeviceId() = relativePath.substringBefore('/', "")
221+
googleCloudApi.walkEntires(
222+
gcsPath = resultPath
223+
).forEach { visitor ->
224+
val runNumber = DeviceRun.create(visitor.fullDeviceId()).runNumber
225+
if (runNumber == testRunNumber) {
226+
val testName = testNames.keys.find { testName ->
227+
visitor.fileName.startsWith(testName)
228+
}
229+
val testIdentifier = testNames[testName]
230+
if (testIdentifier != null) {
231+
screenshotArtifactsBlobs.getOrPut(testIdentifier) {
232+
mutableListOf()
233+
}.add(
234+
TestRunnerService.TestCaseArtifact(
235+
ResultFileResourceImpl(visitor),
236+
visitor.fileName.substringAfterLast(".")
237+
)
238+
)
239+
}
240+
}
241+
}
242+
screenshotArtifacts = screenshotArtifactsBlobs
243+
return screenshotArtifacts
244+
}
245+
209246
suspend fun getTestMatrixResults(
210247
testMatrixId: String
211248
): List<TestRunnerService.TestRunResult>? {
212249
val testMatrix = testLabController.getTestMatrix(testMatrixId) ?: return null
213250
return getTestMatrixResults(testMatrix)
214251
}
215252

253+
suspend fun getTestMatrixResultsScreenshots(
254+
testMatrixId: String,
255+
testIdentifiers: List<TestRunnerService.TestIdentifier>
256+
): Map<TestRunnerService.TestIdentifier, List<TestRunnerService.TestCaseArtifact>>? {
257+
val testMatrix = testLabController.getTestMatrix(testMatrixId) ?: return null
258+
return getTestMatrixResultsScreenshots(testMatrix, testIdentifiers)
259+
}
260+
216261
override suspend fun getTestMatrixResults(
217262
testMatrix: TestMatrix
218263
): List<TestRunnerService.TestRunResult>? {
@@ -221,6 +266,15 @@ internal class TestRunnerServiceImpl internal constructor(
221266
return findResultFiles(resultPath, testMatrix)
222267
}
223268

269+
override suspend fun getTestMatrixResultsScreenshots(
270+
testMatrix: TestMatrix,
271+
testIdentifiers: List<TestRunnerService.TestIdentifier>
272+
): Map<TestRunnerService.TestIdentifier, List<TestRunnerService.TestCaseArtifact>>? {
273+
if (!testMatrix.isComplete()) return null
274+
val resultPath = GcsPath(testMatrix.resultStorage.googleCloudStorage.gcsPath)
275+
return findScreenshotFiles(resultPath, testIdentifiers)
276+
}
277+
224278
companion object {
225279
private const val MERGED_TEST_RESULT_SUFFIX = "-test_results_merged.xml"
226280
private const val LOGCAT_FILE_NAME = "logcat"
@@ -248,23 +302,34 @@ internal class TestRunnerServiceImpl internal constructor(
248302
fullDeviceId: String,
249303
) : TestRunnerService.TestResultFiles {
250304
private val xmlResultBlobs = mutableListOf<TestRunnerService.ResultFileResource>()
251-
private val testCaseLogcatBlobs = mutableMapOf<TestRunnerService.TestIdentifier, TestRunnerService.ResultFileResource>()
305+
private val testCaseArtifactBlobs = mutableMapOf<TestRunnerService.TestIdentifier, MutableList<TestRunnerService.TestCaseArtifact>>()
252306

253307
override var logcat: TestRunnerService.ResultFileResource? = null
254308
internal set
255309
override var instrumentationResult: TestRunnerService.ResultFileResource? = null
256310
internal set
257311
override val xmlResults: List<TestRunnerService.ResultFileResource> = xmlResultBlobs
258-
override val testCaseLogcats: Map<TestRunnerService.TestIdentifier, TestRunnerService.ResultFileResource> = testCaseLogcatBlobs
312+
override val testCaseArtifacts: Map<TestRunnerService.TestIdentifier, List<TestRunnerService.TestCaseArtifact>> = testCaseArtifactBlobs
259313
override val deviceRun: DeviceRun = DeviceRun.create(fullDeviceId)
260314

261315
internal fun addXmlResult(resultFileResource: TestRunnerService.ResultFileResource) {
262316
xmlResultBlobs.add(resultFileResource)
263317
}
264-
internal fun addTestCaseLogcat(testCase: TestRunnerService.TestIdentifier, resultFileResource: TestRunnerService.ResultFileResource) {
265-
testCaseLogcatBlobs[testCase] = resultFileResource
266-
}
267318

319+
internal fun addTestCaseArtifact(
320+
testCase: TestRunnerService.TestIdentifier,
321+
resultFileResource: TestRunnerService.ResultFileResource,
322+
resourceType: String
323+
) {
324+
testCaseArtifactBlobs.getOrPut(testCase) {
325+
mutableListOf()
326+
}.add(
327+
TestRunnerService.TestCaseArtifact(
328+
resultFileResource,
329+
resourceType
330+
)
331+
)
332+
}
268333
override fun toString(): String {
269334
return """
270335
TestResultFiles(

0 commit comments

Comments
 (0)