Skip to content

Commit 7f914af

Browse files
authored
Exclude third-party libs from coverage statistics #2218 (#2282)
1 parent 981c77d commit 7f914af

File tree

11 files changed

+263
-47
lines changed

11 files changed

+263
-47
lines changed

utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ enum class TestSelectionStrategyType {
632632
/**
633633
* Adds new test only if it increases coverage
634634
*/
635-
COVERAGE_STRATEGY
635+
COVERAGE_STRATEGY,
636636
}
637637

638638
/**

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

Lines changed: 74 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,17 @@ abstract class UtExecution(
140140
var summary: List<DocStatement>? = null,
141141
var testMethodName: String? = null,
142142
var displayName: String? = null
143-
) : UtResult()
143+
) : UtResult() {
144+
abstract fun copy(
145+
stateBefore: EnvironmentModels = this.stateBefore,
146+
stateAfter: EnvironmentModels = this.stateAfter,
147+
result: UtExecutionResult = this.result,
148+
coverage: Coverage? = this.coverage,
149+
summary: List<DocStatement>? = this.summary,
150+
testMethodName: String? = this.testMethodName,
151+
displayName: String? = this.displayName,
152+
): UtExecution
153+
}
144154

145155
/**
146156
* Symbolic execution.
@@ -176,6 +186,27 @@ class UtSymbolicExecution(
176186

177187
var containsMocking: Boolean = false
178188

189+
override fun copy(
190+
stateBefore: EnvironmentModels,
191+
stateAfter: EnvironmentModels,
192+
result: UtExecutionResult,
193+
coverage: Coverage?,
194+
summary: List<DocStatement>?,
195+
testMethodName: String?,
196+
displayName: String?
197+
): UtExecution = UtSymbolicExecution(
198+
stateBefore = stateBefore,
199+
stateAfter = stateAfter,
200+
result = result,
201+
instrumentation = instrumentation,
202+
path = path,
203+
fullPath = fullPath,
204+
coverage = coverage,
205+
summary = summary,
206+
testMethodName = testMethodName,
207+
displayName = displayName
208+
)
209+
179210
override fun toString(): String = buildString {
180211
append("UtSymbolicExecution(")
181212
appendLine()
@@ -200,25 +231,28 @@ class UtSymbolicExecution(
200231
}
201232

202233
fun copy(
203-
stateAfter: EnvironmentModels,
204-
result: UtExecutionResult,
205-
coverage: Coverage,
234+
stateBefore: EnvironmentModels = this.stateBefore,
235+
stateAfter: EnvironmentModels = this.stateAfter,
236+
result: UtExecutionResult = this.result,
237+
coverage: Coverage? = this.coverage,
238+
summary: List<DocStatement>? = this.summary,
239+
testMethodName: String? = this.testMethodName,
240+
displayName: String? = this.displayName,
206241
instrumentation: List<UtInstrumentation> = this.instrumentation,
207-
): UtResult {
208-
return UtSymbolicExecution(
209-
stateBefore,
210-
stateAfter,
211-
result,
212-
instrumentation,
213-
path,
214-
fullPath,
215-
coverage,
216-
summary,
217-
testMethodName,
218-
displayName,
219-
symbolicSteps,
220-
)
221-
}
242+
path: MutableList<Step> = this.path,
243+
fullPath: List<Step> = this.fullPath
244+
): UtExecution = UtSymbolicExecution(
245+
stateBefore = stateBefore,
246+
stateAfter = stateAfter,
247+
result = result,
248+
instrumentation = instrumentation,
249+
path = path,
250+
fullPath = fullPath,
251+
coverage = coverage,
252+
summary = summary,
253+
testMethodName = testMethodName,
254+
displayName = displayName
255+
)
222256
}
223257

224258
/**
@@ -235,12 +269,31 @@ class UtSymbolicExecution(
235269
*/
236270
class UtFailedExecution(
237271
stateBefore: EnvironmentModels,
238-
result: UtExecutionFailure,
272+
result: UtExecutionResult,
239273
coverage: Coverage? = null,
240274
summary: List<DocStatement>? = null,
241275
testMethodName: String? = null,
242276
displayName: String? = null
243-
) : UtExecution(stateBefore, MissingState, result, coverage, summary, testMethodName, displayName)
277+
) : UtExecution(stateBefore, MissingState, result, coverage, summary, testMethodName, displayName) {
278+
override fun copy(
279+
stateBefore: EnvironmentModels,
280+
stateAfter: EnvironmentModels,
281+
result: UtExecutionResult,
282+
coverage: Coverage?,
283+
summary: List<DocStatement>?,
284+
testMethodName: String?,
285+
displayName: String?
286+
): UtExecution {
287+
return UtFailedExecution(
288+
stateBefore,
289+
result,
290+
coverage,
291+
summary,
292+
testMethodName,
293+
displayName
294+
)
295+
}
296+
}
244297

245298
open class EnvironmentModels(
246299
val thisInstance: UtModel?,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package org.utbot.framework.plugin.api
33
/**
44
* Represents a covered bytecode instruction.
55
*
6-
* @param className a fqn of the class.
6+
* @param className the fqn in internal form, i.e. com/rest/order/services/OrderService$InnerClass.
77
* @param methodSignature the signature of the method.
88
* @param lineNumber a number of the line in the source file.
99
* @param id a unique identifier among all instructions in all classes.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import org.utbot.framework.plugin.api.UtSpringContextModel
1212
object SpringModelUtils {
1313
val applicationContextClassId = ClassId("org.springframework.context.ApplicationContext")
1414
val crudRepositoryClassId = ClassId("org.springframework.data.repository.CrudRepository")
15+
val entityClassId = ClassId("javax.persistence.Entity")
1516

1617
val getBeanMethodId = MethodId(
1718
classId = applicationContextClassId,

utbot-framework/src/main/kotlin/org/utbot/framework/minimization/Minimization.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,8 @@ private fun UtModel.calculateSize(used: MutableSet<UtModel> = mutableSetOf()): I
238238

239239
private fun UtStatementModel.calculateSize(used: MutableSet<UtModel> = mutableSetOf()): Int =
240240
when (this) {
241-
is UtDirectSetFieldModel -> 1 + fieldModel.calculateSize(used)
242-
is UtStatementCallModel -> 1 + params.sumOf { it.calculateSize(used) }
241+
is UtDirectSetFieldModel -> 1 + fieldModel.calculateSize(used) + instance.calculateSize(used)
242+
is UtStatementCallModel -> 1 + params.sumOf { it.calculateSize(used) } + (instance?.calculateSize(used) ?: 0)
243243
}
244244

245245
/**

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

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import org.utbot.common.measureTime
1414
import org.utbot.common.runBlockingWithCancellationPredicate
1515
import org.utbot.common.runIgnoringCancellationException
1616
import org.utbot.common.trace
17+
import org.utbot.common.PathUtil.toURL
18+
import org.utbot.common.tryLoadClass
1719
import org.utbot.engine.EngineController
1820
import org.utbot.engine.Mocker
1921
import org.utbot.engine.UtBotSymbolicEngine
@@ -27,6 +29,7 @@ import org.utbot.framework.UtSettings.utBotGenerationTimeoutInMillis
2729
import org.utbot.framework.UtSettings.warmupConcreteExecution
2830
import org.utbot.framework.plugin.api.utils.checkFrameworkDependencies
2931
import org.utbot.framework.minimization.minimizeTestCase
32+
import org.utbot.framework.plugin.api.util.SpringModelUtils.entityClassId
3033
import org.utbot.framework.plugin.api.util.id
3134
import org.utbot.framework.plugin.api.util.utContext
3235
import org.utbot.framework.plugin.services.JdkInfo
@@ -41,6 +44,7 @@ import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrument
4144
import org.utbot.instrumentation.warmup
4245
import org.utbot.taint.TaintConfigurationProvider
4346
import java.io.File
47+
import java.net.URLClassLoader
4448
import java.nio.file.Path
4549
import kotlin.coroutines.cancellation.CancellationException
4650
import kotlin.math.min
@@ -116,11 +120,17 @@ open class TestCaseGenerator(
116120
}
117121
}
118122

119-
fun minimizeExecutions(executions: List<UtExecution>): List<UtExecution> =
120-
if (UtSettings.testMinimizationStrategyType == TestSelectionStrategyType.DO_NOT_MINIMIZE_STRATEGY) {
121-
executions
122-
} else {
123-
minimizeTestCase(executions) { it.result::class.java }
123+
fun minimizeExecutions(classUnderTestId: ClassId, executions: List<UtExecution>): List<UtExecution> =
124+
when (UtSettings.testMinimizationStrategyType) {
125+
TestSelectionStrategyType.DO_NOT_MINIMIZE_STRATEGY -> executions
126+
TestSelectionStrategyType.COVERAGE_STRATEGY ->
127+
minimizeTestCase(
128+
when (applicationContext) {
129+
is SpringApplicationContext -> executions.filterCoveredInstructions(classUnderTestId)
130+
else -> executions
131+
}
132+
)
133+
{ it.result::class.java }
124134
}
125135

126136
@Throws(CancellationException::class)
@@ -268,7 +278,7 @@ open class TestCaseGenerator(
268278
return methods.map { method ->
269279
UtMethodTestSet(
270280
method,
271-
minimizeExecutions(method2executions.getValue(method)),
281+
minimizeExecutions(method.classId, method2executions.getValue(method)),
272282
jimpleBody(method),
273283
method2errors.getValue(method)
274284
)
@@ -351,6 +361,80 @@ open class TestCaseGenerator(
351361
}
352362
}
353363

364+
private fun List<UtExecution>.filterCoveredInstructions(classUnderTestId: ClassId): List<UtExecution> {
365+
// Do nothing when we were launched not from IDEA or when there are no executions
366+
if (buildDirs.isEmpty() || this.isEmpty()) return this
367+
368+
// List of annotations that we want to find in execution instructions
369+
// in order to exclude such instructions for some reason
370+
// E.g. we exclude instructions (which classes have @Entity) when it is not a class under test
371+
// because we are not interested in coverage which was possibly produced by Spring itself
372+
val annotationsToIgnore =
373+
listOfNotNull(utContext.classLoader.tryLoadClass(entityClassId.name))
374+
375+
val buildDirsClassLoader = createBuildDirsClassLoader()
376+
val isClassOnUserClasspathCache = mutableMapOf<String, Boolean>()
377+
378+
// Here we filter out instructions from third-party libraries
379+
// Also, we filter out instructions that operate
380+
// in classes marked with annotations from [annotationsToIgnore]
381+
// and in standard java libs
382+
return this.map { execution ->
383+
val coverage = execution.coverage ?: return@map execution
384+
385+
val filteredCoveredInstructions =
386+
coverage.coveredInstructions
387+
.filter { instruction ->
388+
val instrClassFqn =
389+
instruction.className
390+
.also {
391+
val isInstrClassOnClassPath =
392+
isClassOnUserClasspathCache.getOrPut(it) {
393+
buildDirsClassLoader.findResource(it.plus(".class")) != null
394+
}
395+
if (!isInstrClassOnClassPath) return@filter false
396+
}
397+
.replace('/', '.')
398+
399+
// We do not want to filter out instructions that are in class under test
400+
if (instrClassFqn == classUnderTestId.name) return@filter true
401+
402+
// We do not want to take instructions in classes
403+
// marked with annotations from [annotationsToIgnore]
404+
return@filter !hasAnnotations(instrClassFqn, annotationsToIgnore)
405+
}
406+
.ifEmpty {
407+
coverage.coveredInstructions
408+
.also {
409+
logger.warn("Execution covered instruction list became empty. Proceeding with not filtered instruction list.")
410+
}
411+
}
412+
413+
execution.copy(
414+
coverage = Coverage(
415+
coveredInstructions = filteredCoveredInstructions,
416+
instructionsCount = coverage.instructionsCount,
417+
missedInstructions = coverage.missedInstructions
418+
)
419+
)
420+
}
421+
}
422+
423+
private fun createBuildDirsClassLoader(): URLClassLoader {
424+
val urls = buildDirs.map { it.toURL() }.toTypedArray()
425+
return URLClassLoader(urls, null)
426+
}
427+
428+
private fun hasAnnotations(className: String, annotations: List<Class<*>>): Boolean =
429+
utContext
430+
.classLoader
431+
.loadClass(className)
432+
.annotations
433+
.any { existingAnnotation ->
434+
annotations.any { annotation ->
435+
annotation.isInstance(existingAnnotation)
436+
}
437+
}
354438
}
355439

356440

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ private fun EngineProcessModel.setup(kryoHelper: KryoHelper, watchdog: IdleWatch
106106
Instrumenter.adapter = RdInstrumenter(realProtocol.rdInstrumenterAdapter)
107107
val applicationContext: ApplicationContext = kryoHelper.readObject(params.applicationContext)
108108

109-
testGenerator = TestCaseGenerator(buildDirs = params.buildDir.map { Paths.get(it) },
109+
testGenerator = TestCaseGenerator(
110+
buildDirs = params.buildDir.map { Paths.get(it) },
110111
classpath = params.classpath,
111112
dependencyPaths = params.dependencyPaths,
112113
jdkInfo = JdkInfo(Paths.get(params.jdkInfo.path), params.jdkInfo.version),
@@ -115,7 +116,8 @@ private fun EngineProcessModel.setup(kryoHelper: KryoHelper, watchdog: IdleWatch
115116
runBlocking {
116117
model.isCancelled.startSuspending(Unit)
117118
}
118-
})
119+
}
120+
)
119121
}
120122
watchdog.measureTimeForActiveCall(generate, "Generating tests") { params ->
121123
val methods: List<ExecutableId> = kryoHelper.readObject(params.methods)

utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/UtFuzzedExecution.kt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,29 @@ class UtFuzzedExecution(
3232
val staticFields: Set<FieldId>
3333
get() = stateBefore.statics.keys // TODO: should we keep it for the Fuzzed Execution?
3434

35+
override fun copy(
36+
stateBefore: EnvironmentModels,
37+
stateAfter: EnvironmentModels,
38+
result: UtExecutionResult,
39+
coverage: Coverage?,
40+
summary: List<DocStatement>?,
41+
testMethodName: String?,
42+
displayName: String?
43+
): UtExecution {
44+
return UtFuzzedExecution(
45+
stateBefore,
46+
stateAfter,
47+
result,
48+
coverage,
49+
summary,
50+
testMethodName,
51+
displayName,
52+
fuzzingValues = fuzzingValues,
53+
fuzzedMethodDescription = fuzzedMethodDescription,
54+
instrumentation = instrumentation,
55+
)
56+
}
57+
3558
override fun toString(): String = buildString {
3659
append("UtFuzzedExecution(")
3760
appendLine()
@@ -51,4 +74,28 @@ class UtFuzzedExecution(
5174
append(result)
5275
append(")")
5376
}
77+
78+
fun copy(
79+
stateBefore: EnvironmentModels = this.stateBefore,
80+
stateAfter: EnvironmentModels = this.stateAfter,
81+
result: UtExecutionResult = this.result,
82+
coverage: Coverage? = this.coverage,
83+
summary: List<DocStatement>? = this.summary,
84+
testMethodName: String? = this.testMethodName,
85+
displayName: String? = this.displayName,
86+
fuzzingValues: List<FuzzedValue>? = this.fuzzingValues,
87+
fuzzedMethodDescription: FuzzedMethodDescription? = this.fuzzedMethodDescription,
88+
instrumentation: List<UtInstrumentation> = this.instrumentation,
89+
): UtExecution = UtFuzzedExecution(
90+
stateBefore = stateBefore,
91+
stateAfter = stateAfter,
92+
result = result,
93+
coverage = coverage,
94+
summary = summary,
95+
testMethodName = testMethodName,
96+
displayName = displayName,
97+
fuzzingValues = fuzzingValues,
98+
fuzzedMethodDescription = fuzzedMethodDescription,
99+
instrumentation = instrumentation,
100+
)
54101
}

0 commit comments

Comments
 (0)