Skip to content

Fix incomplete results in the SARIF report #471

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 13, 2022
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 @@ -156,8 +156,8 @@ data class SarifRegion(
*/
fun withStartLine(text: String, startLine: Int): SarifRegion {
val neededLine = text.split('\n').getOrNull(startLine - 1) // to zero-based
val startColumn = neededLine?.let {
neededLine.takeWhile { it.toString().isBlank() }.length + 1 // to one-based
val startColumn = neededLine?.run {
takeWhile { it.toString().isBlank() }.length + 1 // to one-based
}
return SarifRegion(startLine = startLine, startColumn = startColumn)
}
Expand Down
25 changes: 18 additions & 7 deletions utbot-framework/src/main/kotlin/org/utbot/sarif/SarifReport.kt
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,13 @@ class SarifReport(
*/
private val relatedLocationId = 1 // for attaching link to generated test in related locations

private fun shouldProcessUncheckedException(result: UtExecutionResult) = (result is UtImplicitlyThrownException)
|| ((result is UtOverflowFailure) && UtSettings.treatOverflowAsError)

private fun constructSarif(): Sarif {
val sarifResults = mutableListOf<SarifResult>()
val sarifRules = mutableSetOf<SarifRule>()

for (testCase in testCases) {
for (execution in testCase.executions) {
if (shouldProcessUncheckedException(execution.result)) {
if (shouldProcessExecutionResult(execution.result)) {
val (sarifResult, sarifRule) = processUncheckedException(
method = testCase.method,
utExecution = execution,
Expand Down Expand Up @@ -144,7 +141,7 @@ class SarifReport(
if (classFqn == null)
return listOf()
val sourceRelativePath = sourceFinding.getSourceRelativePath(classFqn)
val startLine = extractLineNumber(utExecution) ?: defaultLineNumber
val startLine = getLastLineNumber(utExecution) ?: defaultLineNumber
val sourceCode = sourceFinding.getSourceFile(classFqn)?.readText() ?: ""
val sourceRegion = SarifRegion.withStartLine(sourceCode, startLine)
return listOf(
Expand Down Expand Up @@ -301,10 +298,24 @@ class SarifReport(
return "..."
}

private fun extractLineNumber(utExecution: UtExecution): Int? =
try {
/**
* Returns the number of the last line in the execution path.
*/
private fun getLastLineNumber(utExecution: UtExecution): Int? {
val lastPathLine = try {
utExecution.path.lastOrNull()?.stmt?.javaSourceStartLineNumber
} catch (t: Throwable) {
null
}
// if for some reason we can't extract the last line from the path
val lastCoveredInstruction =
utExecution.coverage?.coveredInstructions?.lastOrNull()?.lineNumber
return lastPathLine ?: lastCoveredInstruction
}

private fun shouldProcessExecutionResult(result: UtExecutionResult): Boolean {
val implicitlyThrown = result is UtImplicitlyThrownException
val overflowFailure = result is UtOverflowFailure && UtSettings.treatOverflowAsError
return implicitlyThrown || overflowFailure
}
}
Original file line number Diff line number Diff line change
@@ -1,41 +1,43 @@
package org.utbot.intellij.plugin.sarif

import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiClass
import org.jetbrains.kotlin.idea.search.allScope
import org.utbot.common.PathUtil.classFqnToPath
import org.utbot.common.PathUtil.safeRelativize
import org.utbot.common.PathUtil.toPath
import org.utbot.sarif.SourceFindingStrategy
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiClass
import org.jetbrains.kotlin.idea.search.allScope
import java.io.File

/**
* The search strategy based on the information available to the PsiClass
* The search strategy based on the information available to the PsiClass.
*/
class SourceFindingStrategyIdea(testClass: PsiClass) : SourceFindingStrategy() {

/**
* Returns the relative path (against `project.basePath`) to the file with generated tests
* Returns the relative path (against `project.basePath`) to the file with generated tests.
*/
override val testsRelativePath: String
get() = safeRelativize(project.basePath, testsFilePath)
?: testsFilePath.toPath().fileName.toString()

/**
* Returns the relative path (against `project.basePath`) to the source file containing the class [classFqn]
* Returns the relative path (against `project.basePath`) to the source file containing the class [classFqn].
*/
override fun getSourceRelativePath(classFqn: String, extension: String?): String =
JavaPsiFacade.getInstance(project)
.findClass(classFqn, project.allScope())?.let { psiClass ->
safeRelativize(project.basePath, psiClass.containingFile.virtualFile.path)
} ?: (classFqnToPath(classFqn) + (extension ?: defaultExtension))
override fun getSourceRelativePath(classFqn: String, extension: String?): String {
val psiClass = findPsiClass(classFqn)
val absolutePath = psiClass?.containingFile?.virtualFile?.path
val relativePath = safeRelativize(project.basePath, absolutePath)
val defaultRelativePath = classFqnToPath(classFqn) + (extension ?: defaultExtension)
return relativePath ?: defaultRelativePath
}

/**
* Finds the source file containing the class [classFqn].
* Returns null if the file does not exist.
*/
override fun getSourceFile(classFqn: String, extension: String?): File? {
val psiClass = JavaPsiFacade.getInstance(project).findClass(classFqn, project.allScope())
val psiClass = findPsiClass(classFqn)
val sourceCodeFile = psiClass?.containingFile?.virtualFile?.path?.let(::File)
return if (sourceCodeFile?.exists() == true) sourceCodeFile else null
}
Expand All @@ -48,7 +50,23 @@ class SourceFindingStrategyIdea(testClass: PsiClass) : SourceFindingStrategy() {

/**
* The file extension to be used in [getSourceRelativePath] if the source file
* was not found by the class qualified name and the `extension` parameter is null
* was not found by the class qualified name and the `extension` parameter is null.
*/
private val defaultExtension = "." + (testClass.containingFile.virtualFile.extension ?: "java")

/**
* Returns PsiClass by given [classFqn].
*/
private fun findPsiClass(classFqn: String): PsiClass? {
val psiFacade = JavaPsiFacade.getInstance(project)
val psiClass = psiFacade.findClass(classFqn, project.allScope())
if (psiClass != null)
return psiClass

// If for some reason `psiClass` was not found by the `findClass` method
val packageName = classFqn.substringBeforeLast('.')
val shortClassName = classFqn.substringAfterLast('.')
val neededPackage = psiFacade.findPackage(packageName)
return neededPackage?.classes?.firstOrNull { it.name == shortClassName }
}
}