Skip to content

Commit

Permalink
Use build service (#73)
Browse files Browse the repository at this point in the history
  • Loading branch information
technoir42 committed Aug 2, 2024
1 parent 648d5bc commit 1c5b089
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 89 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,14 +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 @@ -37,13 +37,13 @@ class MarathonPlugin : Plugin<Project> {
val marathonConfig = extensions.create<MarathonExtension>(MarathonExtension.NAME)
marathonConfig.initDefaults()

tasks.register<MarathonWorkerRunTask>(WORKER_TASK_NAME)

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 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,8 +51,7 @@ abstract class MarathonScheduleTestsToWorkerTask : DefaultTask() {
(componentInfo.applicationOutput?.let { " for app $it" } ?: "")
)

MarathonWorker.ensureStarted()
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,57 +1,60 @@
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)
}

override fun ensureStarted() {
if (isRunning.getAndSet(true)) return

val runnable = WorkerRunnable(componentsChannel, configuration)
override fun await() {
if (!isRunning.getAndSet(false)) return

executor = Executors.newSingleThreadExecutor()
finishFuture = executor.submit(runnable)
startedLatch.await(WAITING_FOR_START_TIMEOUT_MINUTES, TimeUnit.MINUTES)
componentsChannel.close()

startedLatch.countDown()
try {
// Use future to propagate all exceptions from runnable
finishFuture.get()
} finally {
executor.shutdown()
}
}

override fun scheduleTests(componentInfo: ComponentInfo) {
componentsChannel.trySend(componentInfo)
override fun close() {
isRunning.set(false)
componentsChannel.close()
executor.shutdown()
application.close()
}

override fun await() {
if (isRunning.get()) {
startedLatch.await(WAITING_FOR_START_TIMEOUT_MINUTES, TimeUnit.MINUTES)
componentsChannel.close()

try {
// Use future to propagate all exceptions from runnable
finishFuture.get()
} finally {
executor.shutdown()
}
}
private fun ensureStarted() {
if (isRunning.getAndSet(true)) return

val runnable = WorkerRunnable(marathon, componentsChannel)
finishFuture = executor.submit(runnable)

startedLatch.countDown()
}

private companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +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)
fun ensureStarted()
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 1c5b089

Please sign in to comment.