Skip to content

Commit

Permalink
Merge pull request #52 from JetBrains-Research/malakhov/parallel-exec…
Browse files Browse the repository at this point in the history
…ution

Parallel test execution
  • Loading branch information
ilma4 authored Feb 27, 2025
2 parents e8db8b7 + 39cbb9d commit 3885533
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 17 deletions.
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ rgxgen = "2.0"
gradle-publish = "1.2.1"
jacoco = "0.8.12"

kotlinx-coroutines = "1.10.1"

slf4j = "2.0.16"

[libraries]
Expand Down Expand Up @@ -61,6 +63,8 @@ rgxgen = { module = "com.github.curious-odd-man:rgxgen", version.ref = "rgxgen"

slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }

kotlinx-coroutines = {module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines"}

jacoco-core = { module = "org.jacoco:org.jacoco.core", version.ref = "jacoco" }
jacoco-report = { module = "org.jacoco:org.jacoco.report", version.ref = "jacoco" }

Expand Down
33 changes: 24 additions & 9 deletions kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package kotlinx.fuzz

import java.nio.file.Path
import kotlin.io.path.*
import kotlin.io.path.Path
import kotlin.io.path.absolute
import kotlin.io.path.absolutePathString
import kotlin.math.max
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1
Expand All @@ -27,6 +30,7 @@ import kotlin.time.Duration.Companion.seconds
* Default: empty list
* @param maxSingleTargetFuzzTime - max time to fuzz a single target. Default: 1 minute
* @param reproducerPath - Path to store reproducers. Default: `$workDir/reproducers`
* @param threads - Number of cpu threads to use for executing targets in parallel. Default `threads available for jvm / 2`, usually half of logical threads
*/
interface KFuzzConfig {
val fuzzEngine: String
Expand All @@ -39,6 +43,7 @@ interface KFuzzConfig {
val dumpCoverage: Boolean
val reproducerPath: Path
val logLevel: String
val threads: Int

fun toPropertiesMap(): Map<String, String>

Expand Down Expand Up @@ -111,6 +116,13 @@ class KFuzzConfigImpl private constructor() : KFuzzConfig {
toString = { it },
fromString = { it },
)
override var threads: Int by KFuzzConfigProperty(
SystemProperty.THREADS,
defaultValue = max(1, Runtime.getRuntime().availableProcessors() / 2),
validate = { require(it > 0) { "'threads' must be positive" } },
toString = { it.toString() },
fromString = { it.toInt() },
)

override fun toPropertiesMap(): Map<String, String> = configProperties()
.associate { it.systemProperty.name to it.stringValue }
Expand Down Expand Up @@ -149,16 +161,19 @@ class KFuzzConfigImpl private constructor() : KFuzzConfig {
}
}

internal fun fromPropertiesMap(properties: Map<String, String>): KFuzzConfigImpl = wrapConfigErrors {
KFuzzConfigImpl().apply {
configProperties().forEach {
val propertyKey = it.systemProperty.name
it.setFromString(properties[propertyKey] ?: error("map missing property $propertyKey"))
internal fun fromPropertiesMap(properties: Map<String, String>): KFuzzConfigImpl =
wrapConfigErrors {
KFuzzConfigImpl().apply {
configProperties().forEach {
val propertyKey = it.systemProperty.name
it.setFromString(
properties[propertyKey] ?: error("map missing property $propertyKey"),
)
}
assertAllSet()
validate()
}
assertAllSet()
validate()
}
}

internal fun fromAnotherConfig(
config: KFuzzConfig,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ enum class SystemProperty(name: String) {
MAX_SINGLE_TARGET_FUZZ_TIME("kotlinx.fuzz.maxSingleTargetFuzzTime"),
REGRESSION("kotlinx.fuzz.regression"),
REPRODUCER_PATH("kotlinx.fuzz.reproducerPath"),
THREADS("kotlinx.fuzz.threads"),
WORK_DIR("kotlinx.fuzz.workDir"),
;

Expand Down
1 change: 1 addition & 0 deletions kotlinx.fuzz.gradle/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies {
implementation(libs.junit.platform.engine)
implementation(libs.jacoco.core)
implementation(libs.jacoco.report)
implementation(libs.kotlinx.coroutines)


testImplementation(libs.junit.platform.testkit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package kotlinx.fuzz.gradle.junit
import java.lang.reflect.Method
import java.net.URI
import kotlin.reflect.KClass
import kotlinx.coroutines.*
import kotlinx.fuzz.*
import kotlinx.fuzz.log.LoggerFacade
import kotlinx.fuzz.log.debug
Expand Down Expand Up @@ -67,16 +68,23 @@ internal class KotlinxFuzzJunitEngine : TestEngine {
override fun execute(request: ExecutionRequest) {
val root = request.rootTestDescriptor
fuzzEngine.initialise()
root.children.forEach { child -> executeImpl(request, child) }

val dispatcher = Dispatchers.Default.limitedParallelism(config.threads, "kotlinx.fuzz")
runBlocking(dispatcher) {
root.children.map { child -> async { executeImpl(request, child) } }.awaitAll()
}

fuzzEngine.finishExecution()
}

private fun handleContainer(request: ExecutionRequest, descriptor: TestDescriptor) {
private suspend fun handleContainer(
request: ExecutionRequest,
descriptor: TestDescriptor,
): Unit = coroutineScope {
request.engineExecutionListener.executionStarted(descriptor)
descriptor.children.forEach { child -> executeImpl(request, child) }
descriptor.children.map { child -> async { executeImpl(request, child) } }.awaitAll()
request.engineExecutionListener.executionFinished(
descriptor,
TestExecutionResult.successful(),
descriptor, TestExecutionResult.successful(),
)
}

Expand All @@ -90,7 +98,7 @@ internal class KotlinxFuzzJunitEngine : TestEngine {
else -> TestExecutionResult.failed(finding)
}

private fun executeImpl(request: ExecutionRequest, descriptor: TestDescriptor) {
private suspend fun executeImpl(request: ExecutionRequest, descriptor: TestDescriptor) {
when (descriptor) {
is ClassTestDescriptor -> handleContainer(request, descriptor)
is MethodRegressionTestDescriptor -> handleContainer(request, descriptor)
Expand Down Expand Up @@ -144,7 +152,14 @@ internal class KotlinxFuzzJunitEngine : TestEngine {
}

private fun appendTestsInClass(javaClass: Class<*>, engineDescriptor: TestDescriptor) {
engineDescriptor.addChild(ClassTestDescriptor(javaClass, engineDescriptor, config, isRegression))
engineDescriptor.addChild(
ClassTestDescriptor(
javaClass,
engineDescriptor,
config,
isRegression,
),
)
}

companion object {
Expand All @@ -161,6 +176,7 @@ internal class KotlinxFuzzJunitEngine : TestEngine {
parameters.size == 1 &&
parameters[0].type == KFuzzer::class.java

private fun KClass<*>.testInstance(): Any = objectInstance ?: java.getDeclaredConstructor().newInstance()
private fun KClass<*>.testInstance(): Any =
objectInstance ?: java.getDeclaredConstructor().newInstance()
}
}

0 comments on commit 3885533

Please sign in to comment.