Skip to content

Commit 0a3d74c

Browse files
authored
Built-in SARIF reports visualizer (#1166)
1 parent 7c28142 commit 0a3d74c

File tree

15 files changed

+431
-20
lines changed

15 files changed

+431
-20
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ target/
77
.idea/
88
.gradle/
99
*.log
10-
*.rdgen
10+
*.rdgen

utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineMain.kt

+7-7
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,13 @@ private fun EngineProcessModel.setup(
176176
synchronizer.measureExecutionForTermination(writeSarifReport) { params ->
177177
val reportFilePath = Paths.get(params.reportFilePath)
178178
reportFilePath.parent.toFile().mkdirs()
179-
reportFilePath.toFile().writeText(
180-
SarifReport(
181-
testSets[params.testSetsId]!!,
182-
params.generatedTestsCode,
183-
RdSourceFindingStrategyFacade(realProtocol.rdSourceFindingStrategy)
184-
).createReport().toJson()
185-
)
179+
val sarifReport = SarifReport(
180+
testSets[params.testSetsId]!!,
181+
params.generatedTestsCode,
182+
RdSourceFindingStrategyFacade(realProtocol.rdSourceFindingStrategy)
183+
).createReport().toJson()
184+
reportFilePath.toFile().writeText(sarifReport)
185+
sarifReport
186186
}
187187
synchronizer.measureExecutionForTermination(generateTestReport) { params ->
188188
val eventLogMessage = params.eventLogMessage

utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class EngineProcessModel private constructor(
2727
private val _obtainClassId: RdCall<String, ByteArray>,
2828
private val _findMethodsInClassMatchingSelected: RdCall<FindMethodsInClassMatchingSelectedArguments, FindMethodsInClassMatchingSelectedResult>,
2929
private val _findMethodParamNames: RdCall<FindMethodParamNamesArguments, FindMethodParamNamesResult>,
30-
private val _writeSarifReport: RdCall<WriteSarifReportArguments, Unit>,
30+
private val _writeSarifReport: RdCall<WriteSarifReportArguments, String>,
3131
private val _generateTestReport: RdCall<GenerateTestReportArgs, GenerateTestReportResult>
3232
) : RdExtBase() {
3333
//companion
@@ -73,7 +73,7 @@ class EngineProcessModel private constructor(
7373
}
7474

7575

76-
const val serializationHash = 3907671513584285891L
76+
const val serializationHash = -621732450296355904L
7777

7878
}
7979
override val serializersOwner: ISerializersOwner get() = EngineProcessModel
@@ -89,7 +89,7 @@ class EngineProcessModel private constructor(
8989
val obtainClassId: RdCall<String, ByteArray> get() = _obtainClassId
9090
val findMethodsInClassMatchingSelected: RdCall<FindMethodsInClassMatchingSelectedArguments, FindMethodsInClassMatchingSelectedResult> get() = _findMethodsInClassMatchingSelected
9191
val findMethodParamNames: RdCall<FindMethodParamNamesArguments, FindMethodParamNamesResult> get() = _findMethodParamNames
92-
val writeSarifReport: RdCall<WriteSarifReportArguments, Unit> get() = _writeSarifReport
92+
val writeSarifReport: RdCall<WriteSarifReportArguments, String> get() = _writeSarifReport
9393
val generateTestReport: RdCall<GenerateTestReportArgs, GenerateTestReportResult> get() = _generateTestReport
9494
//methods
9595
//initializer
@@ -133,7 +133,7 @@ class EngineProcessModel private constructor(
133133
RdCall<String, ByteArray>(FrameworkMarshallers.String, FrameworkMarshallers.ByteArray),
134134
RdCall<FindMethodsInClassMatchingSelectedArguments, FindMethodsInClassMatchingSelectedResult>(FindMethodsInClassMatchingSelectedArguments, FindMethodsInClassMatchingSelectedResult),
135135
RdCall<FindMethodParamNamesArguments, FindMethodParamNamesResult>(FindMethodParamNamesArguments, FindMethodParamNamesResult),
136-
RdCall<WriteSarifReportArguments, Unit>(WriteSarifReportArguments, FrameworkMarshallers.Void),
136+
RdCall<WriteSarifReportArguments, String>(WriteSarifReportArguments, FrameworkMarshallers.String),
137137
RdCall<GenerateTestReportArgs, GenerateTestReportResult>(GenerateTestReportArgs, GenerateTestReportResult)
138138
)
139139

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt

+49-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package org.utbot.intellij.plugin.generator
22

3+
import com.intellij.analysis.AnalysisScope
34
import com.intellij.codeInsight.CodeInsightUtil
45
import com.intellij.codeInsight.FileModificationService
56
import com.intellij.ide.fileTemplates.FileTemplateManager
67
import com.intellij.ide.fileTemplates.FileTemplateUtil
78
import com.intellij.ide.fileTemplates.JavaTemplateUtil
89
import com.intellij.ide.highlighter.JavaFileType
910
import com.intellij.openapi.application.ApplicationManager
11+
import com.intellij.openapi.application.invokeLater
1012
import com.intellij.openapi.application.runReadAction
1113
import com.intellij.openapi.application.runWriteAction
1214
import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction
@@ -51,12 +53,12 @@ import org.utbot.framework.codegen.model.UtilClassKind
5153
import org.utbot.framework.codegen.model.UtilClassKind.Companion.UT_UTILS_CLASS_NAME
5254
import org.utbot.framework.plugin.api.ClassId
5355
import org.utbot.framework.plugin.api.CodegenLanguage
56+
import org.utbot.intellij.plugin.inspection.UnitTestBotInspectionManager
5457
import org.utbot.intellij.plugin.models.GenerateTestsModel
5558
import org.utbot.intellij.plugin.models.packageName
5659
import org.utbot.intellij.plugin.process.EngineProcess
5760
import org.utbot.intellij.plugin.process.RdTestGenerationResult
5861
import org.utbot.intellij.plugin.sarif.SarifReportIdea
59-
import org.utbot.intellij.plugin.sarif.SourceFindingStrategyIdea
6062
import org.utbot.intellij.plugin.ui.*
6163
import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath
6264
import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater
@@ -65,6 +67,7 @@ import org.utbot.intellij.plugin.util.IntelliJApiHelper.Target.*
6567
import org.utbot.intellij.plugin.util.IntelliJApiHelper.run
6668
import org.utbot.intellij.plugin.util.RunConfigurationHelper
6769
import org.utbot.intellij.plugin.util.extractClassMethodsIncludingNested
70+
import org.utbot.sarif.Sarif
6871
import org.utbot.sarif.SarifReport
6972
import java.nio.file.Path
7073
import java.util.concurrent.CancellationException
@@ -94,6 +97,7 @@ object CodeGenerationController {
9497
val allTestPackages = getPackageDirectories(baseTestDirectory)
9598
val latch = CountDownLatch(classesWithTests.size)
9699
val testFilesPointers = mutableListOf<SmartPsiElementPointer<PsiFile>>()
100+
val srcClassPathToSarifReport = mutableMapOf<Path, Sarif>()
97101
val utilClassListener = UtilClassListener()
98102
var index = 0
99103
for ((srcClass, generateResult) in classesWithTests) {
@@ -119,6 +123,7 @@ object CodeGenerationController {
119123
cut,
120124
testClass,
121125
testFilePointer,
126+
srcClassPathToSarifReport,
122127
model,
123128
latch,
124129
utilClassListener,
@@ -153,6 +158,10 @@ object CodeGenerationController {
153158
}
154159
proc.forceTermination()
155160
UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.SARIF, "Start tests with coverage", 1.0)
161+
162+
invokeLater {
163+
runInspectionsIfNeeded(model.project, srcClassPathToSarifReport)
164+
}
156165
}
157166
}
158167
}
@@ -161,6 +170,25 @@ object CodeGenerationController {
161170
}
162171
}
163172

173+
/**
174+
* Runs the UTBot inspection if there are detected errors.
175+
*/
176+
private fun runInspectionsIfNeeded(
177+
project: Project,
178+
srcClassPathToSarifReport: MutableMap<Path, Sarif>
179+
) {
180+
val sarifHasResults = srcClassPathToSarifReport.any { (_, sarif) ->
181+
sarif.getAllResults().isNotEmpty()
182+
}
183+
if (!sarifHasResults) {
184+
return
185+
}
186+
UnitTestBotInspectionManager
187+
.getInstance(project, srcClassPathToSarifReport)
188+
.createNewGlobalContext()
189+
.doInspections(AnalysisScope(project))
190+
}
191+
164192
private fun proceedTestReport(proc: EngineProcess, model: GenerateTestsModel) {
165193
try {
166194
// Parametrized tests are not supported in tests report yet
@@ -583,6 +611,7 @@ object CodeGenerationController {
583611
classUnderTest: ClassId,
584612
testClass: PsiClass,
585613
filePointer: SmartPsiElementPointer<PsiFile>,
614+
srcClassPathToSarifReport: MutableMap<Path, Sarif>,
586615
model: GenerateTestsModel,
587616
reportsCountDown: CountDownLatch,
588617
utilClassListener: UtilClassListener,
@@ -661,16 +690,20 @@ object CodeGenerationController {
661690
// uploading formatted code
662691
val file = filePointer.containingFile
663692

664-
saveSarifReport(
693+
val srcClassPath = srcClass.containingFile.virtualFile.toNioPath()
694+
val sarifReport = saveSarifReport(
665695
proc,
666696
testSetsId,
667697
testClassUpdated,
668698
classUnderTest,
669699
model,
670700
reportsCountDown,
671701
file?.text ?: generatedTestsCode,
702+
srcClassPathToSarifReport,
703+
srcClassPath,
672704
indicator
673705
)
706+
674707
unblockDocument(testClassUpdated.project, editor.document)
675708
}
676709
}
@@ -706,13 +739,26 @@ object CodeGenerationController {
706739
model: GenerateTestsModel,
707740
reportsCountDown: CountDownLatch,
708741
generatedTestsCode: String,
742+
srcClassPathToSarifReport: MutableMap<Path, Sarif>,
743+
srcClassPath: Path,
709744
indicator: ProgressIndicator
710745
) {
711746
val project = model.project
712747

713748
try {
714749
// saving sarif report
715-
SarifReportIdea.createAndSave(proc, testSetsId, testClassId, model, generatedTestsCode, testClass, reportsCountDown, indicator)
750+
SarifReportIdea.createAndSave(
751+
proc,
752+
testSetsId,
753+
testClassId,
754+
model,
755+
generatedTestsCode,
756+
testClass,
757+
reportsCountDown,
758+
srcClassPathToSarifReport,
759+
srcClassPath,
760+
indicator
761+
)
716762
} catch (e: Exception) {
717763
logger.error(e) { "error in saving sarif report"}
718764
showErrorDialogLater(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.utbot.intellij.plugin.inspection
2+
3+
import com.intellij.codeInspection.LocalQuickFix
4+
import com.intellij.codeInspection.ProblemDescriptor
5+
import com.intellij.icons.AllIcons
6+
import com.intellij.openapi.application.ApplicationManager
7+
import com.intellij.openapi.project.Project
8+
import com.intellij.unscramble.AnalyzeStacktraceUtil
9+
10+
/**
11+
* Button that launches the built-in "Analyze Stack Trace" action. Displayed as a quick fix.
12+
*
13+
* @param exceptionMessage short description of the detected exception.
14+
* @param stackTraceLines list of strings of the form "className.methodName(fileName:lineNumber)".
15+
*/
16+
class AnalyzeStackTraceFix(
17+
private val exceptionMessage: String,
18+
private val stackTraceLines: List<String>
19+
) : LocalQuickFix {
20+
21+
/**
22+
* Without `invokeLater` the [com.intellij.execution.impl.ConsoleViewImpl.myPredefinedFilters] will not be filled.
23+
*
24+
* See [com.intellij.execution.impl.ConsoleViewImpl.createCompositeFilter] for more details.
25+
*/
26+
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
27+
val stackTraceContent = stackTraceLines.joinToString("\n") { "at $it" }
28+
ApplicationManager.getApplication().invokeLater {
29+
AnalyzeStacktraceUtil.addConsole(
30+
/* project = */ project,
31+
/* consoleFactory = */ null,
32+
/* tabTitle = */ "StackTrace",
33+
/* text = */ "$exceptionMessage\n\n$stackTraceContent",
34+
/* icon = */ AllIcons.Actions.Lightning
35+
)
36+
}
37+
}
38+
39+
/**
40+
* This text is displayed on the quick fix button.
41+
*/
42+
override fun getName() = "Analyze stack trace"
43+
44+
override fun getFamilyName() = name
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package org.utbot.intellij.plugin.inspection
2+
3+
import com.intellij.codeInspection.ex.*
4+
import com.intellij.codeInspection.ui.InspectionToolPresentation
5+
import com.intellij.openapi.project.Project
6+
import com.intellij.openapi.util.NotNullLazyValue
7+
import com.intellij.ui.content.ContentManager
8+
import org.utbot.sarif.Sarif
9+
import java.nio.file.Path
10+
import java.util.concurrent.ConcurrentHashMap
11+
import java.util.concurrent.ConcurrentMap
12+
13+
/**
14+
* Overrides some methods of [GlobalInspectionContextImpl] to satisfy the logic of [UnitTestBotInspectionTool].
15+
*/
16+
class UnitTestBotInspectionContext(
17+
project: Project,
18+
contentManager: NotNullLazyValue<out ContentManager>,
19+
private val srcClassPathToSarifReport: MutableMap<Path, Sarif>
20+
) : GlobalInspectionContextImpl(project, contentManager) {
21+
22+
/**
23+
* See [GlobalInspectionContextImpl.myPresentationMap] for more details.
24+
*/
25+
private val myPresentationMap: ConcurrentMap<InspectionToolWrapper<*, *>, InspectionToolPresentation> =
26+
ConcurrentHashMap()
27+
28+
private val globalInspectionToolWrapper by lazy {
29+
val utbotInspectionTool = UnitTestBotInspectionTool.getInstance(srcClassPathToSarifReport)
30+
GlobalInspectionToolWrapper(utbotInspectionTool).also {
31+
it.initialize(/* context = */ this)
32+
}
33+
}
34+
35+
/**
36+
* Returns [InspectionProfileImpl] with only one inspection tool - [UnitTestBotInspectionTool].
37+
*/
38+
override fun getCurrentProfile(): InspectionProfileImpl {
39+
val supplier = InspectionToolsSupplier.Simple(listOf(globalInspectionToolWrapper))
40+
return InspectionProfileImpl("UnitTestBot", supplier, BASE_PROFILE)
41+
}
42+
43+
override fun close(noSuspiciousCodeFound: Boolean) {
44+
myPresentationMap.clear()
45+
super.close(noSuspiciousCodeFound)
46+
}
47+
48+
override fun cleanup() {
49+
myPresentationMap.clear()
50+
super.cleanup()
51+
}
52+
53+
/**
54+
* Overriding is needed to provide [UnitTestBotInspectionToolPresentation]
55+
* instead of the standard implementation of the [InspectionToolPresentation].
56+
*/
57+
override fun getPresentation(toolWrapper: InspectionToolWrapper<*, *>): InspectionToolPresentation {
58+
return myPresentationMap.computeIfAbsent(toolWrapper) {
59+
UnitTestBotInspectionToolPresentation(globalInspectionToolWrapper, context = this)
60+
}
61+
}
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.utbot.intellij.plugin.inspection
2+
3+
import com.intellij.codeInspection.ex.GlobalInspectionContextImpl
4+
import com.intellij.codeInspection.ex.InspectionManagerEx
5+
import com.intellij.openapi.project.Project
6+
import com.intellij.openapi.util.NotNullLazyValue
7+
import com.intellij.ui.content.ContentManager
8+
import org.utbot.sarif.Sarif
9+
import java.nio.file.Path
10+
11+
/**
12+
* Overrides some methods of [InspectionManagerEx] to satisfy the logic of [UnitTestBotInspectionTool].
13+
*/
14+
class UnitTestBotInspectionManager(project: Project) : InspectionManagerEx(project) {
15+
16+
private var srcClassPathToSarifReport: MutableMap<Path, Sarif> = mutableMapOf()
17+
18+
companion object {
19+
fun getInstance(project: Project, srcClassPathToSarifReport: MutableMap<Path, Sarif>) =
20+
UnitTestBotInspectionManager(project).also {
21+
it.srcClassPathToSarifReport = srcClassPathToSarifReport
22+
}
23+
}
24+
25+
/**
26+
* See [InspectionManagerEx.myContentManager] for more details.
27+
*/
28+
private val myContentManager: NotNullLazyValue<ContentManager> by lazy {
29+
NotNullLazyValue.createValue {
30+
getProblemsViewContentManager(project)
31+
}
32+
}
33+
34+
/**
35+
* Overriding is needed to provide [UnitTestBotInspectionContext] instead of [GlobalInspectionContextImpl].
36+
*/
37+
override fun createNewGlobalContext(): GlobalInspectionContextImpl =
38+
UnitTestBotInspectionContext(project, myContentManager, srcClassPathToSarifReport)
39+
}

0 commit comments

Comments
 (0)