Skip to content

Commit 8f1d85c

Browse files
authored
Better naming for functions and comments could be deduced from fuzzer (#288)
1 parent 2186e3d commit 8f1d85c

27 files changed

+509
-137
lines changed

utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ import org.utbot.fuzzer.FallbackModelProvider
161161
import org.utbot.fuzzer.collectConstantsForFuzzer
162162
import org.utbot.fuzzer.defaultModelProviders
163163
import org.utbot.fuzzer.fuzz
164+
import org.utbot.fuzzer.names.MethodBasedNameSuggester
165+
import org.utbot.fuzzer.names.ModelBasedNameSuggester
164166
import org.utbot.instrumentation.ConcreteExecutor
165167
import java.lang.reflect.ParameterizedType
166168
import kotlin.collections.plus
@@ -254,6 +256,7 @@ import soot.jimple.internal.JTableSwitchStmt
254256
import soot.jimple.internal.JThrowStmt
255257
import soot.jimple.internal.JVirtualInvokeExpr
256258
import soot.jimple.internal.JimpleLocal
259+
import soot.tagkit.ParamNamesTag
257260
import soot.toolkits.graph.ExceptionalUnitGraph
258261
import sun.reflect.Reflection
259262
import sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl
@@ -608,12 +611,16 @@ class UtBotSymbolicEngine(
608611
}
609612
}
610613

611-
val methodUnderTestDescription = FuzzedMethodDescription(executableId, collectConstantsForFuzzer(graph))
614+
val methodUnderTestDescription = FuzzedMethodDescription(executableId, collectConstantsForFuzzer(graph)).apply {
615+
compilableName = if (methodUnderTest.isMethod) executableId.name else null
616+
val names = graph.body.method.tags.filterIsInstance<ParamNamesTag>().firstOrNull()?.names
617+
parameterNameMap = { index -> names?.getOrNull(index) }
618+
}
612619
val modelProviderWithFallback = modelProvider(defaultModelProviders { nextDefaultModelId++ }).withFallback(fallbackModelProvider::toModel)
613620
val coveredInstructionTracker = mutableSetOf<Instruction>()
614621
var attempts = UtSettings.fuzzingMaxAttemps
615-
fuzz(methodUnderTestDescription, modelProviderWithFallback).forEachIndexed { index, parameters ->
616-
val initialEnvironmentModels = EnvironmentModels(thisInstance, parameters, mapOf())
622+
fuzz(methodUnderTestDescription, modelProviderWithFallback).forEach { values ->
623+
val initialEnvironmentModels = EnvironmentModels(thisInstance, values.map { it.model }, mapOf())
617624

618625
try {
619626
val concreteExecutionResult =
@@ -634,6 +641,14 @@ class UtBotSymbolicEngine(
634641
}
635642
}
636643

644+
val nameSuggester = sequenceOf(ModelBasedNameSuggester(), MethodBasedNameSuggester())
645+
val testMethodName = try {
646+
nameSuggester.flatMap { it.suggest(methodUnderTestDescription, values, concreteExecutionResult.result) }.firstOrNull()
647+
} catch (t: Throwable) {
648+
logger.error(t) { "Cannot create suggested test name for ${methodUnderTest.displayName}" }
649+
null
650+
}
651+
637652
emit(
638653
UtExecution(
639654
stateBefore = initialEnvironmentModels,
@@ -643,7 +658,8 @@ class UtBotSymbolicEngine(
643658
path = mutableListOf(),
644659
fullPath = emptyList(),
645660
coverage = concreteExecutionResult.coverage,
646-
testMethodName = if (methodUnderTest.isMethod) "test${methodUnderTest.callable.name.capitalize()}ByFuzzer${index}" else null
661+
testMethodName = testMethodName?.testName,
662+
displayName = testMethodName?.displayName
647663
)
648664
)
649665
} catch (e: CancellationException) {

utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import org.utbot.framework.plugin.api.util.primitiveByWrapper
3030
import org.utbot.framework.plugin.api.util.stringClassId
3131
import org.utbot.framework.plugin.api.util.withUtContext
3232
import org.utbot.framework.plugin.api.util.wrapperByPrimitive
33+
import org.utbot.fuzzer.FuzzedValue
3334
import org.utbot.fuzzer.ModelProvider
3435
import org.utbot.instrumentation.ConcreteExecutor
3536
import org.utbot.instrumentation.execute
@@ -179,7 +180,7 @@ object UtBotJavaApi {
179180
description.parametersMap.forEach { (classId, indices) ->
180181
createPrimitiveModels(primitiveValuesSupplier, classId).forEach { model ->
181182
indices.forEach { index ->
182-
consumer.accept(index, model)
183+
consumer.accept(index, FuzzedValue(model))
183184
}
184185
}
185186
}

utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ private object ConstantsFromIfStatement: ConstantsFinder {
108108
val local = useBoxes[(valueIndex + 1) % 2]
109109
var op = sootIfToFuzzedOp(ifStatement)
110110
if (valueIndex == 0) {
111-
op = reverse(op)
111+
op = op.reverseOrElse { it }
112112
}
113113
// Soot loads any integer type as an Int,
114114
// therefore we try to guess target type using second value
@@ -226,12 +226,4 @@ private fun sootIfToFuzzedOp(unit: JIfStmt) = when (unit.condition) {
226226
else -> FuzzedOp.NONE
227227
}
228228

229-
private fun reverse(op: FuzzedOp) = when(op) {
230-
FuzzedOp.GT -> FuzzedOp.LT
231-
FuzzedOp.LT -> FuzzedOp.GT
232-
FuzzedOp.LE -> FuzzedOp.GE
233-
FuzzedOp.GE -> FuzzedOp.LE
234-
else -> op
235-
}
236-
237229
private fun nextDirectUnit(graph: ExceptionalUnitGraph, unit: Unit): Unit? = graph.getSuccsOf(unit).takeIf { it.size == 1 }?.first()

utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ class FuzzedMethodDescription(
2020
val concreteValues: Collection<FuzzedConcreteValue> = emptyList()
2121
) {
2222

23+
/**
24+
* Name that can be used to generate test names
25+
*/
26+
var compilableName: String? = null
27+
28+
/**
29+
* Returns parameter name by its index in the signature
30+
*/
31+
var parameterNameMap: (Int) -> String? = { null }
32+
2333
/**
2434
* Map class id to indices of this class in parameters list.
2535
*/
@@ -47,16 +57,30 @@ data class FuzzedConcreteValue(
4757
val value: Any,
4858
val relativeOp: FuzzedOp = FuzzedOp.NONE,
4959
)
50-
enum class FuzzedOp {
51-
NONE,
52-
EQ,
53-
NE,
54-
GT,
55-
GE,
56-
LT,
57-
LE,
58-
CH, // changed or called
60+
61+
enum class FuzzedOp(val sign: String?) {
62+
NONE(null),
63+
EQ("=="),
64+
NE("!="),
65+
GT(">"),
66+
GE(">="),
67+
LT("<"),
68+
LE("<="),
69+
CH(null), // changed or called
5970
;
6071

6172
fun isComparisonOp() = this == EQ || this == NE || this == GT || this == GE || this == LT || this == LE
73+
74+
fun reverseOrNull() : FuzzedOp? = when(this) {
75+
EQ -> NE
76+
NE -> EQ
77+
GT -> LE
78+
LT -> GE
79+
LE -> GT
80+
GE -> LT
81+
else -> null
82+
}
83+
84+
fun reverseOrElse(another: (FuzzedOp) -> FuzzedOp): FuzzedOp =
85+
reverseOrNull() ?: another(this)
6286
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.utbot.fuzzer
2+
3+
import org.utbot.framework.plugin.api.UtModel
4+
5+
/**
6+
* Fuzzed Value stores information about concrete UtModel, reference to [ModelProvider]
7+
* and reasons about why this value was generated.
8+
*/
9+
class FuzzedValue(
10+
val model: UtModel,
11+
val createdBy: ModelProvider? = null
12+
) {
13+
14+
/**
15+
* Summary is a piece of useful information that clarify why this value has a concrete value.
16+
*
17+
* It supports a special character `%var%` that is used as a placeholder for parameter name.
18+
*
19+
* For example:
20+
* 1. `%var% = 2` for a value that have value 2
21+
* 2. `%var% >= 4` for a value that shouldn't be less than 4
22+
* 3. `foo(%var%) returns true` for values that should be passed as a function parameter
23+
* 4. `%var% has special characters` to describe content
24+
*/
25+
var summary: String? = null
26+
}

utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.utbot.fuzzer
22

3-
import org.utbot.framework.plugin.api.UtModel
43
import org.utbot.fuzzer.providers.ConstantsModelProvider
54
import org.utbot.fuzzer.providers.ObjectModelProvider
65
import org.utbot.fuzzer.providers.PrimitivesModelProvider
@@ -18,8 +17,8 @@ import kotlin.random.Random
1817

1918
private val logger = KotlinLogging.logger {}
2019

21-
fun fuzz(description: FuzzedMethodDescription, vararg modelProviders: ModelProvider): Sequence<List<UtModel>> {
22-
val values = List<MutableList<UtModel>>(description.parameters.size) { mutableListOf() }
20+
fun fuzz(description: FuzzedMethodDescription, vararg modelProviders: ModelProvider): Sequence<List<FuzzedValue>> {
21+
val values = List<MutableList<FuzzedValue>>(description.parameters.size) { mutableListOf() }
2322
modelProviders.forEach { fuzzingProvider ->
2423
fuzzingProvider.generate(description) { index, model ->
2524
values[index].add(model)

utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ fun interface ModelProvider {
1212
* @param description a fuzzed method description
1313
* @param consumer accepts index in the parameter list and [UtModel] for this parameter.
1414
*/
15-
fun generate(description: FuzzedMethodDescription, consumer: BiConsumer<Int, UtModel>)
15+
fun generate(description: FuzzedMethodDescription, consumer: BiConsumer<Int, FuzzedValue>)
1616

1717
/**
1818
* Combines this model provider with `anotherModelProvider` into one instance.
@@ -52,7 +52,7 @@ fun interface ModelProvider {
5252
*/
5353
fun withFallback(modelProvider: ModelProvider) : ModelProvider {
5454
return ModelProvider { description, consumer ->
55-
val providedByDelegateMethodParameters = mutableMapOf<Int, MutableList<UtModel>>()
55+
val providedByDelegateMethodParameters = mutableMapOf<Int, MutableList<FuzzedValue>>()
5656
this@ModelProvider.generate(description) { index, model ->
5757
providedByDelegateMethodParameters.computeIfAbsent(index) { mutableListOf() }.add(model)
5858
}
@@ -64,7 +64,7 @@ fun interface ModelProvider {
6464
val missingParameters =
6565
(0 until description.parameters.size).filter { !providedByDelegateMethodParameters.containsKey(it) }
6666
if (missingParameters.isNotEmpty()) {
67-
val values = mutableMapOf<Int, MutableList<UtModel>>()
67+
val values = mutableMapOf<Int, MutableList<FuzzedValue>>()
6868
modelProvider.generate(description) { i, m -> values.computeIfAbsent(i) { mutableListOf() }.add(m) }
6969
missingParameters.forEach { index ->
7070
values[index]?.let { models ->
@@ -90,7 +90,7 @@ fun interface ModelProvider {
9090
description.parametersMap.forEach { (classId, indices) ->
9191
fallbackModelSupplier(classId)?.let { model ->
9292
indices.forEach { index ->
93-
consumer.accept(index, model)
93+
consumer.accept(index, model.fuzzed())
9494
}
9595
}
9696
}
@@ -103,15 +103,15 @@ fun interface ModelProvider {
103103
return Combined(providers.toList())
104104
}
105105

106-
fun BiConsumer<Int, UtModel>.consumeAll(indices: List<Int>, models: Sequence<UtModel>) {
106+
fun BiConsumer<Int, FuzzedValue>.consumeAll(indices: List<Int>, models: Sequence<FuzzedValue>) {
107107
models.forEach { model ->
108108
indices.forEach { index ->
109109
accept(index, model)
110110
}
111111
}
112112
}
113113

114-
fun BiConsumer<Int, UtModel>.consumeAll(indices: List<Int>, models: List<UtModel>) {
114+
fun BiConsumer<Int, FuzzedValue>.consumeAll(indices: List<Int>, models: List<FuzzedValue>) {
115115
consumeAll(indices, models.asSequence())
116116
}
117117
}
@@ -120,12 +120,14 @@ fun interface ModelProvider {
120120
* Wrapper class that delegates implementation to the [providers].
121121
*/
122122
private class Combined(val providers: List<ModelProvider>): ModelProvider {
123-
override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer<Int, UtModel>) {
123+
override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer<Int, FuzzedValue>) {
124124
providers.forEach { provider ->
125125
provider.generate(description, consumer)
126126
}
127127
}
128128
}
129+
130+
fun UtModel.fuzzed(block: FuzzedValue.() -> Unit = {}): FuzzedValue = FuzzedValue(this, this@ModelProvider).apply(block)
129131
}
130132

131133
inline fun <reified T> ModelProvider.exceptIsInstance(): ModelProvider {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.utbot.fuzzer.names
2+
3+
import org.utbot.framework.plugin.api.UtExecutionResult
4+
import org.utbot.fuzzer.FuzzedMethodDescription
5+
import org.utbot.fuzzer.FuzzedValue
6+
7+
class MethodBasedNameSuggester : NameSuggester {
8+
override fun suggest(description: FuzzedMethodDescription, values: List<FuzzedValue>, result: UtExecutionResult?): Sequence<TestSuggestedInfo> {
9+
return sequenceOf(TestSuggestedInfo("test${description.compilableName?.capitalize() ?: "Created"}ByFuzzer"))
10+
}
11+
}

0 commit comments

Comments
 (0)