From db479385d06f6cbf161a6e7d6e45cdc62961ea1d Mon Sep 17 00:00:00 2001 From: Iurii Zaitsev Date: Wed, 18 Sep 2024 16:44:34 +0200 Subject: [PATCH 1/7] Kotlin clss support without coverage --- .../testspark/appstarter/TestSparkStarter.kt | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt b/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt index 59005f1a5..71e911890 100644 --- a/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt +++ b/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt @@ -14,15 +14,18 @@ import com.intellij.psi.PsiClass import com.intellij.psi.PsiJavaFile import com.intellij.psi.PsiManager import kotlinx.serialization.ExperimentalSerializationApi +import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.research.testspark.bundles.llm.LLMDefaultsBundle import org.jetbrains.research.testspark.core.data.JUnitVersion import org.jetbrains.research.testspark.core.data.TestGenerationData import org.jetbrains.research.testspark.core.monitor.DefaultErrorMonitor +import org.jetbrains.research.testspark.core.test.SupportedLanguage import org.jetbrains.research.testspark.core.test.TestCompiler import org.jetbrains.research.testspark.core.test.data.CodeType import org.jetbrains.research.testspark.data.FragmentToTestData import org.jetbrains.research.testspark.data.ProjectContext import org.jetbrains.research.testspark.data.llm.JsonEncoding +import org.jetbrains.research.testspark.kotlin.KotlinPsiHelperProvider import org.jetbrains.research.testspark.langwrappers.PsiHelperProvider import org.jetbrains.research.testspark.progress.HeadlessProgressIndicator import org.jetbrains.research.testspark.services.LLMSettingsService @@ -36,6 +39,7 @@ import java.io.File import java.nio.file.Path import java.nio.file.Paths import kotlin.system.exitProcess +import kotlin.test.assertIsNot /** * This class is responsible for generating and running tests based on the provided arguments in headless mode. @@ -73,6 +77,9 @@ class TestSparkStarter : ApplicationStarter { val runCoverage = args[10].toBoolean() val testsExecutionResultManager = TestsExecutionResultManager() + // TODO check for suitable refactoring + val language = + if (cutSourceFilePath.toString().endsWith(".kt")) SupportedLanguage.Kotlin else SupportedLanguage.Java println("Test generation requested for $projectPath") @@ -108,13 +115,18 @@ class TestSparkStarter : ApplicationStarter { println("Couldn't open file $cutSourceFilePath") exitProcess(1) } - // get target PsiClass - val psiFile = PsiManager.getInstance(project).findFile(cutSourceVirtualFile) as PsiJavaFile - val targetPsiClass = detectPsiClass(psiFile.classes, classUnderTestName) ?: run { - println("Couldn't find $classUnderTestName in $cutSourceFilePath") - exitProcess(1) - } + val psiFile = PsiManager.getInstance(project).findFile(cutSourceVirtualFile) + val targetPsiClass = detectPsiClass( + when(language) { + SupportedLanguage.Java -> psiFile as PsiJavaFile + SupportedLanguage.Kotlin -> psiFile as KtFile + }.classes, + classUnderTestName + ) ?: run { + println("Couldn't find $classUnderTestName in $cutSourceFilePath") + exitProcess(1) + } println("PsiClass ${targetPsiClass.qualifiedName} is detected! Start the test generation process.") @@ -159,7 +171,10 @@ class TestSparkStarter : ApplicationStarter { val packageName = packageList.joinToString(".") // Get PsiHelper - val psiHelper = PsiHelperProvider.getPsiHelper(psiFile) + val psiHelper = when (language) { + SupportedLanguage.Kotlin -> KotlinPsiHelperProvider().getPsiHelper(psiFile as KtFile) + SupportedLanguage.Java -> PsiHelperProvider.getPsiHelper(psiFile as PsiJavaFile) + } if (psiHelper == null) { // TODO exception: the support for the current language does not exist } @@ -255,6 +270,7 @@ class TestSparkStarter : ApplicationStarter { val targetDirectory = "$out${File.separator}${packageList.joinToString(File.separator)}" println("Run tests in $targetDirectory") File(targetDirectory).walk().forEach { + // TODO Doesn't work for compiled kotlin files if (it.name.endsWith(".class")) { println("Running test ${it.name}") var testcaseName = it.nameWithoutExtension.removePrefix("Generated") From 1a9770a1d6bbd52ae6b3b0b0d3b8f32ee14a9a88 Mon Sep 17 00:00:00 2001 From: Iurii Zaitsev Date: Wed, 18 Sep 2024 16:45:03 +0200 Subject: [PATCH 2/7] Kotlin clss support without coverage --- .../jetbrains/research/testspark/appstarter/TestSparkStarter.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt b/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt index 71e911890..72a25ec98 100644 --- a/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt +++ b/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt @@ -39,7 +39,6 @@ import java.io.File import java.nio.file.Path import java.nio.file.Paths import kotlin.system.exitProcess -import kotlin.test.assertIsNot /** * This class is responsible for generating and running tests based on the provided arguments in headless mode. From 575d04524ea11f0f792ba70a5eed5485e805cf9e Mon Sep 17 00:00:00 2001 From: Iurii Zaitsev Date: Wed, 18 Sep 2024 16:48:21 +0200 Subject: [PATCH 3/7] Indentation fixes --- .../testspark/appstarter/TestSparkStarter.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt b/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt index 72a25ec98..17c9551b6 100644 --- a/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt +++ b/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt @@ -117,15 +117,15 @@ class TestSparkStarter : ApplicationStarter { // get target PsiClass val psiFile = PsiManager.getInstance(project).findFile(cutSourceVirtualFile) val targetPsiClass = detectPsiClass( - when(language) { + when (language) { SupportedLanguage.Java -> psiFile as PsiJavaFile SupportedLanguage.Kotlin -> psiFile as KtFile }.classes, - classUnderTestName - ) ?: run { - println("Couldn't find $classUnderTestName in $cutSourceFilePath") - exitProcess(1) - } + classUnderTestName + ) ?: run { + println("Couldn't find $classUnderTestName in $cutSourceFilePath") + exitProcess(1) + } println("PsiClass ${targetPsiClass.qualifiedName} is detected! Start the test generation process.") From 9c2c9db1821ab0ef353a2f11af107671e00d1488 Mon Sep 17 00:00:00 2001 From: Iurii Zaitsev Date: Thu, 19 Sep 2024 19:41:23 +0200 Subject: [PATCH 4/7] Method level generation --- build.gradle.kts | 4 +++- .../testspark/appstarter/TestSparkStarter.kt | 20 ++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5e6621e29..29c9c8905 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -406,6 +406,7 @@ fun String?.orDefault(default: String): String = this ?: default * @param prompt a txt file containing the LLM's prompt template * @param out The output directory for the project. * @param enableCoverage flag to enable/disable coverage computation + * @param methodName indicates the name of the method under test or empty for class level generation */ tasks.create("headless") { val root: String? by project @@ -418,8 +419,9 @@ tasks.create("headless") { val prompt: String? by project val out: String? by project val enableCoverage: String? by project + val methodName: String? by project - args = listOfNotNull("testspark", root, file, cut, cp, junitv, llm, token, prompt, out, enableCoverage.orDefault("false")) + args = listOfNotNull("testspark", root, file, cut, cp, junitv, llm, token, prompt, out, enableCoverage.orDefault("false"), methodName.orDefault("")) jvmArgs( "-Xmx16G", diff --git a/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt b/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt index 17c9551b6..b537850b7 100644 --- a/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt +++ b/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt @@ -13,6 +13,7 @@ import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.psi.PsiClass import com.intellij.psi.PsiJavaFile import com.intellij.psi.PsiManager +import com.intellij.psi.PsiMethod import kotlinx.serialization.ExperimentalSerializationApi import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.research.testspark.bundles.llm.LLMDefaultsBundle @@ -25,6 +26,7 @@ import org.jetbrains.research.testspark.core.test.data.CodeType import org.jetbrains.research.testspark.data.FragmentToTestData import org.jetbrains.research.testspark.data.ProjectContext import org.jetbrains.research.testspark.data.llm.JsonEncoding +import org.jetbrains.research.testspark.java.JavaPsiMethodWrapper import org.jetbrains.research.testspark.kotlin.KotlinPsiHelperProvider import org.jetbrains.research.testspark.langwrappers.PsiHelperProvider import org.jetbrains.research.testspark.progress.HeadlessProgressIndicator @@ -74,6 +76,8 @@ class TestSparkStarter : ApplicationStarter { val output = args[9] // Run coverage val runCoverage = args[10].toBoolean() + // Method under test name(or empty string for class level generation) + val methodName = args[11] val testsExecutionResultManager = TestsExecutionResultManager() // TODO check for suitable refactoring @@ -197,9 +201,23 @@ class TestSparkStarter : ApplicationStarter { psiHelper.language, projectSDKPath.toString(), ) + val codeType = when (methodName) { + "" -> FragmentToTestData(CodeType.CLASS) + else -> { + val psiMethod = targetPsiClass.methods.find { it.name == methodName } ?: run { + println("Couldn't find method $methodName") + exitProcess(1) + } + FragmentToTestData( + CodeType.METHOD, + psiHelper.generateMethodDescriptor(JavaPsiMethodWrapper(psiMethod as PsiMethod)) + ) + } + } + val uiContext = llmProcessManager.runTestGenerator( indicator, - FragmentToTestData(CodeType.CLASS), + codeType, packageName, projectContext, testGenerationData, From f683a2570e599c9e3e35a4de53e3446399938271 Mon Sep 17 00:00:00 2001 From: Iurii Zaitsev Date: Thu, 19 Sep 2024 19:43:25 +0200 Subject: [PATCH 5/7] Indentation fixes --- .../jetbrains/research/testspark/appstarter/TestSparkStarter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt b/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt index b537850b7..7b4a7f89b 100644 --- a/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt +++ b/src/main/kotlin/org/jetbrains/research/testspark/appstarter/TestSparkStarter.kt @@ -204,7 +204,7 @@ class TestSparkStarter : ApplicationStarter { val codeType = when (methodName) { "" -> FragmentToTestData(CodeType.CLASS) else -> { - val psiMethod = targetPsiClass.methods.find { it.name == methodName } ?: run { + val psiMethod = targetPsiClass.methods.find { it.name == methodName } ?: run { println("Couldn't find method $methodName") exitProcess(1) } From f832423eb1764d252b5eb4e91b6e1f3e915fb442 Mon Sep 17 00:00:00 2001 From: Iurii Zaitsev Date: Fri, 20 Sep 2024 16:42:18 +0200 Subject: [PATCH 6/7] updates for method level generation --- runTestSparkHeadless.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/runTestSparkHeadless.sh b/runTestSparkHeadless.sh index a5b64f474..bbb5c0f8c 100644 --- a/runTestSparkHeadless.sh +++ b/runTestSparkHeadless.sh @@ -23,9 +23,10 @@ if [ $# -ne "12" ]; then 9) Output directory 10) Enable/disable coverage computation ('true' or 'false') 11) Space username - 12) Space password" + 12) Space password + 13) Method under test name(or empty for class-level generation)" exit 1 fi -echo -Proot="$1" -Pfile="$2" -Pcut="$3" -Pcp="$4" -Pjunitv="$5" -Pllm="$6" -Ptoken="$7" -Pprompt="$8" -Pout="$9" -PenableCoverage="${10}" -Dspace.username="${11}" -Dspace.pass="${12}" -"$DIR/gradlew" -p "$DIR" headless -Proot="$1" -Pfile="$2" -Pcut="$3" -Pcp="$4" -Pjunitv="$5" -Pllm="$6" -Ptoken="$7" -Pprompt="$8" -Pout="$9" -PenableCoverage="${10}" -Dspace.username="${11}" -Dspace.pass="${12}" +echo -Proot="$1" -Pfile="$2" -Pcut="$3" -Pcp="$4" -Pjunitv="$5" -Pllm="$6" -Ptoken="$7" -Pprompt="$8" -Pout="$9" -PenableCoverage="${10}" -Dspace.username="${11}" -Dspace.pass="${12}" -PmethodName="${13}" +"$DIR/gradlew" -p "$DIR" headless -Proot="$1" -Pfile="$2" -Pcut="$3" -Pcp="$4" -Pjunitv="$5" -Pllm="$6" -Ptoken="$7" -Pprompt="$8" -Pout="$9" -PenableCoverage="${10}" -Dspace.username="${11}" -Dspace.pass="${12}" -PmethodName="${13}" From 0cac87548406b85e89381ff30d39fafbc4aa4ff7 Mon Sep 17 00:00:00 2001 From: Iurii Zaitsev Date: Mon, 21 Oct 2024 18:56:37 +0200 Subject: [PATCH 7/7] gradle update after v2 migrate --- build.gradle.kts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index b830b9b2c..b1be4b5d4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,6 +3,7 @@ import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType import org.jetbrains.intellij.platform.gradle.TestFrameworkType import org.jetbrains.intellij.platform.gradle.models.ProductRelease import org.jetbrains.intellij.platform.gradle.tasks.RunIdeTask +import org.jetbrains.intellij.platform.gradle.tasks.aware.SplitModeAware.SplitModeTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.io.FileOutputStream import java.net.URL @@ -80,7 +81,10 @@ if (spaceCredentialsProvided()) { dependencies { add(hasGrazieAccess.implementationConfigurationName, kotlin("stdlib")) add(hasGrazieAccess.implementationConfigurationName, "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") - add(hasGrazieAccess.implementationConfigurationName, "org.jetbrains.research:grazie-test-generation:$grazieTestGenerationVersion") + add( + hasGrazieAccess.implementationConfigurationName, + "org.jetbrains.research:grazie-test-generation:$grazieTestGenerationVersion" + ) } tasks.register("checkCredentials") { @@ -448,7 +452,20 @@ tasks.create("headless") { val enableCoverage: String? by project val methodName: String? by project - args = listOfNotNull("testspark", root, file, cut, cp, junitv, llm, token, prompt, out, enableCoverage.orDefault("false"), methodName.orDefault("")) + args = listOfNotNull( + "testspark", + root, + file, + cut, + cp, + junitv, + llm, + token, + prompt, + out, + enableCoverage.orDefault("false"), + methodName.orDefault("") + ) jvmArgs( "-Xmx16G", @@ -457,6 +474,9 @@ tasks.create("headless") { "java.base/jdk.internal.vm=ALL-UNNAMED", "-Didea.system.path", ) + + splitMode = false + splitModeTarget = SplitModeTarget.BACKEND } fun spaceCredentialsProvided() = spaceUsername.isNotEmpty() && spacePassword.isNotEmpty()