@@ -56,17 +56,8 @@ import java.io.File
56
56
import kotlin.contracts.ExperimentalContracts
57
57
import kotlin.contracts.contract
58
58
import org.utbot.common.isAbstract
59
- import org.utbot.common.isStatic
60
- import org.utbot.framework.isFromTrustedLibrary
61
- import org.utbot.framework.plugin.api.TypeReplacementMode.*
62
59
import 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
68
60
import org.utbot.framework.process.OpenModulesContainer
69
- import soot.SootField
70
61
import soot.SootMethod
71
62
72
63
const val SYMBOLIC_NULL_ADDR : Int = 0
@@ -1328,241 +1319,54 @@ enum class TypeReplacementMode {
1328
1319
NoImplementors ,
1329
1320
}
1330
1321
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
-
1404
1322
sealed class SpringConfiguration (val fullDisplayName : String ) {
1405
1323
class JavaConfiguration (val classBinaryName : String ) : SpringConfiguration(classBinaryName)
1406
1324
class XMLConfiguration (val absolutePath : String ) : SpringConfiguration(absolutePath)
1407
1325
}
1408
1326
1409
1327
sealed 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
1418
1334
}
1419
1335
1420
- class PresentSpringSettings (
1336
+ data class PresentSpringSettings (
1421
1337
val configuration : SpringConfiguration ,
1422
- val profiles : Array <String >
1338
+ val profiles : List <String >
1423
1339
) : SpringSettings
1424
1340
}
1425
1341
1426
1342
/* *
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).
1430
1348
*/
1431
- class SpringContextLoadingResult (
1349
+ class ConcreteContextLoadingResult (
1432
1350
val contextLoaded : Boolean ,
1433
1351
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
1462
1364
1463
1365
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
+ )
1566
1370
}
1567
1371
}
1568
1372
0 commit comments