diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/actions/generate/RunAllTestsWithCoverageAction.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/actions/generate/RunAllTestsWithCoverageAction.kt new file mode 100644 index 000000000..a030ef4b4 --- /dev/null +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/actions/generate/RunAllTestsWithCoverageAction.kt @@ -0,0 +1,14 @@ +package org.utbot.cpp.clion.plugin.actions.generate + +import com.intellij.openapi.actionSystem.AnActionEvent +import org.utbot.cpp.clion.plugin.client.requests.RunAllTestsWithCoverageRequest + +class RunAllTestsWithCoverageAction: BaseGenerateTestsAction() { + override fun actionPerformed(e: AnActionEvent) { + RunAllTestsWithCoverageRequest(e).executeUsingCurrentClient() + } + + override fun isDefined(e: AnActionEvent): Boolean { + return e.project != null + } +} diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/handlers/CoverageAndResultsHandler.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/handlers/CoverageAndResultsHandler.kt index d03684b4b..34fd627d0 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/handlers/CoverageAndResultsHandler.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/handlers/CoverageAndResultsHandler.kt @@ -9,10 +9,12 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.withContext import org.utbot.cpp.clion.plugin.actions.FocusAction +import org.utbot.cpp.clion.plugin.coverage.Coverage import org.utbot.cpp.clion.plugin.coverage.UTBotCoverageEngine import org.utbot.cpp.clion.plugin.coverage.UTBotCoverageRunner import org.utbot.cpp.clion.plugin.coverage.UTBotCoverageSuite import org.utbot.cpp.clion.plugin.listeners.UTBotTestResultsReceivedListener +import org.utbot.cpp.clion.plugin.utils.convertFromRemotePathIfNeeded import org.utbot.cpp.clion.plugin.utils.logger import org.utbot.cpp.clion.plugin.utils.notifyError import org.utbot.cpp.clion.plugin.utils.notifyInfo @@ -45,6 +47,30 @@ class CoverageAndResultsHandler( notifyError(response.errorMessage, project) } + data class CoverageCollector( + val fullyCovered: MutableSet = mutableSetOf(), + val partiallyCovered: MutableSet = mutableSetOf(), + val notCovered: MutableSet = mutableSetOf() + ) { + fun toCoverage() = Coverage(fullyCovered, partiallyCovered, notCovered) + } + + val coverage = mutableMapOf() + response.coveragesList.forEach { fileCoverageSimplified -> + val local = fileCoverageSimplified.filePath.convertFromRemotePathIfNeeded(project).normalize() + if (local !in coverage) + coverage[local] = CoverageCollector() + fileCoverageSimplified.fullCoverageLinesList.forEach { sourceLine -> + coverage[local]?.fullyCovered?.add(sourceLine.line) + } + fileCoverageSimplified.partialCoverageLinesList.forEach { sourceLine -> + coverage[local]?.partiallyCovered?.add(sourceLine.line) + } + fileCoverageSimplified.noCoverageLinesList.forEach { sourceLine -> + coverage[local]?.notCovered?.add(sourceLine.line) + } + } + // when we received results, test statuses should be updated in the gutter project.messageBus.syncPublisher(UTBotTestResultsReceivedListener.TOPIC) .testResultsReceived(response.testRunResultsList) @@ -54,11 +80,12 @@ class CoverageAndResultsHandler( val coverageRunner = CoverageRunner.getInstance(UTBotCoverageRunner::class.java) val manager = CoverageDataManager.getInstance(project) val suite = UTBotCoverageSuite( + coverage.mapValues { it.value.toCoverage() }, engine, response.coveragesList, coverageRunner = coverageRunner, name = "UTBot coverage suite", - project = project + project = project, ) manager.coverageGathered(suite) @@ -66,8 +93,6 @@ class CoverageAndResultsHandler( } private fun notifyCoverageReceived() { - if (sourceFilePath != null) { - notifyInfo("Coverage received!", project, FocusAction(sourceFilePath)) - } + notifyInfo("Coverage received!", project, sourceFilePath?.let { FocusAction(it) }) } } diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/requests/RunAllTestsWithCoverageRequest.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/requests/RunAllTestsWithCoverageRequest.kt new file mode 100644 index 000000000..63757c130 --- /dev/null +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/requests/RunAllTestsWithCoverageRequest.kt @@ -0,0 +1,37 @@ +package org.utbot.cpp.clion.plugin.client.requests + +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.project.Project +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import org.utbot.cpp.clion.plugin.UTBot +import org.utbot.cpp.clion.plugin.client.handlers.CoverageAndResultsHandler +import org.utbot.cpp.clion.plugin.grpc.getRunWithCoverageRequestForAllTests +import org.utbot.cpp.clion.plugin.utils.activeProject +import testsgen.Testgen +import testsgen.Testgen.CoverageAndResultsRequest +import testsgen.TestsGenServiceGrpcKt + +class RunAllTestsWithCoverageRequest( + request: CoverageAndResultsRequest, + project: Project, +) : BaseRequest>(request, project) { + + override val logMessage: String = "Sending request to get tests run results and coverage" + + constructor(e: AnActionEvent) : this(getRunWithCoverageRequestForAllTests(e.activeProject()), e.activeProject()) + + override suspend fun Flow.handle(cancellationJob: Job?) { + if (cancellationJob?.isActive == true) { + CoverageAndResultsHandler( + project, + this, + UTBot.message("requests.coverage.description.progress"), + cancellationJob, + ).handle() + } + } + + override suspend fun TestsGenServiceGrpcKt.TestsGenServiceCoroutineStub.send(cancellationJob: Job?): Flow = + createTestsCoverageAndResult(request) +} diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/coverage/Coverage.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/coverage/Coverage.kt new file mode 100644 index 000000000..2e0be8c5d --- /dev/null +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/coverage/Coverage.kt @@ -0,0 +1,7 @@ +package org.utbot.cpp.clion.plugin.coverage + +data class Coverage( + val fullyCovered: Set = setOf(), + val partiallyCovered: Set = setOf(), + val notCovered: Set = setOf() +) diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/coverage/UTBotCoverageRunner.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/coverage/UTBotCoverageRunner.kt index 0f3a5653f..8eb43f10f 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/coverage/UTBotCoverageRunner.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/coverage/UTBotCoverageRunner.kt @@ -8,12 +8,11 @@ import com.intellij.rt.coverage.data.LineCoverage import com.intellij.rt.coverage.data.LineData import com.intellij.rt.coverage.data.ProjectData import com.intellij.util.io.exists -import org.utbot.cpp.clion.plugin.utils.convertFromRemotePathIfNeeded -import testsgen.Testgen import java.io.File import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Path +import java.nio.file.Paths /** * This class is used to convert from our representation of coverage to IntelliJ's [ProjectData] @@ -31,42 +30,38 @@ class UTBotCoverageRunner : CoverageRunner() { */ override fun loadCoverageData(sessionDataFile: File, baseCoverageSuite: CoverageSuite?): ProjectData? { log.debug("loadCoverageData was called!") - val coveragesList = (baseCoverageSuite as? UTBotCoverageSuite)?.coveragesList - coveragesList ?: error("Coverage list is empty in loadCoverageData!") + val fileToCoverageInfo: Map = + (baseCoverageSuite as? UTBotCoverageSuite)?.coverage ?: return null val projectData = ProjectData() var isAnyCoverage = false - for (simplifiedCovInfo in coveragesList) { - val filePathFromServer = simplifiedCovInfo.filePath - if (filePathFromServer.isNotEmpty()) { - isAnyCoverage = true - val localFilePath = filePathFromServer.convertFromRemotePathIfNeeded(baseCoverageSuite.project) - if (!localFilePath.exists()) { - log.warn("Skipping $localFilePath in coverage processing as it does not exist!") - continue - } - val linesCount = getLineCount(localFilePath) - val lines = arrayOfNulls(linesCount) - val classData = projectData.getOrCreateClassData(provideQualifiedNameForFile(localFilePath.toString())) - fun processRanges(rangesList: List, status: Byte) { - rangesList.filterNotNull().forEach { sourceLine -> - val numberInFile = sourceLine.line - 1 - if (numberInFile >= linesCount) { - log.warn("Skipping $localFilePath:${numberInFile} in coverage processing! Number of lines in file is $linesCount!") - return@forEach - } - val lineData = LineData(sourceLine.line + 1, null) - lineData.hits = status.toInt() - lineData.setStatus(status) - // todo: leave comments what is going on - lines[numberInFile + 1] = lineData - classData.registerMethodSignature(lineData) + for ((file, coverage) in fileToCoverageInfo) { + isAnyCoverage = true + if (!file.exists()) { + log.warn("Skipping $file in coverage processing as it does not exist!") + continue + } + val linesCount = getLineCount(file) + val lines = arrayOfNulls(linesCount) + val classData = projectData.getOrCreateClassData(provideQualifiedNameForFile(file.toString())) + fun processLinesBatch(batch: Set, status: Byte) { + // assuming: server's coverage lines indexes start from 1 + batch.forEach { lineIdx -> + System.err.println("Processing idx : $lineIdx") + if (lineIdx > linesCount) { + log.warn("Skipping $file:${lineIdx} in coverage processing! Number of lines in file is $linesCount!") + return@forEach } + val lineData = LineData(lineIdx + 1, null) + lineData.hits = status.toInt() + lineData.setStatus(status) + lines[lineIdx] = lineData + classData.registerMethodSignature(lineData) } - processRanges(simplifiedCovInfo.fullCoverageLinesList, LineCoverage.FULL) - processRanges(simplifiedCovInfo.partialCoverageLinesList, LineCoverage.PARTIAL) - processRanges(simplifiedCovInfo.noCoverageLinesList, LineCoverage.NONE) classData.setLines(lines) } + processLinesBatch(coverage.fullyCovered, LineCoverage.FULL) + processLinesBatch(coverage.partiallyCovered, LineCoverage.PARTIAL) + processLinesBatch(coverage.notCovered, LineCoverage.NONE) } return if (isAnyCoverage) projectData else null } diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/coverage/UTBotCoverageSuite.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/coverage/UTBotCoverageSuite.kt index 4ed885f15..dd48157f9 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/coverage/UTBotCoverageSuite.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/coverage/UTBotCoverageSuite.kt @@ -8,6 +8,7 @@ import com.intellij.openapi.project.Project import com.intellij.rt.coverage.data.ProjectData import testsgen.Testgen import java.io.File +import java.nio.file.Path import java.util.* import java.util.concurrent.TimeUnit @@ -18,6 +19,7 @@ import java.util.concurrent.TimeUnit * @param covLists - coverage information returned from server. */ class UTBotCoverageSuite( + val coverage: Map = emptyMap(), coverageEngine: UTBotCoverageEngine, covLists: List? = null, name: String? = null, diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/grpc/ActionsGrpcRequests.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/grpc/ActionsGrpcRequests.kt index fad3c7e5c..5ca482342 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/grpc/ActionsGrpcRequests.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/grpc/ActionsGrpcRequests.kt @@ -21,6 +21,13 @@ fun getFolderGrpcRequest(e: AnActionEvent): Testgen.FolderRequest { .build() } +fun getRunWithCoverageRequestForAllTests(project: Project): Testgen.CoverageAndResultsRequest = + Testgen.CoverageAndResultsRequest.newBuilder() + .setCoverage(true) + .setSettingsContext(getSettingsContextMessage(project)) + .setProjectContext(getProjectContextMessage(project)) + .build() + fun getFileGrpcRequest(e: AnActionEvent): Testgen.FileRequest { val project = e.activeProject() val filePath = e.getRequiredData(CommonDataKeys.VIRTUAL_FILE).path @@ -81,7 +88,11 @@ fun getPredicateGrpcRequest( .build() } -private fun getPredicateGrpcRequest(predicate: String, returnValue: String, type: Util.ValidationType): Util.PredicateInfo = +private fun getPredicateGrpcRequest( + predicate: String, + returnValue: String, + type: Util.ValidationType +): Util.PredicateInfo = Util.PredicateInfo.newBuilder() .setPredicate(predicate) .setReturnValue(returnValue) diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/targetsToolWindow/UTBotTargetsController.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/targetsToolWindow/UTBotTargetsController.kt index 5a22dea3a..fb2f1ad8f 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/targetsToolWindow/UTBotTargetsController.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/targetsToolWindow/UTBotTargetsController.kt @@ -47,6 +47,7 @@ class UTBotTargetsController(val project: Project) { project, getProjectTargetsGrpcRequest(project), processTargets = { targetsResponse: Testgen.ProjectTargetsResponse -> + System.err.println("Received targets: $targetsResponse") invokeOnEdt { targetsToolWindow.setBusy(false) diff --git a/clion-plugin/src/main/resources/META-INF/plugin.xml b/clion-plugin/src/main/resources/META-INF/plugin.xml index 10f934526..29784c7c6 100644 --- a/clion-plugin/src/main/resources/META-INF/plugin.xml +++ b/clion-plugin/src/main/resources/META-INF/plugin.xml @@ -177,5 +177,8 @@ description="Generates tests for file without context of a project"> +