Skip to content

Commit e856e5f

Browse files
authored
Adds a cluster comment for tests produced by Smart Fuzzer (#858)
* Proposed an initial solution * Fix tests * Fix formatting * Fix formatting
1 parent e6d3f9b commit e856e5f

File tree

9 files changed

+127
-58
lines changed

9 files changed

+127
-58
lines changed

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,13 +1260,6 @@ private fun StringBuilder.appendOptional(name: String, value: Map<*, *>) {
12601260
}
12611261
}
12621262

1263-
/**
1264-
* Enum that represents different type of engines that produce tests.
1265-
*/
1266-
enum class UtExecutionCreator {
1267-
FUZZER, SYMBOLIC_ENGINE
1268-
}
1269-
12701263
/**
12711264
* Entity that represents cluster information that should appear in the comment.
12721265
*/
@@ -1280,7 +1273,6 @@ data class UtClusterInfo(
12801273
*/
12811274
data class UtExecutionCluster(val clusterInfo: UtClusterInfo, val executions: List<UtExecution>)
12821275

1283-
12841276
/**
12851277
* Entity that represents various types of statements in comments.
12861278
*/

utbot-summary-tests/src/test/kotlin/math/SummaryIntMathTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ class SummaryIntMathTest : SummaryTestCaseGeneratorTest(
137137
)
138138

139139
val clusterInfo = listOf(
140-
Pair(UtClusterInfo("SUCCESSFUL EXECUTIONS for method pow(int, int)", null), 14)
140+
Pair(UtClusterInfo("SYMBOLIC EXECUTION ENGINE: SUCCESSFUL EXECUTIONS for method pow(int, int)", null), 14)
141141
)
142142

143143
val method = IntMath::pow

utbot-summary-tests/src/test/kotlin/math/SummaryOfMathTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,10 @@ class SummaryOfMathTest : SummaryTestCaseGeneratorTest(
219219
)
220220

221221
val clusterInfo = listOf(
222-
Pair(UtClusterInfo("SUCCESSFUL EXECUTIONS #0 for method ofDoubles(double[])", null), 3),
222+
Pair(UtClusterInfo("SYMBOLIC EXECUTION ENGINE: SUCCESSFUL EXECUTIONS #0 for method ofDoubles(double[])", null), 3),
223223
Pair(
224224
UtClusterInfo(
225-
"SUCCESSFUL EXECUTIONS #1 for method ofDoubles(double[])", "\n" +
225+
"SYMBOLIC EXECUTION ENGINE: SUCCESSFUL EXECUTIONS #1 for method ofDoubles(double[])", "\n" +
226226
"Common steps:\n" +
227227
"<pre>\n" +
228228
"Tests execute conditions:\n" +
@@ -246,7 +246,7 @@ class SummaryOfMathTest : SummaryTestCaseGeneratorTest(
246246
"</pre>"
247247
), 3
248248
),
249-
Pair(UtClusterInfo("ERROR SUITE for method ofDoubles(double[])", null), 1)
249+
Pair(UtClusterInfo("SYMBOLIC EXECUTION ENGINE: ERROR SUITE for method ofDoubles(double[])", null), 1)
250250
)
251251

252252
summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames, clusterInfo)

utbot-summary/src/main/kotlin/org/utbot/summary/Summarization.kt

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,20 @@ import org.utbot.summary.UtSummarySettings.GENERATE_NAMES
1616
import org.utbot.summary.analysis.ExecutionStructureAnalysis
1717
import org.utbot.summary.ast.JimpleToASTMap
1818
import org.utbot.summary.ast.SourceCodeParser
19-
import org.utbot.summary.comment.SimpleClusterCommentBuilder
19+
import org.utbot.summary.comment.SymbolicExecutionClusterCommentBuilder
2020
import org.utbot.summary.comment.SimpleCommentBuilder
2121
import org.utbot.summary.name.SimpleNameBuilder
2222
import java.io.File
2323
import java.nio.file.Path
2424
import java.nio.file.Paths
2525
import mu.KotlinLogging
26+
import org.utbot.framework.plugin.api.UtConcreteExecutionFailure
27+
import org.utbot.framework.plugin.api.UtExecutionSuccess
28+
import org.utbot.framework.plugin.api.UtExplicitlyThrownException
29+
import org.utbot.framework.plugin.api.UtImplicitlyThrownException
30+
import org.utbot.framework.plugin.api.UtOverflowFailure
31+
import org.utbot.framework.plugin.api.UtSandboxFailure
32+
import org.utbot.framework.plugin.api.UtTimeoutException
2633
import org.utbot.fuzzer.FuzzedMethodDescription
2734
import org.utbot.fuzzer.FuzzedValue
2835
import org.utbot.fuzzer.UtFuzzedExecution
@@ -40,7 +47,7 @@ fun UtMethodTestSet.summarize(sourceFile: File?, searchDirectory: Path = Paths.g
4047
makeDiverseExecutions(this)
4148
val invokeDescriptions = invokeDescriptions(this, searchDirectory)
4249
// every cluster has summary and list of executions
43-
val executionClusters = Summarization(sourceFile, invokeDescriptions).summary(this)
50+
val executionClusters = Summarization(sourceFile, invokeDescriptions).fillSummaries(this)
4451
val updatedExecutions = executionClusters.flatMap { it.executions }
4552
var pos = 0
4653
val clustersInfo = executionClusters.map {
@@ -49,7 +56,10 @@ fun UtMethodTestSet.summarize(sourceFile: File?, searchDirectory: Path = Paths.g
4956
pos += clusterSize
5057
it.clusterInfo to indices
5158
}
52-
this.copy(executions = updatedExecutions, clustersInfo = clustersInfo) // TODO: looks weird and don't create the real copy
59+
this.copy(
60+
executions = updatedExecutions,
61+
clustersInfo = clustersInfo
62+
) // TODO: looks weird and don't create the real copy
5363
} catch (e: Throwable) {
5464
logger.info(e) { "Summary generation error" }
5565
this
@@ -64,7 +74,7 @@ class Summarization(val sourceFile: File?, val invokeDescriptions: List<InvokeDe
6474
private val tagGenerator = TagGenerator()
6575
private val jimpleBodyAnalysis = ExecutionStructureAnalysis()
6676

67-
fun summary(testSet: UtMethodTestSet): List<UtExecutionCluster> {
77+
fun fillSummaries(testSet: UtMethodTestSet): List<UtExecutionCluster> {
6878
val namesCounter = mutableMapOf<String, Int>()
6979

7080
if (testSet.executions.isEmpty()) {
@@ -83,28 +93,61 @@ class Summarization(val sourceFile: File?, val invokeDescriptions: List<InvokeDe
8393

8494
// handles tests produced by fuzzing
8595
val executionsProducedByFuzzer = testSet.executions.filterIsInstance<UtFuzzedExecution>()
96+
val successfulFuzzerExecutions = mutableListOf<UtFuzzedExecution>()
97+
val unsuccessfulFuzzerExecutions = mutableListOf<UtFuzzedExecution>()
8698

8799
if (executionsProducedByFuzzer.isNotEmpty()) {
88100
executionsProducedByFuzzer.forEach { utExecution ->
89101

90102
val nameSuggester = sequenceOf(ModelBasedNameSuggester(), MethodBasedNameSuggester())
91103
val testMethodName = try {
92-
nameSuggester.flatMap { it.suggest(utExecution.fuzzedMethodDescription as FuzzedMethodDescription, utExecution.fuzzingValues as List<FuzzedValue>, utExecution.result) }.firstOrNull()
104+
nameSuggester.flatMap {
105+
it.suggest(
106+
utExecution.fuzzedMethodDescription as FuzzedMethodDescription,
107+
utExecution.fuzzingValues as List<FuzzedValue>,
108+
utExecution.result
109+
)
110+
}.firstOrNull()
93111
} catch (t: Throwable) {
94112
logger.error(t) { "Cannot create suggested test name for $utExecution" } // TODO: add better explanation or default behavoiur
95113
null
96114
}
97115

98116
utExecution.testMethodName = testMethodName?.testName
99-
utExecution.displayName = testMethodName?.displayName
117+
utExecution.displayName = testMethodName?.displayName
118+
119+
when (utExecution.result) {
120+
is UtConcreteExecutionFailure -> unsuccessfulFuzzerExecutions.add(utExecution)
121+
is UtExplicitlyThrownException -> unsuccessfulFuzzerExecutions.add(utExecution)
122+
is UtImplicitlyThrownException -> unsuccessfulFuzzerExecutions.add(utExecution)
123+
is UtOverflowFailure -> unsuccessfulFuzzerExecutions.add(utExecution)
124+
is UtSandboxFailure -> unsuccessfulFuzzerExecutions.add(utExecution)
125+
is UtTimeoutException -> unsuccessfulFuzzerExecutions.add(utExecution)
126+
is UtExecutionSuccess -> successfulFuzzerExecutions.add(utExecution)
127+
}
100128
}
101129

102-
clustersToReturn.add(
103-
UtExecutionCluster(
104-
UtClusterInfo(), // TODO: add something https://github.com/UnitTestBot/UTBotJava/issues/430
105-
executionsProducedByFuzzer
130+
if (successfulFuzzerExecutions.isNotEmpty()) {
131+
val clusterHeader = buildFuzzerClusterHeaderForSuccessfulExecutions(testSet)
132+
133+
clustersToReturn.add(
134+
UtExecutionCluster(
135+
UtClusterInfo(clusterHeader, null),
136+
successfulFuzzerExecutions
137+
)
106138
)
107-
)
139+
}
140+
141+
if (unsuccessfulFuzzerExecutions.isNotEmpty()) {
142+
val clusterHeader = buildFuzzerClusterHeaderForUnsuccessfulExecutions(testSet)
143+
144+
clustersToReturn.add(
145+
UtExecutionCluster(
146+
UtClusterInfo(clusterHeader, null),
147+
unsuccessfulFuzzerExecutions
148+
)
149+
)
150+
}
108151
}
109152

110153
// handles tests produced by symbolic engine, but with empty paths
@@ -113,14 +156,14 @@ class Summarization(val sourceFile: File?, val invokeDescriptions: List<InvokeDe
113156
if (executionsWithEmptyPaths.isNotEmpty()) {
114157
executionsWithEmptyPaths.forEach {
115158
logger.info {
116-
"Test is created by Symbolic Engine. The path for test ${it.testMethodName} " +
159+
"Test is created by Symbolic Execution Engine. The path for test ${it.testMethodName} " +
117160
"for method ${testSet.method.clazz.qualifiedName} is empty and summaries could not be generated."
118161
}
119162
}
120163

121164
clustersToReturn.add(
122165
UtExecutionCluster(
123-
UtClusterInfo(), // TODO: https://github.com/UnitTestBot/UTBotJava/issues/430
166+
UtClusterInfo(),
124167
executionsWithEmptyPaths
125168
)
126169
)
@@ -135,13 +178,13 @@ class Summarization(val sourceFile: File?, val invokeDescriptions: List<InvokeDe
135178
jimpleBodyAnalysis.traceStructuralAnalysis(jimpleBody, clusteredTags, methodUnderTest, invokeDescriptions)
136179
val numberOfSuccessfulClusters = clusteredTags.filter { it.isSuccessful }.size
137180
for (clusterTraceTags in clusteredTags) {
138-
val clusterHeader = clusterTraceTags.summary.takeIf { GENERATE_CLUSTER_COMMENTS }
181+
val clusterHeader = clusterTraceTags.clusterHeader.takeIf { GENERATE_CLUSTER_COMMENTS }
139182
val clusterContent = if (
140183
GENERATE_CLUSTER_COMMENTS && clusterTraceTags.isSuccessful // add only for successful executions
141184
&& numberOfSuccessfulClusters > 1 // there is more than one successful execution
142185
&& clusterTraceTags.traceTags.size > 1 // add if there is more than 1 execution
143186
) {
144-
SimpleClusterCommentBuilder(clusterTraceTags.commonStepsTraceTag, sootToAST)
187+
SymbolicExecutionClusterCommentBuilder(clusterTraceTags.commonStepsTraceTag, sootToAST)
145188
.buildString(methodUnderTest)
146189
.takeIf { it.isNotBlank() }
147190
?.let {
@@ -204,6 +247,20 @@ class Summarization(val sourceFile: File?, val invokeDescriptions: List<InvokeDe
204247
return listOf(UtExecutionCluster(UtClusterInfo(), testSet.executions))
205248
}
206249

250+
private fun buildFuzzerClusterHeaderForSuccessfulExecutions(testSet: UtMethodTestSet): String {
251+
val commentPrefix = "FUZZER:"
252+
val commentPostfix = "for method ${testSet.method.humanReadableName}"
253+
254+
return "$commentPrefix ${ExecutionGroup.SUCCESSFUL_EXECUTIONS.displayName} $commentPostfix"
255+
}
256+
257+
private fun buildFuzzerClusterHeaderForUnsuccessfulExecutions(testSet: UtMethodTestSet): String {
258+
val commentPrefix = "FUZZER:"
259+
val commentPostfix = "for method ${testSet.method.humanReadableName}"
260+
261+
return "$commentPrefix ${ExecutionGroup.EXPLICITLY_THROWN_UNCHECKED_EXCEPTIONS} $commentPostfix"
262+
}
263+
207264
private fun prepareTestSetForByteCodeAnalysis(testSet: UtMethodTestSet): UtMethodTestSet {
208265
val executions =
209266
testSet.executions.filterIsInstance<UtSymbolicExecution>().filter { it.path.isNotEmpty() }
@@ -278,7 +335,8 @@ private fun makeDiverseExecutions(testSet: UtMethodTestSet) {
278335
}
279336

280337
private fun invokeDescriptions(testSet: UtMethodTestSet, searchDirectory: Path): List<InvokeDescription> {
281-
val sootInvokes = testSet.executions.filterIsInstance<UtSymbolicExecution>().flatMap { it.path.invokeJimpleMethods() }.toSet()
338+
val sootInvokes =
339+
testSet.executions.filterIsInstance<UtSymbolicExecution>().flatMap { it.path.invokeJimpleMethods() }.toSet()
282340
return sootInvokes
283341
//TODO(SAT-1170)
284342
.filterNot { "\$lambda" in it.declaringClass.name }

utbot-summary/src/main/kotlin/org/utbot/summary/TagGenerator.kt

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class TagGenerator {
3737
// we only want to find intersections if there is more than one successful execution
3838
if (numberOfSuccessfulClusters > 1 && REMOVE_INTERSECTIONS) {
3939
val commonStepsInSuccessfulEx = listOfSplitSteps
40-
.filterIndexed { i, _ -> clusteredExecutions[i] is SuccessfulExecutionCluster } //search only in successful
40+
.filterIndexed { i, _ -> clusteredExecutions[i] is SuccessfulExecutionCluster } // search only in successful
4141
.map { it.commonSteps }
4242
.filter { it.isNotEmpty() }
4343
if (commonStepsInSuccessfulEx.size > 1) {
@@ -98,11 +98,12 @@ private fun generateExecutionTags(executions: List<UtSymbolicExecution>, splitSt
9898
private fun toClusterExecutions(testSet: UtMethodTestSet): List<ExecutionCluster> {
9999
val methodExecutions = testSet.executions.filterIsInstance<UtSymbolicExecution>()
100100
val clusters = mutableListOf<ExecutionCluster>()
101-
val commentPostfix = "for method ${testSet.method.displayName}"
101+
val commentPrefix = "SYMBOLIC EXECUTION ENGINE:"
102+
val commentPostfix = "for method ${testSet.method.humanReadableName}"
102103

103104
val grouped = methodExecutions.groupBy { it.result.clusterKind() }
104105

105-
val successfulExecutions = grouped[ClusterKind.SUCCESSFUL_EXECUTIONS] ?: emptyList()
106+
val successfulExecutions = grouped[ExecutionGroup.SUCCESSFUL_EXECUTIONS] ?: emptyList()
106107
if (successfulExecutions.isNotEmpty()) {
107108
val clustered =
108109
if (successfulExecutions.size >= MIN_NUMBER_OF_EXECUTIONS_FOR_CLUSTERING) {
@@ -113,28 +114,29 @@ private fun toClusterExecutions(testSet: UtMethodTestSet): List<ExecutionCluster
113114
for (c in clustered) {
114115
clusters +=
115116
SuccessfulExecutionCluster(
116-
"${ClusterKind.SUCCESSFUL_EXECUTIONS.displayName} #${clustered.keys.indexOf(c.key)} $commentPostfix",
117+
"$commentPrefix ${ExecutionGroup.SUCCESSFUL_EXECUTIONS.displayName} #${clustered.keys.indexOf(c.key)} $commentPostfix",
117118
c.value.toList()
118119
)
119120
}
120121
} else {
121122
clusters +=
122123
SuccessfulExecutionCluster(
123-
"${ClusterKind.SUCCESSFUL_EXECUTIONS.displayName} $commentPostfix",
124+
"$commentPrefix ${ExecutionGroup.SUCCESSFUL_EXECUTIONS.displayName} $commentPostfix",
124125
successfulExecutions.toList()
125126
)
126127
}
127128
}
128129

129130
clusters += grouped
130-
.filterNot { (kind, _) -> kind == ClusterKind.SUCCESSFUL_EXECUTIONS }
131+
.filterNot { (kind, _) -> kind == ExecutionGroup.SUCCESSFUL_EXECUTIONS }
131132
.map { (suffixId, group) ->
132-
FailedExecutionCluster("${suffixId.displayName} $commentPostfix", group)
133+
FailedExecutionCluster("$commentPrefix ${suffixId.displayName} $commentPostfix", group)
133134
}
134135
return clusters
135136
}
136137

137-
enum class ClusterKind {
138+
/** The group of execution to be presented in the generated source file with tests. */
139+
enum class ExecutionGroup {
138140
SUCCESSFUL_EXECUTIONS,
139141
ERROR_SUITE,
140142
CHECKED_EXCEPTIONS,
@@ -148,13 +150,13 @@ enum class ClusterKind {
148150
}
149151

150152
private fun UtExecutionResult.clusterKind() = when (this) {
151-
is UtExecutionSuccess -> ClusterKind.SUCCESSFUL_EXECUTIONS
152-
is UtImplicitlyThrownException -> if (this.exception.isCheckedException) ClusterKind.CHECKED_EXCEPTIONS else ClusterKind.ERROR_SUITE
153-
is UtExplicitlyThrownException -> if (this.exception.isCheckedException) ClusterKind.CHECKED_EXCEPTIONS else ClusterKind.EXPLICITLY_THROWN_UNCHECKED_EXCEPTIONS
154-
is UtOverflowFailure -> ClusterKind.OVERFLOWS
155-
is UtTimeoutException -> ClusterKind.TIMEOUTS
156-
is UtConcreteExecutionFailure -> ClusterKind.CRASH_SUITE
157-
is UtSandboxFailure -> ClusterKind.SECURITY
153+
is UtExecutionSuccess -> ExecutionGroup.SUCCESSFUL_EXECUTIONS
154+
is UtImplicitlyThrownException -> if (this.exception.isCheckedException) ExecutionGroup.CHECKED_EXCEPTIONS else ExecutionGroup.ERROR_SUITE
155+
is UtExplicitlyThrownException -> if (this.exception.isCheckedException) ExecutionGroup.CHECKED_EXCEPTIONS else ExecutionGroup.EXPLICITLY_THROWN_UNCHECKED_EXCEPTIONS
156+
is UtOverflowFailure -> ExecutionGroup.OVERFLOWS
157+
is UtTimeoutException -> ExecutionGroup.TIMEOUTS
158+
is UtConcreteExecutionFailure -> ExecutionGroup.CRASH_SUITE
159+
is UtSandboxFailure -> ExecutionGroup.SECURITY
158160
}
159161

160162
/**
@@ -185,7 +187,7 @@ private const val REMOVE_INTERSECTIONS: Boolean = true
185187
* Contains the entities required for summarization
186188
*/
187189
data class TraceTagCluster(
188-
var summary: String,
190+
var clusterHeader: String,
189191
val traceTags: List<TraceTag>,
190192
val commonStepsTraceTag: TraceTagWithoutExecution,
191193
val isSuccessful: Boolean

utbot-summary/src/main/kotlin/org/utbot/summary/Util.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ val <R> UtMethod<R>.javaConstructor: Constructor<*>?
7878
val <R> UtMethod<R>.javaMethod: Method?
7979
get() = (callable as? KFunction<*>)?.javaMethod ?: (callable as? KProperty<*>)?.getter?.javaMethod
8080

81-
val <R> UtMethod<R>.displayName: String
81+
val <R> UtMethod<R>.humanReadableName: String
8282
get() {
8383
val methodName = this.callable.name
8484
val javaMethod = this.javaMethod ?: this.javaConstructor

utbot-summary/src/main/kotlin/org/utbot/summary/comment/SimpleClusterCommentBuilder.kt renamed to utbot-summary/src/main/kotlin/org/utbot/summary/comment/SymbolicExecutionClusterCommentBuilder.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import soot.jimple.internal.JVirtualInvokeExpr
2020
/**
2121
* Inherits from SimpleCommentBuilder
2222
*/
23-
class SimpleClusterCommentBuilder(
23+
class SymbolicExecutionClusterCommentBuilder(
2424
traceTag: TraceTagWithoutExecution,
2525
sootToAST: MutableMap<SootMethod, JimpleToASTMap>
2626
) : SimpleCommentBuilder(traceTag, sootToAST, stringTemplates = StringsTemplatesPlural()) {

0 commit comments

Comments
 (0)