Skip to content

Commit

Permalink
Merge pull request #99 from grzechu92/main
Browse files Browse the repository at this point in the history
ignoreProblematicDevices property added
  • Loading branch information
joshschriever authored Aug 24, 2021
2 parents 10b731c + 53d5531 commit 4876641
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 13 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ gordon {

// Default is no filter
testFilter.set("ExampleTest.runThisMethod,RunThisWholeTestClass,com.example.runthispackage,com.example.RunTestsWithThisAnnotation")

// Default is false - to ignore devices that failed during artifacts installation, may be useful with large number of devices and SinglePool strategy
ignoreProblematicDevices.set(true)
}
```

Expand Down
22 changes: 20 additions & 2 deletions gordon-plugin/src/main/kotlin/com/banno/gordon/AdbExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.banno.gordon
import arrow.core.Either
import arrow.core.computations.either
import arrow.core.flatMap
import arrow.core.right
import com.android.tools.build.bundletool.commands.BuildApksCommand
import com.android.tools.build.bundletool.commands.InstallApksCommand
import com.android.tools.build.bundletool.device.AdbServer
Expand Down Expand Up @@ -157,7 +158,9 @@ internal fun List<Device>.reinstall(
signingConfig: SigningConfig,
instrumentationApk: File,
installTimeoutMillis: Long,
adb: AdbServer
adb: AdbServer,
ignoreProblematicDevices: Boolean,
problematicDevices: MutableList<Device>,
): Either<Throwable, Unit> = either.eager {
val applicationApkSet = applicationAab?.let { buildApkSet(adb, it, signingConfig) }?.bind()

Expand All @@ -174,8 +177,23 @@ internal fun List<Device>.reinstall(
logger.lifecycle("${device.serialNumber}: installing $instrumentationPackage")
device.safeUninstall(installTimeoutMillis, instrumentationPackage)
device.installApk(installTimeoutMillis, instrumentationApk).bind()
}
}.ignoreErrorIfPossible(device, logger, ignoreProblematicDevices, problematicDevices)
}
}.awaitAll()
}.forEach { it.bind() }
}

private fun Either<Throwable, Unit>.ignoreErrorIfPossible(
device: Device,
logger: Logger,
ignoreProblematicDevices: Boolean,
problematicDevices: MutableList<Device>,
): Either<Throwable, Unit> = fold(
ifLeft = {
Either.conditionally(ignoreProblematicDevices, ifFalse = { it }) {
problematicDevices.add(device)
logger.warn("${device.serialNumber}: ignored installation failure", it)
}
},
ifRight = { it.right() }
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ abstract class GordonExtension @Inject constructor(
val testTimeoutMillis: Property<Long> = objects.property()
val testFilter: Property<String> = objects.property()
val testInstrumentationRunner: Property<String> = objects.property()
val ignoreProblematicDevices: Property<Boolean> = objects.property()

init {
poolingStrategy.convention(PoolingStrategy.PoolPerDevice)
Expand All @@ -25,5 +26,6 @@ abstract class GordonExtension @Inject constructor(
testTimeoutMillis.convention(120_000)
testFilter.convention("")
testInstrumentationRunner.convention("")
ignoreProblematicDevices.convention(false)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class GordonPlugin : Plugin<Project> {
task.testTimeoutMillis.set(gordonExtension.testTimeoutMillis)
task.extensionTestFilter.set(gordonExtension.testFilter)
task.extensionTestInstrumentationRunner.set(gordonExtension.testInstrumentationRunner)
task.ignoreProblematicDevices.set(gordonExtension.ignoreProblematicDevices)
}

val testedExtension = project.extensions.getByType<TestedExtension>()
Expand Down
48 changes: 37 additions & 11 deletions gordon-plugin/src/main/kotlin/com/banno/gordon/GordonTestTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.banno.gordon
import arrow.core.Either
import arrow.core.computations.either
import arrow.core.left
import arrow.core.right
import com.android.tools.build.bundletool.device.Device
import kotlinx.coroutines.Dispatchers
import org.gradle.api.DefaultTask
import org.gradle.api.file.Directory
Expand Down Expand Up @@ -67,6 +69,9 @@ internal abstract class GordonTestTask @Inject constructor(
@get:Input
internal val tabletShortestWidthDp: Property<Int> = objects.property()

@get:Internal
internal val ignoreProblematicDevices: Property<Boolean> = objects.property()

@get:Internal
internal val retryQuota: Property<Int> = objects.property()

Expand Down Expand Up @@ -140,22 +145,17 @@ internal abstract class GordonTestTask @Inject constructor(
reportDirectory.get().asFile.clear().bind()

val adb = initializeDefaultAdbServer().bind()
val pools = calculatePools(
val problematicDevices = mutableListOf<Device>()
val originalPools = calculatePools(
adb,
poolingStrategy.get(),
tabletShortestWidthDp.get().takeIf { it > -1 }
).bind()
val testCases = loadTestSuite(instrumentationApk).bind()
.filter { it.matchesFilter(testFilters.get()) }

when {
testCases.isEmpty() -> IllegalStateException("No test cases found").left().bind<Unit>()
pools.isEmpty() -> IllegalStateException("No devices found").left().bind<Unit>()
pools.any { it.devices.isEmpty() } -> {
val emptyPools = pools.filter { it.devices.isEmpty() }.map { it.poolName }
IllegalStateException("No devices found in pools $emptyPools").left().bind<Unit>()
}
}
testCases.validateTestCases().bind()
originalPools.validateDevicePools().bind()

val applicationAab = applicationAab.get().asFile.takeUnless { it == PLACEHOLDER_APPLICATION_AAB }
val applicationPackage = applicationPackage.get().takeUnless { it == PLACEHOLDER_APPLICATION_PACKAGE }
Expand All @@ -169,7 +169,7 @@ internal abstract class GordonTestTask @Inject constructor(
keyPassword = signingConfigCredentials.get().keyPassword
)

pools.flatMap { it.devices }.reinstall(
originalPools.flatMap { it.devices }.reinstall(
dispatcher = Dispatchers.Default,
logger = logger,
applicationPackage = applicationPackage,
Expand All @@ -179,9 +179,14 @@ internal abstract class GordonTestTask @Inject constructor(
signingConfig = signingConfig,
instrumentationApk = instrumentationApk,
installTimeoutMillis = installTimeoutMillis.get(),
adb = adb
adb = adb,
ignoreProblematicDevices = ignoreProblematicDevices.get(),
problematicDevices = problematicDevices,
).bind()

val pools = originalPools.filterProblematicDevices(problematicDevices)
pools.validateDevicePools().bind()

val testResults = runAllTests(
dispatcher = Dispatchers.Default,
logger = logger,
Expand Down Expand Up @@ -222,6 +227,27 @@ internal abstract class GordonTestTask @Inject constructor(
}
}

internal fun List<DevicePool>.validateDevicePools() =
when {
isEmpty() -> IllegalStateException("No devices found").left()
any { it.devices.isEmpty() } -> {
val emptyPools = filter { it.devices.isEmpty() }.map { it.poolName }
IllegalStateException("No devices found in pools $emptyPools").left()
}
else -> Unit.right()
}

internal fun List<TestCase>.validateTestCases() =
when {
isEmpty() -> IllegalStateException("No test cases found").left()
else -> Unit.right()
}

internal fun List<DevicePool>.filterProblematicDevices(problematicDevices: List<Device>) =
map { pool ->
pool.copy(devices = pool.devices - problematicDevices)
}

internal fun TestCase.matchesFilter(filters: List<String>): Boolean {
val fullyQualifiedTestMethod = "$fullyQualifiedClassName.$methodName"

Expand Down

0 comments on commit 4876641

Please sign in to comment.