Skip to content

Commit ef7a1ab

Browse files
committed
[utbot-rd]
enabled test generation report, optimized kryo serialization - UtMethodTestCase no more send to main process, access it through testSetsId
1 parent f071d40 commit ef7a1ab

File tree

9 files changed

+477
-307
lines changed

9 files changed

+477
-307
lines changed

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ data class CodeGeneratorResult(
122122
// null if no util class needed, e.g. when we are generating utils directly into test class
123123
val utilClassKind: UtilClassKind?,
124124
// in idea process this property is null
125-
val testsGenerationReport: TestsGenerationReport?,
125+
val testsGenerationReport: TestsGenerationReport
126126
)
127127

128128
/**

utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineMain.kt

Lines changed: 116 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
package org.utbot.framework.process
22

33
import com.jetbrains.rd.framework.IProtocol
4-
import com.jetbrains.rd.framework.Protocol
54
import com.jetbrains.rd.util.Logger
6-
import com.jetbrains.rd.util.info
75
import com.jetbrains.rd.util.lifetime.Lifetime
8-
import com.jetbrains.rd.util.lifetime.LifetimeDefinition
96
import kotlinx.coroutines.runBlocking
107
import mu.KotlinLogging
118
import org.utbot.analytics.AnalyticsConfigureUtil
129
import org.utbot.common.AbstractSettings
1310
import org.utbot.common.allNestedClasses
11+
import org.utbot.common.appendHtmlLine
12+
import org.utbot.common.nameOfPackage
1413
import org.utbot.engine.util.mockListeners.ForceMockListener
1514
import org.utbot.engine.util.mockListeners.ForceStaticMockListener
1615
import org.utbot.framework.codegen.*
1716
import org.utbot.framework.codegen.model.CodeGenerator
17+
import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport
1818
import org.utbot.framework.plugin.api.*
1919
import org.utbot.framework.plugin.api.Signature
2020
import org.utbot.framework.plugin.api.util.UtContext
@@ -23,29 +23,24 @@ import org.utbot.framework.plugin.api.util.id
2323
import org.utbot.framework.plugin.api.util.jClass
2424
import org.utbot.framework.plugin.services.JdkInfo
2525
import org.utbot.framework.process.generated.*
26+
import org.utbot.framework.util.Conflict
2627
import org.utbot.framework.util.ConflictTriggers
27-
import org.utbot.framework.util.jimpleBody
2828
import org.utbot.instrumentation.util.KryoHelper
2929
import org.utbot.rd.CallsSynchronizer
3030
import org.utbot.rd.ClientProtocolBuilder
3131
import org.utbot.rd.findRdPort
3232
import org.utbot.rd.loggers.UtRdKLoggerFactory
3333
import org.utbot.sarif.RdSourceFindingStrategyFacade
34-
import org.utbot.sarif.SarifRegion
3534
import org.utbot.sarif.SarifReport
3635
import org.utbot.summary.summarize
3736
import soot.SootMethod
3837
import soot.UnitPatchingChain
3938
import soot.util.HashChain
4039
import java.io.File
4140
import java.net.URLClassLoader
42-
import java.nio.file.Path
4341
import java.nio.file.Paths
4442
import java.util.*
45-
import kotlin.reflect.KFunction
46-
import kotlin.reflect.KParameter
4743
import kotlin.reflect.full.functions
48-
import kotlin.reflect.jvm.javaType
4944
import kotlin.time.Duration.Companion.seconds
5045

5146
private val messageFromMainTimeoutMillis = 120.seconds
@@ -87,7 +82,9 @@ suspend fun main(args: Array<String>) = runBlocking {
8782
}
8883

8984
private lateinit var testGenerator: TestCaseGenerator
90-
private lateinit var testSets: List<UtMethodTestSet>
85+
private val testSets: MutableMap<Long, List<UtMethodTestSet>> = mutableMapOf()
86+
private val testGenerationReports: MutableList<TestsGenerationReport> = mutableListOf()
87+
private var idCounter: Long = 0
9188

9289
private fun EngineProcessModel.setup(
9390
kryoHelper: KryoHelper, synchronizer: CallsSynchronizer, realProtocol: IProtocol
@@ -132,27 +129,11 @@ private fun EngineProcessModel.setup(
132129
})
133130
.map { it.summarize(Paths.get(params.searchDirectory)) }
134131
.filterNot { it.executions.isEmpty() && it.errors.isEmpty() }
135-
.apply {
136-
testSets = this
137-
}
138-
.map {
139-
// kryo cannot serialize structures such as JimpleBody or Api.Step.stmt
140-
// because soot entities use classes and collections with complex initialization logic
141-
// which kryo cannot guess.
142-
// so in the idea process such entities will be either null or empty, operating on them supported only
143-
// in engine process.
144-
// jimpleBody can be obtained from executionId, logic for path and fullpath should be discussed
145-
it.copy(jimpleBody = null, executions = it.executions.map { execution ->
146-
if (execution is UtSymbolicExecution) execution.apply {
147-
path = mutableListOf()
148-
fullPath = mutableListOf()
149-
}
150-
else execution
151-
})
152-
}
153132

133+
val id = ++idCounter
154134

155-
GenerateResult(kryoHelper.writeObject(result))
135+
testSets[id] = result
136+
GenerateResult(result.size, id)
156137
}
157138
synchronizer.measureExecutionForTermination(render) { params ->
158139
val testFramework = testFrameworkByName(params.testFramework)
@@ -161,8 +142,10 @@ private fun EngineProcessModel.setup(
161142
} else {
162143
MockitoStaticMocking
163144
}
145+
val classId: ClassId = kryoHelper.readObject(params.classUnderTest)
146+
val testSetsId: Long = params.testSetsId
164147
val codeGenerator = CodeGenerator(
165-
classUnderTest = kryoHelper.readObject(params.classUnderTest),
148+
classUnderTest = classId,
166149
generateUtilClassFile = params.generateUtilClassFile,
167150
paramNames = kryoHelper.readObject(params.paramNames),
168151
testFramework = testFramework,
@@ -177,17 +160,11 @@ private fun EngineProcessModel.setup(
177160
enableTestsTimeout = params.enableTestsTimeout,
178161
testClassPackageName = params.testClassPackageName
179162
)
180-
RenderResult(
181-
kryoHelper.writeObject(
182-
codeGenerator.generateAsStringWithTestReport(kryoHelper.readObject<List<UtMethodTestSet>>(
183-
params.testSets
184-
).map { it.copy(jimpleBody = jimpleBody(it.method)) })
185-
// currently due to moving engine out of idea process we cannot show test generation report
186-
// because it contains CgElements, which kryo cannot deserialize because they use complex collections
187-
// potentially it is possible to implement a lot of additional kryo serializers for that collections
188-
.copy(testsGenerationReport = null)
189-
)
190-
)
163+
codeGenerator.generateAsStringWithTestReport(testSets[testSetsId]!!)
164+
.let {
165+
testGenerationReports.add(it.testsGenerationReport)
166+
RenderResult(it.generatedCode, kryoHelper.writeObject(it.utilClassKind))
167+
}
191168
}
192169
synchronizer.measureExecutionForTermination(stopProcess) { synchronizer.stopProtocol() }
193170
synchronizer.measureExecutionForTermination(obtainClassId) { canonicalName ->
@@ -215,10 +192,107 @@ private fun EngineProcessModel.setup(
215192
val reportFilePath = Paths.get(params.reportFilePath)
216193
reportFilePath.toFile().writeText(
217194
SarifReport(
218-
testSets,
195+
testSets[params.testSetsId]!!,
219196
params.generatedTestsCode,
220197
RdSourceFindingStrategyFacade(realProtocol.rdSourceFindingStrategy)
221198
).createReport()
222199
)
223200
}
201+
synchronizer.measureExecutionForTermination(generateTestReport) { params ->
202+
val eventLogMessage = params.eventLogMessage
203+
val testPackageName: String? = params.testPackageName
204+
var hasWarnings = false
205+
val reports = testGenerationReports
206+
val isMultiPackage = params.isMultiPackage
207+
val (notifyMessage, statistics) = if (reports.size == 1) {
208+
val report = reports.first()
209+
processInitialWarnings(report, params)
210+
211+
val message = buildString {
212+
appendHtmlLine(report.toString(isShort = true))
213+
214+
val classUnderTestPackageName =
215+
report.classUnderTest.java.nameOfPackage
216+
217+
destinationWarningMessage(testPackageName, classUnderTestPackageName)
218+
?.let {
219+
hasWarnings = true
220+
appendHtmlLine(it)
221+
appendHtmlLine()
222+
}
223+
eventLogMessage?.let {
224+
appendHtmlLine(it)
225+
}
226+
}
227+
hasWarnings = hasWarnings || report.hasWarnings
228+
Pair(message, report.detailedStatistics)
229+
} else {
230+
val accumulatedReport = reports.first()
231+
processInitialWarnings(accumulatedReport, params)
232+
233+
val message = buildString {
234+
appendHtmlLine("${reports.sumBy { it.executables.size }} tests generated for ${reports.size} classes.")
235+
236+
if (accumulatedReport.initialWarnings.isNotEmpty()) {
237+
accumulatedReport.initialWarnings.forEach { appendHtmlLine(it()) }
238+
appendHtmlLine()
239+
}
240+
241+
// TODO maybe add statistics info here
242+
243+
for (report in reports) {
244+
val classUnderTestPackageName =
245+
report.classUnderTest.java.nameOfPackage
246+
247+
hasWarnings = hasWarnings || report.hasWarnings
248+
if (!isMultiPackage) {
249+
val destinationWarning =
250+
destinationWarningMessage(testPackageName, classUnderTestPackageName)
251+
if (destinationWarning != null) {
252+
hasWarnings = true
253+
appendHtmlLine(destinationWarning)
254+
appendHtmlLine()
255+
}
256+
}
257+
}
258+
eventLogMessage?.let {
259+
appendHtmlLine(it)
260+
}
261+
}
262+
263+
Pair(message, null)
264+
}
265+
GenerateTestReportResult(notifyMessage, statistics, hasWarnings)
266+
}
267+
}
268+
269+
private fun processInitialWarnings(report: TestsGenerationReport, params: GenerateTestReportArgs) {
270+
val hasInitialWarnings = params.hasInitialWarnings
271+
272+
if (!hasInitialWarnings) {
273+
return
274+
}
275+
276+
report.apply {
277+
params.forceMockWarning?.let {
278+
initialWarnings.add { it }
279+
}
280+
params.forceStaticMockWarnings?.let {
281+
initialWarnings.add { it }
282+
}
283+
params.testFrameworkWarning?.let {
284+
initialWarnings.add { it }
285+
}
286+
}
287+
}
288+
289+
private fun destinationWarningMessage(testPackageName: String?, classUnderTestPackageName: String): String? {
290+
return if (classUnderTestPackageName != testPackageName) {
291+
"""
292+
Warning: Destination package $testPackageName does not match package of the class $classUnderTestPackageName.
293+
This may cause unnecessary usage of reflection for protected or package-private fields and methods access.
294+
""".trimIndent()
295+
} else {
296+
null
297+
}
224298
}

0 commit comments

Comments
 (0)