From 5a6533adcb66ae121a7b5fc312ed9e209dc82c91 Mon Sep 17 00:00:00 2001 From: Sergey Chelombitko Date: Tue, 25 Jun 2024 19:38:02 +0100 Subject: [PATCH] Use BuildService --- .../marathon/MarathonBuildService.kt | 46 +++++++++++++++++++ .../com/malinskiy/marathon/MarathonPlugin.kt | 23 ++++++---- .../MarathonScheduleTestsToWorkerTask.kt | 7 ++- .../marathon/MarathonWorkerRunTask.kt | 10 +++- .../marathon/worker/MarathonWorker.kt | 22 --------- .../marathon/worker/WorkerContext.kt | 26 ++++++----- .../marathon/worker/WorkerHandler.kt | 4 +- .../marathon/worker/WorkerRunnable.kt | 33 +++++-------- 8 files changed, 99 insertions(+), 72 deletions(-) create mode 100644 marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/MarathonBuildService.kt delete mode 100644 marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/worker/MarathonWorker.kt diff --git a/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/MarathonBuildService.kt b/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/MarathonBuildService.kt new file mode 100644 index 000000000..1848baaa5 --- /dev/null +++ b/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/MarathonBuildService.kt @@ -0,0 +1,46 @@ +package com.malinskiy.marathon + +import com.malinskiy.marathon.execution.ComponentInfo +import com.malinskiy.marathon.worker.WorkerContext +import com.malinskiy.marathon.worker.WorkerHandler +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property +import org.gradle.api.services.BuildService +import org.gradle.api.services.BuildServiceParameters + +abstract class MarathonBuildService : BuildService, WorkerHandler { + private val lazyWorkerContext = lazy { + val configuration = createCommonConfiguration( + parameters.marathonConfig.get(), + parameters.adbPath.get().asFile, + parameters.outputDir.get().asFile + ) + WorkerContext(configuration) + } + + override fun scheduleTests(componentInfo: ComponentInfo) { + lazyWorkerContext.value.scheduleTests(componentInfo) + } + + override fun await() { + if (lazyWorkerContext.isInitialized()) { + lazyWorkerContext.value.await() + } + } + + override fun close() { + if (lazyWorkerContext.isInitialized()) { + lazyWorkerContext.value.close() + } + } + + interface Params : BuildServiceParameters { + val adbPath: DirectoryProperty + val outputDir: DirectoryProperty + val marathonConfig: Property + } + + companion object { + const val NAME = "marathon" + } +} diff --git a/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/MarathonPlugin.kt b/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/MarathonPlugin.kt index 2ac265eb1..d23af513c 100644 --- a/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/MarathonPlugin.kt +++ b/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/MarathonPlugin.kt @@ -8,11 +8,14 @@ import com.android.build.api.variant.GeneratesTestApk import com.android.build.api.variant.TestVariant import com.android.build.api.variant.Variant import com.malinskiy.marathon.android.findAdbPath -import com.malinskiy.marathon.worker.MarathonWorker import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.JavaBasePlugin import org.gradle.api.tasks.TaskProvider +import org.gradle.kotlin.dsl.create +import org.gradle.kotlin.dsl.named +import org.gradle.kotlin.dsl.register +import org.gradle.kotlin.dsl.registerIfAbsent class MarathonPlugin : Plugin { override fun apply(project: Project) { @@ -31,16 +34,16 @@ class MarathonPlugin : Plugin { } private fun Project.configureRootProject() { - val marathonConfig = extensions.create(MarathonExtension.NAME, MarathonExtension::class.java) + val marathonConfig = extensions.create(MarathonExtension.NAME) marathonConfig.initDefaults() - tasks.register(WORKER_TASK_NAME, MarathonWorkerRunTask::class.java) - - gradle.projectsEvaluated { - val outputDir = layout.buildDirectory.dir("reports/marathon").get().asFile - val configuration = createCommonConfiguration(marathonConfig, findAdbPath(projectDir), outputDir) - MarathonWorker.initialize(configuration) + gradle.sharedServices.registerIfAbsent(MarathonBuildService.NAME, MarathonBuildService::class) { + parameters.adbPath.set(findAdbPath(projectDir)) + parameters.outputDir.set(layout.buildDirectory.dir("reports/marathon")) + parameters.marathonConfig.set(marathonConfig) } + + tasks.register(WORKER_TASK_NAME) } private fun Project.configureAndroidProject() { @@ -49,7 +52,7 @@ class MarathonPlugin : Plugin { description = "Runs all the instrumentation test variations on all the connected devices" } - val marathonWorkerTask = rootProject.tasks.named(WORKER_TASK_NAME, MarathonWorkerRunTask::class.java) + val marathonWorkerTask = rootProject.tasks.named(WORKER_TASK_NAME) val androidComponents = extensions.getByType(AndroidComponentsExtension::class.java) androidComponents.onVariants { variant -> variant.components @@ -66,7 +69,7 @@ class MarathonPlugin : Plugin { testComponent: Component, marathonWorkerTask: TaskProvider ): TaskProvider = - tasks.register(variant.computeTaskName(TASK_PREFIX, "androidTest"), MarathonScheduleTestsToWorkerTask::class.java) { + tasks.register(variant.computeTaskName(TASK_PREFIX, "androidTest")) { group = JavaBasePlugin.VERIFICATION_GROUP description = "Runs instrumentation tests on all the connected devices for '${variant.name}' " + "variation and generates a report with screenshots" diff --git a/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/MarathonScheduleTestsToWorkerTask.kt b/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/MarathonScheduleTestsToWorkerTask.kt index 5bb9630fd..c9d8c2f09 100644 --- a/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/MarathonScheduleTestsToWorkerTask.kt +++ b/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/MarathonScheduleTestsToWorkerTask.kt @@ -3,10 +3,10 @@ package com.malinskiy.marathon import com.android.build.api.variant.BuiltArtifacts import com.android.build.api.variant.BuiltArtifactsLoader import com.malinskiy.marathon.android.AndroidComponentInfo -import com.malinskiy.marathon.worker.MarathonWorker import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty import org.gradle.api.provider.Property +import org.gradle.api.services.ServiceReference import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.Internal @@ -30,6 +30,9 @@ abstract class MarathonScheduleTestsToWorkerTask : DefaultTask() { @get:Internal abstract val builtArtifactsLoader: Property + @get:ServiceReference(MarathonBuildService.NAME) + abstract val buildService: Property + @TaskAction fun run() { val artifactsLoader = builtArtifactsLoader.get() @@ -48,7 +51,7 @@ abstract class MarathonScheduleTestsToWorkerTask : DefaultTask() { (componentInfo.applicationOutput?.let { " for app $it" } ?: "") ) - MarathonWorker.scheduleTests(componentInfo) + buildService.get().scheduleTests(componentInfo) } private val BuiltArtifacts.singleFile: File diff --git a/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/MarathonWorkerRunTask.kt b/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/MarathonWorkerRunTask.kt index c34132148..3f55b9f0c 100644 --- a/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/MarathonWorkerRunTask.kt +++ b/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/MarathonWorkerRunTask.kt @@ -1,12 +1,18 @@ package com.malinskiy.marathon -import com.malinskiy.marathon.worker.MarathonWorker import org.gradle.api.DefaultTask +import org.gradle.api.provider.Property +import org.gradle.api.services.ServiceReference import org.gradle.api.tasks.TaskAction +import org.gradle.work.DisableCachingByDefault +@DisableCachingByDefault abstract class MarathonWorkerRunTask : DefaultTask() { + @get:ServiceReference(MarathonBuildService.NAME) + abstract val buildService: Property + @TaskAction fun run() { - MarathonWorker.await() + buildService.get().await() } } diff --git a/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/worker/MarathonWorker.kt b/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/worker/MarathonWorker.kt deleted file mode 100644 index 7e7c1a65e..000000000 --- a/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/worker/MarathonWorker.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.malinskiy.marathon.worker - -import com.malinskiy.marathon.execution.ComponentInfo -import com.malinskiy.marathon.execution.Configuration - -object MarathonWorker : WorkerHandler { - - private var context: WorkerContext = WorkerContext() - - override fun initialize(configuration: Configuration) = context.initialize(configuration) - - override fun scheduleTests(componentInfo: ComponentInfo) = context.scheduleTests(componentInfo) - - override fun await() { - try { - context.await() - } finally { - // re-create context to clear the reference for future runs - context = WorkerContext() - } - } -} diff --git a/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/worker/WorkerContext.kt b/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/worker/WorkerContext.kt index f29296679..5247f27e6 100644 --- a/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/worker/WorkerContext.kt +++ b/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/worker/WorkerContext.kt @@ -1,30 +1,27 @@ package com.malinskiy.marathon.worker +import com.malinskiy.marathon.Marathon +import com.malinskiy.marathon.di.marathonStartKoin import com.malinskiy.marathon.execution.ComponentInfo import com.malinskiy.marathon.execution.Configuration import kotlinx.coroutines.channels.Channel import java.util.concurrent.CountDownLatch -import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.Future import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean -class WorkerContext : WorkerHandler { - - private lateinit var configuration: Configuration +internal class WorkerContext(configuration: Configuration) : WorkerHandler { + private val executor = Executors.newSingleThreadExecutor() private val componentsChannel: Channel = Channel(capacity = Channel.UNLIMITED) + private val application = marathonStartKoin(configuration) + private val marathon = application.koin.get() private val isRunning = AtomicBoolean(false) private val startedLatch = CountDownLatch(1) - private lateinit var executor: ExecutorService private lateinit var finishFuture: Future<*> - override fun initialize(configuration: Configuration) { - this.configuration = configuration - } - override fun scheduleTests(componentInfo: ComponentInfo) { ensureStarted() componentsChannel.trySend(componentInfo) @@ -44,12 +41,17 @@ class WorkerContext : WorkerHandler { } } + override fun close() { + isRunning.set(false) + componentsChannel.close() + executor.shutdown() + application.close() + } + private fun ensureStarted() { if (isRunning.getAndSet(true)) return - val runnable = WorkerRunnable(componentsChannel, configuration) - - executor = Executors.newSingleThreadExecutor() + val runnable = WorkerRunnable(marathon, componentsChannel) finishFuture = executor.submit(runnable) startedLatch.countDown() diff --git a/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/worker/WorkerHandler.kt b/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/worker/WorkerHandler.kt index ac626d446..dee82ba03 100644 --- a/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/worker/WorkerHandler.kt +++ b/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/worker/WorkerHandler.kt @@ -1,10 +1,8 @@ package com.malinskiy.marathon.worker import com.malinskiy.marathon.execution.ComponentInfo -import com.malinskiy.marathon.execution.Configuration -interface WorkerHandler { - fun initialize(configuration: Configuration) +interface WorkerHandler : AutoCloseable { fun scheduleTests(componentInfo: ComponentInfo) fun await() } diff --git a/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/worker/WorkerRunnable.kt b/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/worker/WorkerRunnable.kt index 442eb0ff6..e818c4ffe 100644 --- a/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/worker/WorkerRunnable.kt +++ b/marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/worker/WorkerRunnable.kt @@ -1,47 +1,38 @@ package com.malinskiy.marathon.worker import com.malinskiy.marathon.Marathon -import com.malinskiy.marathon.di.marathonStartKoin import com.malinskiy.marathon.execution.ComponentInfo -import com.malinskiy.marathon.execution.Configuration import com.malinskiy.marathon.log.MarathonLogging import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.runBlocking import org.gradle.api.GradleException -class WorkerRunnable( - private val componentsChannel: Channel, - private val configuration: Configuration +internal class WorkerRunnable( + private val marathon: Marathon, + private val componentsChannel: Channel ) : Runnable { private val log = MarathonLogging.logger {} override fun run() = runBlocking { log.debug("Starting Marathon worker") + marathon.start() - val application = marathonStartKoin(configuration) - try { - val marathon = application.koin.get() - marathon.start() - - for (component in componentsChannel) { - log.debug("Scheduling tests for $component") - marathon.scheduleTests(component) - } - - log.debug("Waiting for completion") - stopAndWaitForCompletion(marathon) - } finally { - application.close() + for (component in componentsChannel) { + log.debug("Scheduling tests for $component") + marathon.scheduleTests(component) } + + log.debug("Waiting for completion") + stopAndWaitForCompletion(marathon) } private suspend fun stopAndWaitForCompletion(marathon: Marathon) { val success = marathon.stopAndWaitForCompletion() - val shouldReportFailure = !configuration.ignoreFailures + val shouldReportFailure = !marathon.configuration.ignoreFailures if (!success && shouldReportFailure) { - throw GradleException("Tests failed! See ${configuration.outputDir}/html/index.html") + throw GradleException("Tests failed! See ${marathon.configuration.outputDir}/html/index.html") } } }