diff --git a/subprojects/test-runner/client/build.gradle.kts b/subprojects/test-runner/client/build.gradle.kts index a32346df66..8b840d4b89 100644 --- a/subprojects/test-runner/client/build.gradle.kts +++ b/subprojects/test-runner/client/build.gradle.kts @@ -25,6 +25,7 @@ dependencies { testImplementation(project(":gradle:test-project")) testImplementation(testFixtures(project(":common:logger"))) testImplementation(testFixtures(project(":common:time"))) + testImplementation(testFixtures(project(":test-runner:service"))) testImplementation(libs.kotlinReflect) testImplementation(libs.mockitoKotlin) testImplementation(libs.mockitoJUnitJupiter) diff --git a/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/TestMetricsAggregator.kt b/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/TestMetricsAggregator.kt index 1d3fb2e682..41420a8cfc 100644 --- a/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/TestMetricsAggregator.kt +++ b/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/TestMetricsAggregator.kt @@ -1,18 +1,20 @@ package com.avito.runner.scheduler.metrics +import com.avito.android.Result + internal interface TestMetricsAggregator { - fun initialDelay(): Long? + fun initialDelay(): Result - fun endDelay(): Long? + fun endDelay(): Result - fun medianQueueTime(): Long? + fun medianQueueTime(): Result - fun medianInstallationTime(): Long? + fun medianInstallationTime(): Result - fun suiteTime(): Long? + fun suiteTime(): Result fun totalTime(): Long - fun medianDeviceUtilization(): Long? + fun medianDeviceUtilization(): Result } diff --git a/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/TestMetricsAggregatorImpl.kt b/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/TestMetricsAggregatorImpl.kt index 331005cfea..3a0e8a4450 100644 --- a/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/TestMetricsAggregatorImpl.kt +++ b/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/TestMetricsAggregatorImpl.kt @@ -1,10 +1,12 @@ package com.avito.runner.scheduler.metrics +import com.avito.android.Result import com.avito.math.median import com.avito.runner.scheduler.metrics.model.DeviceKey import com.avito.runner.scheduler.metrics.model.DeviceTimestamps +import com.avito.runner.scheduler.metrics.model.TestTimestamps -internal class TestMetricsAggregatorImpl( +internal data class TestMetricsAggregatorImpl( private val testSuiteStartedTime: Long, private val testSuiteEndedTime: Long, private val deviceTimestamps: Map @@ -12,55 +14,57 @@ internal class TestMetricsAggregatorImpl( private val testTimestamps = deviceTimestamps.flatMap { it.value.testTimestamps.values } - private val firstTestStarted = testTimestamps - .mapNotNull { it.started } + private val firstTestStarted: Result = testTimestamps.filterIsInstance() + .map { it.startTime } .minOrNull() + .toResult { "Cannot calculate first started test time" } - private val lastTestEnded = testTimestamps - .mapNotNull { it.finished } + private val lastTestEnded: Result = testTimestamps.filterIsInstance() + .map { it.finishTime } .maxOrNull() + .toResult { "Cannot calculate last ended test time" } - private val queueTimes: List = testTimestamps - .mapNotNull { it.onDevice } - .map { it - testSuiteStartedTime } + private val queueTimes: List = testTimestamps.filterIsInstance() + .map { it.onDevice - testSuiteStartedTime } - private val installationTimes: List = testTimestamps - .mapNotNull { it.installationTime } + private val installationTimes: List = testTimestamps.filterIsInstance() + .map { it.installationTime } - override fun initialDelay(): Long? = firstTestStarted?.let { it - testSuiteStartedTime } + override fun initialDelay(): Result = firstTestStarted.map { it - testSuiteStartedTime } - override fun endDelay(): Long? = lastTestEnded?.let { testSuiteEndedTime - it } + override fun endDelay(): Result = lastTestEnded.map { testSuiteEndedTime - it } - override fun medianQueueTime(): Long? = queueTimes.aggregateOrNull { it.median() } + override fun medianQueueTime(): Result = queueTimes.aggregate( + { it.median() }, + { "Cannot calculate median queue time" } + ) - override fun medianInstallationTime(): Long? = installationTimes.aggregateOrNull { it.median() } + override fun medianInstallationTime(): Result = installationTimes.aggregate( + { it.median() }, + { "Cannot calculate median installation time" } + ) - override fun suiteTime(): Long? = if (lastTestEnded != null && firstTestStarted != null) { - lastTestEnded - firstTestStarted - } else { - null - } + override fun suiteTime(): Result = lastTestEnded.combine(firstTestStarted) { last, first -> last - first } override fun totalTime() = testSuiteEndedTime - testSuiteStartedTime - override fun medianDeviceUtilization(): Long? = - deviceTimestamps.values - .mapNotNull { it.utilizationPercent } - .aggregateOrNull { it.median() } - - /** - * return null if no data - */ - private fun List.aggregateOrNull(aggregateFunc: (List) -> Number): Long? { - return if (isNotEmpty()) { - val result = aggregateFunc.invoke(this).toLong() - if (result > 0) { - result - } else { - null - } - } else { - null + override fun medianDeviceUtilization(): Result = + deviceTimestamps.values.filterIsInstance() + .map { it.utilizationPercent } + .aggregate({ it.median() }) { "Cannot calculate median device utilization" } + + private inline fun Long?.toResult(lazyMessage: () -> String): Result = when (this) { + null -> Result.Failure(IllegalStateException(lazyMessage())) + else -> Result.Success(this) + } + + private inline fun List.aggregate( + aggregateFunc: (List) -> Number, + lazyMessage: () -> String + ): Result { + return when { + this.isEmpty() -> Result.Failure(IllegalStateException(lazyMessage())) + else -> Result.Success(aggregateFunc(this).toLong()) } } } diff --git a/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/TestMetricsListenerImpl.kt b/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/TestMetricsListenerImpl.kt index 55e2bbdf6c..970bf0c9b2 100644 --- a/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/TestMetricsListenerImpl.kt +++ b/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/TestMetricsListenerImpl.kt @@ -6,6 +6,8 @@ import com.avito.runner.scheduler.metrics.model.DeviceKey import com.avito.runner.scheduler.metrics.model.DeviceTimestamps import com.avito.runner.scheduler.metrics.model.TestKey import com.avito.runner.scheduler.metrics.model.TestTimestamps +import com.avito.runner.scheduler.metrics.model.finish +import com.avito.runner.scheduler.metrics.model.start import com.avito.runner.service.model.DeviceTestCaseRun import com.avito.runner.service.model.intention.Intention import com.avito.runner.service.model.intention.State @@ -31,10 +33,9 @@ internal class TestMetricsListenerImpl( } override suspend fun onDeviceCreated(device: Device, state: State) { - deviceTimestamps[device.key()] = DeviceTimestamps( + deviceTimestamps[device.key()] = DeviceTimestamps.Started( created = timeProvider.nowInMillis(), testTimestamps = mutableMapOf(), - finished = 0 ) } @@ -44,20 +45,18 @@ internal class TestMetricsListenerImpl( logger.warn("Fail to set timestamp value, previous required values not found, this shouldn't happen") null } else { - oldValue.testTimestamps[intention.testKey()] = TestTimestamps( - onDevice = timeProvider.nowInMillis(), - started = null, - finished = null - ) + oldValue.testTimestamps[intention.testKey()] = TestTimestamps.NotStarted(timeProvider.nowInMillis()) oldValue } } } override suspend fun onStatePrepared(device: Device, state: State) { + // empty } override suspend fun onApplicationInstalled(device: Device, installation: DeviceInstallation) { + // empty } override suspend fun onTestStarted(device: Device, intention: Intention) { @@ -74,7 +73,7 @@ internal class TestMetricsListenerImpl( ) null } else { - testTimestamps.copy(started = timeProvider.nowInMillis()) + testTimestamps.start(timeProvider.nowInMillis()) } } oldValue @@ -96,7 +95,7 @@ internal class TestMetricsListenerImpl( ) null } else { - testTimestamps.copy(finished = timeProvider.nowInMillis()) + testTimestamps.finish(timeProvider.nowInMillis()) } } oldValue @@ -105,6 +104,7 @@ internal class TestMetricsListenerImpl( } override suspend fun onIntentionFail(device: Device, intention: Intention, reason: Throwable) { + // empty } override suspend fun onDeviceDied(device: Device, message: String, reason: Throwable) { @@ -117,7 +117,7 @@ internal class TestMetricsListenerImpl( logger.warn("Fail to set timestamp value, previous required values not found, this shouldn't happen") null } else { - oldValue.copy(finished = timeProvider.nowInMillis()) + oldValue.finish(finished = timeProvider.nowInMillis()) } } } @@ -126,31 +126,36 @@ internal class TestMetricsListenerImpl( val aggregator: TestMetricsAggregator = createTestMetricsAggregator() with(testMetricsSender) { - aggregator.initialDelay() - ?.let { sendInitialDelay(it) } - ?: logger.warn("Not sending initial delay, no data") - - aggregator.medianQueueTime() - ?.let { sendMedianQueueTime(it) } - ?: logger.warn("Not sending median test queue time, no data") - - aggregator.medianInstallationTime() - ?.let { sendMedianInstallationTime(it) } - ?: logger.warn("Not sending median test start time, no data") - - aggregator.endDelay() - ?.let { sendEndDelay(it) } - ?: logger.warn("Not sending end delay, no data") - - aggregator.suiteTime() - ?.let { sendSuiteTime(it) } - ?: logger.warn("Not sending suite time, no data") + aggregator.initialDelay().fold( + { sendInitialDelay(it) }, + { logger.warn("Not sending initial delay, no data") } + ) + + aggregator.medianQueueTime().fold( + { sendMedianQueueTime(it) }, + { logger.warn("Not sending median test queue time, no data") } + ) + aggregator.medianInstallationTime().fold( + { sendMedianInstallationTime(it) }, + { logger.warn("Not sending median test start time, no data") } + ) + + aggregator.endDelay().fold( + { sendEndDelay(it) }, + { logger.warn("Not sending end delay, no data") } + ) + + aggregator.suiteTime().fold( + { sendSuiteTime(it) }, + { logger.warn("Not sending suite time, no data") } + ) sendTotalTime(aggregator.totalTime()) - aggregator.medianDeviceUtilization() - ?.let { sendMedianDeviceUtilization(it.toInt()) } - ?: logger.warn("Not sending median device relative wasted time, no data") + aggregator.medianDeviceUtilization().fold( + { sendMedianDeviceUtilization(it.toInt()) }, + { logger.warn("Not sending median device relative wasted time, no data") } + ) } } diff --git a/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/TestMetricsSender.kt b/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/TestMetricsSender.kt index 00311f3877..79c62200ed 100644 --- a/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/TestMetricsSender.kt +++ b/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/TestMetricsSender.kt @@ -34,6 +34,6 @@ internal class TestMetricsSender( } fun sendMedianDeviceUtilization(percent: Int) { - statsDSender.send(GaugeLongMetric(prefix.append("device-utilization.median"), percent.toLong())) + statsDSender.send(GaugeLongMetric(prefix.append("device-utilization", "median"), percent.toLong())) } } diff --git a/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/model/DeviceTimestamps.kt b/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/model/DeviceTimestamps.kt index 5511fc7cfe..4b748bc731 100644 --- a/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/model/DeviceTimestamps.kt +++ b/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/model/DeviceTimestamps.kt @@ -2,31 +2,35 @@ package com.avito.runner.scheduler.metrics.model import com.avito.math.percentOf -internal data class DeviceTimestamps( - val created: Long? = null, - val testTimestamps: MutableMap, - val finished: Long? = null +internal sealed class DeviceTimestamps( + open val created: Long, + open val testTimestamps: MutableMap, ) { - private val totalTime: Long? - get() = if (finished != null && created != null) { - finished - created - } else { - null - } - - private val effectiveWorkTime - get() = testTimestamps.values.mapNotNull { it.effectiveWorkTime }.sum() - - val utilizationPercent: Int? - get() { - val localTotalTime = totalTime - return if (localTotalTime != null) { - effectiveWorkTime.percentOf(localTotalTime).toInt() - } else { - null // in case if device died and finish time not logged - } - } + data class Started( + override val created: Long, + override val testTimestamps: MutableMap, + ) : DeviceTimestamps(created, testTimestamps) + + data class Finished( + override val created: Long, + override val testTimestamps: MutableMap, + val finished: Long + ) : DeviceTimestamps(created, testTimestamps) { + + private val totalTime = finished - created + + private val effectiveWorkTime = testTimestamps.values.filterIsInstance() + .map { it.effectiveWorkTime } + .sum() + + val utilizationPercent: Int = effectiveWorkTime.percentOf(totalTime).toInt() + } companion object } + +internal fun DeviceTimestamps.finish(finished: Long): DeviceTimestamps = when (this) { + is DeviceTimestamps.Started -> DeviceTimestamps.Finished(this.created, this.testTimestamps, finished) + is DeviceTimestamps.Finished -> error("$this is in its finished state already") +} diff --git a/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/model/TestTimestamps.kt b/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/model/TestTimestamps.kt index 33b30bac02..1d15507d62 100644 --- a/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/model/TestTimestamps.kt +++ b/subprojects/test-runner/client/src/main/kotlin/com/avito/runner/scheduler/metrics/model/TestTimestamps.kt @@ -1,27 +1,25 @@ package com.avito.runner.scheduler.metrics.model -internal data class TestTimestamps( - val onDevice: Long? = null, - val started: Long? = null, - val finished: Long? = null -) { - val effectiveWorkTime: Long? = if (finished != null && onDevice != null) { - finished - onDevice - } else { - null - } +internal sealed class TestTimestamps { - val installationTime: Long? = if (started != null && onDevice != null) { - started - onDevice - } else { - null - } + data class NotStarted(val onDevice: Long) : TestTimestamps() + + data class Started(val onDevice: Long, val startTime: Long) : TestTimestamps() - val executionTime: Long? = if (finished != null && started != null) { - finished - started - } else { - null + data class Finished(val onDevice: Long, val startTime: Long, val finishTime: Long) : TestTimestamps() { + val effectiveWorkTime = finishTime - onDevice + val installationTime = startTime - onDevice } +} + +internal fun TestTimestamps.start(currentTimeMillis: Long): TestTimestamps = when (this) { + is TestTimestamps.NotStarted -> TestTimestamps.Started(this.onDevice, currentTimeMillis) + is TestTimestamps.Started -> error("Can't start already started $this") + is TestTimestamps.Finished -> error("Can't start already finished $this") +} - companion object +internal fun TestTimestamps.finish(currentTimeMillis: Long): TestTimestamps = when (this) { + is TestTimestamps.NotStarted -> error("Can't finish not started $this") + is TestTimestamps.Started -> TestTimestamps.Finished(this.onDevice, this.startTime, currentTimeMillis) + is TestTimestamps.Finished -> error("Can't finish already finished $this") } diff --git a/subprojects/test-runner/client/src/test/kotlin/com/avito/runner/scheduler/metrics/TestMetricsAggregatorTest.kt b/subprojects/test-runner/client/src/test/kotlin/com/avito/runner/scheduler/metrics/TestMetricsAggregatorTest.kt index 62291c508d..2bee6ae823 100644 --- a/subprojects/test-runner/client/src/test/kotlin/com/avito/runner/scheduler/metrics/TestMetricsAggregatorTest.kt +++ b/subprojects/test-runner/client/src/test/kotlin/com/avito/runner/scheduler/metrics/TestMetricsAggregatorTest.kt @@ -18,8 +18,8 @@ internal class TestMetricsAggregatorTest { deviceTimestamps = mapOf( "12345".toDeviceKey() to DeviceTimestamps.createStubInstance( testTimestamps = mutableMapOf( - "test1".toTestKey() to TestTimestamps.createStubInstance(started = 25), - "test2".toTestKey() to TestTimestamps.createStubInstance(started = 35) + "test1".toTestKey() to TestTimestamps.Finished(onDevice = 10, startTime = 25, finishTime = 30), + "test2".toTestKey() to TestTimestamps.Finished(onDevice = 10, startTime = 25, finishTime = 30), ) ) ) @@ -27,7 +27,7 @@ internal class TestMetricsAggregatorTest { val result = aggregator.initialDelay() - assertThat(result).isEqualTo(15) + assertThat(result.getOrThrow()).isEqualTo(15) } @Test @@ -37,8 +37,8 @@ internal class TestMetricsAggregatorTest { deviceTimestamps = mapOf( "12345".toDeviceKey() to DeviceTimestamps.createStubInstance( testTimestamps = mutableMapOf( - "test1".toTestKey() to TestTimestamps.createStubInstance(finished = 25), - "test2".toTestKey() to TestTimestamps.createStubInstance(finished = 35) + "test1".toTestKey() to TestTimestamps.Finished(onDevice = 10, startTime = 20, finishTime = 25), + "test2".toTestKey() to TestTimestamps.Finished(onDevice = 10, startTime = 30, finishTime = 35), ) ) ) @@ -46,7 +46,7 @@ internal class TestMetricsAggregatorTest { val result = aggregator.endDelay() - assertThat(result).isEqualTo(15) + assertThat(result.getOrThrow()).isEqualTo(15) } @Test @@ -55,10 +55,10 @@ internal class TestMetricsAggregatorTest { deviceTimestamps = mapOf( "12345".toDeviceKey() to DeviceTimestamps.createStubInstance( testTimestamps = mutableMapOf( - "test1".toTestKey() to TestTimestamps.createStubInstance(started = 10, finished = 20), - "test2".toTestKey() to TestTimestamps.createStubInstance(started = 15, finished = 45), - "test3".toTestKey() to TestTimestamps.createStubInstance(started = 10, finished = 25), - "test4".toTestKey() to TestTimestamps.createStubInstance(started = 30, finished = 35) + "test1".toTestKey() to TestTimestamps.Finished(onDevice = 10, startTime = 10, finishTime = 20), + "test2".toTestKey() to TestTimestamps.Finished(onDevice = 10, startTime = 15, finishTime = 45), + "test3".toTestKey() to TestTimestamps.Finished(onDevice = 10, startTime = 10, finishTime = 25), + "test4".toTestKey() to TestTimestamps.Finished(onDevice = 10, startTime = 30, finishTime = 35), ) ) ) @@ -66,7 +66,7 @@ internal class TestMetricsAggregatorTest { val result = aggregator.suiteTime() - assertThat(result).isEqualTo(35) + assertThat(result.getOrThrow()).isEqualTo(35) } @Test @@ -88,10 +88,10 @@ internal class TestMetricsAggregatorTest { deviceTimestamps = mapOf( "12345".toDeviceKey() to DeviceTimestamps.createStubInstance( testTimestamps = mutableMapOf( - "test1".toTestKey() to TestTimestamps.createStubInstance(onDevice = 10), - "test2".toTestKey() to TestTimestamps.createStubInstance(onDevice = 15), - "test3".toTestKey() to TestTimestamps.createStubInstance(onDevice = 10), - "test4".toTestKey() to TestTimestamps.createStubInstance(onDevice = 30) + "test1".toTestKey() to TestTimestamps.Finished(onDevice = 10, startTime = 40, finishTime = 50), + "test2".toTestKey() to TestTimestamps.Finished(onDevice = 15, startTime = 40, finishTime = 50), + "test3".toTestKey() to TestTimestamps.Finished(onDevice = 10, startTime = 40, finishTime = 50), + "test4".toTestKey() to TestTimestamps.Finished(onDevice = 30, startTime = 40, finishTime = 50), ) ) ) @@ -99,7 +99,7 @@ internal class TestMetricsAggregatorTest { val result = aggregator.medianQueueTime() - assertThat(result).isEqualTo(7) // median is 7.5, but rounded (ok for unix time) + assertThat(result.getOrThrow()).isEqualTo(7) // median is 7.5, but rounded (ok for unix time) } @Test @@ -108,10 +108,10 @@ internal class TestMetricsAggregatorTest { deviceTimestamps = mapOf( "12345".toDeviceKey() to DeviceTimestamps.createStubInstance( testTimestamps = mutableMapOf( - "test1".toTestKey() to TestTimestamps.createStubInstance(onDevice = 10, started = 13), - "test2".toTestKey() to TestTimestamps.createStubInstance(onDevice = 15, started = 16), - "test3".toTestKey() to TestTimestamps.createStubInstance(onDevice = 10, started = 19), - "test4".toTestKey() to TestTimestamps.createStubInstance(onDevice = 30, started = 35) + "test1".toTestKey() to TestTimestamps.Finished(onDevice = 10, startTime = 13, finishTime = 15), + "test2".toTestKey() to TestTimestamps.Finished(onDevice = 15, startTime = 16, finishTime = 18), + "test3".toTestKey() to TestTimestamps.Finished(onDevice = 10, startTime = 19, finishTime = 25), + "test4".toTestKey() to TestTimestamps.Finished(onDevice = 30, startTime = 35, finishTime = 36), ) ) ) @@ -119,7 +119,7 @@ internal class TestMetricsAggregatorTest { val result = aggregator.medianInstallationTime() - assertThat(result).isEqualTo(4) + assertThat(result.getOrThrow()).isEqualTo(4) } @Test @@ -129,9 +129,9 @@ internal class TestMetricsAggregatorTest { "12345".toDeviceKey() to DeviceTimestamps.createStubInstance( created = 0, testTimestamps = mutableMapOf( - "test1".toTestKey() to TestTimestamps.createStubInstance(onDevice = 10, finished = 15), - "test2".toTestKey() to TestTimestamps.createStubInstance(onDevice = 20, finished = 25), - "test4".toTestKey() to TestTimestamps.createStubInstance(onDevice = 35, finished = 45) + "test1".toTestKey() to TestTimestamps.Finished(onDevice = 10, startTime = 10, finishTime = 15), + "test2".toTestKey() to TestTimestamps.Finished(onDevice = 20, startTime = 20, finishTime = 25), + "test4".toTestKey() to TestTimestamps.Finished(onDevice = 35, startTime = 35, finishTime = 45), ), finished = 50 ) @@ -140,7 +140,7 @@ internal class TestMetricsAggregatorTest { val result = aggregator.medianDeviceUtilization() - assertThat(result).isEqualTo(40) + assertThat(result.getOrThrow()).isEqualTo(40) } private fun createTestMetricsAggregator( diff --git a/subprojects/test-runner/client/src/test/kotlin/com/avito/runner/scheduler/metrics/model/DeviceTimestamps.kt b/subprojects/test-runner/client/src/test/kotlin/com/avito/runner/scheduler/metrics/model/DeviceTimestamps.kt index e1453dbde1..358c63463e 100644 --- a/subprojects/test-runner/client/src/test/kotlin/com/avito/runner/scheduler/metrics/model/DeviceTimestamps.kt +++ b/subprojects/test-runner/client/src/test/kotlin/com/avito/runner/scheduler/metrics/model/DeviceTimestamps.kt @@ -4,4 +4,4 @@ internal fun DeviceTimestamps.Companion.createStubInstance( created: Long = 0, testTimestamps: MutableMap = mutableMapOf(), finished: Long = 0 -) = DeviceTimestamps(created = created, testTimestamps = testTimestamps, finished = finished) +) = DeviceTimestamps.Finished(created = created, testTimestamps = testTimestamps, finished = finished) diff --git a/subprojects/test-runner/client/src/test/kotlin/com/avito/runner/scheduler/metrics/model/TestTimestamps.kt b/subprojects/test-runner/client/src/test/kotlin/com/avito/runner/scheduler/metrics/model/TestTimestamps.kt deleted file mode 100644 index 3bbac89a78..0000000000 --- a/subprojects/test-runner/client/src/test/kotlin/com/avito/runner/scheduler/metrics/model/TestTimestamps.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.avito.runner.scheduler.metrics.model - -internal fun TestTimestamps.Companion.createStubInstance( - onDevice: Long? = null, - started: Long? = null, - finished: Long? = null -) = TestTimestamps(onDevice = onDevice, started = started, finished = finished) diff --git a/subprojects/test-runner/client/src/test/kotlin/com/avito/runner/scheduler/runner/RunnerIntegrationTest.kt b/subprojects/test-runner/client/src/test/kotlin/com/avito/runner/scheduler/runner/RunnerIntegrationTest.kt index 5232bc8619..01bfbb9832 100644 --- a/subprojects/test-runner/client/src/test/kotlin/com/avito/runner/scheduler/runner/RunnerIntegrationTest.kt +++ b/subprojects/test-runner/client/src/test/kotlin/com/avito/runner/scheduler/runner/RunnerIntegrationTest.kt @@ -603,7 +603,7 @@ class RunnerIntegrationTest { loggerFactory = loggerFactory, devices = devices, testListener = testListener, - deviceMetricsListener = StubDeviceListener, + deviceMetricsListener = StubDeviceListener(), deviceWorkersDispatcher = TestDispatcher, timeProvider = StubTimeProvider() ) diff --git a/subprojects/test-runner/service/build.gradle.kts b/subprojects/test-runner/service/build.gradle.kts index e1eb438041..b17f8bbd84 100644 --- a/subprojects/test-runner/service/build.gradle.kts +++ b/subprojects/test-runner/service/build.gradle.kts @@ -1,6 +1,7 @@ plugins { id("convention.kotlin-jvm") id("convention.publish-kotlin-library") + id("convention.test-fixtures") id("convention.libraries") } diff --git a/subprojects/test-runner/service/src/main/kotlin/com/avito/runner/service/worker/DeviceWorker.kt b/subprojects/test-runner/service/src/main/kotlin/com/avito/runner/service/worker/DeviceWorker.kt index bf9064d3ca..5c3d20f050 100644 --- a/subprojects/test-runner/service/src/main/kotlin/com/avito/runner/service/worker/DeviceWorker.kt +++ b/subprojects/test-runner/service/src/main/kotlin/com/avito/runner/service/worker/DeviceWorker.kt @@ -55,52 +55,55 @@ internal class DeviceWorker( deviceListener.onDeviceCreated(device, state) - for (intention in intentionsRouter.observeIntentions(state)) { + try { - deviceListener.onIntentionReceived(device, intention) + for (intention in intentionsRouter.observeIntentions(state)) { - when (val status = device.deviceStatus()) { + deviceListener.onIntentionReceived(device, intention) - is Freeze -> { - onDeviceDieOnIntention(intention, status.reason) - return@launch - } + when (val status = device.deviceStatus()) { - is Alive -> { + is Freeze -> { + onDeviceDieOnIntention(intention, status.reason) + return@launch + } - val (newState, preparingError) = prepareDeviceState( - currentState = state, - intendedState = intention.state - ) + is Alive -> { - when { + val (newState, preparingError) = prepareDeviceState( + currentState = state, + intendedState = intention.state + ) - preparingError != null -> { - onDeviceDieOnIntention(intention, preparingError) - return@launch - } + when { + + preparingError != null -> { + onDeviceDieOnIntention(intention, preparingError) + return@launch + } - newState != null -> { - state = newState + newState != null -> { + state = newState - deviceListener.onStatePrepared(device, newState) + deviceListener.onStatePrepared(device, newState) - deviceListener.onTestStarted(device, intention) + deviceListener.onTestStarted(device, intention) - val result = executeAction(action = intention.action) + val result = executeAction(action = intention.action) - deviceListener.onTestCompleted( - device = device, - intention = intention, - result = result - ) + deviceListener.onTestCompleted( + device = device, + intention = intention, + result = result + ) + } } } } } + } finally { + deviceListener.onFinished(device) } - - deviceListener.onFinished(device) } private suspend fun prepareDeviceState( diff --git a/subprojects/test-runner/service/src/main/kotlin/com/avito/runner/service/worker/listener/MessagesDeviceListener.kt b/subprojects/test-runner/service/src/main/kotlin/com/avito/runner/service/worker/listener/MessagesDeviceListener.kt index 6e09404ac2..a05962b216 100644 --- a/subprojects/test-runner/service/src/main/kotlin/com/avito/runner/service/worker/listener/MessagesDeviceListener.kt +++ b/subprojects/test-runner/service/src/main/kotlin/com/avito/runner/service/worker/listener/MessagesDeviceListener.kt @@ -60,5 +60,6 @@ internal class MessagesDeviceListener(private val messagesChannel: Channel = + Channel(Channel.UNLIMITED) + + val router = IntentionsRouter(loggerFactory = loggerFactory).apply { + intentions.forEach { sendIntention(it) } + } + + val stubListener = StubDeviceListener() + + val worker = provideDeviceWorker( + device = successfulDevice, + router = router, + deviceListener = CompositeDeviceListener( + listOf( + MessagesDeviceListener(resultsChannel), + stubListener + ) + ) + ).run(this) + + router.cancel() + worker.join() + + assertThat(stubListener.isFinished).isTrue() + } + @Test fun `fail with device died event - device is freeze before processing intentions`() = runBlockingTest { @@ -142,9 +226,9 @@ class DeviceWorkerTest { val router = IntentionsRouter(loggerFactory = loggerFactory) val worker = provideDeviceWorker( - results = resultsChannel, device = freezeDevice, - router = router + router = router, + deviceListener = MessagesDeviceListener(resultsChannel) ).run(this) router.cancel() @@ -202,9 +286,9 @@ class DeviceWorkerTest { } val worker = provideDeviceWorker( - results = resultsChannel, device = freezeDevice, - router = router + router = router, + deviceListener = MessagesDeviceListener(resultsChannel) ).run(this) router.cancel() @@ -228,15 +312,15 @@ class DeviceWorkerTest { } private fun provideDeviceWorker( - results: Channel, device: Device, - router: IntentionsRouter + router: IntentionsRouter, + deviceListener: DeviceListener ) = DeviceWorker( intentionsRouter = router, device = device, outputDirectory = File(""), testListener = NoOpTestListener, - deviceListener = MessagesDeviceListener(results), + deviceListener = deviceListener, timeProvider = StubTimeProvider(), dispatchers = TestDispatcher ) diff --git a/subprojects/test-runner/service/src/test/kotlin/com/avito/runner/service/IntentionExecutionServiceTest.kt b/subprojects/test-runner/service/src/test/kotlin/com/avito/runner/service/IntentionExecutionServiceTest.kt index db9446cb5d..ad724a3041 100644 --- a/subprojects/test-runner/service/src/test/kotlin/com/avito/runner/service/IntentionExecutionServiceTest.kt +++ b/subprojects/test-runner/service/src/test/kotlin/com/avito/runner/service/IntentionExecutionServiceTest.kt @@ -208,7 +208,7 @@ class IntentionExecutionServiceTest { devices = devices, intentionsRouter = intentionsRouter, testListener = NoOpTestListener, - deviceMetricsListener = StubDeviceListener, + deviceMetricsListener = StubDeviceListener(), deviceWorkersDispatcher = TestDispatcher, timeProvider = StubTimeProvider() ) diff --git a/subprojects/test-runner/shared-test/src/main/kotlin/com/avito/runner/service/worker/listener/StubDeviceListener.kt b/subprojects/test-runner/service/src/testFixtures/kotlin/com/avito/runner/service/worker/listener/StubDeviceListener.kt similarity index 77% rename from subprojects/test-runner/shared-test/src/main/kotlin/com/avito/runner/service/worker/listener/StubDeviceListener.kt rename to subprojects/test-runner/service/src/testFixtures/kotlin/com/avito/runner/service/worker/listener/StubDeviceListener.kt index a7f77c9452..61ba26ae08 100644 --- a/subprojects/test-runner/shared-test/src/main/kotlin/com/avito/runner/service/worker/listener/StubDeviceListener.kt +++ b/subprojects/test-runner/service/src/testFixtures/kotlin/com/avito/runner/service/worker/listener/StubDeviceListener.kt @@ -6,32 +6,46 @@ import com.avito.runner.service.model.intention.State import com.avito.runner.service.worker.device.Device import com.avito.runner.service.worker.model.DeviceInstallation -object StubDeviceListener : DeviceListener { +class StubDeviceListener : DeviceListener { + + var isDeviceCreated: Boolean = false + private set + var isFinished: Boolean = false + private set override suspend fun onDeviceCreated(device: Device, state: State) { + isDeviceCreated = true } override suspend fun onIntentionReceived(device: Device, intention: Intention) { + // empty } override suspend fun onApplicationInstalled(device: Device, installation: DeviceInstallation) { + // empty } override suspend fun onStatePrepared(device: Device, state: State) { + // empty } override suspend fun onTestStarted(device: Device, intention: Intention) { + // empty } override suspend fun onTestCompleted(device: Device, intention: Intention, result: DeviceTestCaseRun) { + // empty } override suspend fun onIntentionFail(device: Device, intention: Intention, reason: Throwable) { + // empty } override suspend fun onDeviceDied(device: Device, message: String, reason: Throwable) { + // empty } override suspend fun onFinished(device: Device) { + isFinished = true } }