Skip to content

Commit 1c5b089

Browse files
authored
Use build service (#73)
1 parent 648d5bc commit 1c5b089

File tree

8 files changed

+110
-89
lines changed

8 files changed

+110
-89
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.malinskiy.marathon
2+
3+
import com.malinskiy.marathon.execution.ComponentInfo
4+
import com.malinskiy.marathon.worker.WorkerContext
5+
import com.malinskiy.marathon.worker.WorkerHandler
6+
import org.gradle.api.file.DirectoryProperty
7+
import org.gradle.api.provider.Property
8+
import org.gradle.api.services.BuildService
9+
import org.gradle.api.services.BuildServiceParameters
10+
11+
abstract class MarathonBuildService : BuildService<MarathonBuildService.Params>, WorkerHandler {
12+
private val lazyWorkerContext = lazy {
13+
val configuration = createCommonConfiguration(
14+
parameters.marathonConfig.get(),
15+
parameters.adbPath.get().asFile,
16+
parameters.outputDir.get().asFile
17+
)
18+
WorkerContext(configuration)
19+
}
20+
21+
override fun scheduleTests(componentInfo: ComponentInfo) {
22+
lazyWorkerContext.value.scheduleTests(componentInfo)
23+
}
24+
25+
override fun await() {
26+
if (lazyWorkerContext.isInitialized()) {
27+
lazyWorkerContext.value.await()
28+
}
29+
}
30+
31+
override fun close() {
32+
if (lazyWorkerContext.isInitialized()) {
33+
lazyWorkerContext.value.close()
34+
}
35+
}
36+
37+
interface Params : BuildServiceParameters {
38+
val adbPath: DirectoryProperty
39+
val outputDir: DirectoryProperty
40+
val marathonConfig: Property<MarathonExtension>
41+
}
42+
43+
companion object {
44+
const val NAME = "marathon"
45+
}
46+
}

marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/MarathonPlugin.kt

+7-7
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ import com.android.build.api.variant.GeneratesTestApk
88
import com.android.build.api.variant.TestVariant
99
import com.android.build.api.variant.Variant
1010
import com.malinskiy.marathon.android.findAdbPath
11-
import com.malinskiy.marathon.worker.MarathonWorker
1211
import org.gradle.api.Plugin
1312
import org.gradle.api.Project
1413
import org.gradle.api.plugins.JavaBasePlugin
1514
import org.gradle.api.tasks.TaskProvider
1615
import org.gradle.kotlin.dsl.create
1716
import org.gradle.kotlin.dsl.named
1817
import org.gradle.kotlin.dsl.register
18+
import org.gradle.kotlin.dsl.registerIfAbsent
1919

2020
class MarathonPlugin : Plugin<Project> {
2121
override fun apply(project: Project) {
@@ -37,13 +37,13 @@ class MarathonPlugin : Plugin<Project> {
3737
val marathonConfig = extensions.create<MarathonExtension>(MarathonExtension.NAME)
3838
marathonConfig.initDefaults()
3939

40-
tasks.register<MarathonWorkerRunTask>(WORKER_TASK_NAME)
41-
42-
gradle.projectsEvaluated {
43-
val outputDir = layout.buildDirectory.dir("reports/marathon").get().asFile
44-
val configuration = createCommonConfiguration(marathonConfig, findAdbPath(projectDir), outputDir)
45-
MarathonWorker.initialize(configuration)
40+
gradle.sharedServices.registerIfAbsent(MarathonBuildService.NAME, MarathonBuildService::class) {
41+
parameters.adbPath.set(findAdbPath(projectDir))
42+
parameters.outputDir.set(layout.buildDirectory.dir("reports/marathon"))
43+
parameters.marathonConfig.set(marathonConfig)
4644
}
45+
46+
tasks.register<MarathonWorkerRunTask>(WORKER_TASK_NAME)
4747
}
4848

4949
private fun Project.configureAndroidProject() {

marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/MarathonScheduleTestsToWorkerTask.kt

+5-3
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ package com.malinskiy.marathon
33
import com.android.build.api.variant.BuiltArtifacts
44
import com.android.build.api.variant.BuiltArtifactsLoader
55
import com.malinskiy.marathon.android.AndroidComponentInfo
6-
import com.malinskiy.marathon.worker.MarathonWorker
76
import org.gradle.api.DefaultTask
87
import org.gradle.api.file.DirectoryProperty
98
import org.gradle.api.provider.Property
9+
import org.gradle.api.services.ServiceReference
1010
import org.gradle.api.tasks.Input
1111
import org.gradle.api.tasks.InputFiles
1212
import org.gradle.api.tasks.Internal
@@ -30,6 +30,9 @@ abstract class MarathonScheduleTestsToWorkerTask : DefaultTask() {
3030
@get:Internal
3131
abstract val builtArtifactsLoader: Property<BuiltArtifactsLoader>
3232

33+
@get:ServiceReference(MarathonBuildService.NAME)
34+
abstract val buildService: Property<MarathonBuildService>
35+
3336
@TaskAction
3437
fun run() {
3538
val artifactsLoader = builtArtifactsLoader.get()
@@ -48,8 +51,7 @@ abstract class MarathonScheduleTestsToWorkerTask : DefaultTask() {
4851
(componentInfo.applicationOutput?.let { " for app $it" } ?: "")
4952
)
5053

51-
MarathonWorker.ensureStarted()
52-
MarathonWorker.scheduleTests(componentInfo)
54+
buildService.get().scheduleTests(componentInfo)
5355
}
5456

5557
private val BuiltArtifacts.singleFile: File
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
package com.malinskiy.marathon
22

3-
import com.malinskiy.marathon.worker.MarathonWorker
43
import org.gradle.api.DefaultTask
4+
import org.gradle.api.provider.Property
5+
import org.gradle.api.services.ServiceReference
56
import org.gradle.api.tasks.TaskAction
7+
import org.gradle.work.DisableCachingByDefault
68

9+
@DisableCachingByDefault
710
abstract class MarathonWorkerRunTask : DefaultTask() {
11+
@get:ServiceReference(MarathonBuildService.NAME)
12+
abstract val buildService: Property<MarathonBuildService>
13+
814
@TaskAction
915
fun run() {
10-
MarathonWorker.await()
16+
buildService.get().await()
1117
}
1218
}

marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/worker/MarathonWorker.kt

-24
This file was deleted.

marathon-gradle-plugin/src/main/kotlin/com/malinskiy/marathon/worker/WorkerContext.kt

+31-28
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,60 @@
11
package com.malinskiy.marathon.worker
22

3+
import com.malinskiy.marathon.Marathon
4+
import com.malinskiy.marathon.di.marathonStartKoin
35
import com.malinskiy.marathon.execution.ComponentInfo
46
import com.malinskiy.marathon.execution.Configuration
57
import kotlinx.coroutines.channels.Channel
68
import java.util.concurrent.CountDownLatch
7-
import java.util.concurrent.ExecutorService
89
import java.util.concurrent.Executors
910
import java.util.concurrent.Future
1011
import java.util.concurrent.TimeUnit
1112
import java.util.concurrent.atomic.AtomicBoolean
1213

13-
class WorkerContext : WorkerHandler {
14-
15-
private lateinit var configuration: Configuration
14+
internal class WorkerContext(configuration: Configuration) : WorkerHandler {
15+
private val executor = Executors.newSingleThreadExecutor()
1616
private val componentsChannel: Channel<ComponentInfo> = Channel(capacity = Channel.UNLIMITED)
1717

18+
private val application = marathonStartKoin(configuration)
19+
private val marathon = application.koin.get<Marathon>()
1820
private val isRunning = AtomicBoolean(false)
1921
private val startedLatch = CountDownLatch(1)
2022

21-
private lateinit var executor: ExecutorService
2223
private lateinit var finishFuture: Future<*>
2324

24-
override fun initialize(configuration: Configuration) {
25-
this.configuration = configuration
25+
override fun scheduleTests(componentInfo: ComponentInfo) {
26+
ensureStarted()
27+
componentsChannel.trySend(componentInfo)
2628
}
2729

28-
override fun ensureStarted() {
29-
if (isRunning.getAndSet(true)) return
30-
31-
val runnable = WorkerRunnable(componentsChannel, configuration)
30+
override fun await() {
31+
if (!isRunning.getAndSet(false)) return
3232

33-
executor = Executors.newSingleThreadExecutor()
34-
finishFuture = executor.submit(runnable)
33+
startedLatch.await(WAITING_FOR_START_TIMEOUT_MINUTES, TimeUnit.MINUTES)
34+
componentsChannel.close()
3535

36-
startedLatch.countDown()
36+
try {
37+
// Use future to propagate all exceptions from runnable
38+
finishFuture.get()
39+
} finally {
40+
executor.shutdown()
41+
}
3742
}
3843

39-
override fun scheduleTests(componentInfo: ComponentInfo) {
40-
componentsChannel.trySend(componentInfo)
44+
override fun close() {
45+
isRunning.set(false)
46+
componentsChannel.close()
47+
executor.shutdown()
48+
application.close()
4149
}
4250

43-
override fun await() {
44-
if (isRunning.get()) {
45-
startedLatch.await(WAITING_FOR_START_TIMEOUT_MINUTES, TimeUnit.MINUTES)
46-
componentsChannel.close()
47-
48-
try {
49-
// Use future to propagate all exceptions from runnable
50-
finishFuture.get()
51-
} finally {
52-
executor.shutdown()
53-
}
54-
}
51+
private fun ensureStarted() {
52+
if (isRunning.getAndSet(true)) return
53+
54+
val runnable = WorkerRunnable(marathon, componentsChannel)
55+
finishFuture = executor.submit(runnable)
56+
57+
startedLatch.countDown()
5558
}
5659

5760
private companion object {
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
package com.malinskiy.marathon.worker
22

33
import com.malinskiy.marathon.execution.ComponentInfo
4-
import com.malinskiy.marathon.execution.Configuration
54

6-
interface WorkerHandler {
7-
fun initialize(configuration: Configuration)
8-
fun ensureStarted()
5+
interface WorkerHandler : AutoCloseable {
96
fun scheduleTests(componentInfo: ComponentInfo)
107
fun await()
118
}
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,38 @@
11
package com.malinskiy.marathon.worker
22

33
import com.malinskiy.marathon.Marathon
4-
import com.malinskiy.marathon.di.marathonStartKoin
54
import com.malinskiy.marathon.execution.ComponentInfo
6-
import com.malinskiy.marathon.execution.Configuration
75
import com.malinskiy.marathon.log.MarathonLogging
86
import kotlinx.coroutines.channels.Channel
97
import kotlinx.coroutines.runBlocking
108
import org.gradle.api.GradleException
119

12-
class WorkerRunnable(
13-
private val componentsChannel: Channel<ComponentInfo>,
14-
private val configuration: Configuration
10+
internal class WorkerRunnable(
11+
private val marathon: Marathon,
12+
private val componentsChannel: Channel<ComponentInfo>
1513
) : Runnable {
1614

1715
private val log = MarathonLogging.logger {}
1816

1917
override fun run() = runBlocking {
2018
log.debug("Starting Marathon worker")
19+
marathon.start()
2120

22-
val application = marathonStartKoin(configuration)
23-
try {
24-
val marathon = application.koin.get<Marathon>()
25-
marathon.start()
26-
27-
for (component in componentsChannel) {
28-
log.debug("Scheduling tests for $component")
29-
marathon.scheduleTests(component)
30-
}
31-
32-
log.debug("Waiting for completion")
33-
stopAndWaitForCompletion(marathon)
34-
} finally {
35-
application.close()
21+
for (component in componentsChannel) {
22+
log.debug("Scheduling tests for $component")
23+
marathon.scheduleTests(component)
3624
}
25+
26+
log.debug("Waiting for completion")
27+
stopAndWaitForCompletion(marathon)
3728
}
3829

3930
private suspend fun stopAndWaitForCompletion(marathon: Marathon) {
4031
val success = marathon.stopAndWaitForCompletion()
4132

42-
val shouldReportFailure = !configuration.ignoreFailures
33+
val shouldReportFailure = !marathon.configuration.ignoreFailures
4334
if (!success && shouldReportFailure) {
44-
throw GradleException("Tests failed! See ${configuration.outputDir}/html/index.html")
35+
throw GradleException("Tests failed! See ${marathon.configuration.outputDir}/html/index.html")
4536
}
4637
}
4738
}

0 commit comments

Comments
 (0)