From 261c39b96e61515d6cd8d6a346a45f18078c42e9 Mon Sep 17 00:00:00 2001 From: Dima Golovinov Date: Thu, 18 Apr 2024 11:12:01 +0200 Subject: [PATCH 1/2] Support baseline-include-absent flag in baseline cli --- baseline-cli/build.gradle.kts | 5 + baseline-cli/src/main/kotlin/BaselineCli.kt | 25 ++--- baseline-cli/src/main/kotlin/Main.kt | 44 ++++---- .../src/test/kotlin/BaselineCliTest.kt | 105 +++++++----------- qodana.yaml | 2 + 5 files changed, 81 insertions(+), 100 deletions(-) diff --git a/baseline-cli/build.gradle.kts b/baseline-cli/build.gradle.kts index f304ff5..069e3ba 100644 --- a/baseline-cli/build.gradle.kts +++ b/baseline-cli/build.gradle.kts @@ -3,9 +3,14 @@ plugins { alias(libs.plugins.shadow) } +repositories { + mavenCentral() +} + dependencies { implementation(projects.sarif) implementation(libs.gson) + implementation("com.github.ajalt.clikt:clikt:4.3.0") } application { diff --git a/baseline-cli/src/main/kotlin/BaselineCli.kt b/baseline-cli/src/main/kotlin/BaselineCli.kt index 789bfa7..70fe7ad 100644 --- a/baseline-cli/src/main/kotlin/BaselineCli.kt +++ b/baseline-cli/src/main/kotlin/BaselineCli.kt @@ -11,16 +11,13 @@ import java.nio.file.Path import java.nio.file.Paths object BaselineCli { - fun process(map: Map, cliPrinter: (String) -> Unit, errPrinter: (String) -> Unit): Int { - val sarifPath = map["sarifReport"]!! - val baselinePath = map["baselineReport"] - val failThreshold = map["failThreshold"]?.toIntOrNull() - if (!Files.exists(Paths.get(sarifPath))) { + fun process(options: BaselineOptions, cliPrinter: (String) -> Unit, errPrinter: (String) -> Unit): Int { + if (!Files.exists(Paths.get(options.sarifPath))) { errPrinter("Please provide a valid SARIF report path") return ERROR_EXIT } val sarifReport = try { - SarifUtil.readReport(Paths.get(sarifPath)) + SarifUtil.readReport(Paths.get(options.sarifPath)) } catch (e: Exception) { errPrinter("Error reading SARIF report: ${e.message}") return ERROR_EXIT @@ -30,18 +27,19 @@ object BaselineCli { RuleUtil.findRuleDescriptor(sarifReport, id)?.shortDescription?.text ?: id } val printer = CommandLineResultsPrinter(simpleMemoize(resolveInspectionName), cliPrinter) - return if (baselinePath != null) { + return if (options.baselinePath != null) { compareBaselineThreshold( sarifReport, - Paths.get(sarifPath), - Paths.get(baselinePath), - failThreshold, + Paths.get(options.sarifPath), + Paths.get(options.baselinePath), + options.failThreshold, + options.includeAbsent, printer, cliPrinter, errPrinter ) } else { - compareThreshold(sarifReport, Paths.get(sarifPath), failThreshold, printer, cliPrinter, errPrinter) + compareThreshold(sarifReport, Paths.get(options.sarifPath), options.failThreshold, printer, cliPrinter, errPrinter) } } @@ -91,6 +89,7 @@ object BaselineCli { sarifPath: Path, baselinePath: Path, failThreshold: Int?, + includeAbsent: Boolean, printer: CommandLineResultsPrinter, cliPrinter: (String) -> Unit, errPrinter: (String) -> Unit @@ -106,8 +105,8 @@ object BaselineCli { errPrinter("Error reading baseline report: ${e.message}") return ERROR_EXIT } - val baselineCalculation = BaselineCalculation.compare(sarifReport, baseline, BaselineCalculation.Options()) - printer.printResultsWithBaselineState(sarifReport.runs.first().results, false) + val baselineCalculation = BaselineCalculation.compare(sarifReport, baseline, BaselineCalculation.Options(includeAbsent)) + printer.printResultsWithBaselineState(sarifReport.runs.first().results, includeAbsent) val invocation = processResultCount(baselineCalculation.newResults, failThreshold, cliPrinter, errPrinter) sarifReport.runs.first().invocations = listOf(invocation) SarifUtil.writeReport(sarifPath, sarifReport) diff --git a/baseline-cli/src/main/kotlin/Main.kt b/baseline-cli/src/main/kotlin/Main.kt index e43fcba..12f74f8 100644 --- a/baseline-cli/src/main/kotlin/Main.kt +++ b/baseline-cli/src/main/kotlin/Main.kt @@ -1,31 +1,31 @@ import BaselineCli.process +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.flag +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.required +import com.github.ajalt.clikt.parameters.types.int import kotlin.system.exitProcess const val THRESHOLD_EXIT = 255 const val ERROR_EXIT = 1 -fun main(args: Array) { - val map = mutableMapOf() - - var i = 0 - while (i < args.size) { - when (args[i]) { - "-r" -> map["sarifReport"] = args.getOrNull(i + 1) ?: printUsageAndExit() - "-b" -> map["baselineReport"] = args.getOrNull(i + 1) ?: printUsageAndExit() - "-f" -> map["failThreshold"] = args.getOrNull(i + 1) ?: printUsageAndExit() - } - i += 2 - } - - if (!map.contains("sarifReport")) { - printUsageAndExit() +class BaselineCommand : CliktCommand() { + private val sarifReport: String by option("-r", help = "Sarif report path").required() + private val baselineReport: String? by option("-b", help = "Baseline report path") + private val baselineIncludeAbsent: Boolean by option("-i", help = "Baseline include absent status").flag() + private val failThreshold: Int? by option("-f", help = "Fail threshold").int() + override fun run() { + val ret = process( + BaselineOptions(sarifReport, baselineReport, failThreshold, baselineIncludeAbsent), + { println(it) }, + { System.err.println(it) }) + exitProcess(ret) } - - val ret = process(map, { println(it) }, { System.err.println(it) }) - exitProcess(ret) } -fun printUsageAndExit(): Nothing { - println("Usage: -r [-b ] [-f ]") - exitProcess(1) -} \ No newline at end of file +data class BaselineOptions(val sarifPath: String, + val baselinePath: String? = null, + val failThreshold: Int? = null, + val includeAbsent: Boolean = false) + +fun main(args: Array) = BaselineCommand().main(args) diff --git a/baseline-cli/src/test/kotlin/BaselineCliTest.kt b/baseline-cli/src/test/kotlin/BaselineCliTest.kt index ae58238..fb83eea 100644 --- a/baseline-cli/src/test/kotlin/BaselineCliTest.kt +++ b/baseline-cli/src/test/kotlin/BaselineCliTest.kt @@ -8,25 +8,18 @@ import kotlin.io.path.Path class BaselineCliTest { - private val sarif = run { - val tmp = Files.createTempFile(null, ".sarif") - Files.copy(Path("src/test/resources/report.equal.sarif.json"), tmp, StandardCopyOption.REPLACE_EXISTING) - tmp.toFile().also(File::deleteOnExit).absolutePath - } + private val sarif = copySarifFromResources("report.equal.sarif.json") + private val emptySarif = copySarifFromResources("empty.sarif.json") + private val corruptedSarif = Path("src/test/resources/corrupted.sarif.json").toString() private val stdout = StringBuilder() private val stderr = StringBuilder() @Test fun `test when sarifReport does not exist in the provided path`() { - // Arrange - val map = mutableMapOf().apply { - this["sarifReport"] = "nonExistentPath.sarif" - } - // Act val exitCode = assertDoesNotThrow { - BaselineCli.process(map, stdout::append, stderr::append) + BaselineCli.process(BaselineOptions("nonExistentPath.sarif"), stdout::append, stderr::append) } // Assert @@ -36,14 +29,9 @@ class BaselineCliTest { @Test fun `test when there is an error reading sarifReport`() { - // Arrange - val map = mutableMapOf().apply { - this["sarifReport"] = Path("src/test/resources/corrupted.sarif.json").toString() - } - // Act val exitCode = assertDoesNotThrow { - BaselineCli.process(map, stdout::append, stderr::append) + BaselineCli.process(BaselineOptions(corruptedSarif), stdout::append, stderr::append) } // Assert @@ -53,15 +41,9 @@ class BaselineCliTest { @Test fun `test when baselineReport path is provided and file does not exist`() { - // Arrange - val map = mutableMapOf().apply { - this["sarifReport"] = sarif - this["baselineReport"] = "nonExistentBaselineReport.sarif" - } - // Act val exitCode = assertDoesNotThrow { - BaselineCli.process(map, stdout::append, stderr::append) + BaselineCli.process(BaselineOptions(sarif, "nonExistentBaselineReport.sarif"), stdout::append, stderr::append) } // Assert @@ -71,15 +53,9 @@ class BaselineCliTest { @Test fun `test when there is a error reading baselineReport`() { - // Arrange - val map = mutableMapOf().apply { - this["sarifReport"] = sarif - this["baselineReport"] = Path("src/test/resources/corrupted.sarif.json").toString() - } - // Act val exitCode = assertDoesNotThrow { - BaselineCli.process(map, stdout::append, stderr::append) + BaselineCli.process(BaselineOptions(sarif, corruptedSarif), stdout::append, stderr::append) } // Assert @@ -89,14 +65,9 @@ class BaselineCliTest { @Test fun `test when failThreshold is not present and results count is less than the failThreshold default value`() { - // Arrange - val map = mutableMapOf().apply { - this["sarifReport"] = sarif - } - // Act val exitCode = assertDoesNotThrow { - BaselineCli.process(map, stdout::append, stderr::append) + BaselineCli.process(BaselineOptions(sarif), stdout::append, stderr::append) } // Assert @@ -106,15 +77,9 @@ class BaselineCliTest { @Test fun `test when failThreshold is provided and results count in sarifReport is more than failThreshold`() { - // Arrange - val map = mutableMapOf().apply { - this["sarifReport"] = sarif - this["failThreshold"] = "0" - } - // Act val exitCode = assertDoesNotThrow { - BaselineCli.process(map, stdout::append, stderr::append) + BaselineCli.process(BaselineOptions(sarif, failThreshold = 0), stdout::append, stderr::append) } // Assert @@ -124,16 +89,9 @@ class BaselineCliTest { @Test fun `test when failThreshold is provided and newResults count in baselineCalculation is more than failThreshold`() { - // Arrange - val map = mutableMapOf().apply { - this["sarifReport"] = sarif - this["baselineReport"] = Path("src/test/resources/empty.sarif.json").toString() - this["failThreshold"] = "1" - } - // Act val exitCode = assertDoesNotThrow { - BaselineCli.process(map, stdout::append, stderr::append) + BaselineCli.process(BaselineOptions(sarif, emptySarif, failThreshold = 1), stdout::append, stderr::append) } // Assert @@ -143,15 +101,9 @@ class BaselineCliTest { @Test fun `test when failThreshold is not provided or is less than newResults count in baselineCalculation`() { - // Arrange - val map = mutableMapOf().apply { - this["sarifReport"] = sarif - this["baselineReport"] = Path("src/test/resources/empty.sarif.json").toString() - } - // Act val exitCode = assertDoesNotThrow { - BaselineCli.process(map, stdout::append, stderr::append) + BaselineCli.process(BaselineOptions(sarif, emptySarif), stdout::append, stderr::append) } // Assert @@ -161,17 +113,40 @@ class BaselineCliTest { @Test fun `test when rule description is available`() { - // Arrange - val map = mutableMapOf().apply { - this["sarifReport"] = Path("src/test/resources/report.with-description.sarif.json").toString() - } - // Act - assertDoesNotThrow { BaselineCli.process(map, stdout::append, stderr::append) } + assertDoesNotThrow { BaselineCli.process(BaselineOptions(Path("src/test/resources/report.with-description.sarif.json").toString()), stdout::append, stderr::append) } // Assert assertTrue(stdout.contains("Result of method call ignored")) // the unresolved ID assertFalse(stdout.contains("ResultOfMethodCallIgnored")) // the unresolved ID } + @Test + fun `test include absent true`() { + // Act + assertDoesNotThrow { BaselineCli.process(BaselineOptions(emptySarif, sarif, includeAbsent = true), stdout::append, stderr::append) } + + // Assert + assertTrue(stdout.contains("ABSENT: 2")) + val content = File(emptySarif).readText(charset("UTF-8")) + assertTrue(content.contains("absent")) + } + + @Test + fun `test include absent false`() { + // Act + assertDoesNotThrow { BaselineCli.process(BaselineOptions(emptySarif, sarif, includeAbsent = false), stdout::append, stderr::append) } + + // Assert + assertFalse(stdout.contains("ABSENT: 2")) + val content = File(emptySarif).readText(charset("UTF-8")) + assertFalse(content.contains("absent")) + } + + + private fun copySarifFromResources(name: String) = run { + val tmp = Files.createTempFile(null, ".sarif") + Files.copy(Path("src/test/resources/$name"), tmp, StandardCopyOption.REPLACE_EXISTING) + tmp.toFile().also(File::deleteOnExit).absolutePath + } } diff --git a/qodana.yaml b/qodana.yaml index a965490..e6a7bb4 100644 --- a/qodana.yaml +++ b/qodana.yaml @@ -9,3 +9,5 @@ exclude: - name: All paths: - sarif/src/test/resources/ +dependencyIgnores: + - name: "colormath-jvm" \ No newline at end of file From 413ffca8c7887025302bb4bd9f42c9da9f1a2aa6 Mon Sep 17 00:00:00 2001 From: Dima Golovinov Date: Thu, 18 Apr 2024 11:28:44 +0200 Subject: [PATCH 2/2] Apply review suggestions --- baseline-cli/build.gradle.kts | 6 +----- gradle/libs.versions.toml | 2 ++ 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/baseline-cli/build.gradle.kts b/baseline-cli/build.gradle.kts index 069e3ba..e34639e 100644 --- a/baseline-cli/build.gradle.kts +++ b/baseline-cli/build.gradle.kts @@ -3,14 +3,10 @@ plugins { alias(libs.plugins.shadow) } -repositories { - mavenCentral() -} - dependencies { implementation(projects.sarif) implementation(libs.gson) - implementation("com.github.ajalt.clikt:clikt:4.3.0") + implementation(libs.clikt) } application { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 19883f2..400835b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,6 +3,7 @@ dokka = "1.9.10" shadow = "8.1.1" gson = "2.8.9" +clikt = "4.3.0" [plugins] shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" } @@ -11,3 +12,4 @@ dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } [libraries] gson = { module = "com.google.code.gson:gson", version.ref = "gson" } dokkaBase = { module = "org.jetbrains.dokka:dokka-base", version.ref = "dokka" } +clikt = { module = "com.github.ajalt.clikt:clikt", version.ref = "clikt" }