@@ -4,6 +4,7 @@ import org.utbot.common.PathUtil.fileExtension
44import org.utbot.common.PathUtil.toPath
55import org.utbot.framework.UtSettings
66import org.utbot.framework.plugin.api.*
7+ import java.nio.file.Path
78import kotlin.io.path.nameWithoutExtension
89
910/* *
@@ -27,6 +28,41 @@ class SarifReport(
2728 reports.fold(Sarif .empty()) { sarif: Sarif , report: String ->
2829 sarif.copy(runs = sarif.runs + Sarif .fromJson(report).runs)
2930 }.toJson()
31+
32+ /* *
33+ * Minimizes SARIF results between several reports.
34+ *
35+ * More complex version of the [SarifReport.minimizeResults].
36+ */
37+ fun minimizeSarifResults (srcPathToSarif : MutableMap <Path , Sarif >): MutableMap <Path , Sarif > {
38+ val pathToSarifResult = srcPathToSarif.entries.flatMap { (path, sarif) ->
39+ sarif.getAllResults().map { sarifResult ->
40+ path to sarifResult
41+ }
42+ }
43+ val groupedResults = pathToSarifResult.groupBy { (_, sarifResult) ->
44+ sarifResult.ruleId to sarifResult.locations
45+ }
46+ val minimizedResults = groupedResults.map { (_, sarifResultsGroup) ->
47+ sarifResultsGroup.minByOrNull { (_, sarifResult) ->
48+ sarifResult.totalCodeFlowLocations()
49+ }!!
50+ }
51+ val groupedByPath = minimizedResults
52+ .groupBy { (path, _) -> path }
53+ .mapValues { (_, resultsWithPath) ->
54+ resultsWithPath.map { (_, sarifResult) -> sarifResult } // remove redundant path
55+ }
56+ val pathToSarifTool = srcPathToSarif.mapValues { (_, sarif) ->
57+ sarif.runs.first().tool
58+ }
59+ val paths = pathToSarifTool.keys intersect groupedByPath.keys
60+ return paths.associateWith { path ->
61+ val sarifTool = pathToSarifTool[path]!!
62+ val sarifResults = groupedByPath[path]!!
63+ Sarif .fromRun(SarifRun (sarifTool, sarifResults))
64+ }.toMutableMap()
65+ }
3066 }
3167
3268 /* *
@@ -67,6 +103,8 @@ class SarifReport(
67103 */
68104 private val relatedLocationId = 1 // for attaching link to generated test in related locations
69105
106+ private val stackTraceLengthForStackOverflow = 50 // stack overflow error may have too many elements
107+
70108 /* *
71109 * Minimizes detected errors and removes duplicates.
72110 *
@@ -125,7 +163,7 @@ class SarifReport(
125163 [Generated test for this case]($relatedLocationId )
126164 """ .trimIndent()
127165 ),
128- getLocations(utExecution, classFqn),
166+ getLocations(method, utExecution, classFqn),
129167 getRelatedLocations(utExecution),
130168 getCodeFlows(method, utExecution, executionFailure)
131169 )
@@ -146,16 +184,23 @@ class SarifReport(
146184 return Pair (sarifResult, sarifRule)
147185 }
148186
149- private fun getLocations (utExecution : UtExecution , classFqn : String? ): List <SarifPhysicalLocationWrapper > {
187+ private fun getLocations (
188+ method : ExecutableId ,
189+ utExecution : UtExecution ,
190+ classFqn : String?
191+ ): List <SarifLocationWrapper > {
150192 if (classFqn == null )
151193 return listOf ()
152- val sourceRelativePath = sourceFinding.getSourceRelativePath(classFqn)
153- val startLine = getLastLineNumber(utExecution, classFqn) ? : defaultLineNumber
154- val sourceCode = sourceFinding.getSourceFile(classFqn)?.readText() ? : " "
194+ val (startLine, classWithErrorFqn) = getLastLineNumberWithClassFqn(method, utExecution, classFqn)
195+ val sourceCode = sourceFinding.getSourceFile(classWithErrorFqn)?.readText() ? : " "
155196 val sourceRegion = SarifRegion .withStartLine(sourceCode, startLine)
197+ val sourceRelativePath = sourceFinding.getSourceRelativePath(classWithErrorFqn)
156198 return listOf (
157199 SarifPhysicalLocationWrapper (
158200 SarifPhysicalLocation (SarifArtifact (sourceRelativePath), sourceRegion)
201+ ),
202+ SarifLogicalLocationsWrapper (
203+ listOf (SarifLogicalLocation (classWithErrorFqn)) // class name without method name
159204 )
160205 )
161206 }
@@ -181,31 +226,9 @@ class SarifReport(
181226 utExecution : UtExecution ,
182227 executionFailure : UtExecutionFailure
183228 ): List <SarifCodeFlow > {
184- /* Example of a typical stack trace:
185- - java.lang.Math.multiplyExact(Math.java:867)
186- - com.abc.Util.multiply(Util.java:10)
187- - com.abc.Util.multiply(Util.java:6)
188- - com.abc.Main.example(Main.java:11) // <- `lastMethodCallIndex`
189- - sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
190- - ...
191- */
192- val stackTrace = executionFailure.exception.stackTrace
193-
194- val lastMethodCallIndex = stackTrace.indexOfLast {
195- it.className == method.classId.name && it.methodName == method.name
196- }
197- if (lastMethodCallIndex == - 1 )
198- return listOf ()
199-
200- val stackTraceFiltered = stackTrace
201- .take(lastMethodCallIndex + 1 ) // taking all elements before the last `method` call
202- .filter {
203- ! it.className.startsWith(" org.utbot." ) // filter all internal calls
204- }
205-
206- val stackTraceResolved = stackTraceFiltered.mapNotNull {
207- findStackTraceElementLocation(it)
208- }.toMutableList()
229+ val stackTraceResolved = filterStackTrace(method, utExecution, executionFailure)
230+ .mapNotNull { findStackTraceElementLocation(it) }
231+ .toMutableList()
209232 if (stackTraceResolved.isEmpty())
210233 return listOf () // empty stack trace is not shown
211234
@@ -235,6 +258,40 @@ class SarifReport(
235258 )
236259 }
237260
261+ private fun filterStackTrace (
262+ method : ExecutableId ,
263+ utExecution : UtExecution ,
264+ executionFailure : UtExecutionFailure
265+ ): List <StackTraceElement > {
266+ /* Example of a typical stack trace:
267+ - java.lang.Math.multiplyExact(Math.java:867)
268+ - com.abc.Util.multiply(Util.java:10)
269+ - com.abc.Util.multiply(Util.java:6)
270+ - com.abc.Main.example(Main.java:11) // <- `lastMethodCallIndex`
271+ - sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
272+ - ...
273+ */
274+ var stackTrace = executionFailure.exception.stackTrace.toList()
275+
276+ val lastMethodCallIndex = stackTrace.indexOfLast {
277+ it.className == method.classId.name && it.methodName == method.name
278+ }
279+ if (lastMethodCallIndex != - 1 ) {
280+ // taking all elements before the last `method` call
281+ stackTrace = stackTrace.take(lastMethodCallIndex + 1 )
282+ }
283+
284+ if (executionFailure.exception is StackOverflowError ) {
285+ stackTrace = stackTrace.takeLast(stackTraceLengthForStackOverflow)
286+ }
287+
288+ val stackTraceFiltered = stackTrace.filter {
289+ ! it.className.startsWith(" org.utbot." ) // filter all internal calls
290+ }
291+
292+ return stackTraceFiltered
293+ }
294+
238295 private fun findStackTraceElementLocation (stackTraceElement : StackTraceElement ): SarifFlowLocationWrapper ? {
239296 val lineNumber = stackTraceElement.lineNumber
240297 if (lineNumber < 1 )
@@ -327,15 +384,21 @@ class SarifReport(
327384 }
328385
329386 /* *
330- * Returns the number of the last line in the execution path which is located in the [classFqn].
387+ * Returns the number of the last line in the execution path
388+ * And the name of the class in which it is located.
331389 */
332- private fun getLastLineNumber (utExecution : UtExecution , classFqn : String ): Int? {
333- val classFqnPath = classFqn.replace(" ." , " /" )
390+ private fun getLastLineNumberWithClassFqn (
391+ method : ExecutableId ,
392+ utExecution : UtExecution ,
393+ defaultClassFqn : String
394+ ): Pair <Int , String > {
334395 val coveredInstructions = utExecution.coverage?.coveredInstructions
335- val lastCoveredInstruction = coveredInstructions?.lastOrNull { it.className == classFqnPath }
336- ? : coveredInstructions?.lastOrNull()
396+ val lastCoveredInstruction = coveredInstructions?.lastOrNull()
337397 if (lastCoveredInstruction != null )
338- return lastCoveredInstruction.lineNumber
398+ return Pair (
399+ lastCoveredInstruction.lineNumber,
400+ lastCoveredInstruction.className.replace(' /' , ' .' )
401+ )
339402
340403 // if for some reason we can't extract the last line from the coverage
341404 val lastPathElementLineNumber = try {
@@ -345,7 +408,20 @@ class SarifReport(
345408 } catch (t: Throwable ) {
346409 null
347410 }
348- return lastPathElementLineNumber
411+ if (lastPathElementLineNumber != null ) {
412+ return Pair (lastPathElementLineNumber, defaultClassFqn)
413+ }
414+
415+ val methodDefinitionLine = getMethodDefinitionLineNumber(method)
416+ return Pair (methodDefinitionLine ? : defaultLineNumber, defaultClassFqn)
417+ }
418+
419+ private fun getMethodDefinitionLineNumber (method : ExecutableId ): Int? {
420+ val sourceFile = sourceFinding.getSourceFile(method.classId.canonicalName)
421+ val lineNumber = sourceFile?.readLines()?.indexOfFirst { line ->
422+ line.contains(" ${method.name} (" ) // method definition
423+ }
424+ return if (lineNumber == null || lineNumber == - 1 ) null else lineNumber + 1 // to one-based
349425 }
350426
351427 private fun shouldProcessExecutionResult (result : UtExecutionResult ): Boolean {
0 commit comments