@@ -56,17 +56,8 @@ import java.io.File
5656import kotlin.contracts.ExperimentalContracts
5757import kotlin.contracts.contract
5858import org.utbot.common.isAbstract
59- import org.utbot.common.isStatic
60- import org.utbot.framework.isFromTrustedLibrary
61- import org.utbot.framework.plugin.api.TypeReplacementMode.*
6259import org.utbot.framework.plugin.api.util.SpringModelUtils
63- import org.utbot.framework.plugin.api.util.allDeclaredFieldIds
64- import org.utbot.framework.plugin.api.util.allSuperTypes
65- import org.utbot.framework.plugin.api.util.fieldId
66- import org.utbot.framework.plugin.api.util.isSubtypeOf
67- import org.utbot.framework.plugin.api.util.utContext
6860import org.utbot.framework.process.OpenModulesContainer
69- import soot.SootField
7061import soot.SootMethod
7162
7263const val SYMBOLIC_NULL_ADDR : Int = 0
@@ -1328,241 +1319,54 @@ enum class TypeReplacementMode {
13281319 NoImplementors ,
13291320}
13301321
1331- interface CodeGenerationContext
1332-
1333- interface SpringCodeGenerationContext : CodeGenerationContext {
1334- val springTestType: SpringTestType
1335- val springSettings: SpringSettings
1336- val springContextLoadingResult: SpringContextLoadingResult ?
1337- }
1338-
1339- /* *
1340- * A context to use when no specific data is required.
1341- *
1342- * @param mockFrameworkInstalled shows if we have installed framework dependencies
1343- * @param staticsMockingIsConfigured shows if we have installed static mocking tools
1344- */
1345- open class ApplicationContext (
1346- val mockFrameworkInstalled : Boolean = true ,
1347- staticsMockingIsConfigured : Boolean = true ,
1348- ) : CodeGenerationContext {
1349- var staticsMockingIsConfigured = staticsMockingIsConfigured
1350- private set
1351-
1352- init {
1353- /* *
1354- * Situation when mock framework is not installed but static mocking is configured is semantically incorrect.
1355- *
1356- * However, it may be obtained in real application after this actions:
1357- * - fully configure mocking (dependency installed + resource file created)
1358- * - remove mockito-core dependency from project
1359- * - forget to remove mock-maker file from resource directory
1360- *
1361- * Here we transform this configuration to semantically correct.
1362- */
1363- if (! mockFrameworkInstalled && staticsMockingIsConfigured) {
1364- this .staticsMockingIsConfigured = false
1365- }
1366- }
1367-
1368- /* *
1369- * Shows if there are any restrictions on type implementors.
1370- */
1371- open val typeReplacementMode: TypeReplacementMode = AnyImplementor
1372-
1373- /* *
1374- * Finds a type to replace the original abstract type
1375- * if it is guided with some additional information.
1376- */
1377- open fun replaceTypeIfNeeded (type : RefType ): ClassId ? = null
1378-
1379- /* *
1380- * Sets the restrictions on speculative not null
1381- * constraints in current application context.
1382- *
1383- * @see docs/SpeculativeFieldNonNullability.md for more information.
1384- */
1385- open fun avoidSpeculativeNotNullChecks (field : SootField ): Boolean =
1386- UtSettings .maximizeCoverageUsingReflection || ! field.declaringClass.isFromTrustedLibrary()
1387-
1388- /* *
1389- * Checks whether accessing [field] (with a method invocation or field access) speculatively
1390- * cannot produce [NullPointerException] (according to its finality or accessibility).
1391- *
1392- * @see docs/SpeculativeFieldNonNullability.md for more information.
1393- */
1394- open fun speculativelyCannotProduceNullPointerException (
1395- field : SootField ,
1396- classUnderTest : ClassId ,
1397- ): Boolean = field.isFinal || ! field.isPublic
1398-
1399- open fun preventsFurtherTestGeneration (): Boolean = false
1400-
1401- open fun getErrors (): List <UtError > = emptyList()
1402- }
1403-
14041322sealed class SpringConfiguration (val fullDisplayName : String ) {
14051323 class JavaConfiguration (val classBinaryName : String ) : SpringConfiguration(classBinaryName)
14061324 class XMLConfiguration (val absolutePath : String ) : SpringConfiguration(absolutePath)
14071325}
14081326
14091327sealed interface SpringSettings {
1410- class AbsentSpringSettings : SpringSettings {
1411- // Denotes no configuration and no profile setting
1412-
1413- // NOTICE:
1414- // `class` should not be replaced with `object`
1415- // in order to avoid issues caused by Kryo deserialization
1416- // that creates new instances breaking `when` expressions
1417- // that check reference equality instead of type equality
1328+ object AbsentSpringSettings : SpringSettings {
1329+ // NOTE that overriding equals is required just because without it
1330+ // we will lose equality for objects after deserialization
1331+ override fun equals (other : Any? ): Boolean = other is AbsentSpringSettings
1332+
1333+ override fun hashCode (): Int = 0
14181334 }
14191335
1420- class PresentSpringSettings (
1336+ data class PresentSpringSettings (
14211337 val configuration : SpringConfiguration ,
1422- val profiles : Array <String >
1338+ val profiles : List <String >
14231339 ) : SpringSettings
14241340}
14251341
14261342/* *
1427- * [contextLoaded] can be `true` while [exceptions] is not empty,
1428- * if we failed to use most specific SpringApi available (e.g. SpringBoot), but
1429- * were able to successfully fall back to less specific SpringApi (e.g. PureSpring).
1343+ * Result of loading concrete execution context (e.g. Spring application context).
1344+ *
1345+ * [contextLoaded] can be `true` while [exceptions] is not empty. For example, we may fail
1346+ * to load context with most specific SpringApi available (e.g. SpringBoot),
1347+ * but successfully fall back to less specific SpringApi (e.g. PureSpring).
14301348 */
1431- class SpringContextLoadingResult (
1349+ class ConcreteContextLoadingResult (
14321350 val contextLoaded : Boolean ,
14331351 val exceptions : List <Throwable >
1434- )
1435-
1436- /* *
1437- * Data we get from Spring application context
1438- * to manage engine and code generator behaviour.
1439- *
1440- * @param beanDefinitions describes bean definitions (bean name, type, some optional additional data)
1441- * @param shouldUseImplementors describes it we want to replace interfaces with injected types or not
1442- */
1443- // TODO move this class to utbot-framework so we can use it as abstract factory
1444- // to get rid of numerous `when`s and polymorphically create things like:
1445- // - Instrumentation<UtConcreteExecutionResult>
1446- // - FuzzedType (to get rid of thisInstanceFuzzedTypeWrapper)
1447- // - JavaValueProvider
1448- // - CgVariableConstructor
1449- // - CodeGeneratorResult (generateForSpringClass)
1450- // Right now this refactoring is blocked because some interfaces need to get extracted and moved to utbot-framework-api
1451- // As an alternative we can just move ApplicationContext itself to utbot-framework
1452- class SpringApplicationContext (
1453- mockInstalled : Boolean ,
1454- staticsMockingIsConfigured : Boolean ,
1455- val beanDefinitions : List <BeanDefinitionData > = emptyList(),
1456- private val shouldUseImplementors : Boolean ,
1457- override val springTestType : SpringTestType ,
1458- override val springSettings : SpringSettings ,
1459- ): ApplicationContext(mockInstalled, staticsMockingIsConfigured), SpringCodeGenerationContext {
1460-
1461- override var springContextLoadingResult: SpringContextLoadingResult ? = null
1352+ ) {
1353+ val utErrors: List <UtError > get() =
1354+ exceptions.map { UtError (it.message ? : " Concrete context loading failed" , it) }
1355+
1356+ fun andThen (onSuccess : () -> ConcreteContextLoadingResult ) =
1357+ if (contextLoaded) {
1358+ val otherResult = onSuccess()
1359+ ConcreteContextLoadingResult (
1360+ contextLoaded = otherResult.contextLoaded,
1361+ exceptions = exceptions + otherResult.exceptions
1362+ )
1363+ } else this
14621364
14631365 companion object {
1464- private val logger = KotlinLogging .logger {}
1465- }
1466-
1467- private var areInjectedClassesInitialized : Boolean = false
1468- private var areAllInjectedTypesInitialized: Boolean = false
1469-
1470- // Classes representing concrete types that are actually used in Spring application
1471- private val springInjectedClasses: Set <ClassId >
1472- get() {
1473- if (! areInjectedClassesInitialized) {
1474- for (beanTypeName in beanDefinitions.map { it.beanTypeName }) {
1475- try {
1476- val beanClass = utContext.classLoader.loadClass(beanTypeName)
1477- if (! beanClass.isAbstract && ! beanClass.isInterface &&
1478- ! beanClass.isLocalClass && (! beanClass.isMemberClass || beanClass.isStatic)) {
1479- springInjectedClassesStorage + = beanClass.id
1480- }
1481- } catch (e: Throwable ) {
1482- // For some Spring beans (e.g. with anonymous classes)
1483- // it is possible to have problems with classes loading.
1484- when (e) {
1485- is ClassNotFoundException , is NoClassDefFoundError , is IllegalAccessError ->
1486- logger.warn { " Failed to load bean class for $beanTypeName (${e.message} )" }
1487-
1488- else -> throw e
1489- }
1490- }
1491- }
1492-
1493- // This is done to be sure that this storage is not empty after the first class loading iteration.
1494- // So, even if all loaded classes were filtered out, we will not try to load them again.
1495- areInjectedClassesInitialized = true
1496- }
1497-
1498- return springInjectedClassesStorage
1499- }
1500-
1501- private val allInjectedTypes: Set <ClassId >
1502- get() {
1503- if (! areAllInjectedTypesInitialized) {
1504- allInjectedTypesStorage = springInjectedClasses.flatMap { it.allSuperTypes() }.toSet()
1505- areAllInjectedTypesInitialized = true
1506- }
1507-
1508- return allInjectedTypesStorage
1509- }
1510-
1511- // imitates `by lazy` (we can't use actual `by lazy` because communication via RD breaks it)
1512- private var allInjectedTypesStorage: Set <ClassId > = emptySet()
1513-
1514- // This is a service field to model the lazy behavior of [springInjectedClasses].
1515- // Do not call it outside the getter.
1516- //
1517- // Actually, we should just call [springInjectedClasses] with `by lazy`, but we had problems
1518- // with a strange `kotlin.UNINITIALIZED_VALUE` in `speculativelyCannotProduceNullPointerException` method call.
1519- private val springInjectedClassesStorage = mutableSetOf<ClassId >()
1520-
1521- override val typeReplacementMode: TypeReplacementMode =
1522- if (shouldUseImplementors) KnownImplementor else NoImplementors
1523-
1524- /* *
1525- * Replaces an interface type with its implementor type
1526- * if there is the unique implementor in bean definitions.
1527- */
1528- override fun replaceTypeIfNeeded (type : RefType ): ClassId ? =
1529- if (type.isAbstractType) {
1530- springInjectedClasses.singleOrNull { it.isSubtypeOf(type.id) }
1531- } else {
1532- null
1533- }
1534-
1535- override fun avoidSpeculativeNotNullChecks (field : SootField ): Boolean = false
1536-
1537- /* *
1538- * In Spring applications we can mark as speculatively not null
1539- * fields if they are mocked and injecting into class under test so on.
1540- *
1541- * Fields are not mocked if their actual type is obtained from [springInjectedClasses].
1542- *
1543- */
1544- override fun speculativelyCannotProduceNullPointerException (
1545- field : SootField ,
1546- classUnderTest : ClassId ,
1547- ): Boolean = field.fieldId in classUnderTest.allDeclaredFieldIds && field.type.classId !in allInjectedTypes
1548-
1549- override fun preventsFurtherTestGeneration (): Boolean =
1550- super .preventsFurtherTestGeneration() || springContextLoadingResult?.contextLoaded == false
1551-
1552- override fun getErrors (): List <UtError > =
1553- springContextLoadingResult?.exceptions?.map { exception ->
1554- UtError (
1555- " Failed to load Spring application context" ,
1556- exception
1557- )
1558- }.orEmpty() + super .getErrors()
1559-
1560- fun getBeansAssignableTo (classId : ClassId ): List <BeanDefinitionData > = beanDefinitions.filter { beanDef ->
1561- // some bean classes may fail to load
1562- runCatching {
1563- val beanClass = ClassId (beanDef.beanTypeName).jClass
1564- classId.jClass.isAssignableFrom(beanClass)
1565- }.getOrElse { false }
1366+ fun successWithoutExceptions () = ConcreteContextLoadingResult (
1367+ contextLoaded = true ,
1368+ exceptions = emptyList()
1369+ )
15661370 }
15671371}
15681372
0 commit comments