From b13c803df289a70a433c632d314d16fe1c0ccb23 Mon Sep 17 00:00:00 2001 From: Ivan Volkov <65076429+volivan239@users.noreply.github.com> Date: Fri, 30 Sep 2022 13:33:04 +0300 Subject: [PATCH] Fix build directory for classes from Kotlin files (#1019) --- .../utbot/cli/GenerateTestsAbstractCommand.kt | 2 +- .../org/utbot/external/api/UtBotJavaApi.kt | 4 +- .../framework/plugin/api/TestCaseGenerator.kt | 12 +- .../org/utbot/framework/util/SootUtils.kt | 174 ++++------ .../TestSpecificTestCaseGenerator.kt | 2 +- .../plugin/GenerateTestsAndSarifReportTask.kt | 2 +- .../generator/UtTestsDialogProcessor.kt | 319 +++++++++--------- .../main/kotlin/org/utbot/contest/Contest.kt | 4 +- 8 files changed, 242 insertions(+), 277 deletions(-) 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 d6dd1ddf40..fb4a300f32 100644 --- a/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsAbstractCommand.kt +++ b/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsAbstractCommand.kt @@ -197,7 +197,7 @@ abstract class GenerateTestsAbstractCommand(name: String, help: String) : UtSettings.treatOverflowAsError = treatOverflowAsError == TreatOverflowAsError.AS_ERROR return TestCaseGenerator( - workingDirectory, + listOf(workingDirectory), classPathNormalized, System.getProperty("java.class.path"), JdkInfoDefaultProvider().info 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 544f44c958..7cf9b8f3e5 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 @@ -113,7 +113,7 @@ object UtBotJavaApi { testSets.addAll(withUtContext(utContext) { val buildPath = FileUtil.isolateClassFiles(classUnderTest).toPath() - TestCaseGenerator(buildPath, classpath, dependencyClassPath, jdkInfo = JdkInfoDefaultProvider().info) + TestCaseGenerator(listOf(buildPath), classpath, dependencyClassPath, jdkInfo = JdkInfoDefaultProvider().info) .generate( methodsForAutomaticGeneration.map { it.methodToBeTestedFromUserInput.executableId @@ -173,7 +173,7 @@ object UtBotJavaApi { return withUtContext(UtContext(classUnderTest.classLoader)) { val buildPath = FileUtil.isolateClassFiles(classUnderTest).toPath() - TestCaseGenerator(buildPath, classpath, dependencyClassPath, jdkInfo = JdkInfoDefaultProvider().info) + TestCaseGenerator(listOf(buildPath), classpath, dependencyClassPath, jdkInfo = JdkInfoDefaultProvider().info) .generate( methodsForAutomaticGeneration.map { it.methodToBeTestedFromUserInput.executableId 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 689bed78b4..c63e49e733 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 @@ -59,14 +59,14 @@ import kotlin.system.measureTimeMillis * 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]. + * because it requires initializing Soot for the current [buildDirs] and [classpath]. * * @param jdkInfo specifies the JRE and the runtime library version used for analysing system classes and user's code. - * @param forceSootReload forces to reinitialize Soot even if the previous buildDir equals to [buildDir] and previous + * @param forceSootReload forces to reinitialize Soot even if the previous buildDirs equals to [buildDirs] and previous * classpath equals to [classpath]. This is the case for plugin scenario, as the source code may be modified. */ open class TestCaseGenerator( - private val buildDir: Path, + private val buildDirs: List, private val classpath: String?, private val dependencyPaths: String, private val jdkInfo: JdkInfo, @@ -78,13 +78,13 @@ open class TestCaseGenerator( private val timeoutLogger: KLogger = KotlinLogging.logger(logger.name + ".timeout") private val classpathForEngine: String - get() = buildDir.toString() + (classpath?.let { File.pathSeparator + it } ?: "") + get() = (buildDirs + listOfNotNull(classpath)).joinToString(File.pathSeparator) init { if (!isCanceled()) { checkFrameworkDependencies(dependencyPaths) - logger.trace("Initializing ${this.javaClass.name} with buildDir = $buildDir, classpath = $classpath") + logger.trace("Initializing ${this.javaClass.name} with buildDirs = ${buildDirs.joinToString(File.pathSeparator)}, classpath = $classpath") if (disableCoroutinesDebug) { @@ -92,7 +92,7 @@ open class TestCaseGenerator( } timeoutLogger.trace().bracket("Soot initialization") { - SootUtils.runSoot(buildDir, classpath, forceSootReload, jdkInfo) + SootUtils.runSoot(buildDirs, classpath, forceSootReload, jdkInfo) } //warmup diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt index 8a6a15f956..e92dadf74f 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt @@ -1,41 +1,7 @@ package org.utbot.framework.util -import org.utbot.api.mock.UtMock import org.utbot.common.FileUtil -import org.utbot.engine.UtNativeStringWrapper import org.utbot.engine.jimpleBody -import org.utbot.engine.overrides.Boolean -import org.utbot.engine.overrides.Byte -import org.utbot.engine.overrides.Character -import org.utbot.engine.overrides.Class -import org.utbot.engine.overrides.Integer -import org.utbot.engine.overrides.Long -import org.utbot.engine.overrides.PrintStream -import org.utbot.engine.overrides.Short -import org.utbot.engine.overrides.System -import org.utbot.engine.overrides.UtArrayMock -import org.utbot.engine.overrides.UtLogicMock -import org.utbot.engine.overrides.UtOverrideMock -import org.utbot.engine.overrides.collections.AbstractCollection -import org.utbot.engine.overrides.collections.AssociativeArray -import org.utbot.engine.overrides.collections.Collection -import org.utbot.engine.overrides.collections.List -import org.utbot.engine.overrides.collections.RangeModifiableUnlimitedArray -import org.utbot.engine.overrides.collections.UtArrayList -import org.utbot.engine.overrides.collections.UtGenericAssociative -import org.utbot.engine.overrides.collections.UtGenericStorage -import org.utbot.engine.overrides.collections.UtHashMap -import org.utbot.engine.overrides.collections.UtHashSet -import org.utbot.engine.overrides.collections.UtLinkedList -import org.utbot.engine.overrides.collections.UtLinkedListWithNullableCheck -import org.utbot.engine.overrides.collections.UtOptional -import org.utbot.engine.overrides.collections.UtOptionalDouble -import org.utbot.engine.overrides.collections.UtOptionalInt -import org.utbot.engine.overrides.collections.UtOptionalLong -import org.utbot.engine.overrides.stream.* -import org.utbot.engine.overrides.strings.UtString -import org.utbot.engine.overrides.strings.UtStringBuffer -import org.utbot.engine.overrides.strings.UtStringBuilder import org.utbot.engine.pureJavaSignature import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.services.JdkInfo @@ -56,40 +22,40 @@ object SootUtils { * * @param jdkInfo specifies the JRE and the runtime library version used for analysing system classes and user's * code. - * @param forceReload forces to reinitialize Soot even if the [previousBuildDir] equals to the class buildDir. + * @param forceReload forces to reinitialize Soot even if the [previousBuildDirs] equals to the class buildDir. */ - fun runSoot(clazz: java.lang.Class<*>, forceReload: kotlin.Boolean, jdkInfo: JdkInfo) { + fun runSoot(clazz: Class<*>, forceReload: Boolean, jdkInfo: JdkInfo) { val buildDir = FileUtil.locateClassPath(clazz) ?: FileUtil.isolateClassFiles(clazz) val buildDirPath = buildDir.toPath() - runSoot(buildDirPath, null, forceReload, jdkInfo) + runSoot(listOf(buildDirPath), null, forceReload, jdkInfo) } /** * @param jdkInfo specifies the JRE and the runtime library version used for analysing system classes and user's * code. - * @param forceReload forces to reinitialize Soot even if the [previousBuildDir] equals to [buildDirPath] and + * @param forceReload forces to reinitialize Soot even if the [previousBuildDirs] equals to [buildDirPaths] and * [previousClassPath] equals to [classPath]. */ - fun runSoot(buildDirPath: Path, classPath: String?, forceReload: kotlin.Boolean, jdkInfo: JdkInfo) { + fun runSoot(buildDirPaths: List, classPath: String?, forceReload: Boolean, jdkInfo: JdkInfo) { synchronized(this) { - if (buildDirPath != previousBuildDir || classPath != previousClassPath || forceReload) { - initSoot(buildDirPath, classPath, jdkInfo) - previousBuildDir = buildDirPath + if (buildDirPaths != previousBuildDirs || classPath != previousClassPath || forceReload) { + initSoot(buildDirPaths, classPath, jdkInfo) + previousBuildDirs = buildDirPaths previousClassPath = classPath } } } - private var previousBuildDir: Path? = null + private var previousBuildDirs: List? = null private var previousClassPath: String? = null } /** * Convert code to Jimple */ -private fun initSoot(buildDir: Path, classpath: String?, jdkInfo: JdkInfo) { +private fun initSoot(buildDirs: List, classpath: String?, jdkInfo: JdkInfo) { G.reset() val options = Options.v() @@ -105,7 +71,7 @@ private fun initSoot(buildDir: Path, classpath: String?, jdkInfo: JdkInfo) { + if (!classpath.isNullOrEmpty()) File.pathSeparator + "$classpath" else "" ) set_src_prec(Options.src_prec_only_class) - set_process_dir(listOf("$buildDir")) + set_process_dir(buildDirs.map { it.toString() }) set_keep_line_number(true) set_ignore_classpath_errors(true) // gradle/build/resources/main does not exists, but it's not a problem set_output_format(Options.output_format_jimple) @@ -141,69 +107,69 @@ fun jimpleBody(method: ExecutableId): JimpleBody = method.sootMethod.jimpleBody() -private fun addBasicClasses(vararg classes: java.lang.Class<*>) { +private fun addBasicClasses(vararg classes: Class<*>) { classes.forEach { Scene.v().addBasicClass(it.name, SootClass.BODIES) } } private val classesToLoad = arrayOf( - AbstractCollection::class, - UtMock::class, - UtOverrideMock::class, - UtLogicMock::class, - UtArrayMock::class, - Boolean::class, - Byte::class, - Character::class, - Class::class, - Integer::class, - Long::class, - Short::class, - System::class, - UtOptional::class, - UtOptionalInt::class, - UtOptionalLong::class, - UtOptionalDouble::class, - UtArrayList::class, - UtArrayList.UtArrayListIterator::class, - UtLinkedList::class, - UtLinkedListWithNullableCheck::class, - UtLinkedList.UtLinkedListIterator::class, - UtLinkedList.ReverseIteratorWrapper::class, - UtHashSet::class, - UtHashSet.UtHashSetIterator::class, - UtHashMap::class, - UtHashMap.Entry::class, - UtHashMap.LinkedEntryIterator::class, - UtHashMap.LinkedEntrySet::class, - UtHashMap.LinkedHashIterator::class, - UtHashMap.LinkedKeyIterator::class, - UtHashMap.LinkedKeySet::class, - UtHashMap.LinkedValueIterator::class, - UtHashMap.LinkedValues::class, - RangeModifiableUnlimitedArray::class, - AssociativeArray::class, - UtGenericStorage::class, - UtGenericAssociative::class, - PrintStream::class, - UtNativeStringWrapper::class, - UtString::class, - UtStringBuilder::class, - UtStringBuffer::class, - Stream::class, - Arrays::class, - Collection::class, - List::class, - UtStream::class, - UtIntStream::class, - UtLongStream::class, - UtDoubleStream::class, - UtStream.UtStreamIterator::class, - UtIntStream.UtIntStreamIterator::class, - UtLongStream.UtLongStreamIterator::class, - UtDoubleStream.UtDoubleStreamIterator::class, - IntStream::class, - LongStream::class, - DoubleStream::class, + org.utbot.engine.overrides.collections.AbstractCollection::class, + org.utbot.api.mock.UtMock::class, + org.utbot.engine.overrides.UtOverrideMock::class, + org.utbot.engine.overrides.UtLogicMock::class, + org.utbot.engine.overrides.UtArrayMock::class, + org.utbot.engine.overrides.Boolean::class, + org.utbot.engine.overrides.Byte::class, + org.utbot.engine.overrides.Character::class, + org.utbot.engine.overrides.Class::class, + org.utbot.engine.overrides.Integer::class, + org.utbot.engine.overrides.Long::class, + org.utbot.engine.overrides.Short::class, + org.utbot.engine.overrides.System::class, + org.utbot.engine.overrides.collections.UtOptional::class, + org.utbot.engine.overrides.collections.UtOptionalInt::class, + org.utbot.engine.overrides.collections.UtOptionalLong::class, + org.utbot.engine.overrides.collections.UtOptionalDouble::class, + org.utbot.engine.overrides.collections.UtArrayList::class, + org.utbot.engine.overrides.collections.UtArrayList.UtArrayListIterator::class, + org.utbot.engine.overrides.collections.UtLinkedList::class, + org.utbot.engine.overrides.collections.UtLinkedListWithNullableCheck::class, + org.utbot.engine.overrides.collections.UtLinkedList.UtLinkedListIterator::class, + org.utbot.engine.overrides.collections.UtLinkedList.ReverseIteratorWrapper::class, + org.utbot.engine.overrides.collections.UtHashSet::class, + org.utbot.engine.overrides.collections.UtHashSet.UtHashSetIterator::class, + org.utbot.engine.overrides.collections.UtHashMap::class, + org.utbot.engine.overrides.collections.UtHashMap.Entry::class, + org.utbot.engine.overrides.collections.UtHashMap.LinkedEntryIterator::class, + org.utbot.engine.overrides.collections.UtHashMap.LinkedEntrySet::class, + org.utbot.engine.overrides.collections.UtHashMap.LinkedHashIterator::class, + org.utbot.engine.overrides.collections.UtHashMap.LinkedKeyIterator::class, + org.utbot.engine.overrides.collections.UtHashMap.LinkedKeySet::class, + org.utbot.engine.overrides.collections.UtHashMap.LinkedValueIterator::class, + org.utbot.engine.overrides.collections.UtHashMap.LinkedValues::class, + org.utbot.engine.overrides.collections.RangeModifiableUnlimitedArray::class, + org.utbot.engine.overrides.collections.AssociativeArray::class, + org.utbot.engine.overrides.collections.UtGenericStorage::class, + org.utbot.engine.overrides.collections.UtGenericAssociative::class, + org.utbot.engine.overrides.PrintStream::class, + org.utbot.engine.UtNativeStringWrapper::class, + org.utbot.engine.overrides.strings.UtString::class, + org.utbot.engine.overrides.strings.UtStringBuilder::class, + org.utbot.engine.overrides.strings.UtStringBuffer::class, + org.utbot.engine.overrides.stream.Stream::class, + org.utbot.engine.overrides.stream.Arrays::class, + org.utbot.engine.overrides.collections.Collection::class, + org.utbot.engine.overrides.collections.List::class, + org.utbot.engine.overrides.stream.UtStream::class, + org.utbot.engine.overrides.stream.UtIntStream::class, + org.utbot.engine.overrides.stream.UtLongStream::class, + org.utbot.engine.overrides.stream.UtDoubleStream::class, + org.utbot.engine.overrides.stream.UtStream.UtStreamIterator::class, + org.utbot.engine.overrides.stream.UtIntStream.UtIntStreamIterator::class, + org.utbot.engine.overrides.stream.UtLongStream.UtLongStreamIterator::class, + org.utbot.engine.overrides.stream.UtDoubleStream.UtDoubleStreamIterator::class, + org.utbot.engine.overrides.stream.IntStream::class, + org.utbot.engine.overrides.stream.LongStream::class, + org.utbot.engine.overrides.stream.DoubleStream::class, ).map { it.java }.toTypedArray() \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/TestSpecificTestCaseGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/TestSpecificTestCaseGenerator.kt index cdee49d15b..a15080bddd 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/TestSpecificTestCaseGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/TestSpecificTestCaseGenerator.kt @@ -36,7 +36,7 @@ class TestSpecificTestCaseGenerator( engineActions: MutableList<(UtBotSymbolicEngine) -> Unit> = mutableListOf(), isCanceled: () -> Boolean = { false }, ): TestCaseGenerator( - buildDir, + listOf(buildDir), classpath, dependencyPaths, JdkInfoDefaultProvider().info, 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 0352f8b890..2f7bac2262 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 @@ -90,7 +90,7 @@ open class GenerateTestsAndSarifReportTask @Inject constructor( withUtContext(UtContext(sourceSet.classLoader)) { val testCaseGenerator = TestCaseGenerator( - sourceSet.workingDirectory, + listOf(sourceSet.workingDirectory), sourceSet.runtimeClasspath, dependencyPaths, JdkInfoDefaultProvider().info 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 c5e2f6e84f..9aad3aa474 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 @@ -1,12 +1,8 @@ package org.utbot.intellij.plugin.generator -import com.intellij.compiler.impl.CompositeScope -import com.intellij.compiler.impl.OneProjectItemCompileScope import com.intellij.openapi.application.PathManager import com.intellij.openapi.application.ReadAction import com.intellij.openapi.application.invokeLater -import com.intellij.openapi.compiler.CompileContext -import com.intellij.openapi.compiler.CompilerManager import com.intellij.openapi.compiler.CompilerPaths import com.intellij.openapi.components.service import com.intellij.openapi.module.Module @@ -21,7 +17,10 @@ import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.PsiClass import com.intellij.psi.PsiMethod import com.intellij.refactoring.util.classMembers.MemberInfo +import com.intellij.task.ProjectTaskManager import com.intellij.util.concurrency.AppExecutorUtil +import com.intellij.util.containers.nullize +import com.intellij.util.io.exists import mu.KotlinLogging import org.jetbrains.kotlin.idea.util.module import org.utbot.analytics.EngineAnalyticsContext @@ -116,172 +115,167 @@ object UtTestsDialogProcessor { } private fun createTests(project: Project, model: GenerateTestsModel) { - CompilerManager.getInstance(project) - .make( - // Compile only chosen classes and their dependencies before generation. - CompositeScope( - model.srcClasses.map { OneProjectItemCompileScope(project, it.containingFile.virtualFile) } - .toTypedArray() - ) - ) { aborted: Boolean, errors: Int, _: Int, _: CompileContext -> - if (!aborted && errors == 0) { - (object : Task.Backgroundable(project, "Generate tests") { - - override fun run(indicator: ProgressIndicator) { - val startTime = System.currentTimeMillis() - val secondsTimeout = TimeUnit.MILLISECONDS.toSeconds(model.timeout) - val totalTimeout = model.timeout * model.srcClasses.size - - indicator.isIndeterminate = false - indicator.text = "Generate tests: read classes" - - val timerHandler = AppExecutorUtil.getAppScheduledExecutorService().scheduleWithFixedDelay({ - indicator.fraction = (System.currentTimeMillis() - startTime).toDouble() / totalTimeout - }, 0, 500, TimeUnit.MILLISECONDS) - - val buildPaths = ReadAction - .nonBlocking { findPaths(model.srcClasses) } - .executeSynchronously() - ?: return - - val (buildDir, classpath, classpathList, pluginJarsPath) = buildPaths - val classLoader = urlClassLoader(listOf(buildDir) + classpathList) - val context = UtContext(classLoader) - - val testSetsByClass = mutableMapOf>() - val psi2KClass = mutableMapOf>() - var processedClasses = 0 - val totalClasses = model.srcClasses.size - - configureML() - - val testCaseGenerator = TestCaseGenerator( - Paths.get(buildDir), - classpath, - pluginJarsPath.joinToString(separator = File.pathSeparator), - JdkInfoService.provide(), - isCanceled = { indicator.isCanceled }) - - for (srcClass in model.srcClasses) { - val (methods, className) = ReadAction.nonBlocking, String?>> { - val canonicalName = srcClass.canonicalName - val clazz = classLoader.loadClass(canonicalName).kotlin - psi2KClass[srcClass] = clazz - - val srcMethods = if (model.extractMembersFromSrcClasses) { - val chosenMethods = model.selectedMembers.filter { it.member is PsiMethod } - val chosenNestedClasses = model.selectedMembers.mapNotNull { it.member as? PsiClass } - chosenMethods + chosenNestedClasses.flatMap { - it.extractClassMethodsIncludingNested(false) - } - } else { - srcClass.extractClassMethodsIncludingNested(false) - } - DumbService.getInstance(project).runReadActionInSmartMode(Computable { - clazz.allNestedClasses.flatMap { - findMethodsInClassMatchingSelected(it, srcMethods) - } - }) to srcClass.name - }.executeSynchronously() - - if (methods.isEmpty()) { - logger.error { "No methods matching selected found in class $className." } - continue + val promise = ProjectTaskManager.getInstance(project).compile( + // Compile only chosen classes and their dependencies before generation. + *model.srcClasses.map { it.containingFile.virtualFile }.toTypedArray() + ) + promise.onSuccess { + (object : Task.Backgroundable(project, "Generate tests") { + + override fun run(indicator: ProgressIndicator) { + val startTime = System.currentTimeMillis() + val secondsTimeout = TimeUnit.MILLISECONDS.toSeconds(model.timeout) + val totalTimeout = model.timeout * model.srcClasses.size + + indicator.isIndeterminate = false + indicator.text = "Generate tests: read classes" + + val timerHandler = AppExecutorUtil.getAppScheduledExecutorService().scheduleWithFixedDelay({ + indicator.fraction = (System.currentTimeMillis() - startTime).toDouble() / totalTimeout + }, 0, 500, TimeUnit.MILLISECONDS) + + val buildPaths = ReadAction + .nonBlocking { findPaths(model.srcClasses) } + .executeSynchronously() + ?: return + + val (buildDirs, classpath, classpathList, pluginJarsPath) = buildPaths + val classLoader = urlClassLoader(buildDirs + classpathList) + val context = UtContext(classLoader) + + val testSetsByClass = mutableMapOf>() + val psi2KClass = mutableMapOf>() + var processedClasses = 0 + val totalClasses = model.srcClasses.size + + configureML() + + val testCaseGenerator = TestCaseGenerator( + buildDirs.map { pathStr -> Paths.get(pathStr) }, + classpath, + pluginJarsPath.joinToString(separator = File.pathSeparator), + JdkInfoService.provide(), + isCanceled = { indicator.isCanceled }) + + for (srcClass in model.srcClasses) { + val (methods, className) = ReadAction.nonBlocking, String?>> { + val canonicalName = srcClass.canonicalName + val clazz = classLoader.loadClass(canonicalName).kotlin + psi2KClass[srcClass] = clazz + + val srcMethods = if (model.extractMembersFromSrcClasses) { + val chosenMethods = model.selectedMembers.filter { it.member is PsiMethod } + val chosenNestedClasses = model.selectedMembers.mapNotNull { it.member as? PsiClass } + chosenMethods + chosenNestedClasses.flatMap { + it.extractClassMethodsIncludingNested(false) } - - indicator.text = "Generate test cases for class $className" - if (totalClasses > 1) { - indicator.fraction = - indicator.fraction.coerceAtLeast(0.9 * processedClasses / totalClasses) + } else { + srcClass.extractClassMethodsIncludingNested(false) + } + DumbService.getInstance(project).runReadActionInSmartMode(Computable { + clazz.allNestedClasses.flatMap { + findMethodsInClassMatchingSelected(it, srcMethods) } + }) to srcClass.name + }.executeSynchronously() - // set timeout for concrete execution and for generated tests - UtSettings.concreteExecutionTimeoutInChildProcess = model.hangingTestsTimeout.timeoutMs - - UtSettings.useCustomJavaDocTags = model.commentStyle == JavaDocCommentStyle.CUSTOM_JAVADOC_TAGS - - val searchDirectory = ReadAction - .nonBlocking { - project.basePath?.let { Paths.get(it) } - ?: Paths.get(srcClass.containingFile.virtualFile.parent.path) - } - .executeSynchronously() - - withStaticsSubstitutionRequired(true) { - val mockFrameworkInstalled = model.mockFramework?.isInstalled ?: true - - if (!mockFrameworkInstalled) { - ForceMockListener.create(testCaseGenerator, model.conflictTriggers) - } - - if (!model.staticsMocking.isConfigured) { - ForceStaticMockListener.create(testCaseGenerator, model.conflictTriggers) - } - - val notEmptyCases = runCatching { - withUtContext(context) { - testCaseGenerator - .generate( - methods, - model.mockStrategy, - model.chosenClassesToMockAlways, - model.timeout, - generate = testFlow { - generationTimeout = model.timeout - isSymbolicEngineEnabled = true - isFuzzingEnabled = UtSettings.useFuzzing - fuzzingValue = project.service().fuzzingValue - } - ) - .map { it.summarize(searchDirectory) } - .filterNot { it.executions.isEmpty() && it.errors.isEmpty() } - } - }.getOrDefault(listOf()) - - if (notEmptyCases.isEmpty()) { - if (model.srcClasses.size > 1) { - logger.error { "Failed to generate any tests cases for class $className" } - } else { - showErrorDialogLater( - model.project, - errorMessage(className, secondsTimeout), - title = "Failed to generate unit tests for class $className" - ) - } - } else { - testSetsByClass[srcClass] = notEmptyCases - } - - timerHandler.cancel(true) - } - processedClasses++ + if (methods.isEmpty()) { + logger.error { "No methods matching selected found in class $className." } + continue + } + + indicator.text = "Generate test cases for class $className" + if (totalClasses > 1) { + indicator.fraction = + indicator.fraction.coerceAtLeast(0.9 * processedClasses / totalClasses) + } + + // set timeout for concrete execution and for generated tests + UtSettings.concreteExecutionTimeoutInChildProcess = model.hangingTestsTimeout.timeoutMs + + UtSettings.useCustomJavaDocTags = model.commentStyle == JavaDocCommentStyle.CUSTOM_JAVADOC_TAGS + + val searchDirectory = ReadAction + .nonBlocking { + project.basePath?.let { Paths.get(it) } + ?: Paths.get(srcClass.containingFile.virtualFile.parent.path) } + .executeSynchronously() - if (processedClasses == 0) { - invokeLater { - Messages.showInfoMessage( - model.project, - "No methods for test generation were found among selected items", - "No methods found" - ) - } - return + withStaticsSubstitutionRequired(true) { + val mockFrameworkInstalled = model.mockFramework?.isInstalled ?: true + + if (!mockFrameworkInstalled) { + ForceMockListener.create(testCaseGenerator, model.conflictTriggers) } - indicator.fraction = indicator.fraction.coerceAtLeast(0.9) - indicator.text = "Generate code for tests" - // Commented out to generate tests for collected executions even if action was canceled. - // indicator.checkCanceled() + if (!model.staticsMocking.isConfigured) { + ForceStaticMockListener.create(testCaseGenerator, model.conflictTriggers) + } - invokeLater { + val notEmptyCases = runCatching { withUtContext(context) { - generateTests(model, testSetsByClass, psi2KClass) + testCaseGenerator + .generate( + methods, + model.mockStrategy, + model.chosenClassesToMockAlways, + model.timeout, + generate = testFlow { + generationTimeout = model.timeout + isSymbolicEngineEnabled = true + isFuzzingEnabled = UtSettings.useFuzzing + fuzzingValue = project.service().fuzzingValue + } + ) + .map { it.summarize(searchDirectory) } + .filterNot { it.executions.isEmpty() && it.errors.isEmpty() } } + }.getOrDefault(listOf()) + + if (notEmptyCases.isEmpty()) { + if (model.srcClasses.size > 1) { + logger.error { "Failed to generate any tests cases for class $className" } + } else { + showErrorDialogLater( + model.project, + errorMessage(className, secondsTimeout), + title = "Failed to generate unit tests for class $className" + ) + } + } else { + testSetsByClass[srcClass] = notEmptyCases } + + timerHandler.cancel(true) + } + processedClasses++ + } + + if (processedClasses == 0) { + invokeLater { + Messages.showInfoMessage( + model.project, + "No methods for test generation were found among selected items", + "No methods found" + ) } - }).queue() + return + } + + indicator.fraction = indicator.fraction.coerceAtLeast(0.9) + indicator.text = "Generate code for tests" + // Commented out to generate tests for collected executions even if action was canceled. + // indicator.checkCanceled() + + invokeLater { + withUtContext(context) { + generateTests(model, testSetsByClass, psi2KClass) + } + } } - } + }).queue() + } } private val PsiClass.canonicalName: String @@ -372,7 +366,12 @@ object UtTestsDialogProcessor { private fun findPaths(srcClasses: Set): BuildPaths? { val srcModule = findSrcModule(srcClasses) - val buildDir = CompilerPaths.getModuleOutputPath(srcModule, false) ?: return null + + val buildDirs = CompilerPaths.getOutputPaths(arrayOf(srcModule)) + .toList() + .filter { Paths.get(it).exists() } + .nullize() ?: return null + val pathsList = OrderEnumerator.orderEntries(srcModule).recursively().pathsList val (classpath, classpathList) = if (IntelliJApiHelper.isAndroidStudio()) { @@ -390,11 +389,11 @@ object UtTestsDialogProcessor { } val pluginJarsPath = Paths.get(PathManager.getPluginsPath(), "utbot-intellij", "lib").toFile().listFiles() ?: error("Can't find plugin folder.") - return BuildPaths(buildDir, classpath, classpathList, pluginJarsPath.map { it.path }) + return BuildPaths(buildDirs, classpath, classpathList, pluginJarsPath.map { it.path }) } data class BuildPaths( - val buildDir: String, + val buildDirs: List, val classpath: String, val classpathList: List, val pluginJarsPath: List 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 65e32871e9..2f91a7516b 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 @@ -189,7 +189,7 @@ 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(cut.classfileDir.toPath(), classpathString, dependencyPath, JdkInfoService.provide()) + TestCaseGenerator(listOf(cut.classfileDir.toPath()), classpathString, dependencyPath, JdkInfoService.provide()) } logger.info().bracket("warmup (first): kotlin reflection :: init") { prepareClass(ConcreteExecutorPool::class.java, "") @@ -230,7 +230,7 @@ fun runGeneration( val testCaseGenerator = logger.info().bracket("2nd optional soot initialization") { - TestCaseGenerator(cut.classfileDir.toPath(), classpathString, dependencyPath, JdkInfoService.provide()) + TestCaseGenerator(listOf(cut.classfileDir.toPath()), classpathString, dependencyPath, JdkInfoService.provide()) }