From b14addab48433f94bfc595cfbc33fb598ee92217 Mon Sep 17 00:00:00 2001 From: akazankov Date: Sat, 5 Aug 2023 14:18:57 +0200 Subject: [PATCH] allow limiting number of concurrent runs Running all test classes and methods at once consumes a lot of CPU/RAM/network, that might cause problems on weak machines with large amount of tests --- .../kotlin/ru/fix/corounit/engine/ClassRunner.kt | 11 ++++++++--- .../ru/fix/corounit/engine/Configuration.kt | 9 +++++++++ .../ru/fix/corounit/engine/ExecutionRunner.kt | 15 ++++++++++++++- .../src/test/resources/junit-platform.properties | 2 ++ readme.md | 6 ++++++ 5 files changed, 39 insertions(+), 4 deletions(-) diff --git a/corounit-engine/src/main/kotlin/ru/fix/corounit/engine/ClassRunner.kt b/corounit-engine/src/main/kotlin/ru/fix/corounit/engine/ClassRunner.kt index 02249f9..deb2d30 100644 --- a/corounit-engine/src/main/kotlin/ru/fix/corounit/engine/ClassRunner.kt +++ b/corounit-engine/src/main/kotlin/ru/fix/corounit/engine/ClassRunner.kt @@ -3,6 +3,7 @@ package ru.fix.corounit.engine import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope +import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.withContext import mu.KotlinLogging import org.junit.jupiter.api.* @@ -49,7 +50,7 @@ class ClassRunner( .firstOrNull { it.parameters.size == 1 } } - suspend fun executeClass() { + suspend fun executeClass(methodsRunsSemaphore: Semaphore?) { val classContext = TestClassContextElement(classDesc.clazz) val pluginsClassContext = context.pluginDispatcher.beforeTestClass(classContext) @@ -62,7 +63,9 @@ class ClassRunner( supervisorScope { for (methodDesc in classDesc.methodDescriptors) { - executeMethod(classContext, methodDesc, testInstance) + methodsRunsSemaphore.withPermitIfNotNull { + executeMethod(classContext, methodDesc, testInstance) + } } } @@ -76,7 +79,9 @@ class ClassRunner( supervisorScope { for (methodDesc in classDesc.methodDescriptors) { val testInstance = context.pluginDispatcher.createTestClassInstance(classDesc.clazz) - executeMethod(classContext, methodDesc, testInstance) + methodsRunsSemaphore.withPermitIfNotNull { + executeMethod(classContext, methodDesc, testInstance) + } } } diff --git a/corounit-engine/src/main/kotlin/ru/fix/corounit/engine/Configuration.kt b/corounit-engine/src/main/kotlin/ru/fix/corounit/engine/Configuration.kt index 97e48e5..181fe0d 100644 --- a/corounit-engine/src/main/kotlin/ru/fix/corounit/engine/Configuration.kt +++ b/corounit-engine/src/main/kotlin/ru/fix/corounit/engine/Configuration.kt @@ -5,6 +5,15 @@ import org.junit.platform.engine.ConfigurationParameters import java.util.concurrent.ForkJoinPool class Configuration(configurationParameters: ConfigurationParameters) { + + val concurrentTestClassesLimit = configurationParameters.get("corounit.execution.concurrent.test.classes.limit") + .map { it?.toInt() } + .orElse(null) + + val concurrentTestMethodsLimit = configurationParameters.get("corounit.execution.concurrent.test.methods.limit") + .map { it?.toInt() } + .orElse(null) + val parallelism = configurationParameters.get("corounit.execution.parallelism") .map { it?.toInt() } .orElse(Math.max(ForkJoinPool.getCommonPoolParallelism(), 2))!! diff --git a/corounit-engine/src/main/kotlin/ru/fix/corounit/engine/ExecutionRunner.kt b/corounit-engine/src/main/kotlin/ru/fix/corounit/engine/ExecutionRunner.kt index 066b58d..834b17b 100644 --- a/corounit-engine/src/main/kotlin/ru/fix/corounit/engine/ExecutionRunner.kt +++ b/corounit-engine/src/main/kotlin/ru/fix/corounit/engine/ExecutionRunner.kt @@ -1,16 +1,29 @@ package ru.fix.corounit.engine import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.sync.withPermit class ExecutionRunner(private val context: ExecutionContext) { + private val classesRunsSemaphore = context.configuration.concurrentTestClassesLimit?.let { Semaphore(it) } + private val methodsRunsSemaphore = context.configuration.concurrentTestMethodsLimit?.let { Semaphore(it) } + suspend fun executeExecution(execDesc: CorounitExecutionDescriptor) { context.notifyListenerAndRunInSupervisorScope(execDesc) { for (classDesc in execDesc.children.mapNotNull { it as? CorounitClassDescriptior }) { launch { - ClassRunner(context, classDesc).executeClass() + classesRunsSemaphore.withPermitIfNotNull { + ClassRunner(context, classDesc).executeClass(methodsRunsSemaphore) + } } } } } +} + +suspend fun Semaphore?.withPermitIfNotNull(block: suspend () -> Unit) = if(this != null) { + withPermit { block() } +} else { + block() } \ No newline at end of file diff --git a/corounit-engine/src/test/resources/junit-platform.properties b/corounit-engine/src/test/resources/junit-platform.properties index 2b6235c..8ecc035 100644 --- a/corounit-engine/src/test/resources/junit-platform.properties +++ b/corounit-engine/src/test/resources/junit-platform.properties @@ -1,3 +1,5 @@ +corounit.execution.concurrent.test.classes.limit=5 +corounit.execution.concurrent.test.methods.limit=10 corounit.execution.parallelism=2 # Corounit engine tests share single CorounitConfig class to assert interaction with engine plugins # In order to ensure that one test do not affect another we are running them sequentially diff --git a/readme.md b/readme.md index af68585..448aa24 100644 --- a/readme.md +++ b/readme.md @@ -95,6 +95,8 @@ You can use default `junit-platform.properties` file in test resources to specif ```properties # junit-platform.properties +corounit.execution.concurrent.test.classes.limit=5 +corounit.execution.concurrent.test.methods.limit=10 corounit.execution.parallelism=4 corounit.testinstance.lifecycle.default=per_class ``` @@ -376,6 +378,10 @@ class TestClass{ ## Corounit Properties You can add corounit properties within default JUnit property file at `src/test/resources/junit-platform.properties`: +* `corounit.execution.concurrent.test.classes.limit=null` + Max number of running concurrently test classes +* `corounit.execution.concurrent.test.methods.limit=null` + Max number of running concurrently test methods * `corounit.execution.parallelism=4` How many threads corounite engine will use to execute tests. * `corounit.testinstance.lifecycle.default=per_class`