Skip to content

Commit a8885c2

Browse files
Refactor TestCaseGenerator and CodeGenerator and their usages #508 (#540)
1 parent 66caf83 commit a8885c2

File tree

17 files changed

+266
-314
lines changed

17 files changed

+266
-314
lines changed

utbot-cli/src/main/kotlin/org/utbot/cli/BunchTestGeneratorCommand.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,15 @@ class BunchTestGeneratorCommand : GenerateTestsAbstractCommand(
9797
val targetMethods = classUnderTest.targetMethods()
9898
if (targetMethods.isEmpty()) return
9999

100-
initializeEngine(workingDirectory)
100+
val testCaseGenerator = initializeGenerator(workingDirectory)
101101

102102
// utContext is used in `generate`, `generateTest`, `generateReport`
103103
withUtContext(UtContext(classLoader)) {
104104

105105
val testClassName = "${classUnderTest.simpleName}Test"
106106

107107
val testSets = generateTestSets(
108+
testCaseGenerator,
108109
targetMethods,
109110
searchDirectory = workingDirectory,
110111
chosenClassesToMockAlways = (Mocker.defaultSuperClassesToMockAlwaysNames + classesToMockAlways)

utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsAbstractCommand.kt

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,13 @@ abstract class GenerateTestsAbstractCommand(name: String, help: String) :
154154
classLoader.loadClass(classFqn).kotlin
155155

156156
protected fun generateTestSets(
157+
testCaseGenerator: TestCaseGenerator,
157158
targetMethods: List<UtMethod<*>>,
158159
sourceCodeFile: Path? = null,
159160
searchDirectory: Path,
160161
chosenClassesToMockAlways: Set<ClassId>
161162
): List<UtMethodTestSet> =
162-
TestCaseGenerator.generate(
163+
testCaseGenerator.generate(
163164
targetMethods,
164165
mockStrategy,
165166
chosenClassesToMockAlways,
@@ -190,30 +191,26 @@ abstract class GenerateTestsAbstractCommand(name: String, help: String) :
190191
classUnderTest
191192
).generateAsString(testSets, testClassname)
192193

193-
protected fun initializeEngine(workingDirectory: Path) {
194+
protected fun initializeGenerator(workingDirectory: Path): TestCaseGenerator {
194195
val classPathNormalized =
195196
classLoader.urLs.joinToString(separator = File.pathSeparator) { it.toPath().absolutePath }
196-
197-
// TODO: SAT-1566
198-
// Set UtSettings parameters.
197+
// TODO: SAT-1566 Set UtSettings parameters.
199198
UtSettings.treatOverflowAsError = treatOverflowAsError == TreatOverflowAsError.AS_ERROR
200199

201-
TestCaseGenerator.init(workingDirectory, classPathNormalized, System.getProperty("java.class.path"))
200+
return TestCaseGenerator(workingDirectory, classPathNormalized, System.getProperty("java.class.path"))
202201
}
203202

204203
private fun initializeCodeGenerator(testFramework: String, classUnderTest: KClass<*>): CodeGenerator {
205204
val generateWarningsForStaticMocking =
206205
forceStaticMocking == ForceStaticMocking.FORCE && staticsMocking is NoStaticMocking
207-
return CodeGenerator().apply {
208-
init(
209-
testFramework = testFrameworkByName(testFramework),
210-
classUnderTest = classUnderTest.java,
211-
codegenLanguage = codegenLanguage,
212-
staticsMocking = staticsMocking,
213-
forceStaticMocking = forceStaticMocking,
214-
generateWarningsForStaticMocking = generateWarningsForStaticMocking,
215-
)
216-
}
206+
return CodeGenerator(
207+
testFramework = testFrameworkByName(testFramework),
208+
classUnderTest = classUnderTest.java,
209+
codegenLanguage = codegenLanguage,
210+
staticsMocking = staticsMocking,
211+
forceStaticMocking = forceStaticMocking,
212+
generateWarningsForStaticMocking = generateWarningsForStaticMocking,
213+
)
217214
}
218215

219216
protected fun KClass<*>.targetMethods() =

utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsCommand.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ class GenerateTestsCommand :
9696
val classUnderTest: KClass<*> = loadClassBySpecifiedFqn(targetClassFqn)
9797
val targetMethods = classUnderTest.targetMethods()
9898
.filterWhen(UtSettings.skipTestGenerationForSyntheticMethods) { !isKnownSyntheticMethod(it) }
99-
initializeEngine(workingDirectory)
99+
val testCaseGenerator = initializeGenerator(workingDirectory)
100100

101101
if (targetMethods.isEmpty()) {
102102
throw Exception("Nothing to process. No methods were provided")
@@ -107,6 +107,7 @@ class GenerateTestsCommand :
107107
val testClassName = output?.toPath()?.toFile()?.nameWithoutExtension
108108
?: "${classUnderTest.simpleName}Test"
109109
val testSets = generateTestSets(
110+
testCaseGenerator,
110111
targetMethods,
111112
Paths.get(sourceCodeFile),
112113
searchDirectory = workingDirectory,

utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,7 @@ object UtBotJavaApi {
8282
}
8383

8484
return withUtContext(utContext) {
85-
val testGenerator = CodeGenerator().apply {
86-
init(
85+
val codeGenerator = CodeGenerator(
8786
classUnderTest = classUnderTest,
8887
testFramework = testFramework,
8988
mockFramework = mockFramework,
@@ -93,12 +92,8 @@ object UtBotJavaApi {
9392
generateWarningsForStaticMocking = generateWarningsForStaticMocking,
9493
testClassPackageName = testClassPackageName
9594
)
96-
}
9795

98-
testGenerator.generateAsString(
99-
testSets,
100-
destinationClassName
101-
)
96+
codeGenerator.generateAsString(testSets, destinationClassName)
10297
}
10398
}
10499

@@ -122,12 +117,8 @@ object UtBotJavaApi {
122117
val testSets: MutableList<UtMethodTestSet> = mutableListOf()
123118

124119
testSets.addAll(withUtContext(utContext) {
125-
TestCaseGenerator
126-
.apply {
127-
init(
128-
FileUtil.isolateClassFiles(classUnderTest.kotlin).toPath(), classpath, dependencyClassPath
129-
)
130-
}
120+
val buildPath = FileUtil.isolateClassFiles(classUnderTest.kotlin).toPath()
121+
TestCaseGenerator(buildPath, classpath, dependencyClassPath)
131122
.generate(
132123
methodsForAutomaticGeneration.map {
133124
toUtMethod(
@@ -189,12 +180,9 @@ object UtBotJavaApi {
189180
}
190181

191182
return withUtContext(UtContext(classUnderTest.classLoader)) {
192-
TestCaseGenerator
193-
.apply {
194-
init(
195-
FileUtil.isolateClassFiles(classUnderTest.kotlin).toPath(), classpath, dependencyClassPath
196-
)
197-
}.generate(
183+
val buildPath = FileUtil.isolateClassFiles(classUnderTest.kotlin).toPath()
184+
TestCaseGenerator(buildPath, classpath, dependencyClassPath)
185+
.generate(
198186
methodsForAutomaticGeneration.map {
199187
toUtMethod(
200188
it.methodToBeTestedFromUserInput,

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,42 +18,36 @@ import org.utbot.framework.plugin.api.UtMethod
1818
import org.utbot.framework.plugin.api.UtMethodTestSet
1919
import org.utbot.framework.plugin.api.util.id
2020

21-
class CodeGenerator {
22-
private lateinit var context: CgContext
23-
24-
fun init(
25-
classUnderTest: Class<*>,
26-
params: MutableMap<UtMethod<*>, List<String>> = mutableMapOf(),
27-
testFramework: TestFramework = TestFramework.defaultItem,
28-
mockFramework: MockFramework? = MockFramework.defaultItem,
29-
staticsMocking: StaticsMocking = StaticsMocking.defaultItem,
30-
forceStaticMocking: ForceStaticMocking = ForceStaticMocking.defaultItem,
31-
generateWarningsForStaticMocking: Boolean = true,
32-
codegenLanguage: CodegenLanguage = CodegenLanguage.defaultItem,
33-
parameterizedTestSource: ParametrizedTestSource = ParametrizedTestSource.defaultItem,
34-
runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.defaultItem,
35-
hangingTestsTimeout: HangingTestsTimeout = HangingTestsTimeout(),
36-
enableTestsTimeout: Boolean = true,
37-
testClassPackageName: String = classUnderTest.packageName,
38-
) {
39-
context = CgContext(
40-
classUnderTest = classUnderTest.id,
41-
// TODO: remove existingNames parameter completely
42-
existingMethodNames = mutableSetOf(),
43-
paramNames = params,
44-
testFramework = testFramework,
45-
mockFramework = mockFramework ?: MockFramework.MOCKITO,
46-
codegenLanguage = codegenLanguage,
47-
parameterizedTestSource = parameterizedTestSource,
48-
staticsMocking = staticsMocking,
49-
forceStaticMocking = forceStaticMocking,
50-
generateWarningsForStaticMocking = generateWarningsForStaticMocking,
51-
runtimeExceptionTestsBehaviour = runtimeExceptionTestsBehaviour,
52-
hangingTestsTimeout = hangingTestsTimeout,
53-
enableTestsTimeout = enableTestsTimeout,
54-
testClassPackageName = testClassPackageName
55-
)
56-
}
21+
class CodeGenerator(
22+
private val classUnderTest: Class<*>,
23+
params: MutableMap<UtMethod<*>, List<String>> = mutableMapOf(),
24+
testFramework: TestFramework = TestFramework.defaultItem,
25+
mockFramework: MockFramework? = MockFramework.defaultItem,
26+
staticsMocking: StaticsMocking = StaticsMocking.defaultItem,
27+
forceStaticMocking: ForceStaticMocking = ForceStaticMocking.defaultItem,
28+
generateWarningsForStaticMocking: Boolean = true,
29+
codegenLanguage: CodegenLanguage = CodegenLanguage.defaultItem,
30+
parameterizedTestSource: ParametrizedTestSource = ParametrizedTestSource.defaultItem,
31+
runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.defaultItem,
32+
hangingTestsTimeout: HangingTestsTimeout = HangingTestsTimeout(),
33+
enableTestsTimeout: Boolean = true,
34+
testClassPackageName: String = classUnderTest.packageName,
35+
) {
36+
private var context: CgContext = CgContext(
37+
classUnderTest = classUnderTest.id,
38+
paramNames = params,
39+
testFramework = testFramework,
40+
mockFramework = mockFramework ?: MockFramework.MOCKITO,
41+
codegenLanguage = codegenLanguage,
42+
parameterizedTestSource = parameterizedTestSource,
43+
staticsMocking = staticsMocking,
44+
forceStaticMocking = forceStaticMocking,
45+
generateWarningsForStaticMocking = generateWarningsForStaticMocking,
46+
runtimeExceptionTestsBehaviour = runtimeExceptionTestsBehaviour,
47+
hangingTestsTimeout = hangingTestsTimeout,
48+
enableTestsTimeout = enableTestsTimeout,
49+
testClassPackageName = testClassPackageName
50+
)
5751

5852
//TODO: we support custom test class name only in utbot-online, probably support them in plugin as well
5953
fun generateAsString(testSets: Collection<UtMethodTestSet>, testClassCustomName: String? = null): String =

utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt

Lines changed: 55 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
11
package org.utbot.framework.plugin.api
22

3-
import org.utbot.common.FileUtil
3+
import kotlinx.coroutines.CoroutineScope
4+
import kotlinx.coroutines.GlobalScope
5+
import kotlinx.coroutines.cancel
6+
import kotlinx.coroutines.flow.Flow
7+
import kotlinx.coroutines.flow.collect
8+
import kotlinx.coroutines.flow.flattenConcat
9+
import kotlinx.coroutines.flow.flowOf
10+
import kotlinx.coroutines.isActive
11+
import kotlinx.coroutines.launch
12+
import kotlinx.coroutines.runBlocking
13+
import kotlinx.coroutines.yield
14+
import mu.KLogger
15+
import mu.KotlinLogging
416
import org.utbot.common.bracket
517
import org.utbot.common.runBlockingWithCancellationPredicate
618
import org.utbot.common.runIgnoringCancellationException
719
import org.utbot.common.trace
820
import org.utbot.engine.EngineController
921
import org.utbot.engine.Mocker
22+
import org.utbot.engine.UtBotSymbolicEngine
1023
import org.utbot.framework.TestSelectionStrategyType
1124
import org.utbot.framework.UtSettings
1225
import org.utbot.framework.UtSettings.checkSolverTimeoutMillis
@@ -34,85 +47,58 @@ import java.util.*
3447
import kotlin.coroutines.cancellation.CancellationException
3548
import kotlin.math.min
3649
import kotlin.reflect.KCallable
37-
import kotlinx.coroutines.CoroutineScope
38-
import kotlinx.coroutines.GlobalScope
39-
import kotlinx.coroutines.cancel
40-
import kotlinx.coroutines.flow.Flow
41-
import kotlinx.coroutines.flow.collect
42-
import kotlinx.coroutines.flow.flattenConcat
43-
import kotlinx.coroutines.flow.flowOf
44-
import kotlinx.coroutines.isActive
45-
import kotlinx.coroutines.launch
46-
import kotlinx.coroutines.runBlocking
47-
import kotlinx.coroutines.yield
48-
import mu.KotlinLogging
49-
import org.utbot.engine.UtBotSymbolicEngine
50-
51-
object TestCaseGenerator {
5250

53-
private val logger = KotlinLogging.logger {}
54-
private val timeoutLogger = KotlinLogging.logger(logger.name + ".timeout")
51+
/**
52+
* Generates test cases: one by one or a whole set for the method under test.
53+
*
54+
* Note: the instantiating of [TestCaseGenerator] may take some time,
55+
* because it requires initializing Soot for the current [buildDir] and [classpath].
56+
*/
57+
open class TestCaseGenerator(
58+
private val buildDir: Path,
59+
private val classpath: String?,
60+
private val dependencyPaths: String,
61+
val engineActions: MutableList<(UtBotSymbolicEngine) -> Unit> = mutableListOf(),
62+
val isCanceled: () -> Boolean = { false },
63+
) {
64+
private val logger: KLogger = KotlinLogging.logger {}
65+
private val timeoutLogger: KLogger = KotlinLogging.logger(logger.name + ".timeout")
5566

56-
lateinit var engineActions: MutableList<(UtBotSymbolicEngine) -> Unit>
57-
lateinit var isCanceled: () -> Boolean
58-
59-
//properties to save time on soot initialization
60-
private var previousBuildDir: Path? = null
61-
private var previousClasspath: String? = null
62-
private var previousTimestamp: Long? = null
63-
private var dependencyPaths: String = ""
64-
65-
fun init(
66-
buildDir: Path,
67-
classpath: String?,
68-
dependencyPaths: String,
69-
engineActions: MutableList<(UtBotSymbolicEngine) -> Unit> = mutableListOf(),
70-
isCanceled: () -> Boolean = { false },
71-
) {
72-
this.isCanceled = isCanceled
73-
this.engineActions = engineActions
74-
if (isCanceled()) return
67+
private val classpathForEngine: String
68+
get() = buildDir.toString() + (classpath?.let { File.pathSeparator + it } ?: "")
7569

76-
checkFrameworkDependencies(dependencyPaths)
70+
init {
71+
if (!isCanceled()) {
72+
checkFrameworkDependencies(dependencyPaths)
7773

78-
logger.trace("Initializing ${this.javaClass.name} with buildDir = $buildDir, classpath = $classpath")
74+
logger.trace("Initializing ${this.javaClass.name} with buildDir = $buildDir, classpath = $classpath")
7975

80-
//optimization: maxLastModifiedRecursivelyMillis can take time
81-
val timestamp = if (UtSettings.classfilesCanChange) maxLastModifiedRecursivelyMillis(buildDir, classpath) else 0
82-
if (buildDir == previousBuildDir && classpath == previousClasspath && timestamp == previousTimestamp) {
83-
logger.info { "Ignoring soot initialization because parameters are the same as on previous initialization" }
84-
return
85-
}
8676

87-
if (disableCoroutinesDebug) {
88-
System.setProperty(kotlinx.coroutines.DEBUG_PROPERTY_NAME, kotlinx.coroutines.DEBUG_PROPERTY_VALUE_OFF)
89-
}
77+
if (disableCoroutinesDebug) {
78+
System.setProperty(kotlinx.coroutines.DEBUG_PROPERTY_NAME, kotlinx.coroutines.DEBUG_PROPERTY_VALUE_OFF)
79+
}
9080

91-
timeoutLogger.trace().bracket("Soot initialization") {
92-
runSoot(buildDir, classpath)
93-
}
81+
timeoutLogger.trace().bracket("Soot initialization") {
82+
runSoot(buildDir, classpath)
83+
}
9484

95-
previousBuildDir = buildDir
96-
previousClasspath = classpath
97-
previousTimestamp = timestamp
98-
this.dependencyPaths = dependencyPaths
99-
100-
//warmup
101-
if (warmupConcreteExecution) {
102-
ConcreteExecutor(
103-
UtExecutionInstrumentation,
104-
classpathForEngine,
105-
dependencyPaths
106-
).apply {
107-
classLoader = utContext.classLoader
108-
withUtContext(UtContext(Warmup::class.java.classLoader)) {
109-
runBlocking {
110-
constructExecutionsForWarmup().forEach { (method, data) ->
111-
executeAsync(method, emptyArray(), data)
85+
//warmup
86+
if (warmupConcreteExecution) {
87+
ConcreteExecutor(
88+
UtExecutionInstrumentation,
89+
classpathForEngine,
90+
dependencyPaths
91+
).apply {
92+
classLoader = utContext.classLoader
93+
withUtContext(UtContext(Warmup::class.java.classLoader)) {
94+
runBlocking {
95+
constructExecutionsForWarmup().forEach { (method, data) ->
96+
executeAsync(method, emptyArray(), data)
97+
}
11298
}
11399
}
100+
warmup()
114101
}
115-
warmup()
116102
}
117103
}
118104
}
@@ -254,18 +240,6 @@ object TestCaseGenerator {
254240
)
255241
}
256242

257-
private val classpathForEngine: String
258-
get() = previousBuildDir!!.toString() + (previousClasspath?.let { File.pathSeparator + it } ?: "")
259-
260-
private fun maxLastModifiedRecursivelyMillis(buildDir: Path, classpath: String?): Long {
261-
val paths = mutableListOf<File>()
262-
paths += buildDir.toFile()
263-
if (classpath != null) {
264-
paths += classpath.split(File.pathSeparatorChar).map { File(it) }
265-
}
266-
return FileUtil.maxLastModifiedRecursivelyMillis(paths)
267-
}
268-
269243
private fun createSymbolicEngine(
270244
controller: EngineController,
271245
method: UtMethod<*>,

0 commit comments

Comments
 (0)