@@ -6,9 +6,11 @@ import com.android.build.gradle.api.BaseVariantOutput
66import com.android.build.gradle.api.TestVariant
77import com.appunite.firebasetestlabplugin.cloud.CloudTestResultDownloader
88import com.appunite.firebasetestlabplugin.cloud.FirebaseTestLabProcessCreator
9+ import com.appunite.firebasetestlabplugin.cloud.ProcessData
910import com.appunite.firebasetestlabplugin.cloud.TestType
1011import com.appunite.firebasetestlabplugin.model.Device
1112import com.appunite.firebasetestlabplugin.model.TestResults
13+ import com.appunite.firebasetestlabplugin.tasks.InstrumentationShardingTask
1214import com.appunite.firebasetestlabplugin.utils.Constants
1315import org.apache.tools.ant.taskdefs.condition.Os
1416import org.gradle.api.GradleException
@@ -21,8 +23,9 @@ import org.gradle.kotlin.dsl.register
2123import groovy.lang.Closure
2224import java.io.ByteArrayOutputStream
2325import java.io.File
26+ import java.io.Serializable
2427
25- internal class FirebaseTestLabPlugin : Plugin <Project > {
28+ class FirebaseTestLabPlugin : Plugin <Project > {
2629
2730 open class HiddenExec : Exec () {
2831 init {
@@ -69,7 +72,7 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
6972 }
7073 }
7174
72- data class Sdk (val gcloud : File , val gsutil : File )
75+ data class Sdk (val gcloud : File , val gsutil : File ): Serializable
7376
7477 private fun createDownloadSdkTask (project : Project , cloudSdkPath : String? ): Sdk =
7578 if (cloudSdkPath != null ) {
@@ -190,16 +193,9 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
190193 throw IllegalStateException (" If you want to clear directory before run you need to setup cloudBucketName and cloudDirectoryName" )
191194 }
192195
193- val firebaseTestLabProcessCreator = FirebaseTestLabProcessCreator (
194- sdk,
195- cloudBucketName,
196- cloudDirectoryName,
197- project.logger
198- )
199-
200196 (project.extensions.findByName(ANDROID ) as AppExtension ).apply {
201197 testVariants.toList().forEach { testVariant ->
202- createGroupedTestLabTask(devices, testVariant, firebaseTestLabProcessCreator, ignoreFailures, downloader)
198+ createGroupedTestLabTask(devices, testVariant, ignoreFailures, downloader, sdk, cloudBucketName, cloudDirectoryName )
203199 }
204200 }
205201
@@ -209,19 +205,23 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
209205
210206 data class DeviceAppMap (val device : Device , val apk : BaseVariantOutput )
211207
212- data class Test (val device : Device , val apk : BaseVariantOutput , val testApk : BaseVariantOutput )
213-
208+ data class Test (val device : Device , val apk : BaseVariantOutput , val testApk : BaseVariantOutput ): Serializable
209+
214210 private fun createGroupedTestLabTask (
215- devices : List <Device >,
216- variant : TestVariant ,
217- firebaseTestLabProcessCreator : FirebaseTestLabProcessCreator ,
218- ignoreFailures : Boolean ,
219- downloader : CloudTestResultDownloader ? ) {
211+ devices : List <Device >,
212+ variant : TestVariant ,
213+ ignoreFailures : Boolean ,
214+ downloader : CloudTestResultDownloader ? ,
215+ sdk : Sdk ,
216+ cloudBucketName : String? ,
217+ cloudDirectoryName : String?
218+ ) {
220219 val variantName = variant.testedVariant?.name?.capitalize() ? : " "
221220
222221 val cleanTask = " firebaseTestLabClean${variantName.capitalize()} "
223222
224- val runTestsTask = taskPrefixExecute + variantName.capitalize()
223+ val variantSuffix = variantName.capitalize()
224+ val runTestsTask = taskPrefixExecute + variantSuffix
225225 val runTestsTaskInstrumentation = " ${runTestsTask} Instrumentation"
226226 val runTestsTaskRobo = " ${runTestsTask} Robo"
227227
@@ -266,19 +266,73 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
266266 dependsOn(taskSetup)
267267 dependsOn(arrayOf(test.apk.assemble))
268268 doLast {
269- val result = firebaseTestLabProcessCreator.callFirebaseTestLab(test.device, test.apk.outputFile, TestType .Robo )
269+ val result = FirebaseTestLabProcessCreator .callFirebaseTestLab(ProcessData (
270+ sdk = sdk,
271+ gCloudBucketName = cloudBucketName,
272+ gCloudDirectory = cloudDirectoryName,
273+ device = test.device,
274+ apk = test.apk.outputFile,
275+
276+ testType = TestType .Robo
277+ ))
270278 processResult(result, ignoreFailures)
271279 }
272280 })
273281 }
274-
275- val instrumentationTasks = combineAll(appVersions, variant.outputs, {deviceAndMap, testApk -> Test (deviceAndMap.device, deviceAndMap.apk, testApk)})
276- .map {
277- test ->
278- val devicePart = test.device.name.capitalize()
279- val apkPart = dashToCamelCase(test.apk.name).capitalize()
280- val testApkPart = test.testApk.let { if (it.filters.isEmpty()) " " else dashToCamelCase(it.name).capitalize() }
281- val taskName = " $runTestsTaskInstrumentation$devicePart$apkPart$testApkPart "
282+
283+ val testResultFile = File (project.buildDir, " TestResults.txt" )
284+
285+ val instrumentationTasks: List <Task > = combineAll(appVersions, variant.outputs)
286+ { deviceAndMap, testApk -> Test (deviceAndMap.device, deviceAndMap.apk, testApk) }
287+ .map { test ->
288+ val devicePart = test.device.name.capitalize()
289+ val apkPart = dashToCamelCase(test.apk.name).capitalize()
290+ val testApkPart = test.testApk.let { if (it.filters.isEmpty()) " " else dashToCamelCase(it.name).capitalize() }
291+ val taskName = " $runTestsTaskInstrumentation$devicePart$apkPart$testApkPart "
292+ val numShards = test.device.numShards
293+
294+ if (numShards > 0 ) {
295+ project.tasks.create(taskName, InstrumentationShardingTask ::class .java) {
296+ group = Constants .FIREBASE_TEST_LAB
297+ description = " Run Instrumentation test for ${test.device.name} device on $variantName /${test.apk.name} in Firebase Test Lab"
298+ this .processData = ProcessData (
299+ sdk = sdk,
300+ gCloudBucketName = cloudBucketName,
301+ gCloudDirectory = cloudDirectoryName,
302+ device = test.device,
303+ apk = test.apk.outputFile,
304+ testType = TestType .Instrumentation (test.testApk.outputFile)
305+ )
306+ this .stateFile = testResultFile
307+
308+ if (downloader != null ) {
309+ mustRunAfter(cleanTask)
310+ }
311+ dependsOn(taskSetup)
312+ dependsOn(arrayOf(test.apk.assemble, test.testApk.assemble))
313+
314+ doFirst {
315+ testResultFile.writeText(" " )
316+ }
317+
318+ doLast {
319+ val testResults = testResultFile.readText()
320+ val resultCode: Int? = testResults.toIntOrNull()
321+
322+ logger.lifecycle(" TESTS RESULTS: Every digit represents single shard." )
323+ logger.lifecycle(" \" 0\" means -> tests for particular shard passed." )
324+ logger.lifecycle(" \" 1\" means -> tests for particular shard failed." )
325+
326+ logger.lifecycle(" RESULTS_CODE: $resultCode " )
327+ logger.lifecycle(" When result code is equal to 0 means that all tests for all shards passed, otherwise some of them failed." )
328+
329+ if (resultCode != null ) {
330+ processResult(resultCode, ignoreFailures)
331+ }
332+ }
333+ }
334+
335+ } else {
282336 project.task(taskName, closureOf<Task > {
283337 inputs.files(test.testApk.outputFile, test.apk.outputFile)
284338 group = Constants .FIREBASE_TEST_LAB
@@ -289,13 +343,21 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
289343 dependsOn(taskSetup)
290344 dependsOn(arrayOf(test.apk.assemble, test.testApk.assemble))
291345 doLast {
292- val result = firebaseTestLabProcessCreator.callFirebaseTestLab(test.device, test.apk.outputFile, TestType .Instrumentation (test.testApk.outputFile))
346+ val result = FirebaseTestLabProcessCreator .callFirebaseTestLab(ProcessData (
347+ sdk = sdk,
348+ gCloudBucketName = cloudBucketName,
349+ gCloudDirectory = cloudDirectoryName,
350+ device = test.device,
351+ apk = test.apk.outputFile,
352+ testType = TestType .Instrumentation (test.testApk.outputFile)
353+ ))
293354 processResult(result, ignoreFailures)
294355 }
295356 })
296357 }
358+ }
297359
298- val allInstrumentation = addExecuteAndDownload (runTestsTaskInstrumentation, downloader, cleanTask , closureOf<Task > {
360+ val allInstrumentation: Task = project.task (runTestsTaskInstrumentation, closureOf<Task > {
299361 group = Constants .FIREBASE_TEST_LAB
300362 description = " Run all Instrumentation tests for $variantName in Firebase Test Lab"
301363 dependsOn(instrumentationTasks)
@@ -316,7 +378,7 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
316378 }
317379 })
318380
319- val allRobo = project.task(runTestsTaskRobo, closureOf<Task > {
381+ val allRobo: Task = project.task(runTestsTaskRobo, closureOf<Task > {
320382 group = Constants .FIREBASE_TEST_LAB
321383 description = " Run all Robo tests for $variantName in Firebase Test Lab"
322384 dependsOn(roboTasks)
@@ -337,14 +399,29 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
337399 }
338400 })
339401
340- addExecuteAndDownload (runTestsTask, downloader, cleanTask , closureOf<Task > {
402+ project.task (runTestsTask, closureOf<Task > {
341403 group = Constants .FIREBASE_TEST_LAB
342404 description = " Run all tests for $variantName in Firebase Test Lab"
343405 dependsOn(allRobo, allInstrumentation)
344406 })
407+
408+ if (downloader != null ) {
409+ listOf (variantSuffix, " ${variantSuffix} Instrumentation" ).map{suffix ->
410+ project.task(taskPrefixDownload + suffix, closureOf<Task > {
411+ group = Constants .FIREBASE_TEST_LAB
412+ description = " Run Android Tests in Firebase Test Lab and download artifacts from google storage"
413+ dependsOn(taskSetup)
414+ dependsOn(taskPrefixExecute + suffix)
415+ mustRunAfter(cleanTask)
345416
417+ doLast {
418+ downloader.getResults()
419+ }
420+ })
421+ }
422+ }
346423 }
347-
424+
348425 private fun processResult (result : TestResults , ignoreFailures : Boolean ) {
349426 if (result.isSuccessful) {
350427 project.logger.lifecycle(result.message)
@@ -356,30 +433,22 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
356433 }
357434 }
358435 }
359-
360- private fun addExecuteAndDownload (name : String , downloader : CloudTestResultDownloader ? , cleanTask : String , taskClosure : Closure <Any ?>): Task {
361- val runTask = project.task(name, taskClosure)
362- if (downloader != null ) {
363- val configuration = name.substring(taskPrefixExecute.length)
364- project.task(taskPrefixDownload + configuration, closureOf<Task > {
365- group = Constants .FIREBASE_TEST_LAB
366- description = " Run Android Tests for $configuration in Firebase Test Lab and download artifacts from google storage"
367- dependsOn(taskSetup)
368- dependsOn(name)
369- mustRunAfter(cleanTask)
370-
371- doLast {
372- downloader.getResults()
373- }
374- })
436+
437+ private fun processResult (resultCode : Int , ignoreFailures : Boolean ) =
438+ if (resultCode == 0 ) {
439+ project.logger.lifecycle(" SUCCESS: All tests passed." )
440+ } else {
441+ if (ignoreFailures) {
442+ println (" FAILURE: Tests failed." )
443+ project.logger.error(" FAILURE: Tests failed." )
444+ } else {
445+ throw GradleException (" FAILURE: Tests failed." )
446+ }
375447 }
376- return runTask
377- }
378448}
379449
380-
381450private fun <T1 , T2 , R > combineAll (l1 : Collection <T1 >, l2 : Collection <T2 >, func : (T1 , T2 ) -> R ): List <R > =
382451 l1.flatMap { t1 -> l2.map { t2 -> func(t1, t2)} }
383452
384- private fun dashToCamelCase (dash : String ): String =
453+ fun dashToCamelCase (dash : String ): String =
385454 dash.split(' -' , ' _' ).joinToString(" " ) { it.capitalize() }
0 commit comments