diff --git a/integration-test/build.gradle.kts b/integration-test/build.gradle.kts index 997759f02..c310e6c47 100644 --- a/integration-test/build.gradle.kts +++ b/integration-test/build.gradle.kts @@ -119,8 +119,6 @@ spek2 { tasks { afterEvaluate { val runSpekJvmTest by getting(Test::class) { - // enable concurrent discovery - systemProperty("spek2.discovery.concurrent", "") filter { includeTestsMatching("org.spekframework.spek2.*") } diff --git a/spek-runtime/src/commonMain/kotlin/org/spekframework/spek2/runtime/Executor.kt b/spek-runtime/src/commonMain/kotlin/org/spekframework/spek2/runtime/Executor.kt index e5c47272f..63b4a46e8 100644 --- a/spek-runtime/src/commonMain/kotlin/org/spekframework/spek2/runtime/Executor.kt +++ b/spek-runtime/src/commonMain/kotlin/org/spekframework/spek2/runtime/Executor.kt @@ -10,20 +10,29 @@ import org.spekframework.spek2.runtime.scope.ScopeImpl import org.spekframework.spek2.runtime.scope.TestScopeImpl import kotlin.coroutines.EmptyCoroutineContext +interface TestHandle { + fun await() +} + +expect class TestRunner(concurrency: Int) { + fun runTest(test: suspend () -> Unit): TestHandle +} + class Executor { - suspend fun execute(request: ExecutionRequest) { + fun execute(request: ExecutionRequest, concurrency: Int) { + val runner = TestRunner(concurrency) request.executionListener.executionStart() - // note that this call will be run in parallel depending on the CoroutineDispatcher used - supervisorScope { - request.roots.map { async { execute(it, request.executionListener) } } - .forEach { job -> - try { - job.await() - } catch (e: Throwable) { - println("An error has occurred: ${e.message}") - } + request.roots + .map { + runner.runTest { execute(it, request.executionListener) } + } + .forEach { handle -> + try { + handle.await() + } catch (e: Throwable) { + println("An error has occurred: ${e.message}") } - } + } request.executionListener.executionFinish() } diff --git a/spek-runtime/src/commonMain/kotlin/org/spekframework/spek2/runtime/SpekRuntime.kt b/spek-runtime/src/commonMain/kotlin/org/spekframework/spek2/runtime/SpekRuntime.kt index 0b2c161ef..0ad5dd296 100644 --- a/spek-runtime/src/commonMain/kotlin/org/spekframework/spek2/runtime/SpekRuntime.kt +++ b/spek-runtime/src/commonMain/kotlin/org/spekframework/spek2/runtime/SpekRuntime.kt @@ -65,13 +65,12 @@ class SpekRuntime { fun execute(request: ExecutionRequest) { doRunBlocking { - if (isConcurrentExecutionEnabled(false)) { - withContext(Dispatchers.Default) { - Executor().execute(request) - } + val concurrency = if (isConcurrentExecutionEnabled(false)) { + getExecutionParallelism() } else { - Executor().execute(request) + 1 } + Executor().execute(request, concurrency) } } @@ -113,6 +112,7 @@ class SpekRuntime { } expect fun isConcurrentDiscoveryEnabled(default: Boolean): Boolean +expect fun getExecutionParallelism(): Int expect fun isConcurrentExecutionEnabled(default: Boolean): Boolean expect fun getGlobalTimeoutSetting(default: Long): Long diff --git a/spek-runtime/src/jvmMain/kotlin/org/spekframework/spek2/runtime/executors.kt b/spek-runtime/src/jvmMain/kotlin/org/spekframework/spek2/runtime/executors.kt index 16d989f4b..4ebd00bfa 100644 --- a/spek-runtime/src/jvmMain/kotlin/org/spekframework/spek2/runtime/executors.kt +++ b/spek-runtime/src/jvmMain/kotlin/org/spekframework/spek2/runtime/executors.kt @@ -2,9 +2,41 @@ package org.spekframework.spek2.runtime import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.runBlocking +import java.util.concurrent.CompletableFuture +import java.util.concurrent.ExecutionException +import java.util.concurrent.Executors actual fun doRunBlocking(block: suspend CoroutineScope.() -> Unit) { runBlocking { block() } } + +actual class TestRunner actual constructor(concurrency: Int) { + private val executor = Executors.newFixedThreadPool(concurrency) { r -> + Thread(r).also { + it.isDaemon = true + } + } + + actual fun runTest(test: suspend () -> Unit): TestHandle { + val handle = CompletableFuture() + + executor.submit { + runBlocking { + test() + handle.complete(null) + } + } + + return object : TestHandle { + override fun await() { + try { + handle.get() + } catch (e: ExecutionException) { + throw e.cause!! + } + } + } + } +} \ No newline at end of file diff --git a/spek-runtime/src/jvmMain/kotlin/org/spekframework/spek2/runtime/timeout.kt b/spek-runtime/src/jvmMain/kotlin/org/spekframework/spek2/runtime/timeout.kt index 57294ede0..855e47ddf 100644 --- a/spek-runtime/src/jvmMain/kotlin/org/spekframework/spek2/runtime/timeout.kt +++ b/spek-runtime/src/jvmMain/kotlin/org/spekframework/spek2/runtime/timeout.kt @@ -22,4 +22,8 @@ actual fun isConcurrentExecutionEnabled(default: Boolean): Boolean { actual fun measureTime(block: () -> Unit): Long { return measureTimeMillis(block) +} + +actual fun getExecutionParallelism(): Int { + return Runtime.getRuntime().availableProcessors() } \ No newline at end of file diff --git a/spek-runtime/src/nativeMain/kotlin/org/spekframework/spek2/runtime/executors.kt b/spek-runtime/src/nativeMain/kotlin/org/spekframework/spek2/runtime/executors.kt index 16d989f4b..e56dc0e62 100644 --- a/spek-runtime/src/nativeMain/kotlin/org/spekframework/spek2/runtime/executors.kt +++ b/spek-runtime/src/nativeMain/kotlin/org/spekframework/spek2/runtime/executors.kt @@ -8,3 +8,17 @@ actual fun doRunBlocking(block: suspend CoroutineScope.() -> Unit) { block() } } + +actual class TestRunner actual constructor(concurrency: Int) { + actual fun runTest(test: suspend () -> Unit): TestHandle { + doRunBlocking { + test() + } + + return object : TestHandle { + override fun await() { + // do nothing + } + } + } +} \ No newline at end of file diff --git a/spek-runtime/src/nativeMain/kotlin/org/spekframework/spek2/runtime/timeout.kt b/spek-runtime/src/nativeMain/kotlin/org/spekframework/spek2/runtime/timeout.kt index a67560958..34e289e74 100644 --- a/spek-runtime/src/nativeMain/kotlin/org/spekframework/spek2/runtime/timeout.kt +++ b/spek-runtime/src/nativeMain/kotlin/org/spekframework/spek2/runtime/timeout.kt @@ -19,4 +19,8 @@ actual fun isConcurrentExecutionEnabled(default: Boolean): Boolean { @UseExperimental(ExperimentalTime::class) actual fun measureTime(block: () -> Unit): Long { return MonoClock.measureTime(block).inMilliseconds.toLong() +} + +actual fun getExecutionParallelism(): Int { + return 1 } \ No newline at end of file