Skip to content

Commit b42b698

Browse files
authored
Limit number of Spring context resets during reruns (#2626)
1 parent 7ce1752 commit b42b698

File tree

3 files changed

+54
-27
lines changed

3 files changed

+54
-27
lines changed

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,21 @@ object UtSettings : AbstractSettings(logger, defaultKeyForSettingsPath, defaultS
597597
var disableUnsatChecking by getBooleanProperty(false)
598598

599599
// endregion
600+
601+
// region Spring-related options
602+
603+
/**
604+
* When generating integration tests we only partially reset context in between executions to save time.
605+
* For example, entity id generators do not get reset. It may lead to non-reproduceable results if
606+
* IDs leak to the output of the method under test.
607+
*
608+
* To cope with that, we rerun executions that are left after minimization, fully resetting Spring context
609+
* between executions. However, full context reset is slow, so we use this setting to limit number of
610+
* tests per method that are rerun with full context reset in case minimization outputs too many tests.
611+
*/
612+
var maxSpringContextResetsPerMethod by getIntProperty(25, 0, Int.MAX_VALUE)
613+
614+
// endregion
600615
}
601616

602617
/**

utbot-framework/src/main/kotlin/org/utbot/framework/context/custom/RerunningConcreteExecutionContext.kt

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrument
1313

1414
class RerunningConcreteExecutionContext(
1515
private val delegateContext: ConcreteExecutionContext,
16+
private val maxRerunsPerMethod: Int,
1617
private val rerunTimeoutInMillis: Long = 10L * UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis,
1718
) : ConcreteExecutionContext by delegateContext {
1819
companion object {
@@ -23,31 +24,40 @@ class RerunningConcreteExecutionContext(
2324
executions: List<UtExecution>,
2425
methodUnderTest: ExecutableId,
2526
rerunExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
26-
): List<UtExecution> = delegateContext.transformExecutionsAfterMinimization(
27-
executions,
28-
methodUnderTest,
29-
rerunExecutor
30-
).map { execution ->
31-
runBlocking {
32-
val result = try {
33-
rerunExecutor.executeConcretely(
34-
methodUnderTest = methodUnderTest,
35-
stateBefore = execution.stateBefore,
36-
instrumentation = emptyList(),
37-
timeoutInMillis = rerunTimeoutInMillis,
38-
isRerun = true,
39-
)
40-
} catch (e: Throwable) {
41-
// we can't update execution result if we don't have a result
42-
logger.warn(e) { "Rerun failed, keeping original result for execution [$execution]" }
43-
return@runBlocking execution
44-
}
45-
execution.copy(
46-
stateBefore = result.stateBefore,
47-
stateAfter = result.stateAfter,
48-
result = result.result,
49-
coverage = result.coverage,
50-
)
51-
}
27+
): List<UtExecution> {
28+
@Suppress("NAME_SHADOWING")
29+
val executions = delegateContext.transformExecutionsAfterMinimization(
30+
executions,
31+
methodUnderTest,
32+
rerunExecutor
33+
)
34+
// it's better to rerun executions with non-empty coverage,
35+
// because executions with empty coverage are often duplicated
36+
.sortedBy { it.coverage?.coveredInstructions.isNullOrEmpty() }
37+
return executions
38+
.take(maxRerunsPerMethod)
39+
.map { execution ->
40+
runBlocking {
41+
val result = try {
42+
rerunExecutor.executeConcretely(
43+
methodUnderTest = methodUnderTest,
44+
stateBefore = execution.stateBefore,
45+
instrumentation = emptyList(),
46+
timeoutInMillis = rerunTimeoutInMillis,
47+
isRerun = true,
48+
)
49+
} catch (e: Throwable) {
50+
// we can't update execution result if we don't have a result
51+
logger.warn(e) { "Rerun failed, keeping original result for execution [$execution]" }
52+
return@runBlocking execution
53+
}
54+
execution.copy(
55+
stateBefore = result.stateBefore,
56+
stateAfter = result.stateAfter,
57+
result = result.result,
58+
coverage = result.coverage,
59+
)
60+
}
61+
} + executions.drop(maxRerunsPerMethod)
5262
}
5363
}

utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContextImpl.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import org.utbot.common.dynamicPropertiesOf
55
import org.utbot.common.isAbstract
66
import org.utbot.common.isStatic
77
import org.utbot.common.withValue
8+
import org.utbot.framework.UtSettings
89
import org.utbot.framework.codegen.generator.AbstractCodeGenerator
910
import org.utbot.framework.codegen.generator.CodeGeneratorParams
1011
import org.utbot.framework.codegen.generator.SpringCodeGenerator
@@ -118,7 +119,8 @@ class SpringApplicationContextImpl(
118119
delegateConcreteExecutionContext,
119120
classpathWithoutDependencies,
120121
springApplicationContext = this
121-
)
122+
),
123+
maxRerunsPerMethod = UtSettings.maxSpringContextResetsPerMethod
122124
)
123125
}.transformInstrumentationFactory { delegateInstrumentationFactory ->
124126
RemovingConstructFailsUtExecutionInstrumentation.Factory(delegateInstrumentationFactory)

0 commit comments

Comments
 (0)