Skip to content

Extract utbot-spring-framework module from utbot-framework #2570

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Sep 7, 2023
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ if (includeRiderInBuild.toBoolean()) {

include("utbot-ui-commons")

include("utbot-spring-framework")
include("utbot-spring-commons-api")
include("utbot-spring-commons")
include("utbot-spring-analyzer")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ interface MutableDynamicProperties<TOwner> : DynamicProperties<TOwner> {
operator fun <T> set(property: DynamicProperty<TOwner, T>, value: T)
}

fun <TOwner, T> MutableDynamicProperties<TOwner>.getOrPut(property: DynamicProperty<TOwner, T>, default: () -> T): T {
if (property !in this) set(property, default())
return getValue(property)
}

fun <TOwner> Iterable<InitialisedDynamicProperty<TOwner, *>>.toMutableDynamicProperties(): MutableDynamicProperties<TOwner> =
DynamicPropertiesImpl<TOwner>().also { properties ->
forEach { properties.add(it) }
Expand Down
15 changes: 0 additions & 15 deletions utbot-framework/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ plugins {
}

configurations {
fetchSpringAnalyzerJar
fetchSpringCommonsJar
fetchInstrumentationJar
}

Expand Down Expand Up @@ -48,23 +46,10 @@ dependencies {
implementation group: 'com.github.UnitTestBot.ksmt', name: 'ksmt-z3', version: ksmtVersion

fetchInstrumentationJar project(path: ':utbot-instrumentation', configuration: 'instrumentationArchive')

implementation project(':utbot-spring-commons-api')
implementation project(':utbot-spring-analyzer')
fetchSpringAnalyzerJar project(path: ':utbot-spring-analyzer', configuration: 'springAnalyzerJar')
fetchSpringCommonsJar project(path: ':utbot-spring-commons', configuration: 'springCommonsJar')
}

processResources {
from(configurations.fetchInstrumentationJar) {
into "lib"
}

from(configurations.fetchSpringAnalyzerJar) {
into "lib"
}

from(configurations.fetchSpringCommonsJar) {
into "lib"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,12 @@ class AssembleModelGenerator(private val basePackageName: String) {
is UtClassRefModel,
is UtVoidModel,
is UtEnumConstantModel,
is UtCustomModel -> utModel
is UtCustomModel -> utModel // for example, UtSpringContextModel
is UtLambdaModel -> assembleLambdaModel(utModel)
is UtArrayModel -> assembleArrayModel(utModel)
is UtCompositeModel -> assembleCompositeModel(utModel)
is UtAssembleModel -> assembleAssembleModel(utModel)
// Python, JavaScript, UtSpringContextModel are supposed to be here as well
// Python, JavaScript are supposed to be here as well
else -> utModel
}
} catch (e: AssembleException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -711,47 +711,6 @@ enum class ProjectType {
JavaScript,
}

abstract class DependencyInjectionFramework(
override val id: String,
override val displayName: String,
override val description: String = "Use $displayName as dependency injection framework",
val testFrameworkDisplayName: String,
/**
* Generation Spring specific tests requires special spring test framework being installed,
* so we can use `TestContextManager` from `spring-test` to configure test context in
* spring-analyzer and to run integration tests.
*/
var testFrameworkInstalled: Boolean = false
) : CodeGenerationSettingItem {
var isInstalled = false

companion object : CodeGenerationSettingBox {
override val defaultItem: DependencyInjectionFramework get() = SpringBoot
override val allItems: List<DependencyInjectionFramework> get() = listOf(SpringBoot, SpringBeans)

val installedItems get() = allItems.filter { it.isInstalled }

/**
* Generation Spring specific tests requires special spring test framework being installed,
* so we can use `TestContextManager` from `spring-test` to configure test context in
* spring-analyzer and to run integration tests.
*/
var testFrameworkInstalled: Boolean = false
}
}

object SpringBeans : DependencyInjectionFramework(
id = "spring-beans",
displayName = "Spring Beans",
testFrameworkDisplayName = "spring-test",
)

object SpringBoot : DependencyInjectionFramework(
id = "spring-boot",
displayName = "Spring Boot",
testFrameworkDisplayName = "spring-boot-test",
)

/**
* Extended [UtModel] model with testSet id and execution id.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,6 @@ internal val mockitoClassId = BuiltinClassId(
simpleName = "Mockito",
)

internal val mockClassId = BuiltinClassId(
canonicalName = "org.mockito.Mock",
simpleName = "Mock",
)

internal val spyClassId = BuiltinClassId(
canonicalName = "org.mockito.Spy",
simpleName = "Spy"
)

internal val injectMocksClassId = BuiltinClassId(
canonicalName = "org.mockito.InjectMocks",
simpleName = "InjectMocks",
)

internal val ongoingStubbingClassId = BuiltinClassId(
canonicalName = "org.mockito.stubbing.OngoingStubbing",
simpleName = "OngoingStubbing",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ internal class UtilClassFileMethodProvider(language: CodegenLanguage)
val UTIL_CLASS_VERSION = "2.1"
}

internal class TestClassUtilMethodProvider(testClassId: ClassId) : UtilMethodProvider(testClassId)
class TestClassUtilMethodProvider(testClassId: ClassId) : UtilMethodProvider(testClassId)

internal fun selectUtilClassId(codegenLanguage: CodegenLanguage): ClassId =
when (codegenLanguage) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import kotlinx.collections.immutable.PersistentSet
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.persistentSetOf
import org.utbot.common.DynamicProperty
import org.utbot.common.MutableDynamicProperties
import org.utbot.common.mutableDynamicPropertiesOf
import org.utbot.framework.codegen.domain.UtModelWrapper
import org.utbot.framework.codegen.domain.ProjectType
import org.utbot.framework.codegen.domain.builtin.TestClassUtilMethodProvider
Expand All @@ -25,7 +28,6 @@ import org.utbot.framework.codegen.tree.importIfNeeded
import org.utbot.framework.plugin.api.BuiltinClassId
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.codegen.services.language.CgLanguageAssistant
import org.utbot.framework.codegen.tree.fieldmanager.CgAbstractClassFieldManager
import org.utbot.framework.plugin.api.CodegenLanguage
import org.utbot.framework.plugin.api.ExecutableId
import org.utbot.framework.plugin.api.FieldId
Expand All @@ -38,6 +40,9 @@ import org.utbot.framework.plugin.api.util.isCheckedException
import org.utbot.framework.plugin.api.util.isSubtypeOf
import org.utbot.framework.plugin.api.util.jClass

typealias CgContextProperties = MutableDynamicProperties<CgContext>
typealias CgContextProperty<T> = DynamicProperty<CgContext, T>

/**
* Interface for all code generation context aware entities
*
Expand Down Expand Up @@ -243,10 +248,16 @@ interface CgContextOwner {
var successfulExecutionsModels: List<UtModel>

/**
* Managers to process annotated fields of the class under test
* relevant for the current generation type.
* Many of [CgContext] properties are only needed in some specific scenarios
* (e.g. Spring-related properties and parametrized test specific properties).
*
* To avoid overly inflating [CgContext] interface such properties should
* be added as dynamic properties.
*
* @see DynamicProperty
*/
val relevantFieldManagers: MutableList<CgAbstractClassFieldManager>
// TODO make parameterized test specific properties dynamic
val properties: CgContextProperties

fun block(init: () -> Unit): Block {
val prevBlock = currentBlock
Expand Down Expand Up @@ -509,8 +520,8 @@ class CgContext(
RuntimeExceptionTestsBehaviour.defaultItem,
override val hangingTestsTimeout: HangingTestsTimeout = HangingTestsTimeout(),
override val enableTestsTimeout: Boolean = true,
override val relevantFieldManagers: MutableList<CgAbstractClassFieldManager> = mutableListOf(),
override var containsReflectiveCall: Boolean = false,
override val properties: CgContextProperties = mutableDynamicPropertiesOf(),
) : CgContextOwner {
override lateinit var statesCache: EnvironmentFieldStateCache
override lateinit var actual: CgVariable
Expand Down Expand Up @@ -667,5 +678,6 @@ class CgContext(
hangingTestsTimeout = this.hangingTestsTimeout,
enableTestsTimeout = this.enableTestsTimeout,
containsReflectiveCall = this.containsReflectiveCall,
properties = this.properties,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ interface CgCallableAccessManager {
operator fun ClassId.get(fieldId: FieldId): CgStaticFieldAccess
}

internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableAccessManager,
class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableAccessManager,
CgContextOwner by context {

private val statementConstructor by lazy { getStatementConstructorBy(context) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ import org.utbot.framework.plugin.api.BuiltinMethodId
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.CodegenLanguage
import org.utbot.framework.plugin.api.ConstructorId
import org.utbot.framework.plugin.api.util.SpringModelUtils.extendWithClassId
import org.utbot.framework.plugin.api.util.SpringModelUtils.runWithClassId
import org.utbot.framework.plugin.api.util.SpringModelUtils.springExtensionClassId
import org.utbot.framework.plugin.api.util.booleanArrayClassId
import org.utbot.framework.plugin.api.util.byteArrayClassId
import org.utbot.framework.plugin.api.util.charArrayClassId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ fun CgContextOwner.importIfNeeded(type: ClassId) {
}
}

internal fun CgContextOwner.importIfNeeded(method: MethodId) {
fun CgContextOwner.importIfNeeded(method: MethodId) {
val name = method.name
val packageName = method.classId.packageName
method.takeIf { it.isStatic && packageName != testClassPackageName && packageName != "java.lang" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ sealed class UtilClassKind(
* @return `null` if [CgContext.utilMethodProvider] is not [UtilClassFileMethodProvider],
* because it means that util methods will be taken from some other provider (e.g. [TestClassUtilMethodProvider]).
*/
internal fun fromCgContextOrNull(context: CgContext): UtilClassKind? {
fun fromCgContextOrNull(context: CgContext): UtilClassKind? {
if (context.requiredUtilMethods.isEmpty()) return null
if (!context.mockFrameworkUsed) {
return RegularUtUtils(context.codegenLanguage)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ import org.utbot.framework.util.ConflictTriggers
import org.utbot.framework.util.SootUtils
import org.utbot.framework.util.jimpleBody
import org.utbot.framework.util.toModel
import org.utbot.framework.plugin.api.SpringSettings.*
import org.utbot.framework.plugin.api.SpringTestType.*
import org.utbot.instrumentation.ConcreteExecutor
import org.utbot.instrumentation.warmup
import org.utbot.taint.TaintConfigurationProvider
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package org.utbot.framework.plugin.api.utils

import org.utbot.framework.codegen.domain.DependencyInjectionFramework
import org.utbot.framework.codegen.domain.Junit4
import org.utbot.framework.codegen.domain.Junit5
import org.utbot.framework.codegen.domain.SpringBeans
import org.utbot.framework.codegen.domain.SpringBoot
import org.utbot.framework.codegen.domain.TestFramework
import org.utbot.framework.codegen.domain.TestNg
import org.utbot.framework.plugin.api.MockFramework
Expand Down Expand Up @@ -61,36 +58,6 @@ fun MockFramework.patterns(): Patterns {
return Patterns(moduleLibraryPatterns, libraryPatterns)
}

fun DependencyInjectionFramework.patterns(): Patterns {
val moduleLibraryPatterns = when (this) {
SpringBoot -> springBootModulePatterns
SpringBeans -> springBeansModulePatterns
else -> throw UnsupportedOperationException("Unknown dependency injection framework $this")
}
val libraryPatterns = when (this) {
SpringBoot -> springBootPatterns
SpringBeans -> springBeansPatterns
else -> throw UnsupportedOperationException("Unknown dependency injection framework $this")
}

return Patterns(moduleLibraryPatterns, libraryPatterns)
}

fun DependencyInjectionFramework.testPatterns(): Patterns {
val moduleLibraryPatterns = when (this) {
SpringBoot -> springBootTestModulePatterns
SpringBeans -> springBeansTestModulePatterns
else -> throw UnsupportedOperationException("Unknown dependency injection framework $this")
}
val libraryPatterns = when (this) {
SpringBoot -> springBootTestPatterns
SpringBeans -> springBeansTestPatterns
else -> throw UnsupportedOperationException("Unknown dependency injection framework $this")
}

return Patterns(moduleLibraryPatterns, libraryPatterns)
}

val JUNIT_4_JAR_PATTERN = Regex("junit-4(\\.1[2-9])(\\.[0-9]+)?")
val JUNIT_4_MVN_PATTERN = Regex("junit:junit:4(\\.1[2-9])(\\.[0-9]+)?")
val junit4Patterns = listOf(JUNIT_4_JAR_PATTERN, JUNIT_4_MVN_PATTERN)
Expand Down Expand Up @@ -127,32 +94,3 @@ val mockitoModulePatterns = listOf(MOCKITO_BASIC_MODULE_PATTERN)
const val MOCKITO_EXTENSIONS_FOLDER = "mockito-extensions"
const val MOCKITO_MOCKMAKER_FILE_NAME = "org.mockito.plugins.MockMaker"
val MOCKITO_EXTENSIONS_FILE_CONTENT = "mock-maker-inline"

val SPRING_BEANS_JAR_PATTERN = Regex("spring-beans-([0-9]+)(\\.[0-9]+){1,2}")
val SPRING_BEANS_MVN_PATTERN = Regex("org\\.springframework:spring-beans:([0-9]+)(\\.[0-9]+){1,2}")
val springBeansPatterns = listOf(SPRING_BEANS_JAR_PATTERN, SPRING_BEANS_MVN_PATTERN)

val SPRING_BEANS_BASIC_MODULE_PATTERN = Regex("spring-beans")
val springBeansModulePatterns = listOf(SPRING_BEANS_BASIC_MODULE_PATTERN)

val SPRING_BEANS_TEST_JAR_PATTERN = Regex("spring-test-([0-9]+)(\\.[0-9]+){1,2}")
val SPRING_BEANS_TEST_MVN_PATTERN = Regex("org\\.springframework:spring-test:([0-9]+)(\\.[0-9]+){1,2}")
val springBeansTestPatterns = listOf(SPRING_BEANS_TEST_JAR_PATTERN, SPRING_BEANS_TEST_MVN_PATTERN)

val SPRING_BEANS_TEST_BASIC_MODULE_PATTERN = Regex("spring-test")
val springBeansTestModulePatterns = listOf(SPRING_BEANS_TEST_BASIC_MODULE_PATTERN)

val SPRING_BOOT_JAR_PATTERN = Regex("spring-boot-([0-9]+)(\\.[0-9]+){1,2}")
val SPRING_BOOT_MVN_PATTERN = Regex("org\\.springframework\\.boot:spring-boot:([0-9]+)(\\.[0-9]+){1,2}")
val springBootPatterns = listOf(SPRING_BOOT_JAR_PATTERN, SPRING_BOOT_MVN_PATTERN)

val SPRING_BOOT_BASIC_MODULE_PATTERN = Regex("spring-boot")
val springBootModulePatterns = listOf(SPRING_BOOT_BASIC_MODULE_PATTERN)

val SPRING_BOOT_TEST_JAR_PATTERN = Regex("spring-boot-test-([0-9]+)(\\.[0-9]+){1,2}")
val SPRING_BOOT_TEST_MVN_PATTERN = Regex("org\\.springframework\\.boot:spring-boot-test:([0-9]+)(\\.[0-9]+){1,2}")

val springBootTestPatterns = listOf(SPRING_BOOT_TEST_JAR_PATTERN, SPRING_BOOT_TEST_MVN_PATTERN)

val SPRING_BOOT_TEST_BASIC_MODULE_PATTERN = Regex("spring-boot-test")
val springBootTestModulePatterns = listOf(SPRING_BOOT_TEST_BASIC_MODULE_PATTERN)
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,14 @@ import org.utbot.framework.plugin.api.util.method
import org.utbot.framework.plugin.api.utils.ClassNameUtils
import org.utbot.framework.plugin.services.JdkInfo
import org.utbot.framework.process.generated.*
import org.utbot.framework.process.generated.BeanAdditionalData
import org.utbot.framework.process.generated.BeanDefinitionData
import org.utbot.framework.process.kryo.KryoHelper
import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter
import org.utbot.rd.IdleWatchdog
import org.utbot.rd.ClientProtocolBuilder
import org.utbot.rd.RdSettingsContainerFactory
import org.utbot.rd.generated.settingsModel
import org.utbot.rd.terminateOnException
import org.utbot.sarif.RdSourceFindingStrategyFacade
import org.utbot.sarif.SarifReport
import org.utbot.spring.process.SpringAnalyzerProcess
import org.utbot.summary.summarizeAll
import org.utbot.taint.TaintConfigurationProviderUserRules
import java.io.File
Expand Down Expand Up @@ -90,27 +86,6 @@ private fun EngineProcessModel.setup(kryoHelper: KryoHelper, watchdog: IdleWatch
)
UtContext.setUtContext(UtContext(classLoader))
}
watchdog.measureTimeForActiveCall(getSpringBeanDefinitions, "Getting Spring bean definitions") { params ->
try {
val springAnalyzerProcess = SpringAnalyzerProcess.createBlocking(params.classpath.toList())
val result = springAnalyzerProcess.terminateOnException { _ ->
springAnalyzerProcess.getBeanDefinitions(
kryoHelper.readObject(params.springSettings)
)
}
springAnalyzerProcess.terminate()
val beanDefinitions = result.beanDefinitions
.map { data ->
val additionalData = data.additionalData?.let { BeanAdditionalData(it.factoryMethodName, it.parameterTypes, it.configClassFqn) }
BeanDefinitionData(data.beanName, data.beanTypeFqn, additionalData)
}
.toTypedArray()
SpringAnalyzerResult(beanDefinitions)
} catch (e: Exception) {
logger.error(e) { "Spring Analyzer crashed, resorting to using empty bean list" }
SpringAnalyzerResult(emptyArray())
}
}
watchdog.measureTimeForActiveCall(createTestGenerator, "Creating Test Generator") { params ->
AnalyticsConfigureUtil.configureML()
Instrumenter.adapter = RdInstrumenter(realProtocol.rdInstrumenterAdapter)
Expand Down Expand Up @@ -273,6 +248,11 @@ private fun EngineProcessModel.setup(kryoHelper: KryoHelper, watchdog: IdleWatch
}
GenerateTestReportResult(notifyMessage, statistics, hasWarnings)
}
watchdog.measureTimeForActiveCall(perform, "Performing dynamic task") { params ->
val task = kryoHelper.readObject<EngineProcessTask<Any?>>(params.engineProcessTask)
val result = task.perform(kryoHelper)
kryoHelper.writeObject(result)
}
Comment on lines +251 to +255
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have only one post-processing task? Perhaps it could be a list of such tasks performed consequently.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. It's not a post-processing task, the order in witch callbacks are registered in the engine process has nothing to do with the order in which they are called by the IDE process. For example, callback for findMethodsInClassMatchingSelected is registered after the callback for generate, despite generate being called after findMethodsInClassMatchingSelected.
  2. If we need to perform multiple tasks we can call perform multiple times, the same way generate is called multiple times when we generate tests for multiple classes.

}

private fun processInitialWarnings(report: TestsGenerationReport, params: GenerateTestReportArgs) {
Expand Down
Loading