diff --git a/utbot-cli/src/main/kotlin/org/utbot/cli/BunchTestGeneratorCommand.kt b/utbot-cli/src/main/kotlin/org/utbot/cli/BunchTestGeneratorCommand.kt index e2d19b5fcd..e2bcadb6a1 100644 --- a/utbot-cli/src/main/kotlin/org/utbot/cli/BunchTestGeneratorCommand.kt +++ b/utbot-cli/src/main/kotlin/org/utbot/cli/BunchTestGeneratorCommand.kt @@ -97,7 +97,7 @@ class BunchTestGeneratorCommand : GenerateTestsAbstractCommand( val targetMethods = classUnderTest.targetMethods() if (targetMethods.isEmpty()) return - initializeEngine(workingDirectory) + val testCaseGenerator = initializeGenerator(workingDirectory) // utContext is used in `generate`, `generateTest`, `generateReport` withUtContext(UtContext(classLoader)) { @@ -105,6 +105,7 @@ class BunchTestGeneratorCommand : GenerateTestsAbstractCommand( val testClassName = "${classUnderTest.simpleName}Test" val testSets = generateTestSets( + testCaseGenerator, targetMethods, searchDirectory = workingDirectory, chosenClassesToMockAlways = (Mocker.defaultSuperClassesToMockAlwaysNames + classesToMockAlways) diff --git a/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsAbstractCommand.kt b/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsAbstractCommand.kt index 87fc8bab42..097d4d0dea 100644 --- a/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsAbstractCommand.kt +++ b/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsAbstractCommand.kt @@ -154,12 +154,13 @@ abstract class GenerateTestsAbstractCommand(name: String, help: String) : classLoader.loadClass(classFqn).kotlin protected fun generateTestSets( + testCaseGenerator: TestCaseGenerator, targetMethods: List>, sourceCodeFile: Path? = null, searchDirectory: Path, chosenClassesToMockAlways: Set ): List = - TestCaseGenerator.generate( + testCaseGenerator.generate( targetMethods, mockStrategy, chosenClassesToMockAlways, @@ -190,30 +191,26 @@ abstract class GenerateTestsAbstractCommand(name: String, help: String) : classUnderTest ).generateAsString(testSets, testClassname) - protected fun initializeEngine(workingDirectory: Path) { + protected fun initializeGenerator(workingDirectory: Path): TestCaseGenerator { val classPathNormalized = classLoader.urLs.joinToString(separator = File.pathSeparator) { it.toPath().absolutePath } - - // TODO: SAT-1566 - // Set UtSettings parameters. + // TODO: SAT-1566 Set UtSettings parameters. UtSettings.treatOverflowAsError = treatOverflowAsError == TreatOverflowAsError.AS_ERROR - TestCaseGenerator.init(workingDirectory, classPathNormalized, System.getProperty("java.class.path")) + return TestCaseGenerator(workingDirectory, classPathNormalized, System.getProperty("java.class.path")) } private fun initializeCodeGenerator(testFramework: String, classUnderTest: KClass<*>): CodeGenerator { val generateWarningsForStaticMocking = forceStaticMocking == ForceStaticMocking.FORCE && staticsMocking is NoStaticMocking - return CodeGenerator().apply { - init( - testFramework = testFrameworkByName(testFramework), - classUnderTest = classUnderTest.java, - codegenLanguage = codegenLanguage, - staticsMocking = staticsMocking, - forceStaticMocking = forceStaticMocking, - generateWarningsForStaticMocking = generateWarningsForStaticMocking, - ) - } + return CodeGenerator( + testFramework = testFrameworkByName(testFramework), + classUnderTest = classUnderTest.java, + codegenLanguage = codegenLanguage, + staticsMocking = staticsMocking, + forceStaticMocking = forceStaticMocking, + generateWarningsForStaticMocking = generateWarningsForStaticMocking, + ) } protected fun KClass<*>.targetMethods() = diff --git a/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsCommand.kt b/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsCommand.kt index 9c88095e31..b738f57eae 100644 --- a/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsCommand.kt +++ b/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsCommand.kt @@ -96,7 +96,7 @@ class GenerateTestsCommand : val classUnderTest: KClass<*> = loadClassBySpecifiedFqn(targetClassFqn) val targetMethods = classUnderTest.targetMethods() .filterWhen(UtSettings.skipTestGenerationForSyntheticMethods) { !isKnownSyntheticMethod(it) } - initializeEngine(workingDirectory) + val testCaseGenerator = initializeGenerator(workingDirectory) if (targetMethods.isEmpty()) { throw Exception("Nothing to process. No methods were provided") @@ -107,6 +107,7 @@ class GenerateTestsCommand : val testClassName = output?.toPath()?.toFile()?.nameWithoutExtension ?: "${classUnderTest.simpleName}Test" val testSets = generateTestSets( + testCaseGenerator, targetMethods, Paths.get(sourceCodeFile), searchDirectory = workingDirectory, diff --git a/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt b/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt index 7c55c7bc22..6d5b69f26a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt @@ -82,8 +82,7 @@ object UtBotJavaApi { } return withUtContext(utContext) { - val testGenerator = CodeGenerator().apply { - init( + val codeGenerator = CodeGenerator( classUnderTest = classUnderTest, testFramework = testFramework, mockFramework = mockFramework, @@ -93,12 +92,8 @@ object UtBotJavaApi { generateWarningsForStaticMocking = generateWarningsForStaticMocking, testClassPackageName = testClassPackageName ) - } - testGenerator.generateAsString( - testSets, - destinationClassName - ) + codeGenerator.generateAsString(testSets, destinationClassName) } } @@ -122,12 +117,8 @@ object UtBotJavaApi { val testSets: MutableList = mutableListOf() testSets.addAll(withUtContext(utContext) { - TestCaseGenerator - .apply { - init( - FileUtil.isolateClassFiles(classUnderTest.kotlin).toPath(), classpath, dependencyClassPath - ) - } + val buildPath = FileUtil.isolateClassFiles(classUnderTest.kotlin).toPath() + TestCaseGenerator(buildPath, classpath, dependencyClassPath) .generate( methodsForAutomaticGeneration.map { toUtMethod( @@ -189,12 +180,9 @@ object UtBotJavaApi { } return withUtContext(UtContext(classUnderTest.classLoader)) { - TestCaseGenerator - .apply { - init( - FileUtil.isolateClassFiles(classUnderTest.kotlin).toPath(), classpath, dependencyClassPath - ) - }.generate( + val buildPath = FileUtil.isolateClassFiles(classUnderTest.kotlin).toPath() + TestCaseGenerator(buildPath, classpath, dependencyClassPath) + .generate( methodsForAutomaticGeneration.map { toUtMethod( it.methodToBeTestedFromUserInput, diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt index 349f526e5c..01a3199c7c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt @@ -18,42 +18,36 @@ import org.utbot.framework.plugin.api.UtMethod import org.utbot.framework.plugin.api.UtMethodTestSet import org.utbot.framework.plugin.api.util.id -class CodeGenerator { - private lateinit var context: CgContext - - fun init( - classUnderTest: Class<*>, - params: MutableMap, List> = mutableMapOf(), - testFramework: TestFramework = TestFramework.defaultItem, - mockFramework: MockFramework? = MockFramework.defaultItem, - staticsMocking: StaticsMocking = StaticsMocking.defaultItem, - forceStaticMocking: ForceStaticMocking = ForceStaticMocking.defaultItem, - generateWarningsForStaticMocking: Boolean = true, - codegenLanguage: CodegenLanguage = CodegenLanguage.defaultItem, - parameterizedTestSource: ParametrizedTestSource = ParametrizedTestSource.defaultItem, - runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.defaultItem, - hangingTestsTimeout: HangingTestsTimeout = HangingTestsTimeout(), - enableTestsTimeout: Boolean = true, - testClassPackageName: String = classUnderTest.packageName, - ) { - context = CgContext( - classUnderTest = classUnderTest.id, - // TODO: remove existingNames parameter completely - existingMethodNames = mutableSetOf(), - paramNames = params, - testFramework = testFramework, - mockFramework = mockFramework ?: MockFramework.MOCKITO, - codegenLanguage = codegenLanguage, - parameterizedTestSource = parameterizedTestSource, - staticsMocking = staticsMocking, - forceStaticMocking = forceStaticMocking, - generateWarningsForStaticMocking = generateWarningsForStaticMocking, - runtimeExceptionTestsBehaviour = runtimeExceptionTestsBehaviour, - hangingTestsTimeout = hangingTestsTimeout, - enableTestsTimeout = enableTestsTimeout, - testClassPackageName = testClassPackageName - ) - } +class CodeGenerator( + private val classUnderTest: Class<*>, + params: MutableMap, List> = mutableMapOf(), + testFramework: TestFramework = TestFramework.defaultItem, + mockFramework: MockFramework? = MockFramework.defaultItem, + staticsMocking: StaticsMocking = StaticsMocking.defaultItem, + forceStaticMocking: ForceStaticMocking = ForceStaticMocking.defaultItem, + generateWarningsForStaticMocking: Boolean = true, + codegenLanguage: CodegenLanguage = CodegenLanguage.defaultItem, + parameterizedTestSource: ParametrizedTestSource = ParametrizedTestSource.defaultItem, + runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.defaultItem, + hangingTestsTimeout: HangingTestsTimeout = HangingTestsTimeout(), + enableTestsTimeout: Boolean = true, + testClassPackageName: String = classUnderTest.packageName, +) { + private var context: CgContext = CgContext( + classUnderTest = classUnderTest.id, + paramNames = params, + testFramework = testFramework, + mockFramework = mockFramework ?: MockFramework.MOCKITO, + codegenLanguage = codegenLanguage, + parameterizedTestSource = parameterizedTestSource, + staticsMocking = staticsMocking, + forceStaticMocking = forceStaticMocking, + generateWarningsForStaticMocking = generateWarningsForStaticMocking, + runtimeExceptionTestsBehaviour = runtimeExceptionTestsBehaviour, + hangingTestsTimeout = hangingTestsTimeout, + enableTestsTimeout = enableTestsTimeout, + testClassPackageName = testClassPackageName + ) //TODO: we support custom test class name only in utbot-online, probably support them in plugin as well fun generateAsString(testSets: Collection, testClassCustomName: String? = null): String = diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt index 6dd1a87058..c9fbaa392d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt @@ -1,12 +1,25 @@ package org.utbot.framework.plugin.api -import org.utbot.common.FileUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flattenConcat +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.yield +import mu.KLogger +import mu.KotlinLogging import org.utbot.common.bracket import org.utbot.common.runBlockingWithCancellationPredicate import org.utbot.common.runIgnoringCancellationException import org.utbot.common.trace import org.utbot.engine.EngineController import org.utbot.engine.Mocker +import org.utbot.engine.UtBotSymbolicEngine import org.utbot.framework.TestSelectionStrategyType import org.utbot.framework.UtSettings import org.utbot.framework.UtSettings.checkSolverTimeoutMillis @@ -34,85 +47,58 @@ import java.util.* import kotlin.coroutines.cancellation.CancellationException import kotlin.math.min import kotlin.reflect.KCallable -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.flattenConcat -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.yield -import mu.KotlinLogging -import org.utbot.engine.UtBotSymbolicEngine - -object TestCaseGenerator { - private val logger = KotlinLogging.logger {} - private val timeoutLogger = KotlinLogging.logger(logger.name + ".timeout") +/** + * Generates test cases: one by one or a whole set for the method under test. + * + * Note: the instantiating of [TestCaseGenerator] may take some time, + * because it requires initializing Soot for the current [buildDir] and [classpath]. + */ +open class TestCaseGenerator( + private val buildDir: Path, + private val classpath: String?, + private val dependencyPaths: String, + val engineActions: MutableList<(UtBotSymbolicEngine) -> Unit> = mutableListOf(), + val isCanceled: () -> Boolean = { false }, +) { + private val logger: KLogger = KotlinLogging.logger {} + private val timeoutLogger: KLogger = KotlinLogging.logger(logger.name + ".timeout") - lateinit var engineActions: MutableList<(UtBotSymbolicEngine) -> Unit> - lateinit var isCanceled: () -> Boolean - - //properties to save time on soot initialization - private var previousBuildDir: Path? = null - private var previousClasspath: String? = null - private var previousTimestamp: Long? = null - private var dependencyPaths: String = "" - - fun init( - buildDir: Path, - classpath: String?, - dependencyPaths: String, - engineActions: MutableList<(UtBotSymbolicEngine) -> Unit> = mutableListOf(), - isCanceled: () -> Boolean = { false }, - ) { - this.isCanceled = isCanceled - this.engineActions = engineActions - if (isCanceled()) return + private val classpathForEngine: String + get() = buildDir.toString() + (classpath?.let { File.pathSeparator + it } ?: "") - checkFrameworkDependencies(dependencyPaths) + init { + if (!isCanceled()) { + checkFrameworkDependencies(dependencyPaths) - logger.trace("Initializing ${this.javaClass.name} with buildDir = $buildDir, classpath = $classpath") + logger.trace("Initializing ${this.javaClass.name} with buildDir = $buildDir, classpath = $classpath") - //optimization: maxLastModifiedRecursivelyMillis can take time - val timestamp = if (UtSettings.classfilesCanChange) maxLastModifiedRecursivelyMillis(buildDir, classpath) else 0 - if (buildDir == previousBuildDir && classpath == previousClasspath && timestamp == previousTimestamp) { - logger.info { "Ignoring soot initialization because parameters are the same as on previous initialization" } - return - } - if (disableCoroutinesDebug) { - System.setProperty(kotlinx.coroutines.DEBUG_PROPERTY_NAME, kotlinx.coroutines.DEBUG_PROPERTY_VALUE_OFF) - } + if (disableCoroutinesDebug) { + System.setProperty(kotlinx.coroutines.DEBUG_PROPERTY_NAME, kotlinx.coroutines.DEBUG_PROPERTY_VALUE_OFF) + } - timeoutLogger.trace().bracket("Soot initialization") { - runSoot(buildDir, classpath) - } + timeoutLogger.trace().bracket("Soot initialization") { + runSoot(buildDir, classpath) + } - previousBuildDir = buildDir - previousClasspath = classpath - previousTimestamp = timestamp - this.dependencyPaths = dependencyPaths - - //warmup - if (warmupConcreteExecution) { - ConcreteExecutor( - UtExecutionInstrumentation, - classpathForEngine, - dependencyPaths - ).apply { - classLoader = utContext.classLoader - withUtContext(UtContext(Warmup::class.java.classLoader)) { - runBlocking { - constructExecutionsForWarmup().forEach { (method, data) -> - executeAsync(method, emptyArray(), data) + //warmup + if (warmupConcreteExecution) { + ConcreteExecutor( + UtExecutionInstrumentation, + classpathForEngine, + dependencyPaths + ).apply { + classLoader = utContext.classLoader + withUtContext(UtContext(Warmup::class.java.classLoader)) { + runBlocking { + constructExecutionsForWarmup().forEach { (method, data) -> + executeAsync(method, emptyArray(), data) + } } } + warmup() } - warmup() } } } @@ -254,18 +240,6 @@ object TestCaseGenerator { ) } - private val classpathForEngine: String - get() = previousBuildDir!!.toString() + (previousClasspath?.let { File.pathSeparator + it } ?: "") - - private fun maxLastModifiedRecursivelyMillis(buildDir: Path, classpath: String?): Long { - val paths = mutableListOf() - paths += buildDir.toFile() - if (classpath != null) { - paths += classpath.split(File.pathSeparatorChar).map { File(it) } - } - return FileUtil.maxLastModifiedRecursivelyMillis(paths) - } - private fun createSymbolicEngine( controller: EngineController, method: UtMethod<*>, diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/GenerateTestsAndSarifReportFacade.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/GenerateTestsAndSarifReportFacade.kt index 990b9430ac..725af793e8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/GenerateTestsAndSarifReportFacade.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/GenerateTestsAndSarifReportFacade.kt @@ -9,7 +9,6 @@ import org.utbot.sarif.SarifReport import org.utbot.sarif.SourceFindingStrategy import org.utbot.summary.summarize import java.io.File -import java.net.URLClassLoader import java.nio.file.Path /** @@ -17,21 +16,15 @@ import java.nio.file.Path * Stores common logic between gradle and maven plugins. */ class GenerateTestsAndSarifReportFacade( - val sarifProperties: SarifExtensionProvider, - val sourceFindingStrategy: SourceFindingStrategy + private val sarifProperties: SarifExtensionProvider, + private val sourceFindingStrategy: SourceFindingStrategy, + private val testCaseGenerator: TestCaseGenerator, ) { - /** * Generates tests and a SARIF report for the class [targetClass]. * Requires withUtContext() { ... }. */ - fun generateForClass( - targetClass: TargetClassWrapper, - workingDirectory: Path, - runtimeClasspath: String - ) { - initializeEngine(runtimeClasspath, workingDirectory) - + fun generateForClass(targetClass: TargetClassWrapper, workingDirectory: Path) { val testSets = generateTestSets(targetClass, workingDirectory) val testClassBody = generateTestCode(targetClass, testSets) targetClass.testsCodeFile.writeText(testClassBody) @@ -57,46 +50,35 @@ class GenerateTestsAndSarifReportFacade( } } - // internal - - private val dependencyPaths by lazy { - val thisClassLoader = this::class.java.classLoader as URLClassLoader - thisClassLoader.urLs.joinToString(File.pathSeparator) { it.path } - } - - private fun initializeEngine(classPath: String, workingDirectory: Path) { - TestCaseGenerator.init(workingDirectory, classPath, dependencyPaths) - } - private fun generateTestSets(targetClass: TargetClassWrapper, workingDirectory: Path): List = - TestCaseGenerator.generate( - targetClass.targetMethods, - sarifProperties.mockStrategy, - sarifProperties.classesToMockAlways, - sarifProperties.generationTimeout - ).map { - it.summarize(targetClass.sourceCodeFile, workingDirectory) - } + testCaseGenerator + .generate( + targetClass.targetMethods, + sarifProperties.mockStrategy, + sarifProperties.classesToMockAlways, + sarifProperties.generationTimeout + ).map { + it.summarize(targetClass.sourceCodeFile, workingDirectory) + } private fun generateTestCode(targetClass: TargetClassWrapper, testSets: List): String = initializeCodeGenerator(targetClass) .generateAsString(testSets, targetClass.testsCodeFile.nameWithoutExtension) - private fun initializeCodeGenerator(targetClass: TargetClassWrapper) = - CodeGenerator().apply { - val isNoStaticMocking = sarifProperties.staticsMocking is NoStaticMocking - val isForceStaticMocking = sarifProperties.forceStaticMocking == ForceStaticMocking.FORCE + private fun initializeCodeGenerator(targetClass: TargetClassWrapper): CodeGenerator { + val isNoStaticMocking = sarifProperties.staticsMocking is NoStaticMocking + val isForceStaticMocking = sarifProperties.forceStaticMocking == ForceStaticMocking.FORCE - init( - classUnderTest = targetClass.classUnderTest.java, - testFramework = sarifProperties.testFramework, - mockFramework = sarifProperties.mockFramework, - staticsMocking = sarifProperties.staticsMocking, - forceStaticMocking = sarifProperties.forceStaticMocking, - generateWarningsForStaticMocking = isNoStaticMocking && isForceStaticMocking, - codegenLanguage = sarifProperties.codegenLanguage - ) - } + return CodeGenerator( + classUnderTest = targetClass.classUnderTest.java, + testFramework = sarifProperties.testFramework, + mockFramework = sarifProperties.mockFramework, + staticsMocking = sarifProperties.staticsMocking, + forceStaticMocking = sarifProperties.forceStaticMocking, + generateWarningsForStaticMocking = isNoStaticMocking && isForceStaticMocking, + codegenLanguage = sarifProperties.codegenLanguage + ) + } /** * Creates a SARIF report for the class [targetClass]. diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/CodeGenerationIntegrationTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/CodeGenerationIntegrationTest.kt index 90f25d73bf..597e4ff9b7 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/CodeGenerationIntegrationTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/CodeGenerationIntegrationTest.kt @@ -29,6 +29,7 @@ import org.junit.jupiter.api.extension.ExtensionContext import org.junit.jupiter.api.fail import org.junit.jupiter.engine.descriptor.ClassTestDescriptor import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor +import java.nio.file.Path @TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestMethodOrder(MethodOrderer.OrderAnnotation::class) @@ -173,6 +174,10 @@ abstract class CodeGenerationIntegrationTest( private val logger = KotlinLogging.logger { } + @JvmStatic + protected val testCaseGeneratorCache = mutableMapOf() + data class BuildInfo(val buildDir: Path, val dependencyPath: String?) + private fun getTestPackageSize(packageName: String): Int = // filter all not disabled tests classes allRunningTestClasses diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/TestSpecificTestCaseGenerator.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/TestSpecificTestCaseGenerator.kt index 1c6219e555..283ee86d8a 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/TestSpecificTestCaseGenerator.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/TestSpecificTestCaseGenerator.kt @@ -5,6 +5,7 @@ import mu.KotlinLogging import org.utbot.common.runBlockingWithCancellationPredicate import org.utbot.common.runIgnoringCancellationException import org.utbot.engine.EngineController +import org.utbot.engine.Mocker import org.utbot.engine.UtBotSymbolicEngine import org.utbot.framework.UtSettings import org.utbot.framework.plugin.api.MockStrategyApi @@ -13,6 +14,7 @@ import org.utbot.framework.plugin.api.UtError import org.utbot.framework.plugin.api.UtExecution import org.utbot.framework.plugin.api.UtMethod import org.utbot.framework.plugin.api.UtMethodTestSet +import org.utbot.framework.plugin.api.util.id import org.utbot.framework.util.jimpleBody import java.nio.file.Path @@ -20,36 +22,43 @@ import java.nio.file.Path * Special [UtMethodTestSet] generator for test methods that has a correct * wrapper for suspend function [TestCaseGenerator.generateAsync]. */ -object TestSpecificTestCaseGenerator { - private val logger = KotlinLogging.logger {} +class TestSpecificTestCaseGenerator( + buildDir: Path, + classpath: String?, + dependencyPaths: String, + engineActions: MutableList<(UtBotSymbolicEngine) -> Unit> = mutableListOf(), + isCanceled: () -> Boolean = { false }, +): TestCaseGenerator(buildDir, classpath, dependencyPaths, engineActions, isCanceled) { - fun init(buildDir: Path, - classpath: String?, - dependencyPaths: String, - engineActions: MutableList<(UtBotSymbolicEngine) -> Unit> = mutableListOf(), - isCanceled: () -> Boolean = { false }, - ) = TestCaseGenerator.init(buildDir, classpath, dependencyPaths, engineActions, isCanceled) + private val logger = KotlinLogging.logger {} fun generate(method: UtMethod<*>, mockStrategy: MockStrategyApi): UtMethodTestSet { - logger.trace { "UtSettings:${System.lineSeparator()}" + UtSettings.toString() } + if (isCanceled()) { + return UtMethodTestSet(method) + } - if (TestCaseGenerator.isCanceled()) return UtMethodTestSet(method) + logger.trace { "UtSettings:${System.lineSeparator()}" + UtSettings.toString() } val executions = mutableListOf() val errors = mutableMapOf() + val mockAlwaysDefaults = Mocker.javaDefaultClasses.mapTo(mutableSetOf()) { it.id } + val defaultTimeEstimator = ExecutionTimeEstimator(UtSettings.utBotGenerationTimeoutInMillis, 1) + runIgnoringCancellationException { - runBlockingWithCancellationPredicate(TestCaseGenerator.isCanceled) { - TestCaseGenerator.generateAsync(EngineController(), method, mockStrategy).collect { - when (it) { - is UtExecution -> executions += it - is UtError -> errors.merge(it.description, 1, Int::plus) + runBlockingWithCancellationPredicate(isCanceled) { + super + .generateAsync(EngineController(), method, mockStrategy, mockAlwaysDefaults, defaultTimeEstimator) + .collect { + when (it) { + is UtExecution -> executions += it + is UtError -> errors.merge(it.description, 1, Int::plus) + } } - } } } - val minimizedExecutions = TestCaseGenerator.minimizeExecutions(executions) + val minimizedExecutions = super.minimizeExecutions(executions) return UtMethodTestSet(method, minimizedExecutions, jimpleBody(method), errors) } } \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/UtModelTestCaseChecker.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/UtModelTestCaseChecker.kt index 40582b9214..101ff6735f 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/UtModelTestCaseChecker.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/UtModelTestCaseChecker.kt @@ -137,8 +137,18 @@ internal abstract class UtModelTestCaseChecker( buildDir = findPathToClassFiles(classLocation) previousClassLocation = classLocation } - TestSpecificTestCaseGenerator.init(buildDir, classpath = null, dependencyPaths = System.getProperty("java.class.path")) - return TestSpecificTestCaseGenerator.generate(method, mockStrategy) + + val buildInfo = CodeGenerationIntegrationTest.Companion.BuildInfo(buildDir, dependencyPath = null) + val testCaseGenerator = testCaseGeneratorCache + .getOrPut(buildInfo) { + TestSpecificTestCaseGenerator( + buildDir, + classpath = null, + dependencyPaths = System.getProperty("java.class.path"), + ) + } + + return testCaseGenerator.generate(method, mockStrategy) } protected inline fun UtExecutionResult.isException(): Boolean = exceptionOrNull() is T diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/UtValueTestCaseChecker.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/UtValueTestCaseChecker.kt index 4828aeaced..43957a73cf 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/UtValueTestCaseChecker.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/UtValueTestCaseChecker.kt @@ -2490,8 +2490,17 @@ abstract class UtValueTestCaseChecker( mockStrategy: MockStrategyApi, additionalDependenciesClassPath: String ): UtMethodTestSet { - TestSpecificTestCaseGenerator.init(buildDir, additionalDependenciesClassPath, System.getProperty("java.class.path")) - return TestSpecificTestCaseGenerator.generate(method, mockStrategy) + val buildInfo = CodeGenerationIntegrationTest.Companion.BuildInfo(buildDir, additionalDependenciesClassPath) + + val testCaseGenerator = testCaseGeneratorCache + .getOrPut(buildInfo) { + TestSpecificTestCaseGenerator( + buildDir, + additionalDependenciesClassPath, + System.getProperty("java.class.path") + ) + } + return testCaseGenerator.generate(method, mockStrategy) } fun executionsModel( @@ -2501,9 +2510,17 @@ abstract class UtValueTestCaseChecker( ): UtMethodTestSet { val additionalDependenciesClassPath = computeAdditionalDependenciesClasspathAndBuildDir(method, additionalDependencies) - TestSpecificTestCaseGenerator.init(buildDir, additionalDependenciesClassPath, System.getProperty("java.class.path")) withUtContext(UtContext(method.clazz.java.classLoader)) { - return TestSpecificTestCaseGenerator.generate(method, mockStrategy) + val buildInfo = CodeGenerationIntegrationTest.Companion.BuildInfo(buildDir, additionalDependenciesClassPath) + val testCaseGenerator = testCaseGeneratorCache + .getOrPut(buildInfo) { + TestSpecificTestCaseGenerator( + buildDir, + additionalDependenciesClassPath, + System.getProperty("java.class.path") + ) + } + return testCaseGenerator.generate(method, mockStrategy) } } @@ -2533,7 +2550,7 @@ abstract class UtValueTestCaseChecker( val substituteStatics: Boolean ) - data class MethodResult(val testSet: UtMethodTestSet, val coverage: Coverage) + data class MethodResult(val testCase: UtMethodTestSet, val coverage: Coverage) } @Suppress("UNCHECKED_CAST") diff --git a/utbot-framework/src/test/kotlin/org/utbot/framework/codegen/TestCodeGeneratorPipeline.kt b/utbot-framework/src/test/kotlin/org/utbot/framework/codegen/TestCodeGeneratorPipeline.kt index d74a2d62ea..5be2a6f34e 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/framework/codegen/TestCodeGeneratorPipeline.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/framework/codegen/TestCodeGeneratorPipeline.kt @@ -1,5 +1,7 @@ package org.utbot.framework.codegen +import mu.KotlinLogging +import org.junit.jupiter.api.Assertions.assertTrue import org.utbot.common.FileUtil import org.utbot.common.bracket import org.utbot.common.info @@ -10,15 +12,12 @@ import org.utbot.framework.codegen.model.CodeGenerator import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.MockFramework import org.utbot.framework.plugin.api.MockStrategyApi -import org.utbot.framework.plugin.api.TestCaseGenerator import org.utbot.framework.plugin.api.UtMethod import org.utbot.framework.plugin.api.UtMethodTestSet import org.utbot.framework.plugin.api.util.UtContext -import org.utbot.framework.plugin.api.util.withUtContext import org.utbot.framework.plugin.api.util.description +import org.utbot.framework.plugin.api.util.withUtContext import kotlin.reflect.KClass -import mu.KotlinLogging -import org.junit.jupiter.api.Assertions.assertTrue private val logger = KotlinLogging.logger {} @@ -206,21 +205,18 @@ class TestCodeGeneratorPipeline(private val testFrameworkConfiguration: TestFram val params = mutableMapOf, List>() val codeGenerator = with(testFrameworkConfiguration) { - CodeGenerator() - .apply { - init( - classUnderTest.java, - params = params, - testFramework = testFramework, - staticsMocking = staticsMocking, - forceStaticMocking = forceStaticMocking, - generateWarningsForStaticMocking = false, - codegenLanguage = codegenLanguage, - parameterizedTestSource = parametrizedTestSource, - runtimeExceptionTestsBehaviour = runtimeExceptionTestsBehaviour, - enableTestsTimeout = enableTestsTimeout - ) - } + CodeGenerator( + classUnderTest.java, + params = params, + testFramework = testFramework, + staticsMocking = staticsMocking, + forceStaticMocking = forceStaticMocking, + generateWarningsForStaticMocking = false, + codegenLanguage = codegenLanguage, + parameterizedTestSource = parametrizedTestSource, + runtimeExceptionTestsBehaviour = runtimeExceptionTestsBehaviour, + enableTestsTimeout = enableTestsTimeout + ) } val testClassCustomName = "${classUnderTest.java.simpleName}GeneratedTest" @@ -233,17 +229,6 @@ class TestCodeGeneratorPipeline(private val testFrameworkConfiguration: TestFram processResults(results, classesChecks) } - @Suppress("unused") - internal fun checkResults( - targetClasses: List>, - testSets: List = listOf(), - lastStage: Stage = TestExecution, - vararg checks: StageStatusCheck - ) { - val results = executeTestGenerationPipeline(targetClasses, testSets, lastStage) - processResults(results, results.map { it.classUnderTest to checks.toList() }) - } - private fun processResults( results: List, classesChecks: List, List>> @@ -271,29 +256,6 @@ class TestCodeGeneratorPipeline(private val testFrameworkConfiguration: TestFram } } - private fun executeTestGenerationPipeline( - targetClasses: List>, - testSets: List, - lastStage: Stage = TestExecution - ): List = targetClasses.map { - val buildDir = FileUtil.isolateClassFiles(it).toPath() - val classPath = System.getProperty("java.class.path") - val dependencyPath = System.getProperty("java.class.path") - TestCaseGenerator.init(buildDir, classPath, dependencyPath) - - val pipelineStages = runPipelinesStages( - listOf( - ClassPipeline( - StageContext(it, testSets, testSets.size), - StageStatusCheck(lastStage = lastStage, status = SUCCESS) - ) - ) - ) - - pipelineStages.singleOrNull() ?: error("A single result's expected, but got ${pipelineStages.size} instead") - } - - companion object { val CodegenLanguage.defaultCodegenPipeline: TestCodeGeneratorPipeline get() = TestCodeGeneratorPipeline( diff --git a/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/GenerateTestsAndSarifReportTask.kt b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/GenerateTestsAndSarifReportTask.kt index 44242d9606..9c2f3b0eb2 100644 --- a/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/GenerateTestsAndSarifReportTask.kt +++ b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/GenerateTestsAndSarifReportTask.kt @@ -5,6 +5,7 @@ import org.gradle.api.DefaultTask import org.gradle.api.tasks.TaskAction import org.utbot.common.bracket import org.utbot.common.debug +import org.utbot.framework.plugin.api.TestCaseGenerator import org.utbot.framework.plugin.api.util.UtContext import org.utbot.framework.plugin.api.util.withUtContext import org.utbot.framework.plugin.sarif.GenerateTestsAndSarifReportFacade @@ -13,6 +14,8 @@ import org.utbot.gradle.plugin.wrappers.GradleProjectWrapper import org.utbot.gradle.plugin.wrappers.SourceFindingStrategyGradle import org.utbot.gradle.plugin.wrappers.SourceSetWrapper import org.utbot.framework.plugin.sarif.TargetClassWrapper +import java.io.File +import java.net.URLClassLoader import javax.inject.Inject /** @@ -59,6 +62,11 @@ open class GenerateTestsAndSarifReportTask @Inject constructor( // overwriting the getLogger() function from the DefaultTask private val logger: KLogger = org.utbot.gradle.plugin.logger + private val dependencyPaths by lazy { + val thisClassLoader = this::class.java.classLoader as URLClassLoader + thisClassLoader.urLs.joinToString(File.pathSeparator) { it.path } + } + /** * Generates tests and a SARIF report for classes in the [gradleProject] and in all its child projects. */ @@ -77,8 +85,10 @@ open class GenerateTestsAndSarifReportTask @Inject constructor( private fun generateForSourceSet(sourceSet: SourceSetWrapper) { logger.debug().bracket("Generating tests for the '${sourceSet.sourceSet.name}' source set") { withUtContext(UtContext(sourceSet.classLoader)) { + val testCaseGenerator = + TestCaseGenerator(sourceSet.workingDirectory, sourceSet.runtimeClasspath, dependencyPaths) sourceSet.targetClasses.forEach { targetClass -> - generateForClass(sourceSet, targetClass) + generateForClass(sourceSet, targetClass, testCaseGenerator) } } } @@ -87,15 +97,15 @@ open class GenerateTestsAndSarifReportTask @Inject constructor( /** * Generates tests and a SARIF report for the class [targetClass]. */ - private fun generateForClass(sourceSet: SourceSetWrapper, targetClass: TargetClassWrapper) { + private fun generateForClass( + sourceSet: SourceSetWrapper, + targetClass: TargetClassWrapper, + testCaseGenerator: TestCaseGenerator, + ) { logger.debug().bracket("Generating tests for the $targetClass") { - val sourceFindingStrategy = - SourceFindingStrategyGradle(sourceSet, targetClass.testsCodeFile.path) - val generateTestsAndSarifReportFacade = - GenerateTestsAndSarifReportFacade(sarifProperties, sourceFindingStrategy) - generateTestsAndSarifReportFacade.generateForClass( - targetClass, sourceSet.workingDirectory, sourceSet.runtimeClasspath - ) + val sourceFindingStrategy = SourceFindingStrategyGradle(sourceSet, targetClass.testsCodeFile.path) + GenerateTestsAndSarifReportFacade(sarifProperties, sourceFindingStrategy, testCaseGenerator) + .generateForClass(targetClass, sourceSet.workingDirectory) } } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt index 2d77810dec..d37f6b4150 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt @@ -250,8 +250,7 @@ object CodeGenerationController { val params = findMethodParams(classUnderTest, selectedMethods) - val codeGenerator = CodeGenerator().apply { - init( + val codeGenerator = CodeGenerator( classUnderTest = classUnderTest.java, params = params.toMutableMap(), testFramework = testFramework, @@ -266,7 +265,6 @@ object CodeGenerationController { enableTestsTimeout = true, testClassPackageName = testClass.packageName ) - } val editor = CodeInsightUtil.positionCursorAtLBrace(testClass.project, file, testClass) //TODO: Use PsiDocumentManager.getInstance(model.project).getDocument(file) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt index 7a48219f75..00227ef822 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt @@ -132,6 +132,13 @@ object UtTestsDialogProcessor { var processedClasses = 0 val totalClasses = model.srcClasses.size + val testCaseGenerator = TestCaseGenerator( + Paths.get(buildDir), + classpath, + pluginJarsPath.joinToString(separator = File.pathSeparator), + isCanceled = { indicator.isCanceled } + ) + for (srcClass in model.srcClasses) { val methods = ReadAction.nonBlocking>> { val clazz = classLoader.loadClass(srcClass.qualifiedName).kotlin @@ -167,14 +174,6 @@ object UtTestsDialogProcessor { withSubstitutionCondition(shouldSubstituteStatics) { val mockFrameworkInstalled = model.mockFramework?.isInstalled ?: true - val testCaseGenerator = TestCaseGenerator.apply { - init( - Paths.get(buildDir), - classpath, - pluginJarsPath.joinToString(separator = File.pathSeparator), - ) { indicator.isCanceled } - } - val forceMockListener = if (!mockFrameworkInstalled) { ForceMockListener().apply { testCaseGenerator.engineActions.add { engine -> engine.attachMockListener(this) } diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/Contest.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/Contest.kt index 0b6f1e4729..4b563a31c4 100644 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/Contest.kt +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/Contest.kt @@ -119,10 +119,6 @@ fun main(args: Array) { withUtContext(context) { - - //soot initialization - TestCaseGenerator.init(classfileDir, classpathString, dependencyPath) - logger.info().bracket("warmup: kotlin reflection :: init") { prepareClass(ConcreteExecutorPool::class, "") prepareClass(Warmup::class, "") @@ -193,13 +189,12 @@ fun runGeneration( setOptions() //will not be executed in real contest logger.info().bracket("warmup: 1st optional soot initialization and executor warmup (not to be counted in time budget)") { - TestCaseGenerator.init(cut.classfileDir.toPath(), classpathString, dependencyPath) + TestCaseGenerator(cut.classfileDir.toPath(), classpathString, dependencyPath) } logger.info().bracket("warmup (first): kotlin reflection :: init") { prepareClass(ConcreteExecutorPool::class, "") prepareClass(Warmup::class, "") } - } //remaining budget @@ -214,15 +209,13 @@ fun runGeneration( val statsForClass = StatsForClass() - val codeGenerator = CodeGenerator().apply { - init( + val codeGenerator = CodeGenerator( cut.classId.jClass, testFramework = junitByVersion(junitVersion), staticsMocking = staticsMocking, forceStaticMocking = forceStaticMocking, generateWarningsForStaticMocking = false ) - } // Doesn't work /* val concreteExecutorForCoverage = @@ -248,10 +241,10 @@ fun runGeneration( // nothing to process further if (filteredMethods.isEmpty()) return@runBlocking statsForClass - val testCaseGenerator = TestCaseGenerator - logger.info().bracket("2nd optional soot initialization") { - testCaseGenerator.init(cut.classfileDir.toPath(), classpathString, dependencyPath) - } + val testCaseGenerator = + logger.info().bracket("2nd optional soot initialization") { + TestCaseGenerator(cut.classfileDir.toPath(), classpathString, dependencyPath) + } val engineJob = CoroutineScope(SupervisorJob() + newSingleThreadContext("SymbolicExecution") + currentContext ).launch { diff --git a/utbot-maven/src/main/kotlin/org/utbot/maven/plugin/GenerateTestsAndSarifReportMojo.kt b/utbot-maven/src/main/kotlin/org/utbot/maven/plugin/GenerateTestsAndSarifReportMojo.kt index bedc8b9c41..9b7208ebb6 100644 --- a/utbot-maven/src/main/kotlin/org/utbot/maven/plugin/GenerateTestsAndSarifReportMojo.kt +++ b/utbot-maven/src/main/kotlin/org/utbot/maven/plugin/GenerateTestsAndSarifReportMojo.kt @@ -9,6 +9,7 @@ import org.apache.maven.plugins.annotations.Parameter import org.apache.maven.project.MavenProject import org.utbot.common.bracket import org.utbot.common.debug +import org.utbot.framework.plugin.api.TestCaseGenerator import org.utbot.framework.plugin.api.util.UtContext import org.utbot.framework.plugin.api.util.withUtContext import org.utbot.framework.plugin.sarif.GenerateTestsAndSarifReportFacade @@ -17,6 +18,7 @@ import org.utbot.maven.plugin.extension.SarifMavenConfigurationProvider import org.utbot.maven.plugin.wrappers.MavenProjectWrapper import org.utbot.maven.plugin.wrappers.SourceFindingStrategyMaven import java.io.File +import java.net.URLClassLoader internal val logger = KotlinLogging.logger {} @@ -136,6 +138,11 @@ class GenerateTestsAndSarifReportMojo : AbstractMojo() { */ lateinit var rootMavenProjectWrapper: MavenProjectWrapper + private val dependencyPaths by lazy { + val thisClassLoader = this::class.java.classLoader as URLClassLoader + thisClassLoader.urLs.joinToString(File.pathSeparator) { it.path } + } + /** * Entry point: called when the user starts this maven task. */ @@ -166,8 +173,10 @@ class GenerateTestsAndSarifReportMojo : AbstractMojo() { private fun generateForProjectRecursively(mavenProjectWrapper: MavenProjectWrapper) { logger.debug().bracket("Generating tests for the '${mavenProjectWrapper.mavenProject.name}' source set") { withUtContext(UtContext(mavenProjectWrapper.classLoader)) { + val testCaseGenerator = + TestCaseGenerator(mavenProjectWrapper.workingDirectory, mavenProjectWrapper.runtimeClasspath, dependencyPaths) mavenProjectWrapper.targetClasses.forEach { targetClass -> - generateForClass(mavenProjectWrapper, targetClass) + generateForClass(mavenProjectWrapper, targetClass, testCaseGenerator) } } } @@ -179,15 +188,18 @@ class GenerateTestsAndSarifReportMojo : AbstractMojo() { /** * Generates tests and a SARIF report for the class [targetClass]. */ - private fun generateForClass(mavenProjectWrapper: MavenProjectWrapper, targetClass: TargetClassWrapper) { + private fun generateForClass( + mavenProjectWrapper: MavenProjectWrapper, + targetClass: TargetClassWrapper, + testCaseGenerator: TestCaseGenerator, + ) { logger.debug().bracket("Generating tests for the $targetClass") { val sourceFindingStrategy = SourceFindingStrategyMaven(mavenProjectWrapper, targetClass.testsCodeFile.path) val generateTestsAndSarifReportFacade = - GenerateTestsAndSarifReportFacade(sarifProperties, sourceFindingStrategy) - generateTestsAndSarifReportFacade.generateForClass( - targetClass, mavenProjectWrapper.workingDirectory, mavenProjectWrapper.runtimeClasspath - ) + GenerateTestsAndSarifReportFacade(sarifProperties, sourceFindingStrategy, testCaseGenerator) + generateTestsAndSarifReportFacade + .generateForClass(targetClass, mavenProjectWrapper.workingDirectory) } }