Skip to content

Commit

Permalink
Merge branch 'master' into #848-auto-update-dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
piotradamczyk5 authored Sep 23, 2020
2 parents 6fab067 + 52b7ba4 commit 33b9d16
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 24 deletions.
18 changes: 15 additions & 3 deletions test_runner/src/main/kotlin/ftl/android/AndroidCatalog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,26 @@ object AndroidCatalog {
return SupportedDeviceConfig
}

fun isVirtualDevice(device: AndroidDevice?, projectId: String): Boolean = device?.androidModelId?.let { isVirtualDevice(it, projectId) } ?: false
fun isVirtualDevice(device: AndroidDevice?, projectId: String): Boolean = device
?.androidModelId
?.let { isVirtualDevice(it, projectId) }
?: false

fun isVirtualDevice(modelId: String, projectId: String): Boolean {
val form = deviceCatalog(projectId).models.find { it.id == modelId }?.form ?: "PHYSICAL"
return form == "VIRTUAL"
val form = deviceCatalog(projectId).models
.find { it.id.equals(modelId, ignoreCase = true) }?.form
?: DeviceType.PHYSICAL.name.also {
println("Unable to find device type for $modelId. PHYSICAL used as fallback in cost calculations")
}

return form.equals(DeviceType.VIRTUAL.name, ignoreCase = true)
}
}

enum class DeviceType {
VIRTUAL, PHYSICAL
}

sealed class DeviceConfigCheck
object SupportedDeviceConfig : DeviceConfigCheck()
object UnsupportedModelId : DeviceConfigCheck()
Expand Down
36 changes: 16 additions & 20 deletions test_runner/src/main/kotlin/ftl/reports/outcome/BillableMinutes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package ftl.reports.outcome

import com.google.api.services.toolresults.model.Step
import ftl.android.AndroidCatalog
import ftl.android.DeviceType
import ftl.environment.orUnknown
import ftl.util.billableMinutes
import kotlin.math.min

Expand All @@ -10,33 +12,27 @@ data class BillableMinutes(
val physical: Long = 0
)

fun List<Step>.calculateAndroidBillableMinutes(
projectId: String,
timeoutValue: Long
): BillableMinutes =
groupByDeviceType(projectId).run {
fun List<Step>.calculateAndroidBillableMinutes(projectId: String, timeoutValue: Long): BillableMinutes = this
.groupBy { deviceType(it.deviceModel(), projectId) }
.run {
BillableMinutes(
virtual = get(true)?.sumBillableMinutes(timeoutValue) ?: 0,
physical = get(false)?.sumBillableMinutes(timeoutValue) ?: 0
virtual = get(DeviceType.VIRTUAL)?.sumBillableMinutes(timeoutValue) ?: 0,
physical = get(DeviceType.PHYSICAL)?.sumBillableMinutes(timeoutValue) ?: 0
)
}

private fun List<Step>.groupByDeviceType(projectId: String) =
groupBy {
AndroidCatalog.isVirtualDevice(
it.axisValue(),
projectId
)
}
private fun Step.deviceModel() = dimensionValue.find { it.key.equals("Model", ignoreCase = true) }?.value.orUnknown()

private fun List<Step>.sumBillableMinutes(timeout: Long) =
mapNotNull { step ->
step.getBillableSeconds(default = timeout)
}.map {
billableMinutes(it)
}.sum()
private fun List<Step>.sumBillableMinutes(timeout: Long) = this
.mapNotNull { it.getBillableSeconds(default = timeout) }
.map { billableMinutes(it) }
.sum()

private fun Step.getBillableSeconds(default: Long) =
testExecutionStep?.testTiming?.testProcessDuration?.seconds?.let {
min(it, default)
}

private fun deviceType(modelId: String, projectId: String) =
if (AndroidCatalog.isVirtualDevice(modelId, projectId)) DeviceType.VIRTUAL
else DeviceType.PHYSICAL
136 changes: 136 additions & 0 deletions test_runner/src/test/kotlin/ftl/reports/outcome/BillableMinutesTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package ftl.reports.outcome

import com.google.api.services.testing.model.AndroidModel
import com.google.api.services.toolresults.model.Step
import ftl.android.DeviceType
import ftl.gc.GcTesting
import ftl.http.executeWithRetry
import io.mockk.every
import io.mockk.mockkObject
import io.mockk.unmockkAll
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.contrib.java.lang.system.SystemOutRule

class BillableMinutesTest {

@get:Rule
val output: SystemOutRule = SystemOutRule().enableLog().muteForSuccessfulTests()

private val androidModels = listOf<AndroidModel>(
make { id = "NexusLowRes"; form = DeviceType.VIRTUAL.name },
make { id = "Nexus5"; form = DeviceType.VIRTUAL.name },
make { id = "g3"; form = DeviceType.PHYSICAL.name },
make { id = "sailfish"; form = DeviceType.PHYSICAL.name }
)

@Before
fun setUp() {
output.clearLog()
mockkObject(GcTesting)
every {
GcTesting
.get
.testEnvironmentCatalog()
.get(any())
.setProjectId(any())
.executeWithRetry()
} returns make {
androidDeviceCatalog = make { models = androidModels }
}
}

@After
fun tearDown() = unmockkAll()

@Test
fun `should return billing for virtual device only`() {
val expected = 1L
val step: Step = makeStep()

val minutes = listOf(step).calculateAndroidBillableMinutes(projectId = "anyId", timeoutValue = 1000L)

verifyBilling(minutes, expectedVirtual = expected)
}

@Test
fun `should return billing for physical devices only`() {
val expected = 1L
val step: Step = makeStep(model = "g3")

val minutes = listOf(step).calculateAndroidBillableMinutes(projectId = "anyId", timeoutValue = 1000L)

verifyBilling(minutes, expectedPhysical = expected)
}

@Test
fun `should return mixed billing`() {
val expectedVirtual = 3L
val expectedPhysical = 5L
val step1: Step = makeStep(model = "nexuslowres", duration = 123)
val step2: Step = makeStep(model = "SaIlFisH", duration = 245)

val minutes = listOf(step1, step2).calculateAndroidBillableMinutes(projectId = "anyId", timeoutValue = 1000L)

verifyBilling(minutes, expectedVirtual, expectedPhysical)
}

@Test
fun `should return multiple mixed billing`() {
val expectedVirtual = 9L
val expectedPhysical = 16L
val step1: Step = makeStep(model = "Nexus5", duration = 123)
val step2: Step = makeStep(model = "SaIlFisH", duration = 245)
val step3: Step = makeStep(model = "NEXUSLOWRES", duration = 352)
val step4: Step = makeStep(model = "G3", duration = 657)

val minutes = listOf(step1, step2, step3, step4).calculateAndroidBillableMinutes(projectId = "anyId", timeoutValue = 1000L)

verifyBilling(minutes, expectedVirtual, expectedPhysical)
}

@Test
fun `should return multiple mixed billing - timeouted`() {
val timeout = 120L
val expectedVirtual = 3L
val expectedPhysical = 4L
val step1: Step = makeStep(model = "Nexus5", duration = 23)
val step2: Step = makeStep(model = "SaIlFisH", duration = 245)
val step3: Step = makeStep(model = "NEXUSLOWRES", duration = 352)
val step4: Step = makeStep(model = "G3", duration = 657)

val minutes = listOf(step1, step2, step3, step4).calculateAndroidBillableMinutes(projectId = "anyId", timeoutValue = timeout)

verifyBilling(minutes, expectedVirtual, expectedPhysical)
}

@Test
fun `should return billing for physical device when unable to find device type`() {
val expectedPhysical = 3L
val modelId = "uncommon"
val step: Step = makeStep(model = modelId, duration = 123)

val minutes = listOf(step).calculateAndroidBillableMinutes(projectId = "anyId", timeoutValue = 1000L)

verifyBilling(minutes, expectedPhysical = expectedPhysical)
assertTrue(output.log.contains("Unable to find device type for $modelId. PHYSICAL used as fallback in cost calculations"))
}
}

private fun makeStep(model: String = "NexusLowRes", duration: Long = 15L): Step = make {
dimensionValue = listOf(make { key = "Model"; value = model })
testExecutionStep = make {
testTiming = make {
testProcessDuration = make { seconds = duration }
}
}
}

private fun verifyBilling(billing: BillableMinutes, expectedVirtual: Long = 0, expectedPhysical: Long = 0) {
assertEquals("Physical devices minutes should be $expectedPhysical", expectedPhysical, billing.physical)
assertEquals("Virtual devices minutes should be $expectedVirtual", expectedVirtual, billing.virtual)
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,4 @@ class CreateMatrixOutcomeSummaryKtTest {
}
}

private inline fun <reified T : Any> make(block: T.() -> Unit = {}): T = T::class.createInstance().apply(block)
internal inline fun <reified T : Any> make(block: T.() -> Unit = {}): T = T::class.createInstance().apply(block)

0 comments on commit 33b9d16

Please sign in to comment.