Skip to content

Introduce Spring codegen instances and collect types of mocked class variables #1791

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
Feb 14, 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
Original file line number Diff line number Diff line change
Expand Up @@ -358,23 +358,23 @@ class AssembleModelGenerator(private val basePackageName: String) {
private fun assembleMockCompositeModel(compositeModel: UtCompositeModel): UtCompositeModel {
// We have to create a model before the construction of the fields to avoid
// infinite recursion when some mock contains itself as a field.
val assembledModel = UtCompositeModel(
val assembledCompositeModel = UtCompositeModel(
compositeModel.id,
compositeModel.classId,
isMock = true,
)

instantiatedModels[compositeModel] = assembledModel
instantiatedModels[compositeModel] = assembledCompositeModel

val fields = compositeModel.fields.mapValues { assembleModel(it.value) }.toMutableMap()
val mockBehaviour = compositeModel.mocks
.mapValues { models -> models.value.map { assembleModel(it) } }
.toMutableMap()

assembledModel.fields += fields
assembledModel.mocks += mockBehaviour
assembledCompositeModel.fields += fields
assembledCompositeModel.mocks += mockBehaviour

return assembledModel
return assembledCompositeModel
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour
import org.utbot.framework.codegen.domain.StaticsMocking
import org.utbot.framework.codegen.domain.TestFramework
import org.utbot.framework.codegen.domain.models.CgMethodTestSet
import org.utbot.framework.codegen.domain.models.TestClassModel
import org.utbot.framework.codegen.domain.context.CgContext
import org.utbot.framework.codegen.domain.models.CgClassFile
import org.utbot.framework.codegen.domain.models.builders.SimpleTestClassModelBuilder
import org.utbot.framework.codegen.domain.models.builders.SpringTestClassModelBuilder
import org.utbot.framework.codegen.renderer.CgAbstractRenderer
import org.utbot.framework.codegen.reports.TestsGenerationReport
import org.utbot.framework.codegen.tree.CgSimpleTestClassConstructor
import org.utbot.framework.codegen.tree.ututils.UtilClassKind
import org.utbot.framework.codegen.services.language.CgLanguageAssistant
import org.utbot.framework.codegen.tree.CgSpringTestClassConstructor
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.CodegenLanguage
import org.utbot.framework.plugin.api.ExecutableId
Expand Down Expand Up @@ -73,29 +76,60 @@ open class CodeGenerator(
val cgTestSets = testSets.map { CgMethodTestSet(it) }.toList()
return withCustomContext(testClassCustomName) {
context.withTestClassFileScope {
val astConstructor = CgSimpleTestClassConstructor(context)
val renderer = CgAbstractRenderer.makeRenderer(context)
val testClassModel = TestClassModel.fromTestSets(classUnderTest, cgTestSets)
if (context.isSpringClass) {
generateForSpringClass(cgTestSets)
} else {
generateForSimpleClass(cgTestSets)
}
}
}
}

fun now() = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss.SSS"))
private fun generateForSimpleClass(testSets: List<CgMethodTestSet>): CodeGeneratorResult {
val astConstructor = CgSimpleTestClassConstructor(context)
val testClassModel = SimpleTestClassModelBuilder().createTestClassModel(classUnderTest, testSets)

logger.info { "Code generation phase started at ${now()}" }
val testClassFile = astConstructor.construct(testClassModel)
logger.info { "Code generation phase finished at ${now()}" }
logger.info { "Code generation phase started at ${now()}" }
val testClassFile = astConstructor.construct(testClassModel)
logger.info { "Code generation phase finished at ${now()}" }

logger.info { "Rendering phase started at ${now()}" }
testClassFile.accept(renderer)
logger.info { "Rendering phase finished at ${now()}" }
val generatedCode = renderToString(testClassFile)

CodeGeneratorResult(
generatedCode = renderer.toString(),
utilClassKind = UtilClassKind.fromCgContextOrNull(context),
testsGenerationReport = astConstructor.testsGenerationReport
)
}
}
return CodeGeneratorResult(
generatedCode = generatedCode,
utilClassKind = UtilClassKind.fromCgContextOrNull(context),
testsGenerationReport = astConstructor.testsGenerationReport
)
}

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

logger.info { "Code generation phase started at ${now()}" }
val testClassFile = astConstructor.construct(testClassModel)
logger.info { "Code generation phase finished at ${now()}" }

val generatedCode = renderToString(testClassFile)

return CodeGeneratorResult(
generatedCode = generatedCode,
utilClassKind = UtilClassKind.fromCgContextOrNull(context),
testsGenerationReport = TestsGenerationReport()
)
}

private fun renderToString(testClassFile: CgClassFile): String {
logger.info { "Rendering phase started at ${now()}" }
val renderer = CgAbstractRenderer.makeRenderer(context)
testClassFile.accept(renderer)
logger.info { "Rendering phase finished at ${now()}" }

return renderer.toString()
}

private fun now() = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss.SSS"))

/**
* Wrapper function that configures context as needed for utbot-online:
* - turns on imports optimization in code generator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ 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.TestClassModel
import org.utbot.framework.codegen.domain.models.SimpleTestClassModel
import org.utbot.framework.codegen.domain.models.CgParameterKind
import org.utbot.framework.codegen.services.access.Block
import org.utbot.framework.codegen.tree.EnvironmentFieldStateCache
Expand Down Expand Up @@ -66,6 +66,9 @@ interface CgContextOwner {
// current class under test
val classUnderTest: ClassId

// If test class is configured with Spring, we should do some extra analysis
val isSpringClass: Boolean

// test class currently being generated (if series of nested classes is generated, it is the outermost one)
val outerMostTestClass: ClassId

Expand Down Expand Up @@ -292,7 +295,7 @@ interface CgContextOwner {
* This method does almost all the same as [withTestClassScope], but for nested test classes.
* The difference is that instead of working with [outerMostTestClassContext] it works with [currentTestClassContext].
*/
fun <R> withNestedClassScope(testClassModel: TestClassModel, block: () -> R): R
fun <R> withNestedClassScope(testClassModel: SimpleTestClassModel, block: () -> R): R

/**
* Set [mockFrameworkUsed] flag to true if the block is successfully executed
Expand Down Expand Up @@ -431,6 +434,7 @@ interface CgContextOwner {
*/
data class CgContext(
override val classUnderTest: ClassId,
override val isSpringClass: Boolean = false,
val generateUtilClassFile: Boolean = false,
override var currentExecutable: ExecutableId? = null,
override val collectedExceptions: MutableSet<ClassId> = mutableSetOf(),
Expand Down Expand Up @@ -538,7 +542,7 @@ data class CgContext(
}
}

override fun <R> withNestedClassScope(testClassModel: TestClassModel, block: () -> R): R {
override fun <R> withNestedClassScope(testClassModel: SimpleTestClassModel, block: () -> R): R {
val previousCurrentTestClassInfo = currentTestClassContext
val previousCurrentTestClass = currentTestClass
currentTestClass = createClassIdForNestedClass(testClassModel)
Expand All @@ -551,7 +555,7 @@ data class CgContext(
}
}

private fun createClassIdForNestedClass(testClassModel: TestClassModel): ClassId {
private fun createClassIdForNestedClass(testClassModel: SimpleTestClassModel): ClassId {
val simpleName = "${testClassModel.classUnderTest.simpleName}Test"
return BuiltinClassId(
canonicalName = currentTestClass.canonicalName + "." + simpleName,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,58 +1,35 @@
package org.utbot.framework.codegen.domain.models

import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.util.enclosingClass

// TODO: seems like this class needs to be renamed
/**
* Stores method testsets in a structure that replicates structure of their methods in [classUnderTest].
* Stores method test sets in a structure that replicates structure of their methods in [classUnderTest].
* I.e., if some method is declared in nested class of [classUnderTest], its testset will be put
* in [TestClassModel] in one of [nestedClasses]
*/
data class TestClassModel(
abstract class TestClassModel(
val classUnderTest: ClassId,
val methodTestSets: List<CgMethodTestSet>,
val nestedClasses: List<TestClassModel> = listOf()
) {
companion object {
fun fromTestSets(classUnderTest: ClassId, testSets: List<CgMethodTestSet>): TestClassModel {
// For each class stores list of methods declared in this class (methods from nested classes are excluded)
val class2methodTestSets = testSets.groupBy { it.executableId.classId }
val nestedClasses: List<SimpleTestClassModel>,
)

val classesWithMethodsUnderTest = testSets
.map { it.executableId.classId }
.distinct()
open class SimpleTestClassModel(
classUnderTest: ClassId,
methodTestSets: List<CgMethodTestSet>,
nestedClasses: List<SimpleTestClassModel> = listOf(),
): TestClassModel(classUnderTest, methodTestSets, nestedClasses)

// For each class stores list of its "direct" nested classes
val class2nestedClasses = mutableMapOf<ClassId, MutableSet<ClassId>>()

for (classId in classesWithMethodsUnderTest) {
var currentClass = classId
var enclosingClass = currentClass.enclosingClass
// while we haven't reached the top of nested class hierarchy or the main class under test
while (enclosingClass != null && currentClass != classUnderTest) {
class2nestedClasses.getOrPut(enclosingClass) { mutableSetOf() } += currentClass
currentClass = enclosingClass
enclosingClass = enclosingClass.enclosingClass
}
}
return constructRecursively(classUnderTest, class2methodTestSets, class2nestedClasses)
}
/**
* Extended [SimpleTestClassModel] for Spring analysis reasons
*
* @param injectingMocksClass a class to inject other mocks into
* @param mockedClasses variables of test class to represent mocked instances
*/
class SpringTestClassModel(
classUnderTest: ClassId,
methodTestSets: List<CgMethodTestSet>,
nestedClasses: List<SimpleTestClassModel>,
val injectingMocksClass: ClassId? = null,
val mockedClasses: Set<ClassId> = setOf(),
): TestClassModel(classUnderTest, methodTestSets, nestedClasses)

private fun constructRecursively(
clazz: ClassId,
class2methodTestSets: Map<ClassId, List<CgMethodTestSet>>,
class2nestedClasses: Map<ClassId, Set<ClassId>>
): TestClassModel {
val currentNestedClasses = class2nestedClasses.getOrDefault(clazz, listOf())
val currentMethodTestSets = class2methodTestSets.getOrDefault(clazz, listOf())
return TestClassModel(
clazz,
currentMethodTestSets,
currentNestedClasses.map {
constructRecursively(it, class2methodTestSets, class2nestedClasses)
}
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.utbot.framework.codegen.domain.models.builders

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 {
// For each class stores list of methods declared in this class (methods from nested classes are excluded)
val class2methodTestSets = testSets.groupBy { it.executableId.classId }

val classesWithMethodsUnderTest = testSets
.map { it.executableId.classId }
.distinct()

// For each class stores list of its "direct" nested classes
val class2nestedClasses = mutableMapOf<ClassId, MutableSet<ClassId>>()

for (classId in classesWithMethodsUnderTest) {
var currentClass = classId
var enclosingClass = currentClass.enclosingClass
// while we haven't reached the top of nested class hierarchy or the main class under test
while (enclosingClass != null && currentClass != classUnderTest) {
class2nestedClasses.getOrPut(enclosingClass) { mutableSetOf() } += currentClass
currentClass = enclosingClass
enclosingClass = enclosingClass.enclosingClass
}
}

return constructRecursively(classUnderTest, class2methodTestSets, class2nestedClasses)
}

private fun constructRecursively(
clazz: ClassId,
class2methodTestSets: Map<ClassId, List<CgMethodTestSet>>,
class2nestedClasses: Map<ClassId, Set<ClassId>>
): SimpleTestClassModel {
val currentNestedClasses = class2nestedClasses.getOrDefault(clazz, listOf())
val currentMethodTestSets = class2methodTestSets.getOrDefault(clazz, listOf())
return SimpleTestClassModel(
clazz,
currentMethodTestSets,
currentNestedClasses.map {
constructRecursively(it, class2methodTestSets, class2nestedClasses)
}
)
}
}
Loading