Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import org.utbot.cpp.clion.plugin.utils.notifyError
import org.utbot.cpp.clion.plugin.utils.notifyInfo
import org.utbot.cpp.clion.plugin.utils.notifyUnknownResponse
import org.utbot.cpp.clion.plugin.utils.notifyWarning
import org.utbot.cpp.clion.plugin.utils.refreshAndFindNioFile
import org.utbot.cpp.clion.plugin.utils.markDirtyAndRefresh
import testsgen.Testgen

abstract class ProjectConfigResponseHandler(
Expand Down Expand Up @@ -98,7 +98,7 @@ class CreateBuildDirHandler(
}
else -> notifyUnknownResponse(response, project)
}
refreshAndFindNioFile(project.settings.buildDirPath)
markDirtyAndRefresh(project.settings.buildDirPath)
}
}

Expand All @@ -117,6 +117,6 @@ class GenerateJsonHandler(
)
else -> notifyUnknownResponse(response, project)
}
refreshAndFindNioFile(project.settings.buildDirPath)
markDirtyAndRefresh(project.settings.buildDirPath)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.utbot.cpp.clion.plugin.client.handlers

import com.intellij.openapi.project.Project
import org.utbot.cpp.clion.plugin.utils.convertFromRemotePathIfNeeded
import testsgen.Util
import java.nio.file.Path

class SourceCode private constructor(
val localPath: Path,
val remotePath: String,
val content: String,
val regressionMethodsNumber: Int,
val errorMethodsNumber: Int
) {
constructor(serverSourceCode: Util.SourceCode, project: Project) : this(
serverSourceCode.filePath.convertFromRemotePathIfNeeded(project),
serverSourceCode.filePath,
serverSourceCode.code,
serverSourceCode.regressionMethodsNumber,
serverSourceCode.errorMethodsNumber
)
}
Original file line number Diff line number Diff line change
@@ -1,64 +1,123 @@
package org.utbot.cpp.clion.plugin.client.handlers

import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.util.io.exists
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import org.utbot.cpp.clion.plugin.settings.settings
import org.utbot.cpp.clion.plugin.ui.services.TestsResultsStorage
import org.utbot.cpp.clion.plugin.utils.convertFromRemotePathIfNeeded
import org.utbot.cpp.clion.plugin.utils.createFileWithText
import org.utbot.cpp.clion.plugin.utils.isSarifReport
import org.utbot.cpp.clion.plugin.utils.logger
import org.utbot.cpp.clion.plugin.utils.refreshAndFindNioFile
import org.utbot.cpp.clion.plugin.utils.markDirtyAndRefresh
import org.utbot.cpp.clion.plugin.utils.nioPath
import testsgen.Testgen
import testsgen.Util
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.time.ZoneId

class TestsStreamHandler(
project: Project,
grpcStream: Flow<Testgen.TestsResponse>,
progressName: String,
cancellationJob: Job,
private val onSuccess: (List<Path>)->Unit = {},
private val onError: (Throwable)->Unit = {}
): StreamHandlerWithProgress<Testgen.TestsResponse>(project, grpcStream, progressName, cancellationJob) {
private val onSuccess: (List<Path>) -> Unit = {},
private val onError: (Throwable) -> Unit = {}
) : StreamHandlerWithProgress<Testgen.TestsResponse>(project, grpcStream, progressName, cancellationJob) {

private val myGeneratedTestFilesLocalFS: MutableList<Path> = mutableListOf()

override fun onData(data: Testgen.TestsResponse) {
super.onData(data)
handleSourceCode(data.testSourcesList)
if (data.hasStubs()) {
handleSourceCode(data.stubs.stubSourcesList, true)
}

val testSourceCodes = data.testSourcesList
.map { SourceCode(it, project) }
.filter { !it.localPath.isSarifReport() }
handleTestSources(testSourceCodes)

val stubSourceCodes = data.stubs.stubSourcesList.map { SourceCode(it, project) }
handleStubSources(stubSourceCodes)

val sarifReport =
data.testSourcesList.find { it.filePath.convertFromRemotePathIfNeeded(project).isSarifReport() }?.let {
SourceCode(it, project)
}
sarifReport?.let { handleSarifReport(it) }

// for new generated tests remove previous testResults
project.service<TestsResultsStorage>().clearTestResults(testSourceCodes)

// tell ide to refresh vfs and refresh project tree
markDirtyAndRefresh(project.nioPath)
}

override fun Testgen.TestsResponse.getProgress(): Util.Progress {
return progress
private fun handleSarifReport(sarif: SourceCode) {
backupPreviousClientSarifReport(sarif.localPath)
createSourceCodeFiles(listOf(sarif), "sarif report")
project.logger.info { "Generated SARIF report file ${sarif.localPath}" }
}

private fun handleSourceCode(sources: List<Util.SourceCode>, isStubs: Boolean = false) {
sources.forEach { sourceCode ->
val filePath: Path = sourceCode.filePath.convertFromRemotePathIfNeeded(project)
private fun handleTestSources(sources: List<SourceCode>) {
if (project.settings.isRemoteScenario) {
createSourceCodeFiles(sources, "test")
}

if (!isStubs)
myGeneratedTestFilesLocalFS.add(filePath)
// prepare list of generated test files for further processing
myGeneratedTestFilesLocalFS.addAll(sources.map { it.localPath })

if (sourceCode.code.isNotEmpty()) {
project.logger.trace { "Creating generated test file: $filePath." }
createFileWithText(
filePath,
sourceCode.code
)
sources.forEach { sourceCode ->
val isTestSourceFile = sourceCode.localPath.endsWith("_test.cpp")
val testsGenerationResultMessage = if (isTestSourceFile) {
"Generated ${sourceCode.regressionMethodsNumber} tests in regression suite" +
" and ${sourceCode.errorMethodsNumber} tests in error suite"
} else {
// .h file
"Generated test file ${sourceCode.localPath}"
}
logger.info(testsGenerationResultMessage)
}
}

var infoMessage = "Generated " + if (isStubs) "stub" else "test" + " file"
if (isGeneratedFileTestSourceFile(filePath.toString()))
infoMessage += " with ${sourceCode.regressionMethodsNumber} tests in regression suite" +
" and ${sourceCode.errorMethodsNumber} tests in error suite"
project.logger.info { "$infoMessage: $filePath" }
private fun handleStubSources(sources: List<SourceCode>) {
if (project.settings.isRemoteScenario) {
createSourceCodeFiles(sources, "stub")
}
}

refreshAndFindNioFile(filePath)
private fun createSourceCodeFiles(sourceCodes: List<SourceCode>, fileKind: String) {
sourceCodes.forEach {
project.logger.info { "Write $fileKind file ${it.remotePath} to ${it.localPath}" }
createFileWithText(it.localPath, it.content)
}
}

private fun isGeneratedFileTestSourceFile(fileName: String) = fileName.endsWith("_test.cpp")
override fun Testgen.TestsResponse.getProgress(): Util.Progress = progress

private fun backupPreviousClientSarifReport(previousReportPaths: Path) {
fun Number.pad2(): String = ("0$this").takeLast(2)

if (previousReportPaths.exists()) {
val ctime = Files.getLastModifiedTime(previousReportPaths)
.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime()

val newReportName = "project_code_analysis-" +
ctime.year.toString() +
(ctime.monthValue + 1).pad2() +
ctime.dayOfMonth.pad2() +
ctime.hour.pad2() +
ctime.minute.pad2() +
ctime.second.pad2() +
".sarif"
val newPath = Paths.get(previousReportPaths.parent.toString(), newReportName)
Files.move(previousReportPaths, newPath)
}
}

override fun onCompletion(exception: Throwable?) {
super.onCompletion(exception)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,10 @@ abstract class BaseTestsRequest<R>(request: R, project: Project, private val pro
}
}

open fun getFocusTarget(generatedTestFiles: List<Path>): Path? {
return generatedTestFiles.filter { !isHeaderFile(it) && !isSarifReport(it) }.getLongestCommonPathFromRoot()
}

override fun logRequest() {
logger.info { "$logMessage \n$request" }
}
open fun getFocusTarget(generatedTestFiles: List<Path>): Path? =
generatedTestFiles
.filter { !isHeaderFile(it) && !it.isSarifReport() }
.getLongestCommonPathFromRoot()

open fun getInfoMessage() = "Tests generated!"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.newvfs.BulkFileListener
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
import org.utbot.cpp.clion.plugin.client.handlers.SourceCode
import org.utbot.cpp.clion.plugin.listeners.UTBotTestResultsReceivedListener
import org.utbot.cpp.clion.plugin.utils.convertFromRemotePathIfNeeded
import testsgen.Testgen
Expand All @@ -31,31 +29,18 @@ class TestsResultsStorage(val project: Project) {

forceGutterIconsUpdate()
})

connection.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener {
override fun after(events: MutableList<out VFileEvent>) {
var wasSave = false
events.forEach { event ->
if (event.isFromSave) {
wasSave = true
storage.forEach { entry ->
if (entry.value.testFilePath != event.path) {
storage.remove(entry.key)
}
}
}
}

if (wasSave) {
forceGutterIconsUpdate()
}
}
})

}

fun getTestResultByTestName(testName: String): Testgen.TestResultObject? = storage[testName]

/**
* Cleans the results of previous test run if tests were regenerated.
*/
fun clearTestResults(sourceCodes: List<SourceCode>) {
val localFilePaths = sourceCodes.map { it.localPath }.toSet()
storage.values.removeIf { it.testFilePath.convertFromRemotePathIfNeeded(project) in localFilePaths }
}

private fun shouldForceUpdate(): Boolean {
val currentlyOpenedFilePaths = FileEditorManager.getInstance(project)
.selectedEditors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ import com.intellij.util.io.exists
import kotlin.io.path.writeText
import java.nio.file.Path

fun refreshAndFindNioFile(path: Path, async: Boolean = true, recursive: Boolean = true, reloadChildren: Boolean = true) {
VfsUtil.markDirtyAndRefresh(async, recursive, reloadChildren, path.toFile())
}
fun markDirtyAndRefresh(
path: Path,
async: Boolean = true,
recursive: Boolean = true,
reloadChildren: Boolean = true,
) = VfsUtil.markDirtyAndRefresh(async, recursive, reloadChildren, path.toFile())

fun createFileWithText(filePath: Path, text: String) {
with(filePath) {
Expand All @@ -24,7 +27,3 @@ fun isCPPFileName(fileName: String) = """.*\.(cpp|hpp|h)""".toRegex().matches(fi

fun isHeaderFile(fileName: String) = """.*\.([ch])""".toRegex().matches(fileName)
fun isHeaderFile(path: Path) = isHeaderFile(path.fileName.toString())

fun isSarifReport(fileName: String) = fileName.endsWith(".sarif")

fun isSarifReport(path: Path) = isSarifReport(path.fileName.toString())
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import java.util.*
import kotlin.io.path.div

val Project.path get() = this.basePath ?: error("Project path can't be null!")
val Project.nioPath: Path get() = Paths.get(this.path)

fun relativize(from: String, to: String): String {
val toPath = Paths.get(to)
Expand Down Expand Up @@ -51,6 +52,8 @@ fun Path.visitAllDirectories(action: (Path) -> Unit) {
}
}

fun Path.isSarifReport() = this.fileName.toString().endsWith(".sarif")

fun String.fileNameOrNull(): String? {
return try {
Paths.get(this).fileName.toString()
Expand Down