Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ICTL-908] Test Compiler for Kotlin #282

Merged
merged 59 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
784c3c1
Parsing LLM Response for Kotlin tests
Frosendroska Jun 27, 2024
6fb9f27
last fixes of merge conflicts
Frosendroska Jun 27, 2024
9756d76
klint
Frosendroska Jun 27, 2024
b088cd3
build problem fix
Frosendroska Jun 27, 2024
15a3d4c
refactoring of packages
Frosendroska Jun 27, 2024
6fdfa89
refactoring packages
Frosendroska Jun 27, 2024
5875233
minimal buggy version of kotlin compilation cycle
Frosendroska Jun 30, 2024
2bab2b7
reduced the code
Frosendroska Jul 3, 2024
35fede2
reduced the code, fixed the representation bug
Frosendroska Jul 4, 2024
8f3ab4e
solved imports problem
Frosendroska Jul 4, 2024
e53269a
retirned back the compiler
Frosendroska Jul 7, 2024
0bad8c1
version with kotlinc
Frosendroska Jul 7, 2024
5255426
working kotlinc
Frosendroska Jul 7, 2024
a01e544
changes in compiler
Frosendroska Jul 8, 2024
d056541
solved the problem with package
Frosendroska Jul 8, 2024
48352ea
klint
Frosendroska Jul 8, 2024
e02ff27
Update Run IDE for UI Tests.run.xml
Frosendroska Jul 8, 2024
e5f2e82
klint
Frosendroska Jul 8, 2024
5b18967
Merge branch 'ebraun/improvements/compiler-for-kotlin' of https://git…
Frosendroska Jul 8, 2024
8fd4ca7
refactoring
Frosendroska Jul 8, 2024
f187882
klint
Frosendroska Jul 8, 2024
565d531
Merge remote-tracking branch 'refs/remotes/origin/development' into e…
Frosendroska Jul 8, 2024
79346a8
Merge branch 'development' into ebraun/improvements/parsing-llm-response
Frosendroska Jul 11, 2024
f91b5f7
Merge remote-tracking branch 'refs/remotes/origin/development' into e…
Frosendroska Jul 12, 2024
bde8900
Merge branch 'ebraun/improvements/parsing-llm-response' of https://gi…
Frosendroska Jul 12, 2024
cf2dd5a
Merge remote-tracking branch 'refs/remotes/origin/development' into e…
Frosendroska Jul 12, 2024
efa6236
Merge remote-tracking branch 'refs/remotes/origin/development' into e…
Frosendroska Jul 12, 2024
1e1b056
merge
Frosendroska Jul 12, 2024
02452d3
fixes after the review
Frosendroska Jul 15, 2024
2f76750
deleted companion object
Frosendroska Jul 15, 2024
29240cf
Merge branch 'refs/heads/ebraun/improvements/parsing-llm-response' in…
Frosendroska Jul 15, 2024
af823a5
merge fixes
Frosendroska Jul 15, 2024
2c00a00
factory for parsers
Frosendroska Jul 15, 2024
3169698
interface for compilation
Frosendroska Jul 16, 2024
2e364ea
merge
Frosendroska Jul 16, 2024
8fa28a4
before adjusting the code to the diagram
Frosendroska Jul 16, 2024
9543f12
new implementation of TestAssembler
Frosendroska Jul 16, 2024
90dd9d2
small refactoring
Frosendroska Jul 16, 2024
8934824
deleted todo
Frosendroska Jul 17, 2024
d8d6036
fixed problem with the missing package
Frosendroska Jul 17, 2024
7bb6fec
fixing the compilation bug
Frosendroska Jul 17, 2024
16e44b4
fixed problem with missing } in the test class
Frosendroska Jul 18, 2024
a8e27aa
added more logging
Frosendroska Jul 18, 2024
4e7940d
reduced the duplication in finding package name
Frosendroska Jul 18, 2024
3839f19
added some documentation and renamed the packageLine and packageStrin…
Frosendroska Jul 19, 2024
3982643
klint
Frosendroska Jul 19, 2024
8caa85d
fixing the problem with JavaTestCaseDisplayService
Frosendroska Jul 19, 2024
5eceacc
klint
Frosendroska Jul 19, 2024
9ad25b9
fixing the problem with package
Frosendroska Jul 23, 2024
f121d1c
fix: rename Language
Frosendroska Jul 23, 2024
60bb357
fix: import pattern for Kotlin comment
Frosendroska Jul 23, 2024
c6778e9
fix: renaming of methods in TestClassBuilderHelper
Frosendroska Jul 23, 2024
8b49a6f
fix: added displayTestCase
Frosendroska Jul 23, 2024
0a36f08
fix: findJavaCompilerInDirectory
Frosendroska Jul 23, 2024
a22ea9a
fix: some left fixes
Frosendroska Jul 23, 2024
f6f359a
done with fixes
Frosendroska Jul 23, 2024
13c00a2
deleted unnecessary line
Frosendroska Jul 23, 2024
6e09568
fixed java
Frosendroska Jul 23, 2024
8787862
last renamint
Frosendroska Jul 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ dependencies {

// https://mvnrepository.com/artifact/org.mockito/mockito-all
testImplementation("org.mockito:mockito-all:1.10.19")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.1.0")
Frosendroska marked this conversation as resolved.
Show resolved Hide resolved

// https://mvnrepository.com/artifact/net.jqwik/jqwik
testImplementation("net.jqwik:jqwik:1.6.5")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ data class TestGenerationData(

// Code required of imports and package for generated tests
var importsCode: MutableSet<String> = mutableSetOf(),
var packageLine: String = "",
var packageName: String = "",
var runWith: String = "",
var otherInfo: String = "",

Expand All @@ -37,7 +37,7 @@ data class TestGenerationData(
resultName = ""
fileUrl = ""
importsCode = mutableSetOf()
packageLine = ""
packageName = ""
runWith = ""
otherInfo = ""
polyDepthReducing = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import org.jetbrains.research.testspark.core.generation.llm.prompt.PromptSizeRed
import org.jetbrains.research.testspark.core.monitor.DefaultErrorMonitor
import org.jetbrains.research.testspark.core.monitor.ErrorMonitor
import org.jetbrains.research.testspark.core.progress.CustomProgressIndicator
import org.jetbrains.research.testspark.core.test.Language
import org.jetbrains.research.testspark.core.test.SupportedLanguage
import org.jetbrains.research.testspark.core.test.TestCompiler
import org.jetbrains.research.testspark.core.test.TestsAssembler
import org.jetbrains.research.testspark.core.test.TestsPersistentStorage
Expand Down Expand Up @@ -45,7 +45,7 @@ data class FeedbackResponse(

class LLMWithFeedbackCycle(
private val report: Report,
private val language: Language,
private val language: SupportedLanguage,
private val initialPromptMessage: String,
private val promptSizeReductionStrategy: PromptSizeReductionStrategy,
// filename in which the test suite is saved in result path
Expand Down Expand Up @@ -167,13 +167,15 @@ class LLMWithFeedbackCycle(
generatedTestSuite.updateTestCases(compilableTestCases.toMutableList())
} else {
for (testCaseIndex in generatedTestSuite.testCases.indices) {
val testCaseFilename =
"${getClassWithTestCaseName(generatedTestSuite.testCases[testCaseIndex].name)}.java"
val testCaseFilename = when (language) {
SupportedLanguage.Java -> "${getClassWithTestCaseName(generatedTestSuite.testCases[testCaseIndex].name)}.java"
SupportedLanguage.Kotlin -> "${getClassWithTestCaseName(generatedTestSuite.testCases[testCaseIndex].name)}.kt"
}

val testCaseRepresentation = testsPresenter.representTestCase(generatedTestSuite, testCaseIndex)

val saveFilepath = testStorage.saveGeneratedTest(
generatedTestSuite.packageString,
generatedTestSuite.packageName,
testCaseRepresentation,
resultPath,
testCaseFilename,
Expand All @@ -184,7 +186,7 @@ class LLMWithFeedbackCycle(
}

val generatedTestSuitePath: String = testStorage.saveGeneratedTest(
generatedTestSuite.packageString,
generatedTestSuite.packageName,
testsPresenter.representTestSuite(generatedTestSuite),
resultPath,
testSuiteFilename,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,45 @@ import org.jetbrains.research.testspark.core.generation.llm.network.RequestManag
import org.jetbrains.research.testspark.core.monitor.DefaultErrorMonitor
import org.jetbrains.research.testspark.core.monitor.ErrorMonitor
import org.jetbrains.research.testspark.core.progress.CustomProgressIndicator
import org.jetbrains.research.testspark.core.test.Language
import org.jetbrains.research.testspark.core.test.SupportedLanguage
import org.jetbrains.research.testspark.core.test.TestsAssembler
import org.jetbrains.research.testspark.core.test.data.TestSuiteGeneratedByLLM
import org.jetbrains.research.testspark.core.utils.javaPackagePattern
import org.jetbrains.research.testspark.core.utils.kotlinPackagePattern
import java.util.Locale

// TODO: find a better place for the below functions

/**
* Retrieves the package declaration from the given test suite code for any language.
*
* @param testSuiteCode The generated code of the test suite.
* @return The package name extracted from the test suite code, or an empty string if no package declaration was found.
*/
fun getPackageFromTestSuiteCode(testSuiteCode: String?, language: SupportedLanguage): String {
testSuiteCode ?: return ""
return when (language) {
SupportedLanguage.Kotlin -> kotlinPackagePattern.find(testSuiteCode)?.groups?.get(1)?.value.orEmpty()
SupportedLanguage.Java -> javaPackagePattern.find(testSuiteCode)?.groups?.get(1)?.value.orEmpty()
}
}

/**
* Retrieves the imports code from a given test suite code.
*
* @param testSuiteCode The test suite code from which to extract the imports code. If null, an empty string is returned.
* @param classFQN The fully qualified name of the class to be excluded from the imports code. It will not be included in the result.
* @return The imports code extracted from the test suite code. If no imports are found or the result is empty after filtering, an empty string is returned.
*/
fun getImportsCodeFromTestSuiteCode(testSuiteCode: String?, classFQN: String): MutableSet<String> {
testSuiteCode ?: return mutableSetOf()
return testSuiteCode.replace("\r\n", "\n").split("\n").asSequence()
.filter { it.contains("^import".toRegex()) }
.filterNot { it.contains("evosuite".toRegex()) }
.filterNot { it.contains("RunWith".toRegex()) }
.filterNot { it.contains(classFQN.toRegex()) }.toMutableSet()
}

/**
* Returns the generated class name for a given test case.
*
Expand Down Expand Up @@ -39,7 +71,7 @@ fun getClassWithTestCaseName(testCaseName: String): String {
* @return instance of TestSuiteGeneratedByLLM if the generated test cases are parsable, otherwise null.
*/
fun executeTestCaseModificationRequest(
language: Language,
language: SupportedLanguage,
testCase: String,
task: String,
indicator: CustomProgressIndicator,
Expand All @@ -50,15 +82,7 @@ fun executeTestCaseModificationRequest(
// Update Token information
val prompt = "For this test:\n ```\n $testCase\n ```\nPerform the following task: $task"

var packageName = ""
testCase.split("\n")[0].let {
if (it.startsWith("package")) {
packageName = it
.removePrefix("package ")
.removeSuffix(";")
.trim()
}
}
val packageName = getPackageFromTestSuiteCode(testCase, language)

val response = requestManager.request(
language,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import org.jetbrains.research.testspark.core.data.ChatUserMessage
import org.jetbrains.research.testspark.core.monitor.DefaultErrorMonitor
import org.jetbrains.research.testspark.core.monitor.ErrorMonitor
import org.jetbrains.research.testspark.core.progress.CustomProgressIndicator
import org.jetbrains.research.testspark.core.test.Language
import org.jetbrains.research.testspark.core.test.SupportedLanguage
import org.jetbrains.research.testspark.core.test.TestsAssembler

abstract class RequestManager(var token: String) {
Expand All @@ -31,7 +31,7 @@ abstract class RequestManager(var token: String) {
* @return the generated TestSuite, or null and prompt message
*/
open fun request(
language: Language,
language: SupportedLanguage,
prompt: String,
indicator: CustomProgressIndicator,
packageName: String,
Expand Down Expand Up @@ -65,7 +65,7 @@ abstract class RequestManager(var token: String) {
open fun processResponse(
testsAssembler: TestsAssembler,
packageName: String,
language: Language,
language: SupportedLanguage,
): LLMResponse {
// save the full response in the chat history
val response = testsAssembler.getContent()
Expand All @@ -78,7 +78,7 @@ abstract class RequestManager(var token: String) {
return LLMResponse(ResponseErrorCode.EMPTY_LLM_RESPONSE, null)
}

val testSuiteGeneratedByLLM = testsAssembler.assembleTestSuite(packageName, language)
val testSuiteGeneratedByLLM = testsAssembler.assembleTestSuite()

return if (testSuiteGeneratedByLLM == null) {
LLMResponse(ResponseErrorCode.TEST_SUITE_PARSING_FAILURE, null)
Expand All @@ -97,7 +97,7 @@ abstract class RequestManager(var token: String) {
open fun processUserFeedbackResponse(
testsAssembler: TestsAssembler,
packageName: String,
language: Language,
language: SupportedLanguage,
): LLMResponse {
val response = testsAssembler.getContent()

Expand All @@ -108,7 +108,7 @@ abstract class RequestManager(var token: String) {
return LLMResponse(ResponseErrorCode.EMPTY_LLM_RESPONSE, null)
}

val testSuiteGeneratedByLLM = testsAssembler.assembleTestSuite(packageName, language)
val testSuiteGeneratedByLLM = testsAssembler.assembleTestSuite()

return if (testSuiteGeneratedByLLM == null) {
LLMResponse(ResponseErrorCode.TEST_SUITE_PARSING_FAILURE, null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ internal class PromptBuilder(private var prompt: String) {
fullText += "Here are some information about other methods and classes used by the class under test. Only use them for creating objects, not your own ideas.\n"
}
for (interestingClass in interestingClasses) {
if (interestingClass.qualifiedName.startsWith("java")) {
if (interestingClass.qualifiedName.startsWith("java") || interestingClass.qualifiedName.startsWith("kotlin")) {
continue
}

Expand All @@ -88,7 +88,9 @@ internal class PromptBuilder(private var prompt: String) {
// Skip java methods
// TODO: checks for java methods should be done by a caller to make
// this class as abstract and language agnostic as possible.
if (method.containingClassQualifiedName.startsWith("java")) {
if (method.containingClassQualifiedName.startsWith("java") ||
method.containingClassQualifiedName.startsWith("kotlin")
) {
continue
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ package org.jetbrains.research.testspark.core.test
/**
* Language ID string should be the same as the language name in com.intellij.lang.Language
*/
enum class Language(val languageId: String) {
Java("JAVA"), Kotlin("Kotlin")
enum class SupportedLanguage(val languageId: String) {
Java("JAVA"), Kotlin("kotlin")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.jetbrains.research.testspark.core.test

import org.jetbrains.research.testspark.core.test.data.TestLine

interface TestBodyPrinter {
/**
* Generates a test body as a string based on the provided parameters.
*
* @param testInitiatedText A string containing the upper part of the test case.
* @param lines A mutable list of `TestLine` objects representing the lines of the test body.
* @param throwsException The exception type that the test function throws, if any.
* @param name The name of the test function.
* @return A string representing the complete test body.
*/
fun printTestBody(
testInitiatedText: String,
lines: MutableList<TestLine>,
throwsException: String,
name: String,
): String
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
package org.jetbrains.research.testspark.core.test

import io.github.oshai.kotlinlogging.KotlinLogging
import org.jetbrains.research.testspark.core.test.data.TestCaseGeneratedByLLM
import org.jetbrains.research.testspark.core.utils.CommandLineRunner
import org.jetbrains.research.testspark.core.utils.DataFilesUtil
import java.io.File

data class TestCasesCompilationResult(
val allTestCasesCompilable: Boolean,
val compilableTestCases: MutableSet<TestCaseGeneratedByLLM>,
)

/**
* TestCompiler is a class that is responsible for compiling generated test cases using the proper javac.
* It provides methods for compiling test cases and code files.
*/
open class TestCompiler(
private val javaHomeDirectoryPath: String,
abstract class TestCompiler(
private val libPaths: List<String>,
private val junitLibPaths: List<String>,
) {
private val log = KotlinLogging.logger { this::class.java }

/**
* Compiles the generated files with test cases using the proper javac.
* Compiles a list of test cases and returns the compilation result.
*
* @return true if all the provided test cases are successfully compiled,
* otherwise returns false.
* @param generatedTestCasesPaths A list of file paths where the generated test cases are located.
* @param buildPath All the directories where the compiled code of the project under test is saved. This path is used as a classpath to run each test case.
* @param testCases A mutable list of `TestCaseGeneratedByLLM` objects representing the test cases to be compiled.
* @return A `TestCasesCompilationResult` object containing the overall compilation success status and a set of compilable test cases.
*/
fun compileTestCases(
generatedTestCasesPaths: List<String>,
Expand All @@ -51,53 +43,19 @@ open class TestCompiler(
* Compiles the code at the specified path using the provided project build path.
*
* @param path The path of the code file to compile.
* @param projectBuildPath The project build path to use during compilation.
* @param projectBuildPath All the directories where the compiled code of the project under test is saved. This path is used as a classpath to run each test case.
* @return A pair containing a boolean value indicating whether the compilation was successful (true) or not (false),
* and a string message describing any error encountered during compilation.
*/
fun compileCode(path: String, projectBuildPath: String): Pair<Boolean, String> {
// find the proper javac
val javaCompile = File(javaHomeDirectoryPath).walk()
.filter {
val isCompilerName = if (DataFilesUtil.isWindows()) it.name.equals("javac.exe") else it.name.equals("javac")
isCompilerName && it.isFile
}
.firstOrNull()

if (javaCompile == null) {
val msg = "Cannot find java compiler 'javac' at '$javaHomeDirectoryPath'"
log.error { msg }
throw RuntimeException(msg)
}

println("javac found at '${javaCompile.absolutePath}'")

// compile file
val errorMsg = CommandLineRunner.run(
arrayListOf(
javaCompile.absolutePath,
"-cp",
"\"${getPath(projectBuildPath)}\"",
path,
),
)

log.info { "Error message: '$errorMsg'" }

// create .class file path
val classFilePath = path.replace(".java", ".class")

// check is .class file exists
return Pair(File(classFilePath).exists(), errorMsg)
}
abstract fun compileCode(path: String, projectBuildPath: String): Pair<Boolean, String>

/**
* Generates the path for the command by concatenating the necessary paths.
*
* @param buildPath The path of the build file.
* @return The generated path as a string.
*/
fun getPath(buildPath: String): String {
fun getClassPaths(buildPath: String): String {
// create the path for the command
val separator = DataFilesUtil.classpathSeparator
val dependencyLibPath = libPaths.joinToString(separator.toString())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ data class TestCaseParseResult(

interface TestSuiteParser {
/**
* Extracts test cases from raw text and generates a test suite using the given package name.
* Extracts test cases from raw text and generates a test suite.
*
* @param rawText The raw text provided by the LLM that contains the generated test cases.
* @return A GeneratedTestSuite instance containing the extracted test cases.
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,9 @@ abstract class TestsAssembler {
}

/**
* Extracts test cases from raw text and generates a TestSuite using the given package name.
* Extracts test cases from raw text and generates a TestSuite.
*
* @param packageName The package name to be set in the generated TestSuite.
* @return A TestSuiteGeneratedByLLM object containing the extracted test cases and package name.
* @return A TestSuiteGeneratedByLLM object containing information about the extracted test cases.
*/
abstract fun assembleTestSuite(packageName: String, language: Language): TestSuiteGeneratedByLLM?
abstract fun assembleTestSuite(): TestSuiteGeneratedByLLM?
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package org.jetbrains.research.testspark.core.test
* The TestPersistentStorage interface represents a contract for saving generated tests to a specified file system location.
*/
interface TestsPersistentStorage {

Frosendroska marked this conversation as resolved.
Show resolved Hide resolved
/**
* Save the generated tests to a specified directory.
*
Expand Down
Loading
Loading