Generate test by fuzzing for methods with no parameters #511 by Markoutte · Pull Request #515 · UnitTestBot/UTBotJava · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import org.utbot.framework.plugin.api.util.withUtContext
import org.utbot.framework.plugin.api.util.wrapperByPrimitive
import org.utbot.fuzzer.FuzzedValue
import org.utbot.fuzzer.ModelProvider
import org.utbot.fuzzer.ModelProvider.Companion.yieldValue
import org.utbot.instrumentation.ConcreteExecutor
import org.utbot.instrumentation.execute
import java.lang.reflect.Method
Expand Down Expand Up @@ -175,11 +176,13 @@ object UtBotJavaApi {
}
?.map { UtPrimitiveModel(it) } ?: emptySequence()

val customModelProvider = ModelProvider { description, consumer ->
description.parametersMap.forEach { (classId, indices) ->
createPrimitiveModels(primitiveValuesSupplier, classId).forEach { model ->
indices.forEach { index ->
consumer.accept(index, FuzzedValue(model))
val customModelProvider = ModelProvider { description ->
sequence {
description.parametersMap.forEach { (classId, indices) ->
createPrimitiveModels(primitiveValuesSupplier, classId).forEach { model ->
indices.forEach { index ->
yieldValue(index, FuzzedValue(model))
}
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedParameter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.utbot.fuzzer

/**
* Fuzzed parameter of a method.
*
* @param index of the parameter in method signature
* @param value fuzzed values
*/
class FuzzedParameter(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this class be a data class?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, data classes creates hashCode/equals/toString that aren't necessary in most cases

val index: Int,
val value: FuzzedValue
) {
operator fun component1() = index
operator fun component2() = value
}
2 changes: 1 addition & 1 deletion utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ private val logger = KotlinLogging.logger {}
fun fuzz(description: FuzzedMethodDescription, vararg modelProviders: ModelProvider): Sequence<List<FuzzedValue>> {
val values = List<MutableList<FuzzedValue>>(description.parameters.size) { mutableListOf() }
modelProviders.forEach { fuzzingProvider ->
fuzzingProvider.generate(description) { index, model ->
fuzzingProvider.generate(description).forEach { (index, model) ->
values[index].add(model)
}
}
Expand Down
76 changes: 41 additions & 35 deletions utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@ package org.utbot.fuzzer

import org.utbot.framework.plugin.api.UtModel
import org.utbot.framework.plugin.api.ClassId
import java.util.function.BiConsumer

fun interface ModelProvider {

/**
* Generates values for the method.
*
* @param description a fuzzed method description
* @param consumer accepts index in the parameter list and [UtModel] for this parameter.
* @return sequence that produces [FuzzedParameter].
*/
fun generate(description: FuzzedMethodDescription, consumer: BiConsumer<Int, FuzzedValue>)
fun generate(description: FuzzedMethodDescription): Sequence<FuzzedParameter>

/**
* Combines this model provider with `anotherModelProvider` into one instance.
Expand Down Expand Up @@ -51,25 +50,24 @@ fun interface ModelProvider {
* @param modelProvider is called and every value of [ClassId] is collected which wasn't created by this model provider.
*/
fun withFallback(modelProvider: ModelProvider) : ModelProvider {
return ModelProvider { description, consumer ->
val providedByDelegateMethodParameters = mutableMapOf<Int, MutableList<FuzzedValue>>()
this@ModelProvider.generate(description) { index, model ->
providedByDelegateMethodParameters.computeIfAbsent(index) { mutableListOf() }.add(model)
}
providedByDelegateMethodParameters.forEach { (index, models) ->
models.forEach { model ->
consumer.accept(index, model)
val thisModelProvider = this
return ModelProvider { description ->
sequence {
val providedByDelegateMethodParameters = mutableSetOf<Int>()
thisModelProvider.generate(description).forEach { (index, model) ->
providedByDelegateMethodParameters += index
yieldValue(index, model)
}
}
val missingParameters =
(0 until description.parameters.size).filter { !providedByDelegateMethodParameters.containsKey(it) }
if (missingParameters.isNotEmpty()) {
val values = mutableMapOf<Int, MutableList<FuzzedValue>>()
modelProvider.generate(description) { i, m -> values.computeIfAbsent(i) { mutableListOf() }.add(m) }
missingParameters.forEach { index ->
values[index]?.let { models ->
models.forEach { model ->
consumer.accept(index, model)
val missingParameters =
(0 until description.parameters.size).filter { !providedByDelegateMethodParameters.contains(it) }
if (missingParameters.isNotEmpty()) {
val values = mutableMapOf<Int, MutableList<FuzzedValue>>()
modelProvider.generate(description).forEach { (i, m) -> values.computeIfAbsent(i) { mutableListOf() }.add(m) }
missingParameters.forEach { index ->
values[index]?.let { models ->
models.forEach { model ->
yieldValue(index, model)
}
}
}
}
Expand All @@ -86,15 +84,17 @@ fun interface ModelProvider {
* @param fallbackModelSupplier is called for every [ClassId] which wasn't created by this model provider.
*/
fun withFallback(fallbackModelSupplier: (ClassId) -> UtModel?) : ModelProvider {
return withFallback { description, consumer ->
description.parametersMap.forEach { (classId, indices) ->
fallbackModelSupplier(classId)?.let { model ->
indices.forEach { index ->
consumer.accept(index, model.fuzzed())
return withFallback( ModelProvider { description ->
sequence {
description.parametersMap.forEach { (classId, indices) ->
fallbackModelSupplier(classId)?.let { model ->
indices.forEach { index ->
yieldValue(index, model.fuzzed())
}
}
}
}
}
})
}

companion object {
Expand All @@ -103,26 +103,32 @@ fun interface ModelProvider {
return Combined(providers.toList())
}

fun BiConsumer<Int, FuzzedValue>.consumeAll(indices: List<Int>, models: Sequence<FuzzedValue>) {
models.forEach { model ->
indices.forEach { index ->
accept(index, model)
suspend fun SequenceScope<FuzzedParameter>.yieldValue(index: Int, value: FuzzedValue) {
yield(FuzzedParameter(index, value))
}

suspend fun SequenceScope<FuzzedParameter>.yieldAllValues(indices: List<Int>, models: Sequence<FuzzedValue>) {
indices.forEach { index ->
models.forEach { model ->
yieldValue(index, model)
}
}
}

fun BiConsumer<Int, FuzzedValue>.consumeAll(indices: List<Int>, models: List<FuzzedValue>) {
consumeAll(indices, models.asSequence())
suspend fun SequenceScope<FuzzedParameter>.yieldAllValues(indices: List<Int>, models: List<FuzzedValue>) {
yieldAllValues(indices, models.asSequence())
}
}

/**
* Wrapper class that delegates implementation to the [providers].
*/
private class Combined(val providers: List<ModelProvider>): ModelProvider {
override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer<Int, FuzzedValue>) {
override fun generate(description: FuzzedMethodDescription): Sequence<FuzzedParameter> = sequence {
providers.forEach { provider ->
provider.generate(description, consumer)
provider.generate(description).forEach {
yieldValue(it.index, it.value)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ package org.utbot.fuzzer.providers

import org.utbot.framework.plugin.api.*
import org.utbot.fuzzer.FuzzedMethodDescription
import org.utbot.fuzzer.FuzzedValue
import org.utbot.fuzzer.FuzzedParameter
import org.utbot.fuzzer.ModelProvider
import java.util.function.BiConsumer
import org.utbot.fuzzer.ModelProvider.Companion.yieldValue

/**
* Simple model implementation.
*/
@Suppress("unused")
abstract class AbstractModelProvider: ModelProvider {
override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer<Int, FuzzedValue>) {
override fun generate(description: FuzzedMethodDescription): Sequence<FuzzedParameter> = sequence{
description.parametersMap.forEach { (classId, indices) ->
toModel(classId)?.let { defaultModel ->
indices.forEach { index ->
consumer.accept(index, defaultModel.fuzzed())
yieldValue(index, defaultModel.fuzzed())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,20 @@ import org.utbot.framework.plugin.api.UtArrayModel
import org.utbot.framework.plugin.api.util.defaultValueModel
import org.utbot.framework.plugin.api.util.isArray
import org.utbot.fuzzer.FuzzedMethodDescription
import org.utbot.fuzzer.FuzzedValue
import org.utbot.fuzzer.FuzzedParameter
import org.utbot.fuzzer.ModelProvider
import org.utbot.fuzzer.ModelProvider.Companion.consumeAll
import java.util.function.BiConsumer
import org.utbot.fuzzer.ModelProvider.Companion.yieldAllValues
import java.util.function.IntSupplier

class ArrayModelProvider(
private val idGenerator: IntSupplier
) : ModelProvider {
override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer<Int, FuzzedValue>) {
override fun generate(description: FuzzedMethodDescription): Sequence<FuzzedParameter> = sequence {
description.parametersMap
.asSequence()
.filter { (classId, _) -> classId.isArray }
.forEach { (arrayClassId, indices) ->
consumer.consumeAll(indices, listOf(0, 10).map { arraySize ->
yieldAllValues(indices, listOf(0, 10).map { arraySize ->
UtArrayModel(
id = idGenerator.asInt,
arrayClassId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,28 @@ import org.utbot.framework.plugin.api.UtPrimitiveModel
import org.utbot.framework.plugin.api.util.charClassId
import org.utbot.framework.plugin.api.util.stringClassId
import org.utbot.fuzzer.FuzzedMethodDescription
import org.utbot.fuzzer.FuzzedValue
import org.utbot.fuzzer.FuzzedParameter
import org.utbot.fuzzer.ModelProvider
import java.util.function.BiConsumer
import org.utbot.fuzzer.ModelProvider.Companion.yieldValue

/**
* Collects all char constants and creates string with them.
*/
object CharToStringModelProvider : ModelProvider {
override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer<Int, FuzzedValue>) {
val indices = description.parametersMap[stringClassId] ?: return
override fun generate(description: FuzzedMethodDescription): Sequence<FuzzedParameter> = sequence {
val indices = description.parametersMap[stringClassId] ?: return@sequence
if (indices.isNotEmpty()) {
val string = description.concreteValues.asSequence()
.filter { it.classId == charClassId }
.map { it.value }
.filterIsInstance<Char>()
.joinToString(separator = "")
if (string.isNotEmpty()) {
val model = UtPrimitiveModel(string).fuzzed()
indices.forEach {
consumer.accept(it, model)
sequenceOf(string.reversed(), string).forEach { str ->
val model = UtPrimitiveModel(str).fuzzed()
indices.forEach {
yieldValue(it, model)
}
}
}
}
Expand Down
Loading