Skip to content

Initilial implementation of Spring test classes code generation #1788

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 8 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1502,7 +1502,7 @@ class Traverser(
return createMockedObject(addr, type, mockInfoGenerator, nullEqualityConstraint)
}

val concreteImplementation = when (applicationContext.typeReplacementMode) {
val concreteImplementation: Concrete? = when (applicationContext.typeReplacementMode) {
AnyImplementor -> findConcreteImplementation(addr, type, typeHardConstraint, nullEqualityConstraint)

// If our type is not abstract, both in `KnownImplementors` and `NoImplementors` mode,
Expand All @@ -1520,14 +1520,14 @@ class Traverser(
NoImplementors -> {
if (!type.isAbstractType) {
findConcreteImplementation(addr, type, typeHardConstraint, nullEqualityConstraint)
}
} else {
mockInfoGenerator?.let {
return createMockedObject(addr, type, it, nullEqualityConstraint)
}

mockInfoGenerator?.let {
return createMockedObject(addr, type, it, nullEqualityConstraint)
queuedSymbolicStateUpdates += mkFalse().asHardConstraint()
null
}

queuedSymbolicStateUpdates += mkFalse().asHardConstraint()
null
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ open class CodeGenerator(

private fun generateForSimpleClass(testSets: List<CgMethodTestSet>): CodeGeneratorResult {
val astConstructor = CgSimpleTestClassConstructor(context)
val testClassModel = SimpleTestClassModelBuilder().createTestClassModel(classUnderTest, testSets)
val testClassModel = SimpleTestClassModelBuilder(context).createTestClassModel(classUnderTest, testSets)

logger.info { "Code generation phase started at ${now()}" }
val testClassFile = astConstructor.construct(testClassModel)
Expand All @@ -107,7 +107,7 @@ open class CodeGenerator(

private fun generateForSpringClass(testSets: List<CgMethodTestSet>): CodeGeneratorResult {
val astConstructor = CgSpringTestClassConstructor(context)
val testClassModel = SpringTestClassModelBuilder().createTestClassModel(classUnderTest, testSets)
val testClassModel = SpringTestClassModelBuilder(context).createTestClassModel(classUnderTest, testSets)

logger.info { "Code generation phase started at ${now()}" }
val testClassFile = astConstructor.construct(testClassModel)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.utbot.framework.codegen.domain
import org.utbot.framework.DEFAULT_EXECUTION_TIMEOUT_IN_INSTRUMENTED_PROCESS_MS
import org.utbot.framework.codegen.domain.builtin.mockitoClassId
import org.utbot.framework.codegen.domain.builtin.ongoingStubbingClassId
import org.utbot.framework.codegen.domain.context.CgContext
import org.utbot.framework.codegen.domain.models.CgClassId
import org.utbot.framework.codegen.tree.argumentsClassId
import org.utbot.framework.plugin.api.BuiltinClassId
Expand All @@ -11,6 +12,8 @@ import org.utbot.framework.plugin.api.CodeGenerationSettingBox
import org.utbot.framework.plugin.api.CodeGenerationSettingItem
import org.utbot.framework.plugin.api.MethodId
import org.utbot.framework.plugin.api.TypeParameters
import org.utbot.framework.plugin.api.UtModel
import org.utbot.framework.plugin.api.idOrNull
import org.utbot.framework.plugin.api.isolateCommandLineArgumentsToArgumentFile
import org.utbot.framework.plugin.api.util.booleanArrayClassId
import org.utbot.framework.plugin.api.util.booleanClassId
Expand Down Expand Up @@ -773,3 +776,17 @@ object SpringBoot : DependencyInjectionFramework(
id = "spring-boot",
displayName = "Spring Boot"
)

/**
* Extended id of [UtModel], unique for whole test set.
*
* Allows to distinguish models from different executions,
* even if they have the same value of `UtModel.id`.
*/
data class ModelId(
private val id: Int?,
private val executionId: Int,
)

fun UtModel.withExecutionId(executionId: Int = -1) = ModelId(this.idOrNull(), executionId)

Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ internal val mockitoClassId = BuiltinClassId(
simpleName = "Mockito",
)

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

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 @@ -22,13 +22,15 @@ import kotlinx.collections.immutable.PersistentSet
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.persistentSetOf
import org.utbot.framework.codegen.domain.ModelId
import org.utbot.framework.codegen.domain.ProjectType
import org.utbot.framework.codegen.domain.models.CgMethodTestSet
import org.utbot.framework.codegen.domain.builtin.TestClassUtilMethodProvider
import org.utbot.framework.codegen.domain.builtin.UtilClassFileMethodProvider
import org.utbot.framework.codegen.domain.builtin.UtilMethodProvider
import org.utbot.framework.codegen.domain.models.SimpleTestClassModel
import org.utbot.framework.codegen.domain.models.CgParameterKind
import org.utbot.framework.codegen.domain.withExecutionId
import org.utbot.framework.codegen.services.access.Block
import org.utbot.framework.codegen.tree.EnvironmentFieldStateCache
import org.utbot.framework.codegen.tree.importIfNeeded
Expand Down Expand Up @@ -197,7 +199,7 @@ interface CgContextOwner {
var valueByModel: IdentityHashMap<UtModel, CgValue>

// use it to compare stateBefore and result variables - in case of equality do not create new variable
var valueByModelId: MutableMap<Int?, CgValue>
var valueByModelId: MutableMap<ModelId, CgValue>

// parameters of the method currently being generated
val currentMethodParameters: MutableMap<CgParameterKind, CgVariable>
Expand Down Expand Up @@ -226,6 +228,12 @@ interface CgContextOwner {
*/
var successfulExecutionsModels: List<UtModel>

/**
* Gives a unique identifier to model in test set.
* Determines which execution current model belongs to.
*/
var modelIds: MutableMap<UtModel, ModelId>

fun block(init: () -> Unit): Block {
val prevBlock = currentBlock
return try {
Expand Down Expand Up @@ -311,7 +319,10 @@ interface CgContextOwner {
model?.let {
valueByModel[it] = variable
(model as UtReferenceModel).let { refModel ->
refModel.id.let { id -> valueByModelId[id] = variable }
refModel.id.let {
val modelId = getIdByModel(model)
valueByModelId[modelId] = variable
}
}
}
}
Expand Down Expand Up @@ -428,6 +439,8 @@ interface CgContextOwner {

val getLambdaMethod: MethodId
get() = utilMethodProvider.getLambdaMethodMethodId

fun getIdByModel(model: UtModel): ModelId
}

/**
Expand Down Expand Up @@ -479,6 +492,8 @@ data class CgContext(
override lateinit var actual: CgVariable
override lateinit var successfulExecutionsModels: List<UtModel>

override var modelIds: MutableMap<UtModel, ModelId> = mutableMapOf()

/**
* This property cannot be accessed outside of test class file scope
* (i.e. outside of [CgContextOwner.withTestClassFileScope]).
Expand Down Expand Up @@ -556,6 +571,8 @@ data class CgContext(
}
}

override fun getIdByModel(model: UtModel): ModelId = modelIds.getOrPut(model) { model.withExecutionId() }

private fun createClassIdForNestedClass(testClassModel: SimpleTestClassModel): ClassId {
val simpleName = "${testClassModel.classUnderTest.simpleName}Test"
return BuiltinClassId(
Expand All @@ -574,13 +591,14 @@ data class CgContext(
requiredUtilMethods.clear()
valueByModel.clear()
valueByModelId.clear()
modelIds.clear()
mockFrameworkUsed = false
}


override var valueByModel: IdentityHashMap<UtModel, CgValue> = IdentityHashMap()

override var valueByModelId: MutableMap<Int?, CgValue> = mutableMapOf()
override var valueByModelId: MutableMap<ModelId, CgValue> = mutableMapOf()

override val currentMethodParameters: MutableMap<CgParameterKind, CgVariable> = mutableMapOf()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.utbot.framework.codegen.renderer.CgRendererContext
import org.utbot.framework.codegen.renderer.CgVisitor
import org.utbot.framework.codegen.renderer.auxiliaryClassTextById
import org.utbot.framework.codegen.renderer.utilMethodTextById
import org.utbot.framework.codegen.tree.VisibilityModifier
import org.utbot.framework.plugin.api.BuiltinClassId
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.ConstructorId
Expand Down Expand Up @@ -82,6 +83,7 @@ interface CgElement {
is CgBreakStatement -> visit(element)
is CgContinueStatement -> visit(element)
is CgDeclaration -> visit(element)
is CgFieldDeclaration -> visit(element)
is CgAssignment -> visit(element)
is CgTypeCast -> visit(element)
is CgIsInstance -> visit(element)
Expand Down Expand Up @@ -134,6 +136,7 @@ class CgClass(
val body: CgClassBody,
val isStatic: Boolean,
val isNested: Boolean,
val visibility: VisibilityModifier = VisibilityModifier.PUBLIC,
): CgElement {
val packageName
get() = id.packageName
Expand All @@ -156,8 +159,21 @@ class CgClassBody(
val methodRegions: List<CgMethodsCluster>,
val staticDeclarationRegions: List<CgStaticsRegion>,
val nestedClassRegions: List<CgNestedClassesRegion<*>>,
//TODO: use [CgFieldDeclaration] after PR-1788 merge
val fields: List<CgDeclaration> = emptyList(),
val fields: Set<CgFieldDeclaration> = emptySet(),
) : CgElement

/**
* Field of a class.
* @property ownerClassId [ClassId] of the field owner class.
* @property declaration declaration itself.
* @property annotation optional annotation.
* @property visibility field visibility.
*/
class CgFieldDeclaration(
val ownerClassId: ClassId,
val declaration: CgDeclaration,
val annotation: CgAnnotation? = null,
val visibility: VisibilityModifier = VisibilityModifier.PUBLIC,
) : CgElement

/**
Expand Down Expand Up @@ -272,6 +288,7 @@ sealed class CgMethod(open val isStatic: Boolean) : CgElement {
abstract val annotations: List<CgAnnotation>
abstract val documentation: CgDocumentationComment
abstract val requiredFields: List<CgParameterDeclaration>
abstract val visibility: VisibilityModifier
}

class CgTestMethod(
Expand All @@ -281,6 +298,7 @@ class CgTestMethod(
override val statements: List<CgStatement>,
override val exceptions: Set<ClassId>,
override val annotations: List<CgAnnotation>,
override val visibility: VisibilityModifier = VisibilityModifier.PUBLIC,
val type: CgTestMethodType,
override val documentation: CgDocumentationComment = CgDocumentationComment(emptyList()),
override val requiredFields: List<CgParameterDeclaration> = emptyList(),
Expand All @@ -291,6 +309,7 @@ class CgFrameworkUtilMethod(
override val statements: List<CgStatement>,
override val exceptions: Set<ClassId>,
override val annotations: List<CgAnnotation>,
override val visibility: VisibilityModifier = VisibilityModifier.PUBLIC,
) : CgMethod(isStatic = false) {
override val returnType: ClassId = voidClassId
override val parameters: List<CgParameterDeclaration> = emptyList()
Expand All @@ -301,7 +320,8 @@ class CgFrameworkUtilMethod(
class CgErrorTestMethod(
override val name: String,
override val statements: List<CgStatement>,
override val documentation: CgDocumentationComment = CgDocumentationComment(emptyList())
override val documentation: CgDocumentationComment = CgDocumentationComment(emptyList()),
override val visibility: VisibilityModifier = VisibilityModifier.PUBLIC,
) : CgMethod(isStatic = false) {
override val exceptions: Set<ClassId> = emptySet()
override val returnType: ClassId = voidClassId
Expand All @@ -316,6 +336,7 @@ class CgParameterizedTestDataProviderMethod(
override val returnType: ClassId,
override val annotations: List<CgAnnotation>,
override val exceptions: Set<ClassId>,
override val visibility: VisibilityModifier = VisibilityModifier.PUBLIC,
) : CgMethod(isStatic = true) {
override val parameters: List<CgParameterDeclaration> = emptyList()
override val documentation: CgDocumentationComment = CgDocumentationComment(emptyList())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package org.utbot.framework.codegen.domain.models

import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.UtModel

typealias ClassModels = Map<ClassId, Set<UtModel>>

/**
* Stores method test sets in a structure that replicates structure of their methods in [classUnderTest].
Expand All @@ -13,7 +16,7 @@ abstract class TestClassModel(
val nestedClasses: List<SimpleTestClassModel>,
)

open class SimpleTestClassModel(
class SimpleTestClassModel(
classUnderTest: ClassId,
methodTestSets: List<CgMethodTestSet>,
nestedClasses: List<SimpleTestClassModel> = listOf(),
Expand All @@ -29,7 +32,7 @@ class SpringTestClassModel(
classUnderTest: ClassId,
methodTestSets: List<CgMethodTestSet>,
nestedClasses: List<SimpleTestClassModel>,
val injectingMocksClass: ClassId? = null,
val mockedClasses: Set<ClassId> = setOf(),
val injectedMockModels: ClassModels = mapOf(),
val mockedModels: ClassModels = mapOf(),
): TestClassModel(classUnderTest, methodTestSets, nestedClasses)

Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package org.utbot.framework.codegen.domain.models.builders

import org.utbot.framework.codegen.domain.context.CgContext
import org.utbot.framework.codegen.domain.models.CgMethodTestSet
import org.utbot.framework.codegen.domain.models.SimpleTestClassModel
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.util.enclosingClass

open class SimpleTestClassModelBuilder: TestClassModelBuilder() {
override fun createTestClassModel(classUnderTest: ClassId, testSets: List<CgMethodTestSet>): SimpleTestClassModel {
open class SimpleTestClassModelBuilder(context: CgContext): TestClassModelBuilder() {
override fun createTestClassModel(
classUnderTest: ClassId,
testSets: List<CgMethodTestSet>,
): SimpleTestClassModel {
// For each class stores list of methods declared in this class (methods from nested classes are excluded)
val class2methodTestSets = testSets.groupBy { it.executableId.classId }

Expand Down
Loading