diff --git a/.gitignore b/.gitignore index 011d499004..47b6b875b5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ target/ .idea/ .gradle/ *.log -*.rdgen \ No newline at end of file +*.rdgen diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineMain.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineMain.kt index fe51663852..17c8ea2a92 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineMain.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineMain.kt @@ -176,13 +176,13 @@ private fun EngineProcessModel.setup( synchronizer.measureExecutionForTermination(writeSarifReport) { params -> val reportFilePath = Paths.get(params.reportFilePath) reportFilePath.parent.toFile().mkdirs() - reportFilePath.toFile().writeText( - SarifReport( - testSets[params.testSetsId]!!, - params.generatedTestsCode, - RdSourceFindingStrategyFacade(realProtocol.rdSourceFindingStrategy) - ).createReport().toJson() - ) + val sarifReport = SarifReport( + testSets[params.testSetsId]!!, + params.generatedTestsCode, + RdSourceFindingStrategyFacade(realProtocol.rdSourceFindingStrategy) + ).createReport().toJson() + reportFilePath.toFile().writeText(sarifReport) + sarifReport } synchronizer.measureExecutionForTermination(generateTestReport) { params -> val eventLogMessage = params.eventLogMessage diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt index bda4112811..92abf930d4 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt @@ -27,7 +27,7 @@ class EngineProcessModel private constructor( private val _obtainClassId: RdCall, private val _findMethodsInClassMatchingSelected: RdCall, private val _findMethodParamNames: RdCall, - private val _writeSarifReport: RdCall, + private val _writeSarifReport: RdCall, private val _generateTestReport: RdCall ) : RdExtBase() { //companion @@ -73,7 +73,7 @@ class EngineProcessModel private constructor( } - const val serializationHash = 3907671513584285891L + const val serializationHash = -621732450296355904L } override val serializersOwner: ISerializersOwner get() = EngineProcessModel @@ -89,7 +89,7 @@ class EngineProcessModel private constructor( val obtainClassId: RdCall get() = _obtainClassId val findMethodsInClassMatchingSelected: RdCall get() = _findMethodsInClassMatchingSelected val findMethodParamNames: RdCall get() = _findMethodParamNames - val writeSarifReport: RdCall get() = _writeSarifReport + val writeSarifReport: RdCall get() = _writeSarifReport val generateTestReport: RdCall get() = _generateTestReport //methods //initializer @@ -133,7 +133,7 @@ class EngineProcessModel private constructor( RdCall(FrameworkMarshallers.String, FrameworkMarshallers.ByteArray), RdCall(FindMethodsInClassMatchingSelectedArguments, FindMethodsInClassMatchingSelectedResult), RdCall(FindMethodParamNamesArguments, FindMethodParamNamesResult), - RdCall(WriteSarifReportArguments, FrameworkMarshallers.Void), + RdCall(WriteSarifReportArguments, FrameworkMarshallers.String), RdCall(GenerateTestReportArgs, GenerateTestReportResult) ) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt index f651d568e1..75ffbe0a6e 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt @@ -1,5 +1,6 @@ package org.utbot.intellij.plugin.generator +import com.intellij.analysis.AnalysisScope import com.intellij.codeInsight.CodeInsightUtil import com.intellij.codeInsight.FileModificationService import com.intellij.ide.fileTemplates.FileTemplateManager @@ -7,6 +8,7 @@ import com.intellij.ide.fileTemplates.FileTemplateUtil import com.intellij.ide.fileTemplates.JavaTemplateUtil import com.intellij.ide.highlighter.JavaFileType import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.invokeLater import com.intellij.openapi.application.runReadAction import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction @@ -51,12 +53,12 @@ import org.utbot.framework.codegen.model.UtilClassKind import org.utbot.framework.codegen.model.UtilClassKind.Companion.UT_UTILS_CLASS_NAME import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.intellij.plugin.inspection.UnitTestBotInspectionManager import org.utbot.intellij.plugin.models.GenerateTestsModel import org.utbot.intellij.plugin.models.packageName import org.utbot.intellij.plugin.process.EngineProcess import org.utbot.intellij.plugin.process.RdTestGenerationResult import org.utbot.intellij.plugin.sarif.SarifReportIdea -import org.utbot.intellij.plugin.sarif.SourceFindingStrategyIdea import org.utbot.intellij.plugin.ui.* import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater @@ -65,6 +67,7 @@ import org.utbot.intellij.plugin.util.IntelliJApiHelper.Target.* import org.utbot.intellij.plugin.util.IntelliJApiHelper.run import org.utbot.intellij.plugin.util.RunConfigurationHelper import org.utbot.intellij.plugin.util.extractClassMethodsIncludingNested +import org.utbot.sarif.Sarif import org.utbot.sarif.SarifReport import java.nio.file.Path import java.util.concurrent.CancellationException @@ -94,6 +97,7 @@ object CodeGenerationController { val allTestPackages = getPackageDirectories(baseTestDirectory) val latch = CountDownLatch(classesWithTests.size) val testFilesPointers = mutableListOf>() + val srcClassPathToSarifReport = mutableMapOf() val utilClassListener = UtilClassListener() var index = 0 for ((srcClass, generateResult) in classesWithTests) { @@ -119,6 +123,7 @@ object CodeGenerationController { cut, testClass, testFilePointer, + srcClassPathToSarifReport, model, latch, utilClassListener, @@ -153,6 +158,10 @@ object CodeGenerationController { } proc.forceTermination() UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.SARIF, "Start tests with coverage", 1.0) + + invokeLater { + runInspectionsIfNeeded(model.project, srcClassPathToSarifReport) + } } } } @@ -161,6 +170,25 @@ object CodeGenerationController { } } + /** + * Runs the UTBot inspection if there are detected errors. + */ + private fun runInspectionsIfNeeded( + project: Project, + srcClassPathToSarifReport: MutableMap + ) { + val sarifHasResults = srcClassPathToSarifReport.any { (_, sarif) -> + sarif.getAllResults().isNotEmpty() + } + if (!sarifHasResults) { + return + } + UnitTestBotInspectionManager + .getInstance(project, srcClassPathToSarifReport) + .createNewGlobalContext() + .doInspections(AnalysisScope(project)) + } + private fun proceedTestReport(proc: EngineProcess, model: GenerateTestsModel) { try { // Parametrized tests are not supported in tests report yet @@ -583,6 +611,7 @@ object CodeGenerationController { classUnderTest: ClassId, testClass: PsiClass, filePointer: SmartPsiElementPointer, + srcClassPathToSarifReport: MutableMap, model: GenerateTestsModel, reportsCountDown: CountDownLatch, utilClassListener: UtilClassListener, @@ -661,7 +690,8 @@ object CodeGenerationController { // uploading formatted code val file = filePointer.containingFile - saveSarifReport( + val srcClassPath = srcClass.containingFile.virtualFile.toNioPath() + val sarifReport = saveSarifReport( proc, testSetsId, testClassUpdated, @@ -669,8 +699,11 @@ object CodeGenerationController { model, reportsCountDown, file?.text ?: generatedTestsCode, + srcClassPathToSarifReport, + srcClassPath, indicator ) + unblockDocument(testClassUpdated.project, editor.document) } } @@ -706,13 +739,26 @@ object CodeGenerationController { model: GenerateTestsModel, reportsCountDown: CountDownLatch, generatedTestsCode: String, + srcClassPathToSarifReport: MutableMap, + srcClassPath: Path, indicator: ProgressIndicator ) { val project = model.project try { // saving sarif report - SarifReportIdea.createAndSave(proc, testSetsId, testClassId, model, generatedTestsCode, testClass, reportsCountDown, indicator) + SarifReportIdea.createAndSave( + proc, + testSetsId, + testClassId, + model, + generatedTestsCode, + testClass, + reportsCountDown, + srcClassPathToSarifReport, + srcClassPath, + indicator + ) } catch (e: Exception) { logger.error(e) { "error in saving sarif report"} showErrorDialogLater( diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/AnalyzeStackTraceFix.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/AnalyzeStackTraceFix.kt new file mode 100644 index 0000000000..0ebb9a5c8e --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/AnalyzeStackTraceFix.kt @@ -0,0 +1,45 @@ +package org.utbot.intellij.plugin.inspection + +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.icons.AllIcons +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.Project +import com.intellij.unscramble.AnalyzeStacktraceUtil + +/** + * Button that launches the built-in "Analyze Stack Trace" action. Displayed as a quick fix. + * + * @param exceptionMessage short description of the detected exception. + * @param stackTraceLines list of strings of the form "className.methodName(fileName:lineNumber)". + */ +class AnalyzeStackTraceFix( + private val exceptionMessage: String, + private val stackTraceLines: List +) : LocalQuickFix { + + /** + * Without `invokeLater` the [com.intellij.execution.impl.ConsoleViewImpl.myPredefinedFilters] will not be filled. + * + * See [com.intellij.execution.impl.ConsoleViewImpl.createCompositeFilter] for more details. + */ + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val stackTraceContent = stackTraceLines.joinToString("\n") { "at $it" } + ApplicationManager.getApplication().invokeLater { + AnalyzeStacktraceUtil.addConsole( + /* project = */ project, + /* consoleFactory = */ null, + /* tabTitle = */ "StackTrace", + /* text = */ "$exceptionMessage\n\n$stackTraceContent", + /* icon = */ AllIcons.Actions.Lightning + ) + } + } + + /** + * This text is displayed on the quick fix button. + */ + override fun getName() = "Analyze stack trace" + + override fun getFamilyName() = name +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionContext.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionContext.kt new file mode 100644 index 0000000000..e44cdb5de0 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionContext.kt @@ -0,0 +1,62 @@ +package org.utbot.intellij.plugin.inspection + +import com.intellij.codeInspection.ex.* +import com.intellij.codeInspection.ui.InspectionToolPresentation +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.NotNullLazyValue +import com.intellij.ui.content.ContentManager +import org.utbot.sarif.Sarif +import java.nio.file.Path +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentMap + +/** + * Overrides some methods of [GlobalInspectionContextImpl] to satisfy the logic of [UnitTestBotInspectionTool]. + */ +class UnitTestBotInspectionContext( + project: Project, + contentManager: NotNullLazyValue, + private val srcClassPathToSarifReport: MutableMap +) : GlobalInspectionContextImpl(project, contentManager) { + + /** + * See [GlobalInspectionContextImpl.myPresentationMap] for more details. + */ + private val myPresentationMap: ConcurrentMap, InspectionToolPresentation> = + ConcurrentHashMap() + + private val globalInspectionToolWrapper by lazy { + val utbotInspectionTool = UnitTestBotInspectionTool.getInstance(srcClassPathToSarifReport) + GlobalInspectionToolWrapper(utbotInspectionTool).also { + it.initialize(/* context = */ this) + } + } + + /** + * Returns [InspectionProfileImpl] with only one inspection tool - [UnitTestBotInspectionTool]. + */ + override fun getCurrentProfile(): InspectionProfileImpl { + val supplier = InspectionToolsSupplier.Simple(listOf(globalInspectionToolWrapper)) + return InspectionProfileImpl("UnitTestBot", supplier, BASE_PROFILE) + } + + override fun close(noSuspiciousCodeFound: Boolean) { + myPresentationMap.clear() + super.close(noSuspiciousCodeFound) + } + + override fun cleanup() { + myPresentationMap.clear() + super.cleanup() + } + + /** + * Overriding is needed to provide [UnitTestBotInspectionToolPresentation] + * instead of the standard implementation of the [InspectionToolPresentation]. + */ + override fun getPresentation(toolWrapper: InspectionToolWrapper<*, *>): InspectionToolPresentation { + return myPresentationMap.computeIfAbsent(toolWrapper) { + UnitTestBotInspectionToolPresentation(globalInspectionToolWrapper, context = this) + } + } +} diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionManager.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionManager.kt new file mode 100644 index 0000000000..82ed4e87f8 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionManager.kt @@ -0,0 +1,39 @@ +package org.utbot.intellij.plugin.inspection + +import com.intellij.codeInspection.ex.GlobalInspectionContextImpl +import com.intellij.codeInspection.ex.InspectionManagerEx +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.NotNullLazyValue +import com.intellij.ui.content.ContentManager +import org.utbot.sarif.Sarif +import java.nio.file.Path + +/** + * Overrides some methods of [InspectionManagerEx] to satisfy the logic of [UnitTestBotInspectionTool]. + */ +class UnitTestBotInspectionManager(project: Project) : InspectionManagerEx(project) { + + private var srcClassPathToSarifReport: MutableMap = mutableMapOf() + + companion object { + fun getInstance(project: Project, srcClassPathToSarifReport: MutableMap) = + UnitTestBotInspectionManager(project).also { + it.srcClassPathToSarifReport = srcClassPathToSarifReport + } + } + + /** + * See [InspectionManagerEx.myContentManager] for more details. + */ + private val myContentManager: NotNullLazyValue by lazy { + NotNullLazyValue.createValue { + getProblemsViewContentManager(project) + } + } + + /** + * Overriding is needed to provide [UnitTestBotInspectionContext] instead of [GlobalInspectionContextImpl]. + */ + override fun createNewGlobalContext(): GlobalInspectionContextImpl = + UnitTestBotInspectionContext(project, myContentManager, srcClassPathToSarifReport) +} diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionTool.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionTool.kt new file mode 100644 index 0000000000..469d0527fb --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionTool.kt @@ -0,0 +1,115 @@ +package org.utbot.intellij.plugin.inspection + +import com.intellij.codeInspection.* +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiFile +import org.utbot.sarif.Sarif +import org.utbot.sarif.SarifRegion +import org.utbot.sarif.SarifResult +import java.nio.file.Path + +/** + * Global inspection tool that displays detected errors from the SARIF report. + */ +class UnitTestBotInspectionTool : GlobalSimpleInspectionTool() { + + /** + * Map from the path to the class under test to [Sarif] for it. + */ + private var srcClassPathToSarifReport: MutableMap = mutableMapOf() + + companion object { + fun getInstance(srcClassPathToSarifReport: MutableMap) = + UnitTestBotInspectionTool().also { + it.srcClassPathToSarifReport = srcClassPathToSarifReport + } + } + + override fun getShortName() = "UnitTestBotInspectionTool" + + override fun getDisplayName() = "Unchecked exceptions" + + override fun getGroupDisplayName() = "Errors detected by UnitTestBot" + + /** + * Appends all the errors from the SARIF report for [psiFile] to the [problemDescriptionsProcessor]. + */ + override fun checkFile( + psiFile: PsiFile, + manager: InspectionManager, + problemsHolder: ProblemsHolder, + globalContext: GlobalInspectionContext, + problemDescriptionsProcessor: ProblemDescriptionsProcessor + ) { + val sarifReport = srcClassPathToSarifReport[psiFile.virtualFile.toNioPath()] + ?: return // no results for this file + + for (sarifResult in sarifReport.getAllResults()) { + val srcFileLocation = sarifResult.locations.firstOrNull() + ?: continue + + val errorRegion = srcFileLocation.physicalLocation.region + val errorTextRange = getTextRange(problemsHolder.project, psiFile, errorRegion) + + // see `org.utbot.sarif.SarifReport.processUncheckedException` for the message template + val (exceptionMessage, testCaseMessage) = + sarifResult.message.text.split('\n').take(2) + val sarifResultMessage = "$exceptionMessage $testCaseMessage" + + val testFileLocation = sarifResult.relatedLocations.firstOrNull()?.physicalLocation + val viewGeneratedTestFix = testFileLocation?.let { + ViewGeneratedTestFix( + testFileRelativePath = it.artifactLocation.uri, + lineNumber = it.region.startLine, + columnNumber = it.region.startColumn ?: 1 + ) + } + + val stackTraceLines = sarifResult.extractStackTraceLines() + val analyzeStackTraceFix = AnalyzeStackTraceFix(exceptionMessage, stackTraceLines) + + val problemDescriptor = problemsHolder.manager.createProblemDescriptor( + psiFile, + errorTextRange, + sarifResultMessage, + ProblemHighlightType.ERROR, + /* onTheFly = */ true, + viewGeneratedTestFix, + analyzeStackTraceFix + ) + problemDescriptionsProcessor.addProblemElement( + globalContext.refManager.getReference(psiFile), + problemDescriptor + ) + } + } + + // internal + + /** + * Converts [SarifRegion] to the [TextRange] of the given [file]. + */ + private fun getTextRange(project: Project, file: PsiFile, region: SarifRegion): TextRange { + val documentManager = PsiDocumentManager.getInstance(project) + val document = documentManager.getDocument(file.containingFile) + ?: return TextRange.EMPTY_RANGE + + val lineNumber = region.startLine - 1 // to 0-based + val columnNumber = region.startColumn ?: 1 + + val lineStartOffset = document.getLineStartOffset(lineNumber) + columnNumber - 1 + val lineEndOffset = document.getLineEndOffset(lineNumber) + return TextRange(lineStartOffset, lineEndOffset) + } + + private fun SarifResult.extractStackTraceLines(): List = + this.codeFlows.flatMap { sarifCodeFlow -> + sarifCodeFlow.threadFlows.flatMap { sarifThreadFlow -> + sarifThreadFlow.locations.map { sarifFlowLocationWrapper -> + sarifFlowLocationWrapper.location.message.text + } + } + }.reversed() +} diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionToolPresentation.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionToolPresentation.kt new file mode 100644 index 0000000000..264a37f32e --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionToolPresentation.kt @@ -0,0 +1,24 @@ +package org.utbot.intellij.plugin.inspection + +import com.intellij.codeInspection.CommonProblemDescriptor +import com.intellij.codeInspection.ex.InspectionToolWrapper +import com.intellij.codeInspection.ui.DefaultInspectionToolPresentation + +/** + * Overrides [resolveProblem] to avoid suppressing quick fix buttons. + */ +class UnitTestBotInspectionToolPresentation( + toolWrapper: InspectionToolWrapper<*, *>, + context: UnitTestBotInspectionContext +) : DefaultInspectionToolPresentation(toolWrapper, context) { + + /** + * This method is called when the user clicks on the quick fix button. + * In the case of [UnitTestBotInspectionTool] we do not want to remove the button after applying the fix. + * + * See [DefaultInspectionToolPresentation.resolveProblem] for more details. + */ + override fun resolveProblem(descriptor: CommonProblemDescriptor) { + // nothing + } +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/ViewGeneratedTestFix.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/ViewGeneratedTestFix.kt new file mode 100644 index 0000000000..d0c4605e76 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/ViewGeneratedTestFix.kt @@ -0,0 +1,46 @@ +package org.utbot.intellij.plugin.inspection + +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.openapi.editor.LogicalPosition +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VfsUtil +import org.utbot.common.PathUtil.toPath + +/** + * Button with a link to the [testFileRelativePath]. Displayed as a quick fix. + * + * @param testFileRelativePath path to the generated test file. + * Should be relative to the project root + * @param lineNumber one-based line number + * @param columnNumber one-based column number + */ +class ViewGeneratedTestFix( + val testFileRelativePath: String, + val lineNumber: Int, + val columnNumber: Int +) : LocalQuickFix { + + /** + * Navigates the user to the [lineNumber] line of the [testFileRelativePath] file. + */ + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val testFileAbsolutePath = project.basePath?.toPath()?.resolve(testFileRelativePath) ?: return + val virtualFile = VfsUtil.findFile(testFileAbsolutePath, /* refreshIfNeeded = */ true) ?: return + val editor = FileEditorManager.getInstance(project) + editor.openFile(virtualFile, /* focusEditor = */ true) + val caretModel = editor.selectedTextEditor?.caretModel ?: return + val zeroBasedPosition = LogicalPosition(lineNumber - 1, columnNumber - 1) + caretModel.moveToLogicalPosition(zeroBasedPosition) + val selectionModel = editor.selectedTextEditor?.selectionModel ?: return + selectionModel.selectLineAtCaret() + } + + /** + * This text is displayed on the quick fix button. + */ + override fun getName() = "View generated test" + + override fun getFamilyName() = name +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt index 9b8c814108..b27680ff42 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt @@ -7,6 +7,7 @@ import com.intellij.psi.PsiMethod import com.intellij.psi.impl.file.impl.JavaFileManager import com.intellij.psi.search.GlobalSearchScope import com.intellij.refactoring.util.classMembers.MemberInfo +import com.jetbrains.rd.framework.util.asCompletableFuture import com.jetbrains.rd.util.Logger import com.jetbrains.rd.util.lifetime.Lifetime import com.jetbrains.rd.util.lifetime.throwIfNotAlive @@ -37,6 +38,7 @@ import org.utbot.rd.ProcessWithRdServer import org.utbot.rd.loggers.UtRdKLoggerFactory import org.utbot.rd.rdPortArgument import org.utbot.rd.startUtProcessWithRdServer +import org.utbot.sarif.Sarif import org.utbot.sarif.SourceFindingStrategy import java.io.File import java.nio.file.Path @@ -325,7 +327,7 @@ class EngineProcess(parent: Lifetime, val project: Project) { testSetsId: Long, generatedTestsCode: String, sourceFindingStrategy: SourceFindingStrategy - ) = runBlocking { + ): String = runBlocking { current!!.protocol.rdSourceFindingStrategy.let { it.getSourceFile.set { params -> DumbService.getInstance(project).runReadActionInSmartMode { @@ -349,7 +351,9 @@ class EngineProcess(parent: Lifetime, val project: Project) { } } } - engineModel().writeSarifReport.start(WriteSarifReportArguments(testSetsId, reportFilePath.pathString, generatedTestsCode)) + engineModel().writeSarifReport.startSuspending( + WriteSarifReportArguments(testSetsId, reportFilePath.pathString, generatedTestsCode) + ) } fun generateTestsReport(model: GenerateTestsModel, eventLogMessage: String?): Triple = runBlocking { diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SarifReportIdea.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SarifReportIdea.kt index cd46beff64..8939abcdf4 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SarifReportIdea.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SarifReportIdea.kt @@ -12,6 +12,8 @@ import org.utbot.framework.plugin.api.ClassId import org.utbot.intellij.plugin.generator.UtTestsDialogProcessor import org.utbot.intellij.plugin.models.GenerateTestsModel import org.utbot.intellij.plugin.process.EngineProcess +import org.utbot.sarif.Sarif +import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath import org.utbot.intellij.plugin.util.IntelliJApiHelper import java.nio.file.Path @@ -29,17 +31,23 @@ object SarifReportIdea { generatedTestsCode: String, psiClass: PsiClass, reportsCountDown: CountDownLatch, + srcClassPathToSarifReport: MutableMap, + srcClassPath: Path, indicator: ProgressIndicator ) { UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.SARIF, "Generate SARIF report for ${classId.name}", .5) // building the path to the report file val classFqn = classId.name - val (sarifReportsPath, sourceFinding) = WriteAction.computeAndWait, Exception> { model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot) to SourceFindingStrategyIdea(psiClass) } + val (sarifReportsPath, sourceFinding) = WriteAction.computeAndWait, Exception> { + model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot) to SourceFindingStrategyIdea(psiClass) + } val reportFilePath = sarifReportsPath.resolve("${classFqnToPath(classFqn)}Report.sarif") IntelliJApiHelper.run(IntelliJApiHelper.Target.THREAD_POOL, indicator) { try { - proc.writeSarif(reportFilePath, testSetsId, generatedTestsCode, sourceFinding) + val sarifReportAsJson = proc.writeSarif(reportFilePath, testSetsId, generatedTestsCode, sourceFinding) + val sarifReport = Sarif.fromJson(sarifReportAsJson) + srcClassPathToSarifReport[srcClassPath] = sarifReport } catch (e: Exception) { logger.error { e } } finally { diff --git a/utbot-intellij/src/main/resources/META-INF/plugin.xml b/utbot-intellij/src/main/resources/META-INF/plugin.xml index 81f3409165..a809967b5b 100644 --- a/utbot-intellij/src/main/resources/META-INF/plugin.xml +++ b/utbot-intellij/src/main/resources/META-INF/plugin.xml @@ -88,4 +88,15 @@ ]]> + + + + diff --git a/utbot-intellij/src/main/resources/inspectionDescriptions/UnitTestBotInspectionTool.html b/utbot-intellij/src/main/resources/inspectionDescriptions/UnitTestBotInspectionTool.html new file mode 100644 index 0000000000..a01f16e673 --- /dev/null +++ b/utbot-intellij/src/main/resources/inspectionDescriptions/UnitTestBotInspectionTool.html @@ -0,0 +1,11 @@ + + +

Reports unchecked exceptions detected by UnitTestBot.

+

Example:

+
+void foo(int a) {
+    return 1 / a; // throws ArithmeticException when `a == 0`
+}
+
+ + \ No newline at end of file diff --git a/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt b/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt index 3dc39406cf..b10382b545 100644 --- a/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt +++ b/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt @@ -132,7 +132,7 @@ object EngineProcessModel : Ext(EngineProcessProtocolRoot) { call("obtainClassId", PredefinedType.string, array(PredefinedType.byte)).async call("findMethodsInClassMatchingSelected", findMethodsInClassMatchingSelectedArguments, findMethodsInClassMatchingSelectedResult).async call("findMethodParamNames", findMethodParamNamesArguments, findMethodParamNamesResult).async - call("writeSarifReport", writeSarifReportArguments, PredefinedType.void).async + call("writeSarifReport", writeSarifReportArguments, PredefinedType.string).async call("generateTestReport", generateTestReportArgs, generateTestReportResult).async } } \ No newline at end of file