1
1
package org.utbot.framework.process
2
2
3
3
import com.jetbrains.rd.framework.IProtocol
4
- import com.jetbrains.rd.framework.Protocol
5
4
import com.jetbrains.rd.util.Logger
6
- import com.jetbrains.rd.util.info
7
5
import com.jetbrains.rd.util.lifetime.Lifetime
8
- import com.jetbrains.rd.util.lifetime.LifetimeDefinition
9
6
import kotlinx.coroutines.runBlocking
10
7
import mu.KotlinLogging
11
8
import org.utbot.analytics.AnalyticsConfigureUtil
12
9
import org.utbot.common.AbstractSettings
13
10
import org.utbot.common.allNestedClasses
11
+ import org.utbot.common.appendHtmlLine
12
+ import org.utbot.common.nameOfPackage
14
13
import org.utbot.engine.util.mockListeners.ForceMockListener
15
14
import org.utbot.engine.util.mockListeners.ForceStaticMockListener
16
15
import org.utbot.framework.codegen.*
17
16
import org.utbot.framework.codegen.model.CodeGenerator
17
+ import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport
18
18
import org.utbot.framework.plugin.api.*
19
19
import org.utbot.framework.plugin.api.Signature
20
20
import org.utbot.framework.plugin.api.util.UtContext
@@ -23,29 +23,24 @@ import org.utbot.framework.plugin.api.util.id
23
23
import org.utbot.framework.plugin.api.util.jClass
24
24
import org.utbot.framework.plugin.services.JdkInfo
25
25
import org.utbot.framework.process.generated.*
26
+ import org.utbot.framework.util.Conflict
26
27
import org.utbot.framework.util.ConflictTriggers
27
- import org.utbot.framework.util.jimpleBody
28
28
import org.utbot.instrumentation.util.KryoHelper
29
29
import org.utbot.rd.CallsSynchronizer
30
30
import org.utbot.rd.ClientProtocolBuilder
31
31
import org.utbot.rd.findRdPort
32
32
import org.utbot.rd.loggers.UtRdKLoggerFactory
33
33
import org.utbot.sarif.RdSourceFindingStrategyFacade
34
- import org.utbot.sarif.SarifRegion
35
34
import org.utbot.sarif.SarifReport
36
35
import org.utbot.summary.summarize
37
36
import soot.SootMethod
38
37
import soot.UnitPatchingChain
39
38
import soot.util.HashChain
40
39
import java.io.File
41
40
import java.net.URLClassLoader
42
- import java.nio.file.Path
43
41
import java.nio.file.Paths
44
42
import java.util.*
45
- import kotlin.reflect.KFunction
46
- import kotlin.reflect.KParameter
47
43
import kotlin.reflect.full.functions
48
- import kotlin.reflect.jvm.javaType
49
44
import kotlin.time.Duration.Companion.seconds
50
45
51
46
private val messageFromMainTimeoutMillis = 120 .seconds
@@ -87,7 +82,9 @@ suspend fun main(args: Array<String>) = runBlocking {
87
82
}
88
83
89
84
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
91
88
92
89
private fun EngineProcessModel.setup (
93
90
kryoHelper : KryoHelper , synchronizer : CallsSynchronizer , realProtocol : IProtocol
@@ -132,27 +129,11 @@ private fun EngineProcessModel.setup(
132
129
})
133
130
.map { it.summarize(Paths .get(params.searchDirectory)) }
134
131
.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
- }
153
132
133
+ val id = ++ idCounter
154
134
155
- GenerateResult (kryoHelper.writeObject(result))
135
+ testSets[id] = result
136
+ GenerateResult (result.size, id)
156
137
}
157
138
synchronizer.measureExecutionForTermination(render) { params ->
158
139
val testFramework = testFrameworkByName(params.testFramework)
@@ -161,8 +142,10 @@ private fun EngineProcessModel.setup(
161
142
} else {
162
143
MockitoStaticMocking
163
144
}
145
+ val classId: ClassId = kryoHelper.readObject(params.classUnderTest)
146
+ val testSetsId: Long = params.testSetsId
164
147
val codeGenerator = CodeGenerator (
165
- classUnderTest = kryoHelper.readObject(params.classUnderTest) ,
148
+ classUnderTest = classId ,
166
149
generateUtilClassFile = params.generateUtilClassFile,
167
150
paramNames = kryoHelper.readObject(params.paramNames),
168
151
testFramework = testFramework,
@@ -177,17 +160,11 @@ private fun EngineProcessModel.setup(
177
160
enableTestsTimeout = params.enableTestsTimeout,
178
161
testClassPackageName = params.testClassPackageName
179
162
)
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
+ }
191
168
}
192
169
synchronizer.measureExecutionForTermination(stopProcess) { synchronizer.stopProtocol() }
193
170
synchronizer.measureExecutionForTermination(obtainClassId) { canonicalName ->
@@ -215,10 +192,107 @@ private fun EngineProcessModel.setup(
215
192
val reportFilePath = Paths .get(params.reportFilePath)
216
193
reportFilePath.toFile().writeText(
217
194
SarifReport (
218
- testSets,
195
+ testSets[params.testSetsId] !! ,
219
196
params.generatedTestsCode,
220
197
RdSourceFindingStrategyFacade (realProtocol.rdSourceFindingStrategy)
221
198
).createReport()
222
199
)
223
200
}
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
+ }
224
298
}
0 commit comments