@@ -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
@@ -20,8 +22,9 @@ import org.gradle.kotlin.dsl.closureOf
2022import org.gradle.kotlin.dsl.register
2123import java.io.ByteArrayOutputStream
2224import java.io.File
25+ import java.io.Serializable
2326
24- internal class FirebaseTestLabPlugin : Plugin <Project > {
27+ class FirebaseTestLabPlugin : Plugin <Project > {
2528
2629 open class HiddenExec : Exec () {
2730 init {
@@ -66,7 +69,7 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
6669 }
6770 }
6871
69- data class Sdk (val gcloud : File , val gsutil : File )
72+ data class Sdk (val gcloud : File , val gsutil : File ): Serializable
7073
7174 private fun createDownloadSdkTask (project : Project , cloudSdkPath : String? ): Sdk =
7275 if (cloudSdkPath != null ) {
@@ -187,16 +190,9 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
187190 throw IllegalStateException (" If you want to clear directory before run you need to setup cloudBucketName and cloudDirectoryName" )
188191 }
189192
190- val firebaseTestLabProcessCreator = FirebaseTestLabProcessCreator (
191- sdk,
192- cloudBucketName,
193- cloudDirectoryName,
194- project.logger
195- )
196-
197193 (project.extensions.findByName(ANDROID ) as AppExtension ).apply {
198194 testVariants.toList().forEach { testVariant ->
199- createGroupedTestLabTask(devices, testVariant, firebaseTestLabProcessCreator, ignoreFailures, downloader)
195+ createGroupedTestLabTask(devices, testVariant, ignoreFailures, downloader, sdk, cloudBucketName, cloudDirectoryName )
200196 }
201197 }
202198
@@ -206,14 +202,17 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
206202
207203 data class DeviceAppMap (val device : Device , val apk : BaseVariantOutput )
208204
209- data class Test (val device : Device , val apk : BaseVariantOutput , val testApk : BaseVariantOutput )
210-
205+ data class Test (val device : Device , val apk : BaseVariantOutput , val testApk : BaseVariantOutput ): Serializable
206+
211207 private fun createGroupedTestLabTask (
212- devices : List <Device >,
213- variant : TestVariant ,
214- firebaseTestLabProcessCreator : FirebaseTestLabProcessCreator ,
215- ignoreFailures : Boolean ,
216- downloader : CloudTestResultDownloader ? ) {
208+ devices : List <Device >,
209+ variant : TestVariant ,
210+ ignoreFailures : Boolean ,
211+ downloader : CloudTestResultDownloader ? ,
212+ sdk : Sdk ,
213+ cloudBucketName : String? ,
214+ cloudDirectoryName : String?
215+ ) {
217216 val variantName = variant.testedVariant?.name?.capitalize() ? : " "
218217
219218 val cleanTask = " firebaseTestLabClean${variantName.capitalize()} "
@@ -264,19 +263,73 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
264263 dependsOn(taskSetup)
265264 dependsOn(arrayOf(test.apk.assemble))
266265 doLast {
267- val result = firebaseTestLabProcessCreator.callFirebaseTestLab(test.device, test.apk.outputFile, TestType .Robo )
266+ val result = FirebaseTestLabProcessCreator .callFirebaseTestLab(ProcessData (
267+ sdk = sdk,
268+ gCloudBucketName = cloudBucketName,
269+ gCloudDirectory = cloudDirectoryName,
270+ device = test.device,
271+ apk = test.apk.outputFile,
272+
273+ testType = TestType .Robo
274+ ))
268275 processResult(result, ignoreFailures)
269276 }
270277 })
271278 }
272-
273- val instrumentationTasks = combineAll(appVersions, variant.outputs, {deviceAndMap, testApk -> Test (deviceAndMap.device, deviceAndMap.apk, testApk)})
274- .map {
275- test ->
276- val devicePart = test.device.name.capitalize()
277- val apkPart = dashToCamelCase(test.apk.name).capitalize()
278- val testApkPart = test.testApk.let { if (it.filters.isEmpty()) " " else dashToCamelCase(it.name).capitalize() }
279- val taskName = " $runTestsTaskInstrumentation$devicePart$apkPart$testApkPart "
279+
280+ val testResultFile = File (project.buildDir, " TestResults.txt" )
281+
282+ val instrumentationTasks: List <Task > = combineAll(appVersions, variant.outputs)
283+ { deviceAndMap, testApk -> Test (deviceAndMap.device, deviceAndMap.apk, testApk) }
284+ .map { test ->
285+ val devicePart = test.device.name.capitalize()
286+ val apkPart = dashToCamelCase(test.apk.name).capitalize()
287+ val testApkPart = test.testApk.let { if (it.filters.isEmpty()) " " else dashToCamelCase(it.name).capitalize() }
288+ val taskName = " $runTestsTaskInstrumentation$devicePart$apkPart$testApkPart "
289+ val numShards = test.device.numShards
290+
291+ if (numShards > 0 ) {
292+ project.tasks.create(taskName, InstrumentationShardingTask ::class .java) {
293+ group = Constants .FIREBASE_TEST_LAB
294+ description = " Run Instrumentation test for ${test.device.name} device on $variantName /${test.apk.name} in Firebase Test Lab"
295+ this .processData = ProcessData (
296+ sdk = sdk,
297+ gCloudBucketName = cloudBucketName,
298+ gCloudDirectory = cloudDirectoryName,
299+ device = test.device,
300+ apk = test.apk.outputFile,
301+ testType = TestType .Instrumentation (test.testApk.outputFile)
302+ )
303+ this .stateFile = testResultFile
304+
305+ if (downloader != null ) {
306+ mustRunAfter(cleanTask)
307+ }
308+ dependsOn(taskSetup)
309+ dependsOn(arrayOf(test.apk.assemble, test.testApk.assemble))
310+
311+ doFirst {
312+ testResultFile.writeText(" " )
313+ }
314+
315+ doLast {
316+ val testResults = testResultFile.readText()
317+ val resultCode: Int? = testResults.toIntOrNull()
318+
319+ logger.lifecycle(" TESTS RESULTS: Every digit represents single shard." )
320+ logger.lifecycle(" \" 0\" means -> tests for particular shard passed." )
321+ logger.lifecycle(" \" 1\" means -> tests for particular shard failed." )
322+
323+ logger.lifecycle(" RESULTS_CODE: $resultCode " )
324+ logger.lifecycle(" When result code is equal to 0 means that all tests for all shards passed, otherwise some of them failed." )
325+
326+ if (resultCode != null ) {
327+ processResult(resultCode, ignoreFailures)
328+ }
329+ }
330+ }
331+
332+ } else {
280333 project.task(taskName, closureOf<Task > {
281334 inputs.files(test.testApk.outputFile, test.apk.outputFile)
282335 group = Constants .FIREBASE_TEST_LAB
@@ -287,13 +340,21 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
287340 dependsOn(taskSetup)
288341 dependsOn(arrayOf(test.apk.assemble, test.testApk.assemble))
289342 doLast {
290- val result = firebaseTestLabProcessCreator.callFirebaseTestLab(test.device, test.apk.outputFile, TestType .Instrumentation (test.testApk.outputFile))
343+ val result = FirebaseTestLabProcessCreator .callFirebaseTestLab(ProcessData (
344+ sdk = sdk,
345+ gCloudBucketName = cloudBucketName,
346+ gCloudDirectory = cloudDirectoryName,
347+ device = test.device,
348+ apk = test.apk.outputFile,
349+ testType = TestType .Instrumentation (test.testApk.outputFile)
350+ ))
291351 processResult(result, ignoreFailures)
292352 }
293353 })
294354 }
355+ }
295356
296- val allInstrumentation = project.task(runTestsTaskInstrumentation, closureOf<Task > {
357+ val allInstrumentation: Task = project.task(runTestsTaskInstrumentation, closureOf<Task > {
297358 group = Constants .FIREBASE_TEST_LAB
298359 description = " Run all Instrumentation tests for $variantName in Firebase Test Lab"
299360 dependsOn(instrumentationTasks)
@@ -314,7 +375,7 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
314375 }
315376 })
316377
317- val allRobo = project.task(runTestsTaskRobo, closureOf<Task > {
378+ val allRobo: Task = project.task(runTestsTaskRobo, closureOf<Task > {
318379 group = Constants .FIREBASE_TEST_LAB
319380 description = " Run all Robo tests for $variantName in Firebase Test Lab"
320381 dependsOn(roboTasks)
@@ -340,7 +401,7 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
340401 description = " Run all tests for $variantName in Firebase Test Lab"
341402 dependsOn(allRobo, allInstrumentation)
342403 })
343-
404+
344405 if (downloader != null ) {
345406 project.task(downloadTask, closureOf<Task > {
346407 group = Constants .FIREBASE_TEST_LAB
@@ -355,7 +416,7 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
355416 })
356417 }
357418 }
358-
419+
359420 private fun processResult (result : TestResults , ignoreFailures : Boolean ) {
360421 if (result.isSuccessful) {
361422 project.logger.lifecycle(result.message)
@@ -367,11 +428,22 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
367428 }
368429 }
369430 }
431+
432+ private fun processResult (resultCode : Int , ignoreFailures : Boolean ) =
433+ if (resultCode == 0 ) {
434+ project.logger.lifecycle(" SUCCESS: All tests passed." )
435+ } else {
436+ if (ignoreFailures) {
437+ println (" FAILURE: Tests failed." )
438+ project.logger.error(" FAILURE: Tests failed." )
439+ } else {
440+ throw GradleException (" FAILURE: Tests failed." )
441+ }
442+ }
370443}
371444
372-
373445private fun <T1 , T2 , R > combineAll (l1 : Collection <T1 >, l2 : Collection <T2 >, func : (T1 , T2 ) -> R ): List <R > =
374446 l1.flatMap { t1 -> l2.map { t2 -> func(t1, t2)} }
375447
376- private fun dashToCamelCase (dash : String ): String =
448+ fun dashToCamelCase (dash : String ): String =
377449 dash.split(' -' , ' _' ).joinToString(" " ) { it.capitalize() }
0 commit comments