@@ -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,58 @@ 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: List <Task > = 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 instrumentationTasks: List <Task > = combineAll(appVersions, variant.outputs)
281+ { deviceAndMap, testApk -> Test (deviceAndMap.device, deviceAndMap.apk, testApk) }
282+ .map { test ->
283+ val devicePart = test.device.name.capitalize()
284+ val apkPart = dashToCamelCase(test.apk.name).capitalize()
285+ val testApkPart = test.testApk.let { if (it.filters.isEmpty()) " " else dashToCamelCase(it.name).capitalize() }
286+ val taskName = " $runTestsTaskInstrumentation$devicePart$apkPart$testApkPart "
287+ val numShards = test.device.numShards
288+
289+ val file = File (project.buildDir, " TestResults.txt" )
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 = file
304+
305+ if (downloader != null ) {
306+ mustRunAfter(cleanTask)
307+ }
308+ dependsOn(taskSetup)
309+ dependsOn(arrayOf(test.apk.assemble, test.testApk.assemble))
310+
311+ doLast {
312+ val resultCode = file.readText().toInt()
313+ processResult(resultCode, ignoreFailures)
314+ }
315+ }
316+
317+ } else {
280318 project.task(taskName, closureOf<Task > {
281319 inputs.files(test.testApk.outputFile, test.apk.outputFile)
282320 group = Constants .FIREBASE_TEST_LAB
@@ -287,11 +325,19 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
287325 dependsOn(taskSetup)
288326 dependsOn(arrayOf(test.apk.assemble, test.testApk.assemble))
289327 doLast {
290- val result = firebaseTestLabProcessCreator.callFirebaseTestLab(test.device, test.apk.outputFile, TestType .Instrumentation (test.testApk.outputFile))
328+ val result = FirebaseTestLabProcessCreator .callFirebaseTestLab(ProcessData (
329+ sdk = sdk,
330+ gCloudBucketName = cloudBucketName,
331+ gCloudDirectory = cloudDirectoryName,
332+ device = test.device,
333+ apk = test.apk.outputFile,
334+ testType = TestType .Instrumentation (test.testApk.outputFile)
335+ ))
291336 processResult(result, ignoreFailures)
292337 }
293338 })
294339 }
340+ }
295341
296342 val allInstrumentation: Task = project.task(runTestsTaskInstrumentation, closureOf<Task > {
297343 group = Constants .FIREBASE_TEST_LAB
@@ -334,63 +380,13 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
334380 if (roboTasks.isEmpty()) throw IllegalStateException (" Nothing match your filter" )
335381 }
336382 })
337-
338- combineAll(appVersions, variant.outputs) { deviceAndMap, testApk -> Test (deviceAndMap.device, deviceAndMap.apk, testApk) }
339- .map { test ->
340- val devicePart = test.device.name.capitalize()
341- val apkPart = dashToCamelCase(test.apk.name).capitalize()
342- val testApkPart = test.testApk.let { if (it.filters.isEmpty()) " " else dashToCamelCase(it.name).capitalize() }
343- val numShards = test.device.numShards
344- if (numShards > 1 ) {
345-
346- val shardedTasks: List <Task > = (0 until test.device.numShards).map { shardIndex ->
347- val taskName = " $runTestsTaskInstrumentation$devicePart$apkPart$testApkPart " + " NumShards$numShards " + " ShardIndex$shardIndex "
348- project.task(taskName, closureOf<Task > {
349- inputs.files(test.testApk.outputFile, test.apk.outputFile)
350- group = Constants .FIREBASE_TEST_LAB
351- description = " Run Instrumentation test for ${test.device.name} device on $variantName /${test.apk.name} with ShardNumber $numShards and ShardIndex $shardIndex in Firebase Test Lab"
352- if (downloader != null ) {
353- mustRunAfter(cleanTask)
354- }
355- dependsOn(taskSetup)
356- dependsOn(arrayOf(test.apk.assemble, test.testApk.assemble))
357- doLast {
358- val result = firebaseTestLabProcessCreator.callFirebaseTestLab(test.device, test.apk.outputFile, TestType .Instrumentation (test.testApk.outputFile), shardIndex)
359- processResult(result, ignoreFailures)
360- }
361- })
362- }
363-
364- val runTestsTaskSharded = " firebaseTestLabExecuteAllShardForConfiguration$devicePart$apkPart$testApkPart "
365- project.task(runTestsTaskSharded, closureOf<Task > {
366- group = Constants .FIREBASE_TEST_LAB
367- description = " Run all tests for $variantName sharded in Firebase Test Lab"
368- dependsOn(shardedTasks)
369-
370- doFirst {
371- if (devices.isEmpty()) throw IllegalStateException (" You need to set et least one device in:\n " +
372- " firebaseTestLab {" +
373- " devices {\n " +
374- " nexus6 {\n " +
375- " androidApiLevels = [21]\n " +
376- " deviceIds = [\" Nexus6\" ]\n " +
377- " locales = [\" en\" ]\n " +
378- " }\n " +
379- " } " +
380- " }" )
381-
382- if (roboTasks.isEmpty()) throw IllegalStateException (" Nothing match your filter" )
383- }
384- })
385- }
386- }
387383
388384 project.task(runTestsTask, closureOf<Task > {
389385 group = Constants .FIREBASE_TEST_LAB
390386 description = " Run all tests for $variantName in Firebase Test Lab"
391387 dependsOn(allRobo, allInstrumentation)
392388 })
393-
389+
394390 if (downloader != null ) {
395391 project.task(downloadTask, closureOf<Task > {
396392 group = Constants .FIREBASE_TEST_LAB
@@ -405,7 +401,7 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
405401 })
406402 }
407403 }
408-
404+
409405 private fun processResult (result : TestResults , ignoreFailures : Boolean ) {
410406 if (result.isSuccessful) {
411407 project.logger.lifecycle(result.message)
@@ -417,11 +413,21 @@ internal class FirebaseTestLabPlugin : Plugin<Project> {
417413 }
418414 }
419415 }
416+
417+ private fun processResult (resultCode : Int , ignoreFailures : Boolean ) =
418+ if (resultCode == 0 ) {
419+ project.logger.lifecycle(" SUCCESS: All tests passed." )
420+ } else {
421+ if (ignoreFailures) {
422+ project.logger.error(" FAILURE: Tests failed." )
423+ } else {
424+ throw GradleException (" FAILURE: Tests failed." )
425+ }
426+ }
420427}
421428
422-
423429private fun <T1 , T2 , R > combineAll (l1 : Collection <T1 >, l2 : Collection <T2 >, func : (T1 , T2 ) -> R ): List <R > =
424430 l1.flatMap { t1 -> l2.map { t2 -> func(t1, t2)} }
425431
426- private fun dashToCamelCase (dash : String ): String =
432+ fun dashToCamelCase (dash : String ): String =
427433 dash.split(' -' , ' _' ).joinToString(" " ) { it.capitalize() }
0 commit comments