Skip to content

Commit

Permalink
Use BuildService
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergey Chelombitko committed Aug 2, 2024
1 parent 58cd84d commit 5a6533a
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -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<MarathonBuildService.Params>, 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<MarathonExtension>
}

companion object {
const val NAME = "marathon"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Project> {
override fun apply(project: Project) {
Expand All @@ -31,16 +34,16 @@ class MarathonPlugin : Plugin<Project> {
}

private fun Project.configureRootProject() {
val marathonConfig = extensions.create(MarathonExtension.NAME, MarathonExtension::class.java)
val marathonConfig = extensions.create<MarathonExtension>(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<MarathonWorkerRunTask>(WORKER_TASK_NAME)
}

private fun Project.configureAndroidProject() {
Expand All @@ -49,7 +52,7 @@ class MarathonPlugin : Plugin<Project> {
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<MarathonWorkerRunTask>(WORKER_TASK_NAME)
val androidComponents = extensions.getByType(AndroidComponentsExtension::class.java)
androidComponents.onVariants { variant ->
variant.components
Expand All @@ -66,7 +69,7 @@ class MarathonPlugin : Plugin<Project> {
testComponent: Component,
marathonWorkerTask: TaskProvider<MarathonWorkerRunTask>
): TaskProvider<MarathonScheduleTestsToWorkerTask> =
tasks.register(variant.computeTaskName(TASK_PREFIX, "androidTest"), MarathonScheduleTestsToWorkerTask::class.java) {
tasks.register<MarathonScheduleTestsToWorkerTask>(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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -30,6 +30,9 @@ abstract class MarathonScheduleTestsToWorkerTask : DefaultTask() {
@get:Internal
abstract val builtArtifactsLoader: Property<BuiltArtifactsLoader>

@get:ServiceReference(MarathonBuildService.NAME)
abstract val buildService: Property<MarathonBuildService>

@TaskAction
fun run() {
val artifactsLoader = builtArtifactsLoader.get()
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<MarathonBuildService>

@TaskAction
fun run() {
MarathonWorker.await()
buildService.get().await()
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<ComponentInfo> = Channel(capacity = Channel.UNLIMITED)

private val application = marathonStartKoin(configuration)
private val marathon = application.koin.get<Marathon>()
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)
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
Original file line number Diff line number Diff line change
@@ -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<ComponentInfo>,
private val configuration: Configuration
internal class WorkerRunnable(
private val marathon: Marathon,
private val componentsChannel: Channel<ComponentInfo>
) : 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>()
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")
}
}
}

0 comments on commit 5a6533a

Please sign in to comment.