Skip to content

Commit 2b66d12

Browse files
authored
Add contextLoads() test #2330 (#2397)
* Add and use `PhasesController.executePhaseWithoutTimeout()` * Detect and recover from invalid sequences of interactions with `TestContextManager` and make `SpringApiProviderFacade` collect exceptions * Move `SpringInstrumentationContext` and `SpringUtExecutionInstrumentation` into separate package * `InstrumentedProcessModel.tryLoadingSpringContext()` and use it * Refactor code generation * Add `contextLoads()` test in code generation * Make `ThrowableSerializer` try to use constructors * Fix compilation * Address comments from #2397
1 parent a302d1e commit 2b66d12

File tree

37 files changed

+470
-124
lines changed

37 files changed

+470
-124
lines changed

utbot-core/src/main/kotlin/org/utbot/common/ThreadUtil.kt

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,7 @@ class ThreadBasedExecutor {
3636
* [stopWatch] is used to respect specific situations (such as class loading and transforming) while invoking.
3737
*/
3838
fun invokeWithTimeout(timeoutMillis: Long, stopWatch: StopWatch? = null, action:() -> Any?) : Result<Any?>? {
39-
if (thread?.isAlive != true) {
40-
requestQueue = ArrayBlockingQueue<() -> Any?>(1)
41-
responseQueue = ArrayBlockingQueue<Result<Any?>>(1)
42-
43-
thread = thread(name = "executor", isDaemon = true) {
44-
try {
45-
while (true) {
46-
val next = requestQueue.take()
47-
responseQueue.offer(kotlin.runCatching { next() })
48-
}
49-
} catch (_: InterruptedException) {}
50-
}
51-
}
39+
ensureThreadIsAlive()
5240

5341
requestQueue.offer {
5442
try {
@@ -83,4 +71,27 @@ class ThreadBasedExecutor {
8371
}
8472
return res
8573
}
74+
75+
fun invokeWithoutTimeout(action:() -> Any?) : Result<Any?> {
76+
ensureThreadIsAlive()
77+
78+
requestQueue.offer(action)
79+
return responseQueue.take()
80+
}
81+
82+
private fun ensureThreadIsAlive() {
83+
if (thread?.isAlive != true) {
84+
requestQueue = ArrayBlockingQueue<() -> Any?>(1)
85+
responseQueue = ArrayBlockingQueue<Result<Any?>>(1)
86+
87+
thread = thread(name = "executor", isDaemon = true) {
88+
try {
89+
while (true) {
90+
val next = requestQueue.take()
91+
responseQueue.offer(kotlin.runCatching { next() })
92+
}
93+
} catch (_: InterruptedException) {}
94+
}
95+
}
96+
}
8697
}

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

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ object MissingState : EnvironmentModels(
327327
)
328328

329329
/**
330-
* Error happened in traverse.
330+
* Error happened during test cases generation.
331331
*/
332332
data class UtError(
333333
val description: String,
@@ -1328,6 +1328,7 @@ interface CodeGenerationContext
13281328
interface SpringCodeGenerationContext : CodeGenerationContext {
13291329
val springTestType: SpringTestType
13301330
val springSettings: SpringSettings
1331+
val springContextLoadingResult: SpringContextLoadingResult?
13311332
}
13321333

13331334
/**
@@ -1389,11 +1390,15 @@ open class ApplicationContext(
13891390
field: SootField,
13901391
classUnderTest: ClassId,
13911392
): Boolean = field.isFinal || !field.isPublic
1393+
1394+
open fun preventsFurtherTestGeneration(): Boolean = false
1395+
1396+
open fun getErrors(): List<UtError> = emptyList()
13921397
}
13931398

1394-
sealed interface SpringConfiguration {
1395-
class JavaConfiguration(val classBinaryName: String) : SpringConfiguration
1396-
class XMLConfiguration(val absolutePath: String) : SpringConfiguration
1399+
sealed class SpringConfiguration(val fullDisplayName: String) {
1400+
class JavaConfiguration(val classBinaryName: String) : SpringConfiguration(classBinaryName)
1401+
class XMLConfiguration(val absolutePath: String) : SpringConfiguration(absolutePath)
13971402
}
13981403

13991404
sealed interface SpringSettings {
@@ -1413,6 +1418,16 @@ sealed interface SpringSettings {
14131418
) : SpringSettings
14141419
}
14151420

1421+
/**
1422+
* [contextLoaded] can be `true` while [exceptions] is not empty,
1423+
* if we failed to use most specific SpringApi available (e.g. SpringBoot), but
1424+
* were able to successfully fall back to less specific SpringApi (e.g. PureSpring).
1425+
*/
1426+
class SpringContextLoadingResult(
1427+
val contextLoaded: Boolean,
1428+
val exceptions: List<Throwable>
1429+
)
1430+
14161431
/**
14171432
* Data we get from Spring application context
14181433
* to manage engine and code generator behaviour.
@@ -1438,6 +1453,8 @@ class SpringApplicationContext(
14381453
override val springSettings: SpringSettings,
14391454
): ApplicationContext(mockInstalled, staticsMockingIsConfigured), SpringCodeGenerationContext {
14401455

1456+
override var springContextLoadingResult: SpringContextLoadingResult? = null
1457+
14411458
companion object {
14421459
private val logger = KotlinLogging.logger {}
14431460
}
@@ -1509,6 +1526,17 @@ class SpringApplicationContext(
15091526
field: SootField,
15101527
classUnderTest: ClassId,
15111528
): Boolean = field.fieldId in classUnderTest.allDeclaredFieldIds && field.declaringClass.id !in springInjectedClasses
1529+
1530+
override fun preventsFurtherTestGeneration(): Boolean =
1531+
super.preventsFurtherTestGeneration() || springContextLoadingResult?.contextLoaded == false
1532+
1533+
override fun getErrors(): List<UtError> =
1534+
springContextLoadingResult?.exceptions?.map { exception ->
1535+
UtError(
1536+
"Failed to load Spring application context",
1537+
exception
1538+
)
1539+
}.orEmpty() + super.getErrors()
15121540
}
15131541

15141542
enum class SpringTestType(
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package org.utbot.framework.plugin.api.util
2+
3+
object IndentUtil {
4+
const val TAB = " "
5+
}

utbot-framework-api/src/main/kotlin/org/utbot/framework/process/kryo/ThrowableSerializer.kt

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@ class ThrowableSerializer : Serializer<Throwable>() {
5757

5858
override fun read(kryo: Kryo, input: Input, type: Class<out Throwable>): Throwable? {
5959
fun ThrowableModel.toThrowable(): Throwable {
60-
val throwableFromBytes = this.serializedExceptionBytes?.let { bytes ->
60+
this.serializedExceptionBytes?.let { bytes ->
6161
try {
62-
ByteArrayInputStream(bytes).use { byteInputStream ->
62+
return@toThrowable ByteArrayInputStream(bytes).use { byteInputStream ->
6363
val objectInputStream = IgnoringUidWrappingObjectInputStream(byteInputStream, kryo.classLoader)
6464
objectInputStream.readObject() as Throwable
6565
}
@@ -68,14 +68,31 @@ class ThrowableSerializer : Serializer<Throwable>() {
6868
logger.warn { "Failed to deserialize ${this.classId} from bytes, cause: $e" }
6969
logger.warn { "Falling back to constructing throwable instance from ThrowableModel" }
7070
}
71-
null
7271
}
7372
}
74-
return throwableFromBytes ?: when {
75-
RuntimeException::class.java.isAssignableFrom(classId.jClass) -> RuntimeException(message, cause?.toThrowable())
76-
Error::class.java.isAssignableFrom(classId.jClass) -> Error(message, cause?.toThrowable())
77-
else -> Exception(message, cause?.toThrowable())
78-
}.also {
73+
74+
val cause = cause?.toThrowable()
75+
76+
val messageCauseConstructor = runCatching { classId.jClass.getConstructor(String::class.java, Throwable::class.java) }.getOrNull()
77+
val causeOnlyConstructor = runCatching { classId.jClass.getConstructor(Throwable::class.java) }.getOrNull()
78+
val messageOnlyConstructor = runCatching { classId.jClass.getConstructor(String::class.java) }.getOrNull()
79+
80+
val throwableFromConstructor = runCatching {
81+
when {
82+
messageCauseConstructor != null && message != null && cause != null ->
83+
messageCauseConstructor.newInstance(message, cause)
84+
85+
causeOnlyConstructor != null && cause != null -> causeOnlyConstructor.newInstance(cause)
86+
messageOnlyConstructor != null && message != null -> messageOnlyConstructor.newInstance(message)
87+
else -> null
88+
}
89+
}.getOrNull() as Throwable?
90+
91+
return (throwableFromConstructor ?: when {
92+
RuntimeException::class.java.isAssignableFrom(classId.jClass) -> RuntimeException(message, cause)
93+
Error::class.java.isAssignableFrom(classId.jClass) -> Error(message, cause)
94+
else -> Exception(message, cause)
95+
}).also {
7996
it.stackTrace = stackTrace
8097
}
8198
}

utbot-framework/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ dependencies {
1515
api project(':utbot-framework-api')
1616
api project(':utbot-rd')
1717

18+
implementation project(':utbot-spring-commons-api')
19+
1820
implementation group: 'com.jetbrains.rd', name: 'rd-framework', version: rdVersion
1921
implementation group: 'com.jetbrains.rd', name: 'rd-core', version: rdVersion
2022

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/builtin/SpringBuiltins.kt

Whitespace-only changes.

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgElement.kt

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,19 @@ data class CgTestMethodCluster(
234234
data class CgMethodsCluster(
235235
override val header: String?,
236236
override val content: List<CgRegion<CgMethod>>
237-
) : CgRegion<CgRegion<CgMethod>>()
237+
) : CgRegion<CgRegion<CgMethod>>() {
238+
companion object {
239+
fun withoutDocs(methodsList: List<CgMethod>) = CgMethodsCluster(
240+
header = null,
241+
content = listOf(
242+
CgSimpleRegion(
243+
header = null,
244+
content = methodsList
245+
)
246+
)
247+
)
248+
}
249+
}
238250

239251
/**
240252
* Util entity is either an instance of [CgAuxiliaryClass] or [CgUtilMethod].
@@ -293,10 +305,10 @@ sealed class CgMethod(open val isStatic: Boolean) : CgElement {
293305

294306
class CgTestMethod(
295307
override val name: String,
296-
override val returnType: ClassId,
297-
override val parameters: List<CgParameterDeclaration>,
308+
override val returnType: ClassId = voidClassId,
309+
override val parameters: List<CgParameterDeclaration> = emptyList(),
298310
override val statements: List<CgStatement>,
299-
override val exceptions: Set<ClassId>,
311+
override val exceptions: Set<ClassId> = emptySet(),
300312
override val annotations: List<CgAnnotation>,
301313
override val visibility: VisibilityModifier = VisibilityModifier.PUBLIC,
302314
val type: CgTestMethodType,

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/SpringCodeGenerator.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import org.utbot.framework.plugin.api.SpringSettings.*
2424
class SpringCodeGenerator(
2525
val classUnderTest: ClassId,
2626
val projectType: ProjectType,
27-
val codeGenerationContext: SpringCodeGenerationContext,
27+
val springCodeGenerationContext: SpringCodeGenerationContext,
2828
paramNames: MutableMap<ExecutableId, List<String>> = mutableMapOf(),
2929
generateUtilClassFile: Boolean = false,
3030
testFramework: TestFramework = TestFramework.defaultItem,
@@ -61,11 +61,11 @@ class SpringCodeGenerator(
6161
val testClassModel = SpringTestClassModelBuilder(context).createTestClassModel(classUnderTest, testSets)
6262

6363
logger.info { "Code generation phase started at ${now()}" }
64-
val astConstructor = when (codeGenerationContext.springTestType) {
64+
val astConstructor = when (springCodeGenerationContext.springTestType) {
6565
SpringTestType.UNIT_TEST -> CgSpringUnitTestClassConstructor(context)
6666
SpringTestType.INTEGRATION_TEST ->
67-
when (val settings = codeGenerationContext.springSettings) {
68-
is PresentSpringSettings -> CgSpringIntegrationTestClassConstructor(context, settings)
67+
when (val settings = springCodeGenerationContext.springSettings) {
68+
is PresentSpringSettings -> CgSpringIntegrationTestClassConstructor(context, springCodeGenerationContext, settings)
6969
is AbsentSpringSettings -> error("No Spring settings were provided for Spring integration test generation.")
7070
}
7171
}

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgPrinter.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.utbot.framework.codegen.renderer
22

3+
import org.utbot.framework.plugin.api.util.IndentUtil
4+
35
interface CgPrinter {
46
fun print(text: String)
57
fun println(text: String = "")
@@ -58,6 +60,6 @@ class CgPrinterImpl(
5860
private operator fun String.times(n: Int): String = repeat(n)
5961

6062
companion object {
61-
private const val TAB = " "
63+
private const val TAB = IndentUtil.TAB
6264
}
6365
}

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgAbstractSpringTestClassConstructor.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import org.utbot.framework.codegen.domain.models.CgVariable
1717
import org.utbot.framework.codegen.domain.models.SpringTestClassModel
1818
import org.utbot.framework.codegen.domain.models.builders.TypedModelWrappers
1919
import org.utbot.framework.plugin.api.ClassId
20+
import org.utbot.framework.plugin.api.UtExecution
2021
import org.utbot.framework.plugin.api.UtSpringContextModel
2122
import org.utbot.framework.plugin.api.util.SpringModelUtils.getBeanNameOrNull
2223
import org.utbot.framework.plugin.api.util.id
@@ -36,6 +37,8 @@ abstract class CgAbstractSpringTestClassConstructor(context: CgContext):
3637
fields += constructClassFields(testClassModel)
3738
clearUnwantedVariableModels()
3839

40+
constructAdditionalTestMethods()?.let { methodRegions += it }
41+
3942
for ((testSetIndex, testSet) in testClassModel.methodTestSets.withIndex()) {
4043
updateCurrentExecutable(testSet.executableId)
4144
withTestSetIdScope(testSetIndex) {
@@ -48,7 +51,7 @@ abstract class CgAbstractSpringTestClassConstructor(context: CgContext):
4851
}
4952
}
5053

51-
methodRegions += constructAdditionalMethods()
54+
constructAdditionalUtilMethods()?.let { methodRegions += it }
5255

5356
if (currentTestClass == outerMostTestClass) {
5457
val utilEntities = collectUtilEntities()
@@ -81,7 +84,13 @@ abstract class CgAbstractSpringTestClassConstructor(context: CgContext):
8184

8285
abstract fun constructClassFields(testClassModel: SpringTestClassModel): List<CgFieldDeclaration>
8386

84-
abstract fun constructAdditionalMethods(): CgMethodsCluster
87+
/**
88+
* Here "additional" means that these tests are not obtained from
89+
* [UtExecution]s generated by engine or fuzzer, but have another origin.
90+
*/
91+
open fun constructAdditionalTestMethods(): CgMethodsCluster? = null
92+
93+
open fun constructAdditionalUtilMethods(): CgMethodsCluster? = null
8594

8695
protected fun constructFieldsWithAnnotation(
8796
annotationClassId: ClassId,

0 commit comments

Comments
 (0)