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

Render uncompilable test suite if it was successfully parsed #370

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ publishing {
create<MavenPublication>("maven") {
groupId = group as String
artifactId = "testspark-core"
version = "3.0.0"
version = "3.0.1"
from(components["java"])

artifact(tasks["sourcesJar"])
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
package org.jetbrains.research.testspark.core.data

/**
* Storage of generated tests. Implemented on the basis of org.evosuite.utils.CompactReport structure.
* Stores generated test cases and their coverage.
* Implemented on the basis of `org.evosuite.utils.CompactReport` structure.
*
* `Report`'s member fields were created based on the fields in
* `org.evosuite.utils.CompactReport` for easier transformation.
*/
open class Report {
// Fields were created based on the fields in org.evosuite.utils.CompactReport for easier transformation
var UUT: String = "" // Unit Under Test
/**
* Unit Under Test. This variable stores the name of the class or component that is being tested.
*/
var UUT: String = ""
var allCoveredLines: Set<Int> = setOf()
var allUncoveredLines: Set<Int> = setOf()
var testCaseList: HashMap<Int, TestCase> = hashMapOf()

/**
* AllCoveredLines update
* Calculates the normalized report by updating the set of all covered lines.
*
* @return The normalized report.
*/
fun normalized(): Report {
allCoveredLines = testCaseList.values.map { it.coveredLines }.flatten().toSet()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package org.jetbrains.research.testspark.core.data

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

data class TestGenerationData(
// Result processing
// Report object for each test case
Expand All @@ -23,14 +21,10 @@ data class TestGenerationData(
// changing parameters with a large prompt
var polyDepthReducing: Int = 0,
var inputParamsDepthReducing: Int = 0,

// list of correct test cases during the incorrect compilation
val compilableTestCases: MutableSet<TestCaseGeneratedByLLM> = mutableSetOf(),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this variable because we never used its value (only wrote in it); test cases that are rendered in the sidebar are taken from Report.testCaseList variable.


) {

/**
* Cleaning all old data before new test generation.
* Cleaning all old data before a new test generation.
*/
fun clear() {
testGenerationResultList.clear()
Expand All @@ -42,6 +36,5 @@ data class TestGenerationData(
otherInfo = ""
polyDepthReducing = 0
inputParamsDepthReducing = 0
compilableTestCases.clear()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,55 @@ enum class FeedbackCycleExecutionResult {
SAVING_TEST_FILES_ISSUE,
}

/**
* Represents a response (result) of a feedback cycle.
*
* @param executionResult The result of executing the feedback cycle.
* @param generatedTestSuite The test suite generated by LLM.
* @param compilableTestCases The set of compilable test cases generated by LLM.
*
* @throws IllegalArgumentException if `executionResult` is [FeedbackCycleExecutionResult.OK] and `generatedTestSuite` is null.
*/
data class FeedbackResponse(
val executionResult: FeedbackCycleExecutionResult,
val generatedTestSuite: TestSuiteGeneratedByLLM?,
val compilableTestCases: MutableSet<TestCaseGeneratedByLLM>,
) {
init {
if (executionResult == FeedbackCycleExecutionResult.OK && generatedTestSuite == null) {
if ((executionResult == FeedbackCycleExecutionResult.OK || executionResult == FeedbackCycleExecutionResult.NO_COMPILABLE_TEST_CASES_GENERATED) &&
(generatedTestSuite == null)
) {
throw IllegalArgumentException("Test suite must be provided when FeedbackCycleExecutionResult is OK, got null")
} else if (executionResult != FeedbackCycleExecutionResult.OK && generatedTestSuite != null) {
Vladislav0Art marked this conversation as resolved.
Show resolved Hide resolved
throw IllegalArgumentException(
"Test suite must not be provided when FeedbackCycleExecutionResult is not OK, got $generatedTestSuite",
)
}
}
}

/**
* LLMWithFeedbackCycle class represents a feedback cycle for an LLM.
*
* @property report The `Report` instance used for storing generated tests.
* @property language The `SupportedLanguage` enum value representing the programming language used.
* @property initialPromptMessage The initial prompt message to start the feedback cycle.
* @property promptSizeReductionStrategy The `PromptSizeReductionStrategy` instance used for reducing the prompt size.
* @property testSuiteFilename The name of the file in which the test suite is saved in the result path.
* @property packageName The package name for the generated tests.
* @property resultPath The temporary path where all the generated tests and their Jacoco report are saved.
* @property buildPath All the directories where the compiled code of the project under test is saved.
* @property requestManager The `RequestManager` instance used for making LLM requests.
* @property testsAssembler The `TestsAssembler` instance used for assembling generated tests.
* @property testCompiler The `TestCompiler` instance used for compiling tests.
* @property testStorage The `TestsPersistentStorage` instance used for storing generated tests.
* @property testsPresenter The `TestsPresenter` instance used for presenting generated tests.
* @property indicator The `CustomProgressIndicator` instance used for tracking progress.
* @property requestsCountThreshold The threshold for the maximum number of requests in the feedback cycle.
* @property errorMonitor The `ErrorMonitor` instance used for monitoring errors.
*/
class LLMWithFeedbackCycle(
private val report: Report,
private val language: SupportedLanguage,
private val initialPromptMessage: String,
private val promptSizeReductionStrategy: PromptSizeReductionStrategy,
// filename in which the test suite is saved in result path
// filename in which the test suite is saved in the result path
private val testSuiteFilename: String,
private val packageName: String,
// temp path where all the generated tests and their jacoco report are saved
Expand Down Expand Up @@ -98,6 +125,11 @@ class LLMWithFeedbackCycle(

if (isLastIteration(requestsCount) && compilableTestCases.isEmpty()) {
executionResult = FeedbackCycleExecutionResult.NO_COMPILABLE_TEST_CASES_GENERATED
// record a report with parsable yet potentially
// non-compilable test cases stored in
// the generated test suite
// TODO: ensure generatedTestSuite is always non-null here
generatedTestSuite?.let { recordReport(report, it.testCases) }
break
}

Expand Down Expand Up @@ -130,7 +162,9 @@ class LLMWithFeedbackCycle(
if (promptSizeReductionStrategy.isReductionPossible()) {
nextPromptMessage = promptSizeReductionStrategy.reduceSizeAndGeneratePrompt()
/**
* Current attempt does not count as a failure since it was rejected due to the prompt size exceeding the threshold
* The current attempt does not count as a failure
* since it was rejected due to the prompt size
* exceeding the threshold
*/
requestsCount--
continue
Expand Down Expand Up @@ -245,14 +279,13 @@ class LLMWithFeedbackCycle(

generatedTestsArePassing = true

for (index in testCases.indices) {
report.testCaseList[index] =
TestCase(index, testCases[index].name, testCases[index].toString(), setOf())
}
recordReport(report, testCases)
}

// test suite must not be provided upon failed execution
if (executionResult != FeedbackCycleExecutionResult.OK) {
if (executionResult != FeedbackCycleExecutionResult.OK &&
executionResult != FeedbackCycleExecutionResult.NO_COMPILABLE_TEST_CASES_GENERATED
) {
generatedTestSuite = null
}

Expand All @@ -263,5 +296,17 @@ class LLMWithFeedbackCycle(
)
}

/**
* Records the generated test cases in the given report.
*
* @param report The report object to store the test cases in.
* @param testCases The list of test cases generated by LLM.
*/
private fun recordReport(report: Report, testCases: MutableList<TestCaseGeneratedByLLM>) {
for ((index, test) in testCases.withIndex()) {
report.testCaseList[index] = TestCase(index, test.name, test.toString(), setOf())
}
}

private fun isLastIteration(requestsCount: Int): Boolean = requestsCount > requestsCountThreshold
}
Original file line number Diff line number Diff line change
Expand Up @@ -199,12 +199,14 @@ class LLMProcessManager(
when (feedbackResponse.executionResult) {
FeedbackCycleExecutionResult.OK -> {
log.info("Add ${feedbackResponse.compilableTestCases.size} compilable test cases into generatedTestsData")
// store compilable test cases
generatedTestsData.compilableTestCases.addAll(feedbackResponse.compilableTestCases)
}

FeedbackCycleExecutionResult.NO_COMPILABLE_TEST_CASES_GENERATED -> {
llmErrorManager.errorProcess(LLMMessagesBundle.get("invalidLLMResult"), project, errorMonitor)
if (feedbackResponse.generatedTestSuite != null) {
llmErrorManager.warningProcess(LLMMessagesBundle.get("noCompilableTestCases"), project)
} else {
llmErrorManager.errorProcess(LLMMessagesBundle.get("invalidLLMResult"), project, errorMonitor)
}
}

FeedbackCycleExecutionResult.CANCELED -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ class PromptManager(
*
* @param psiClass the PsiClassWrapper containing the method
* @param lineNumber the line number within the file where the method is located
* @return the method descriptor as a String, or an empty string if no method is found
* @return the method descriptor as `String`, or an empty string if no method is found
*/
private fun getMethodDescriptor(
psiClass: PsiClassWrapper?,
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/properties/llm/LLMMessages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ wrongToken=The provided token for Large Language Model is not correct. Please up
emptyResponse=Large Language Model could not generate any tests for this class. Asking the AI assistance to fix its mistake.
emptyBuildPath=Build path is Empty!\nPlease make sure that IDEA recognizes all of your module or enter proper build path in settings
invalidLLMResult=The result is invalid or uses unknown commands due to randomness and lack of guarantees from Large Language Model.\nPlease try again
noCompilableTestCases=LLM did not manage to make the test suite compilable. Manual effort may help to resolve the issues.
compilationError=The test generated by Large Language Model is not compilable. Asking the AI assistance to fix its mistake.
modifyWithLLMError=Large Language Model was unable to generate a new test. Please, check your request and try again.
serverProblems=Large Language Model server is not responding
Expand Down
Loading