From 799cc54b32a4c9ff70eb57fcba23a441b17a16f7 Mon Sep 17 00:00:00 2001 From: Agustin Isasmendi Date: Mon, 19 May 2025 09:46:06 +0200 Subject: [PATCH 1/9] chore(scanoss): Determine the snippet license only once The `licenses` variable is constant in the loop, so extract it to determine the resulting `license` only once. Signed-off-by: Agustin Isasmendi --- .../scanoss/src/main/kotlin/ScanOssResultParser.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt b/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt index 9522a0b3e0016..0ea89651a4a03 100644 --- a/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt +++ b/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt @@ -144,9 +144,9 @@ private fun getSnippets(details: ScanFileDetails): Set { val url = requireNotNull(details.url) val purls = requireNotNull(details.purls) - val licenses = details.licenseDetails.orEmpty().mapTo(mutableSetOf()) { license -> - SpdxExpression.parse(license.name) - } + val license = details.licenseDetails.orEmpty() + .map { license -> SpdxExpression.parse(license.name) } + .toExpression()?.sorted() ?: SpdxLicenseIdExpression(SpdxConstants.NOASSERTION) val score = matched.substringBeforeLast("%").toFloat() val locations = convertLines(fileUrl, ossLines) @@ -157,8 +157,6 @@ private fun getSnippets(details: ScanFileDetails): Set { return buildSet { purls.forEach { purl -> locations.forEach { snippetLocation -> - val license = licenses.toExpression()?.sorted() ?: SpdxLicenseIdExpression(SpdxConstants.NOASSERTION) - add(Snippet(score, snippetLocation, provenance, purl, license)) } } From 6df9ad16ec5df8e7391d2d74e6751e11ab376a72 Mon Sep 17 00:00:00 2001 From: Agustin Isasmendi Date: Mon, 19 May 2025 09:57:43 +0200 Subject: [PATCH 2/9] test(scanoss): Add tests for license handling edge cases Add test cases to verify behavior when combining the same license from different sources and handling empty license arrays with NOASSERTION value. Signed-off-by: Agustin Isasmendi --- .../test/kotlin/ScanOssResultParserTest.kt | 52 ++++++++++++++++++ .../scanoss-snippet-no-license-data.json | 33 ++++++++++++ ...snippet-same-license-multiple-sources.json | 54 +++++++++++++++++++ 3 files changed, 139 insertions(+) create mode 100644 plugins/scanners/scanoss/src/test/resources/scanoss-snippet-no-license-data.json create mode 100644 plugins/scanners/scanoss/src/test/resources/scanoss-snippet-same-license-multiple-sources.json diff --git a/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt b/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt index bb8716529bc20..47b1e1d9879b0 100644 --- a/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt +++ b/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt @@ -27,6 +27,7 @@ import io.kotest.matchers.collections.containExactlyInAnyOrder import io.kotest.matchers.collections.haveSize import io.kotest.matchers.collections.shouldContain import io.kotest.matchers.should +import io.kotest.matchers.shouldBe import java.time.Instant @@ -38,7 +39,9 @@ import org.ossreviewtoolkit.model.SnippetFinding import org.ossreviewtoolkit.model.TextLocation import org.ossreviewtoolkit.model.VcsInfo import org.ossreviewtoolkit.model.VcsType +import org.ossreviewtoolkit.utils.spdx.SpdxConstants import org.ossreviewtoolkit.utils.spdx.SpdxExpression +import org.ossreviewtoolkit.utils.spdx.toSpdx import org.ossreviewtoolkit.utils.test.readResource class ScanOssResultParserTest : WordSpec({ @@ -131,5 +134,54 @@ class ScanOssResultParserTest : WordSpec({ ) ) } + + "combine the same license from different sources into a single expression" { + // When the same license appears in multiple sources (like scancode and file_header), + // combine them into a single expression rather than duplicating. + val results = readResource("/scanoss-snippet-same-license-multiple-sources.json").let { + JsonUtils.toScanFileResultsFromObject(JsonUtils.toJsonObject(it)) + } + + val time = Instant.now() + val summary = generateSummary(time, time, results) + + // Verify the snippet finding. + summary.snippetFindings should haveSize(1) + val snippet = summary.snippetFindings.first().snippets.first() + + // Consolidate the license into a single expression + // even though it came from both "scancode" and "file_header" sources. + snippet.license shouldBe "LGPL-2.1-or-later".toSpdx() + + // Preserve other snippet details correctly. + with(summary.snippetFindings.first()) { + sourceLocation.path shouldBe "src/check_error.c" + sourceLocation.startLine shouldBe 16 + sourceLocation.endLine shouldBe 24 + } + } + + "handle empty license array with NOASSERTION" { + val results = readResource("/scanoss-snippet-no-license-data.json").let { + JsonUtils.toScanFileResultsFromObject(JsonUtils.toJsonObject(it)) + } + + val time = Instant.now() + val summary = generateSummary(time, time, results) + + // Verify the snippet finding. + summary.snippetFindings should haveSize(1) + val snippet = summary.snippetFindings.first().snippets.first() + + // Use NOASSERTION when no licenses are provided. + snippet.license shouldBe SpdxConstants.NOASSERTION.toSpdx() + + // Preserve other snippet details correctly. + with(summary.snippetFindings.first()) { + sourceLocation.path shouldBe "fake_file.c" + sourceLocation.startLine shouldBe 16 + sourceLocation.endLine shouldBe 24 + } + } } }) diff --git a/plugins/scanners/scanoss/src/test/resources/scanoss-snippet-no-license-data.json b/plugins/scanners/scanoss/src/test/resources/scanoss-snippet-no-license-data.json new file mode 100644 index 0000000000000..de8f4de06cb1b --- /dev/null +++ b/plugins/scanners/scanoss/src/test/resources/scanoss-snippet-no-license-data.json @@ -0,0 +1,33 @@ +{ + "fake_file.c": [ + { + "component": "check", + "file": "fake_file.c", + "file_hash": "4597ef1de00849bb96d42e78f2cfc3a7", + "file_url": "https://api.scanoss.com/file_contents//4597ef1de00849bb96d42e78f2cfc3a7", + "id": "snippet", + "latest": "0.8.1", + "licenses": [], + "lines": "16-24", + "matched": "15%", + "oss_lines": "34-42", + "purl": [ + "pkg:sourceforge/check" + ], + "release_date": "2002-03-02", + "server": { + "kb_version": { + "daily": "25.05.14", + "monthly": "25.04" + }, + "version": "5.4.10" + }, + "source_hash": "74c49597c2934e08b2ce8797f4aa7454", + "status": "pending", + "url": "https://sourceforge.net/projects/check", + "url_hash": "d81953e1dca4c498140c44f5d6fa92d6", + "vendor": "check", + "version": "0.8.1" + } + ] +} diff --git a/plugins/scanners/scanoss/src/test/resources/scanoss-snippet-same-license-multiple-sources.json b/plugins/scanners/scanoss/src/test/resources/scanoss-snippet-same-license-multiple-sources.json new file mode 100644 index 0000000000000..89f954ca0260c --- /dev/null +++ b/plugins/scanners/scanoss/src/test/resources/scanoss-snippet-same-license-multiple-sources.json @@ -0,0 +1,54 @@ +{ + "src/check_error.c": [ + { + "component": "check", + "file": "src/check_error.c", + "file_hash": "4597ef1de00849bb96d42e78f2cfc3a7", + "file_url": "https://api.scanoss.com/file_contents//4597ef1de00849bb96d42e78f2cfc3a7", + "id": "snippet", + "latest": "0.8.1", + "licenses": [ + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/LGPL-2.1-or-later.txt", + "copyleft": "yes", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "name": "LGPL-2.1-or-later", + "osadl_updated": "2025-02-10T14:26:00+0000", + "patent_hints": "yes", + "source": "scancode", + "url": "https://spdx.org/licenses/LGPL-2.1-or-later.html" + }, + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/LGPL-2.1-or-later.txt", + "copyleft": "yes", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "name": "LGPL-2.1-or-later", + "osadl_updated": "2025-02-10T14:26:00+0000", + "patent_hints": "yes", + "source": "file_header", + "url": "https://spdx.org/licenses/LGPL-2.1-or-later.html" + } + ], + "lines": "16-24", + "matched": "15%", + "oss_lines": "34-42", + "purl": [ + "pkg:sourceforge/check" + ], + "release_date": "2002-03-02", + "server": { + "kb_version": { + "daily": "25.05.14", + "monthly": "25.04" + }, + "version": "5.4.10" + }, + "source_hash": "74c49597c2934e08b2ce8797f4aa7454", + "status": "pending", + "url": "https://sourceforge.net/projects/check", + "url_hash": "d81953e1dca4c498140c44f5d6fa92d6", + "vendor": "check", + "version": "0.8.1" + } + ] +} From 334ec51d6a1d9e50c1c37a0212a24db4bd03029d Mon Sep 17 00:00:00 2001 From: Agustin Isasmendi Date: Mon, 19 May 2025 17:27:03 +0200 Subject: [PATCH 3/9] fix(scanoss): Display local file path instead of remote path Correct issue where `getLicenseFindings()`, `getCopyrightFindings()` and `sourceLocations` incorrectly showed remote filepath instead of local file path. Signed-off-by: Agustin Isasmendi --- .../scanoss/src/main/kotlin/ScanOssResultParser.kt | 14 ++++++-------- .../src/test/kotlin/ScanOssResultParserTest.kt | 8 +++++--- .../scanMulti/mappings/scanoss-multi-response.json | 2 +- .../scanSingle/mappings/scanoss-response.json | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt b/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt index 0ea89651a4a03..a2b79e88adf1f 100644 --- a/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt +++ b/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt @@ -51,12 +51,13 @@ internal fun generateSummary(startTime: Instant, endTime: Instant, results: List result.fileDetails.forEach { details -> when (details.matchType) { MatchType.file -> { - licenseFindings += getLicenseFindings(details) - copyrightFindings += getCopyrightFindings(details) + val file = requireNotNull(result.filePath) + licenseFindings += getLicenseFindings(details, file) + copyrightFindings += getCopyrightFindings(details, file) } MatchType.snippet -> { - val file = requireNotNull(details.file) + val file = requireNotNull(result.filePath) val lines = requireNotNull(details.lines) val sourceLocations = convertLines(file, lines) val snippets = getSnippets(details) @@ -90,8 +91,7 @@ internal fun generateSummary(startTime: Instant, endTime: Instant, results: List /** * Get the license findings from the given [details]. */ -private fun getLicenseFindings(details: ScanFileDetails): List { - val path = details.file ?: return emptyList() +private fun getLicenseFindings(details: ScanFileDetails, path: String): List { val score = details.matched?.removeSuffix("%")?.toFloatOrNull() return details.licenseDetails.orEmpty().map { license -> @@ -118,9 +118,7 @@ private fun getLicenseFindings(details: ScanFileDetails): List { /** * Get the copyright findings from the given [details]. */ -private fun getCopyrightFindings(details: ScanFileDetails): List { - val path = details.file ?: return emptyList() - +private fun getCopyrightFindings(details: ScanFileDetails, path: String): List { return details.copyrightDetails.orEmpty().map { copyright -> CopyrightFinding( statement = copyright.name, diff --git a/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt b/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt index 47b1e1d9879b0..904f3c0b8d7bf 100644 --- a/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt +++ b/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt @@ -66,7 +66,8 @@ class ScanOssResultParserTest : WordSpec({ summary.licenseFindings shouldContain LicenseFinding( license = "Apache-2.0", location = TextLocation( - path = "hopscotch-rails-0.1.2.1/vendor/assets/javascripts/hopscotch.js", + path = "/tmp/ort-ScanOss2759786101559527642/Maven/junit/junit/4.12/src/site/resources/" + + "scripts/hopscotch-0.1.2.min.js", startLine = TextLocation.UNKNOWN_LINE, endLine = TextLocation.UNKNOWN_LINE ), @@ -77,7 +78,8 @@ class ScanOssResultParserTest : WordSpec({ summary.copyrightFindings shouldContain CopyrightFinding( statement = "Copyright 2013 LinkedIn Corp.", location = TextLocation( - path = "hopscotch-rails-0.1.2.1/vendor/assets/javascripts/hopscotch.js", + path = "/tmp/ort-ScanOss2759786101559527642/Maven/junit/junit/4.12/src/site/resources/" + + "scripts/hopscotch-0.1.2.min.js", startLine = TextLocation.UNKNOWN_LINE, endLine = TextLocation.UNKNOWN_LINE ) @@ -104,7 +106,7 @@ class ScanOssResultParserTest : WordSpec({ summary.licenseFindings shouldContain LicenseFinding( license = "Apache-2.0", location = TextLocation( - path = "com/vdurmont/semver4j/Range.java", + path = "src/main/java/com/vdurmont/semver4j/Range.java", startLine = TextLocation.UNKNOWN_LINE, endLine = TextLocation.UNKNOWN_LINE ), diff --git a/plugins/scanners/scanoss/src/test/resources/scanMulti/mappings/scanoss-multi-response.json b/plugins/scanners/scanoss/src/test/resources/scanMulti/mappings/scanoss-multi-response.json index 317e7ae388d1e..d722c62243dae 100644 --- a/plugins/scanners/scanoss/src/test/resources/scanMulti/mappings/scanoss-multi-response.json +++ b/plugins/scanners/scanoss/src/test/resources/scanMulti/mappings/scanoss-multi-response.json @@ -10,7 +10,7 @@ }, "response" : { "status" : 200, - "body" : "{ \"utils/src/main/kotlin/random-data-05-06-11.kt\": [ { \"id\": \"snippet\", \"status\": \"pending\", \"lines\": \"1-240\", \"oss_lines\": \"128-367\", \"matched\": \"99%\", \"purl\": [ \"pkg:github/scanoss/ort\" ], \"vendor\": \"scanoss\", \"component\": \"ort\", \"version\": \"e654028\", \"latest\": \"b12f8ee\", \"url\": \"https://github.com/scanoss/ort\", \"release_date\": \"2021-03-18\", \"file\": \"utils/src/main/kotlin/random-data-05-06-11.kt\", \"url_hash\": \"37faa38a820322fa93bf7a8fa8290bb8\", \"file_hash\": \"871fb0c5188c2f620d9b997e225b0095\", \"source_hash\": \"2e91edbe430c4eb195a977d326d6d6c0\", \"file_url\": \"https://osskb.org/api/file_contents/871fb0c5188c2f620d9b997e225b0095\", \"licenses\": [ { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"file_spdx_tag\" }, { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"scancode\" } ], \"server\": { \"version\": \"4.4.2\", \"kb_version\": { \"monthly\": \"22.02\", \"daily\": \"22.03.25\" } } } ], \"5530105e-0752-4750-9c07-4e4604b879a5\": [ { \"id\": \"file\", \"status\": \"pending\", \"lines\": \"all\", \"oss_lines\": \"all\", \"matched\": \"100%\", \"purl\": [ \"pkg:github/scanoss/ort\" ], \"vendor\": \"scanoss\", \"component\": \"ort\", \"version\": \"e654028\", \"latest\": \"b12f8ee\", \"url\": \"https://github.com/scanoss/ort\", \"release_date\": \"2021-03-18\", \"file\": \"scanner/src/main/kotlin/random-data-05-07-04.kt\", \"url_hash\": \"37faa38a820322fa93bf7a8fa8290bb8\", \"file_hash\": \"5c8ab9be40df937e46c53509481107cd\", \"source_hash\": \"5c8ab9be40df937e46c53509481107cd\", \"file_url\": \"https://osskb.org/api/file_contents/5c8ab9be40df937e46c53509481107cd\", \"licenses\": [ { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"file_spdx_tag\" }, { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"scancode\" } ], \"server\": { \"version\": \"4.4.2\", \"kb_version\": { \"monthly\": \"22.02\", \"daily\": \"22.03.25\" } } } ]}", + "body" : "{ \"utils/src/main/kotlin/random-data-05-06-11.kt\": [ { \"id\": \"snippet\", \"status\": \"pending\", \"lines\": \"1-240\", \"oss_lines\": \"128-367\", \"matched\": \"99%\", \"purl\": [ \"pkg:github/scanoss/ort\" ], \"vendor\": \"scanoss\", \"component\": \"ort\", \"version\": \"e654028\", \"latest\": \"b12f8ee\", \"url\": \"https://github.com/scanoss/ort\", \"release_date\": \"2021-03-18\", \"file\": \"examples/example.rules.kts\", \"url_hash\": \"37faa38a820322fa93bf7a8fa8290bb8\", \"file_hash\": \"871fb0c5188c2f620d9b997e225b0095\", \"source_hash\": \"2e91edbe430c4eb195a977d326d6d6c0\", \"file_url\": \"https://osskb.org/api/file_contents/871fb0c5188c2f620d9b997e225b0095\", \"licenses\": [ { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"file_spdx_tag\" }, { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"scancode\" } ], \"server\": { \"version\": \"4.4.2\", \"kb_version\": { \"monthly\": \"22.02\", \"daily\": \"22.03.25\" } } } ], \"scanner/src/main/kotlin/random-data-05-07-04.kt\": [ { \"id\": \"file\", \"status\": \"pending\", \"lines\": \"all\", \"oss_lines\": \"all\", \"matched\": \"100%\", \"purl\": [ \"pkg:github/scanoss/ort\" ], \"vendor\": \"scanoss\", \"component\": \"ort\", \"version\": \"e654028\", \"latest\": \"b12f8ee\", \"url\": \"https://github.com/scanoss/ort\", \"release_date\": \"2021-03-18\", \"file\": \"scanner/src/main/kotlin/random-data-05-07-04.kt\", \"url_hash\": \"37faa38a820322fa93bf7a8fa8290bb8\", \"file_hash\": \"5c8ab9be40df937e46c53509481107cd\", \"source_hash\": \"5c8ab9be40df937e46c53509481107cd\", \"file_url\": \"https://osskb.org/api/file_contents/5c8ab9be40df937e46c53509481107cd\", \"licenses\": [ { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"file_spdx_tag\" }, { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"scancode\" } ], \"server\": { \"version\": \"4.4.2\", \"kb_version\": { \"monthly\": \"22.02\", \"daily\": \"22.03.25\" } } } ]}", "headers" : { "Server" : "nginx/1.14.2", "Date" : "Wed, 16 Mar 2022 13:07:04 GMT", diff --git a/plugins/scanners/scanoss/src/test/resources/scanSingle/mappings/scanoss-response.json b/plugins/scanners/scanoss/src/test/resources/scanSingle/mappings/scanoss-response.json index 2dcfa12b6ba83..416d2555071de 100644 --- a/plugins/scanners/scanoss/src/test/resources/scanSingle/mappings/scanoss-response.json +++ b/plugins/scanners/scanoss/src/test/resources/scanSingle/mappings/scanoss-response.json @@ -10,7 +10,7 @@ }, "response" : { "status" : 200, - "body" : "{ \"bf5401e9-03b3-4c91-906c-cadb90487b8c\": [ { \"id\": \"file\", \"status\": \"pending\", \"lines\": \"all\", \"oss_lines\": \"all\", \"matched\": \"100%\", \"purl\": [ \"pkg:github/scanoss/ort\" ], \"vendor\": \"scanoss\", \"component\": \"ort\", \"version\": \"e654028\", \"latest\": \"b12f8ee\", \"url\": \"https://github.com/scanoss/ort\", \"release_date\": \"2021-03-18\", \"file\": \"scanner/src/main/kotlin/random-data-05-07-04.kt\", \"url_hash\": \"37faa38a820322fa93bf7a8fa8290bb8\", \"file_hash\": \"5c8ab9be40df937e46c53509481107cd\", \"source_hash\": \"5c8ab9be40df937e46c53509481107cd\", \"file_url\": \"https://osskb.org/api/file_contents/5c8ab9be40df937e46c53509481107cd\", \"licenses\": [ { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"file_spdx_tag\" }, { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"scancode\" } ], \"server\": { \"version\": \"4.4.2\", \"kb_version\": { \"monthly\": \"22.02\", \"daily\": \"22.03.25\" } } } ]}", + "body" : "{ \"scanner/src/main/kotlin/random-data-05-07-04.kt\": [ { \"id\": \"file\", \"status\": \"pending\", \"lines\": \"all\", \"oss_lines\": \"all\", \"matched\": \"100%\", \"purl\": [ \"pkg:github/scanoss/ort\" ], \"vendor\": \"scanoss\", \"component\": \"ort\", \"version\": \"e654028\", \"latest\": \"b12f8ee\", \"url\": \"https://github.com/scanoss/ort\", \"release_date\": \"2021-03-18\", \"file\": \"examples/example.rules.kts\", \"url_hash\": \"37faa38a820322fa93bf7a8fa8290bb8\", \"file_hash\": \"5c8ab9be40df937e46c53509481107cd\", \"source_hash\": \"5c8ab9be40df937e46c53509481107cd\", \"file_url\": \"https://osskb.org/api/file_contents/5c8ab9be40df937e46c53509481107cd\", \"licenses\": [ { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"file_spdx_tag\" }, { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"scancode\" } ], \"server\": { \"version\": \"4.4.2\", \"kb_version\": { \"monthly\": \"22.02\", \"daily\": \"22.03.25\" } } } ]}", "headers" : { "Server" : "nginx/1.14.2", "Date" : "Wed, 16 Mar 2022 13:07:04 GMT", From 7a19055692c2fab3868d0eeeaaab221811078b29 Mon Sep 17 00:00:00 2001 From: Agustin Isasmendi Date: Mon, 19 May 2025 15:52:58 +0200 Subject: [PATCH 4/9] fix(scanoss): snippetFindings use remote file path instead of OSSKB URL Correct issue where `snippetFindings` location incorrectly used OSSKB URLs instead of remote file paths. Signed-off-by: Agustin Isasmendi --- .../scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt | 4 ++-- .../scanoss/src/test/kotlin/ScanOssResultParserTest.kt | 2 +- .../scanoss/src/test/kotlin/ScanOssScannerDirectoryTest.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt b/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt index a2b79e88adf1f..71bda85adaba6 100644 --- a/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt +++ b/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt @@ -137,7 +137,7 @@ private fun getCopyrightFindings(details: ScanFileDetails, path: String): List { val matched = requireNotNull(details.matched) - val fileUrl = requireNotNull(details.fileUrl) + val ossFile = requireNotNull(details.file) val ossLines = requireNotNull(details.ossLines) val url = requireNotNull(details.url) val purls = requireNotNull(details.purls) @@ -147,7 +147,7 @@ private fun getSnippets(details: ScanFileDetails): Set { .toExpression()?.sorted() ?: SpdxLicenseIdExpression(SpdxConstants.NOASSERTION) val score = matched.substringBeforeLast("%").toFloat() - val locations = convertLines(fileUrl, ossLines) + val locations = convertLines(ossFile, ossLines) // TODO: No resolved revision is available. Should a ArtifactProvenance be created instead ? val vcsInfo = VcsHost.parseUrl(url.takeUnless { it == "none" }.orEmpty()) val provenance = RepositoryProvenance(vcsInfo, ".") diff --git a/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt b/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt index 904f3c0b8d7bf..c56b7f43f63c4 100644 --- a/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt +++ b/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt @@ -121,7 +121,7 @@ class ScanOssResultParserTest : WordSpec({ Snippet( 98.0f, TextLocation( - "https://osskb.org/api/file_contents/6ff2427335b985212c9b79dfa795799f", + "src/main/java/com/vdurmont/semver4j/Requirement.java", 1, 710 ), diff --git a/plugins/scanners/scanoss/src/test/kotlin/ScanOssScannerDirectoryTest.kt b/plugins/scanners/scanoss/src/test/kotlin/ScanOssScannerDirectoryTest.kt index 6a1a134bc7df7..18cb094ee1652 100644 --- a/plugins/scanners/scanoss/src/test/kotlin/ScanOssScannerDirectoryTest.kt +++ b/plugins/scanners/scanoss/src/test/kotlin/ScanOssScannerDirectoryTest.kt @@ -103,7 +103,7 @@ class ScanOssScannerDirectoryTest : StringSpec({ Snippet( 99.0f, TextLocation( - "https://osskb.org/api/file_contents/871fb0c5188c2f620d9b997e225b0095", + "examples/example.rules.kts", 128, 367 ), From fe73ac3795d5e76a3704dfb9248df39fed2c70a4 Mon Sep 17 00:00:00 2001 From: Agustin Isasmendi Date: Mon, 19 May 2025 16:06:54 +0200 Subject: [PATCH 5/9] refactor(scanoss): Rename variables for better path distinction Rename variables to clearly identify local vs OSS contexts, making code more readable and reducing confusion between different path references. Signed-off-by: Agustin Isasmendi --- .../src/main/kotlin/ScanOssResultParser.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt b/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt index 71bda85adaba6..fe1f21eae4f18 100644 --- a/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt +++ b/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt @@ -51,15 +51,15 @@ internal fun generateSummary(startTime: Instant, endTime: Instant, results: List result.fileDetails.forEach { details -> when (details.matchType) { MatchType.file -> { - val file = requireNotNull(result.filePath) - licenseFindings += getLicenseFindings(details, file) - copyrightFindings += getCopyrightFindings(details, file) + val localFile = requireNotNull(result.filePath) + licenseFindings += getLicenseFindings(details, localFile) + copyrightFindings += getCopyrightFindings(details, localFile) } MatchType.snippet -> { - val file = requireNotNull(result.filePath) - val lines = requireNotNull(details.lines) - val sourceLocations = convertLines(file, lines) + val localFile = requireNotNull(result.filePath) + val localLines = requireNotNull(details.lines) + val sourceLocations = convertLines(localFile, localLines) val snippets = getSnippets(details) snippets.forEach { snippet -> @@ -147,14 +147,14 @@ private fun getSnippets(details: ScanFileDetails): Set { .toExpression()?.sorted() ?: SpdxLicenseIdExpression(SpdxConstants.NOASSERTION) val score = matched.substringBeforeLast("%").toFloat() - val locations = convertLines(ossFile, ossLines) + val ossLocations = convertLines(ossFile, ossLines) // TODO: No resolved revision is available. Should a ArtifactProvenance be created instead ? val vcsInfo = VcsHost.parseUrl(url.takeUnless { it == "none" }.orEmpty()) val provenance = RepositoryProvenance(vcsInfo, ".") return buildSet { purls.forEach { purl -> - locations.forEach { snippetLocation -> + ossLocations.forEach { snippetLocation -> add(Snippet(score, snippetLocation, provenance, purl, license)) } } From 6777557df8839d4e3272331c2c4e23fc146ac368 Mon Sep 17 00:00:00 2001 From: Agustin Isasmendi Date: Mon, 19 May 2025 16:23:06 +0200 Subject: [PATCH 6/9] fix(scanoss): Snippet generation logic to correctly represent match data Replace cartesian product approach (one snippet per PURL-location pair) with one snippet per location using primary PURL as identifier and storing all related PURLs in metadata. Signed-off-by: Agustin Isasmendi --- .../src/main/kotlin/ScanOssResultParser.kt | 41 ++++++++----- .../test/kotlin/ScanOssResultParserTest.kt | 39 ++++++++++++ .../resources/scanoss-multiple-purls.json | 60 +++++++++++++++++++ 3 files changed, 126 insertions(+), 14 deletions(-) create mode 100644 plugins/scanners/scanoss/src/test/resources/scanoss-multiple-purls.json diff --git a/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt b/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt index fe1f21eae4f18..fa33ce70e59b3 100644 --- a/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt +++ b/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt @@ -23,8 +23,11 @@ import com.scanoss.dto.ScanFileDetails import com.scanoss.dto.ScanFileResult import com.scanoss.dto.enums.MatchType +import java.lang.invoke.MethodHandles import java.time.Instant +import org.apache.logging.log4j.kotlin.loggerOf + import org.ossreviewtoolkit.downloader.VcsHost import org.ossreviewtoolkit.model.CopyrightFinding import org.ossreviewtoolkit.model.LicenseFinding @@ -38,6 +41,8 @@ import org.ossreviewtoolkit.utils.spdx.SpdxExpression import org.ossreviewtoolkit.utils.spdx.SpdxLicenseIdExpression import org.ossreviewtoolkit.utils.spdx.toExpression +private val logger = loggerOf(MethodHandles.lookup().lookupClass()) + /** * Generate a summary from the given SCANOSS [result], using [startTime], [endTime] as metadata. This variant can be * used if the result is not read from a local file. @@ -62,12 +67,19 @@ internal fun generateSummary(startTime: Instant, endTime: Instant, results: List val sourceLocations = convertLines(localFile, localLines) val snippets = getSnippets(details) - snippets.forEach { snippet -> - sourceLocations.forEach { sourceLocation -> - // TODO: Aggregate the snippet by source file location. - snippetFindings += SnippetFinding(sourceLocation, setOf(snippet)) + // The number of snippets should match the number of source locations. + if (sourceLocations.size != snippets.size) { + logger.warn { + "Unexpected mismatch in '$localFile': " + + "${sourceLocations.size} source locations vs ${snippets.size} snippets. " + + "This indicates a potential issue with line range conversion." } } + + // Associate each source location with its corresponding snippet. + sourceLocations.zip(snippets).forEach { (location, snippet) -> + snippetFindings += SnippetFinding(location, setOf(snippet)) + } } MatchType.none -> { @@ -132,15 +144,17 @@ private fun getCopyrightFindings(details: ScanFileDetails, path: String): List { +private fun getSnippets(details: ScanFileDetails): List { val matched = requireNotNull(details.matched) val ossFile = requireNotNull(details.file) val ossLines = requireNotNull(details.ossLines) val url = requireNotNull(details.url) - val purls = requireNotNull(details.purls) + val purls = requireNotNull(details.purls).toMutableList() + val primaryPurl = purls.removeFirstOrNull().orEmpty() val license = details.licenseDetails.orEmpty() .map { license -> SpdxExpression.parse(license.name) } @@ -152,12 +166,11 @@ private fun getSnippets(details: ScanFileDetails): Set { val vcsInfo = VcsHost.parseUrl(url.takeUnless { it == "none" }.orEmpty()) val provenance = RepositoryProvenance(vcsInfo, ".") - return buildSet { - purls.forEach { purl -> - ossLocations.forEach { snippetLocation -> - add(Snippet(score, snippetLocation, provenance, purl, license)) - } - } + val additionalData = purls.associateWith { "" } + + // Create one snippet per location, using the first PURL as the primary identifier. + return ossLocations.map { snippetLocation -> + Snippet(score, snippetLocation, provenance, primaryPurl, license, additionalData) } } diff --git a/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt b/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt index c56b7f43f63c4..64efdbb5ac6f0 100644 --- a/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt +++ b/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt @@ -137,6 +137,45 @@ class ScanOssResultParserTest : WordSpec({ ) } + "handle multiple PURLs by extracting first as primary and storing remaining in additionalData" { + val results = readResource("/scanoss-multiple-purls.json").let { + JsonUtils.toScanFileResultsFromObject(JsonUtils.toJsonObject(it)) + } + + val time = Instant.now() + val summary = generateSummary(time, time, results) + + // Verify we have one finding per source location, not per PURL. + summary.snippetFindings should haveSize(2) + + with(summary.snippetFindings.first()) { + // Check source location (local file). + sourceLocation shouldBe TextLocation("hung_task.c", 12, 150) + + // Verify first PURL is extracted as primary identifier. + snippets should haveSize(1) + snippets.first().purl shouldBe "pkg:github/kdrag0n/proton_bluecross" + + // Verify remaining PURLs are stored in additionalData. + snippets.first().additionalData shouldBe + mapOf( + "pkg:github/fake/fake_repository" to "" + ) + + // Check OSS location. + snippets.first().location shouldBe + TextLocation("kernel/hung_task.c", 10, 148) + } + + // Verify same behavior for second snippet. + with(summary.snippetFindings.last()) { + sourceLocation shouldBe TextLocation("hung_task.c", 540, 561) + snippets.first().purl shouldBe "pkg:github/kdrag0n/proton_bluecross" + snippets.first().location shouldBe + TextLocation("kernel/hung_task.c", 86, 107) + } + } + "combine the same license from different sources into a single expression" { // When the same license appears in multiple sources (like scancode and file_header), // combine them into a single expression rather than duplicating. diff --git a/plugins/scanners/scanoss/src/test/resources/scanoss-multiple-purls.json b/plugins/scanners/scanoss/src/test/resources/scanoss-multiple-purls.json new file mode 100644 index 0000000000000..7e02115508b8a --- /dev/null +++ b/plugins/scanners/scanoss/src/test/resources/scanoss-multiple-purls.json @@ -0,0 +1,60 @@ +{ + "hung_task.c": [ + { + "component": "proton_bluecross", + "file": "kernel/hung_task.c", + "file_hash": "581734935cfbe570d280a1265aaa2a6b", + "file_url": "https://api.scanoss.com/file_contents/581734935cfbe570d280a1265aaa2a6b", + "id": "snippet", + "latest": "17", + "licenses": [ + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/GPL-2.0-only.txt", + "copyleft": "yes", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "name": "GPL-2.0-only", + "osadl_updated": "2025-02-10T14:26:00+0000", + "patent_hints": "yes", + "source": "scancode", + "url": "https://spdx.org/licenses/GPL-2.0-only.html" + }, + { + "name": "GPL-2.0-only WITH Linux-syscall-note", + "source": "scancode", + "url": "https://spdx.org/licenses/GPL-2.0-only WITH Linux-syscall-note.html" + }, + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/GPL-2.0-only.txt", + "copyleft": "yes", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "name": "GPL-2.0-only", + "osadl_updated": "2025-02-10T14:26:00+0000", + "patent_hints": "yes", + "source": "scancode", + "url": "https://spdx.org/licenses/GPL-2.0-only.html" + } + ], + "lines": "12-150,540-561", + "matched": "35%", + "oss_lines": "10-148,86-107", + "purl": [ + "pkg:github/kdrag0n/proton_bluecross", + "pkg:github/fake/fake_repository" + ], + "release_date": "2019-02-21", + "server": { + "kb_version": { + "daily": "25.03.27", + "monthly": "25.03" + }, + "version": "5.4.10" + }, + "source_hash": "45dd1e50621a8a32f88fbe0251a470ab", + "status": "pending", + "url": "https://github.com/kdrag0n/proton_bluecross", + "url_hash": "a9c1c67f0930dc42dbd40c29e565bcdd", + "vendor": "kdrag0n", + "version": "15" + } + ] +} From 2a2c6c8e8b911d8081c0b09527bfd1c4ca06b1b8 Mon Sep 17 00:00:00 2001 From: Agustin Isasmendi Date: Mon, 19 May 2025 16:50:00 +0200 Subject: [PATCH 7/9] refactor(scanoss): Improve snippet location pairing with direct mapping Replace separate collection iteration with explicit source-to-OSS location pairing using zip operation. Signed-off-by: Agustin Isasmendi --- .../src/main/kotlin/ScanOssResultParser.kt | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt b/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt index fa33ce70e59b3..8a8a4b0d6c535 100644 --- a/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt +++ b/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt @@ -63,23 +63,7 @@ internal fun generateSummary(startTime: Instant, endTime: Instant, results: List MatchType.snippet -> { val localFile = requireNotNull(result.filePath) - val localLines = requireNotNull(details.lines) - val sourceLocations = convertLines(localFile, localLines) - val snippets = getSnippets(details) - - // The number of snippets should match the number of source locations. - if (sourceLocations.size != snippets.size) { - logger.warn { - "Unexpected mismatch in '$localFile': " + - "${sourceLocations.size} source locations vs ${snippets.size} snippets. " + - "This indicates a potential issue with line range conversion." - } - } - - // Associate each source location with its corresponding snippet. - sourceLocations.zip(snippets).forEach { (location, snippet) -> - snippetFindings += SnippetFinding(location, setOf(snippet)) - } + snippetFindings += createSnippetFindings(details, localFile) } MatchType.none -> { @@ -144,33 +128,52 @@ private fun getCopyrightFindings(details: ScanFileDetails, path: String): List { +private fun createSnippetFindings(details: ScanFileDetails, localFilePath: String): Set { val matched = requireNotNull(details.matched) val ossFile = requireNotNull(details.file) val ossLines = requireNotNull(details.ossLines) + val localLines = requireNotNull(details.lines) val url = requireNotNull(details.url) val purls = requireNotNull(details.purls).toMutableList() + + val score = matched.substringBeforeLast("%").toFloat() val primaryPurl = purls.removeFirstOrNull().orEmpty() val license = details.licenseDetails.orEmpty() .map { license -> SpdxExpression.parse(license.name) } .toExpression()?.sorted() ?: SpdxLicenseIdExpression(SpdxConstants.NOASSERTION) - val score = matched.substringBeforeLast("%").toFloat() - val ossLocations = convertLines(ossFile, ossLines) // TODO: No resolved revision is available. Should a ArtifactProvenance be created instead ? val vcsInfo = VcsHost.parseUrl(url.takeUnless { it == "none" }.orEmpty()) val provenance = RepositoryProvenance(vcsInfo, ".") val additionalData = purls.associateWith { "" } - // Create one snippet per location, using the first PURL as the primary identifier. - return ossLocations.map { snippetLocation -> - Snippet(score, snippetLocation, provenance, primaryPurl, license, additionalData) + // Convert both local and OSS line ranges to source locations. + val sourceLocations = convertLines(localFilePath, localLines) + val ossLocations = convertLines(ossFile, ossLines) + + // The number of source locations should match the number of oss locations. + if (sourceLocations.size != ossLocations.size) { + logger.warn { + "Unexpected mismatch in '$localFilePath': " + + "${sourceLocations.size} source locations vs ${ossLocations.size} oss locations. " + + "This indicates a potential issue with line range conversion." + } + } + + // Directly pair source locations with their corresponding OSS locations and create a SnippetFinding. + return sourceLocations.zip(ossLocations).mapTo(mutableSetOf()) { (sourceLocation, ossLocation) -> + SnippetFinding( + sourceLocation, + setOf( + Snippet(score, ossLocation, provenance, primaryPurl, license, additionalData) + ) + ) } } From 37890c63d90d63dc4e8fc5f3b499b9171c051c6f Mon Sep 17 00:00:00 2001 From: Agustin Isasmendi Date: Wed, 14 May 2025 16:52:18 +0200 Subject: [PATCH 8/9] feat(scanoss): Exclude identified snippets from SnippetFindings Modify `generateSummary()` to filter out snippets that are already identified. Signed-off-by: Agustin Isasmendi --- .../src/main/kotlin/ScanOssResultParser.kt | 9 +- .../test/kotlin/ScanOssResultParserTest.kt | 14 ++ .../resources/scanoss-identified-snippet.json | 133 ++++++++++++++++++ 3 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 plugins/scanners/scanoss/src/test/resources/scanoss-identified-snippet.json diff --git a/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt b/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt index 8a8a4b0d6c535..2979110b4649e 100644 --- a/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt +++ b/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt @@ -22,6 +22,7 @@ package org.ossreviewtoolkit.plugins.scanners.scanoss import com.scanoss.dto.ScanFileDetails import com.scanoss.dto.ScanFileResult import com.scanoss.dto.enums.MatchType +import com.scanoss.dto.enums.StatusType import java.lang.invoke.MethodHandles import java.time.Instant @@ -63,7 +64,13 @@ internal fun generateSummary(startTime: Instant, endTime: Instant, results: List MatchType.snippet -> { val localFile = requireNotNull(result.filePath) - snippetFindings += createSnippetFindings(details, localFile) + if (details.status == StatusType.pending) { + snippetFindings += createSnippetFindings(details, localFile) + } else { + logger.info { "File '$localFile' is identified, not including in snippet findings." } + licenseFindings += getLicenseFindings(details, result.filePath) + copyrightFindings += getCopyrightFindings(details, result.filePath) + } } MatchType.none -> { diff --git a/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt b/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt index 64efdbb5ac6f0..662c807ec891d 100644 --- a/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt +++ b/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt @@ -224,5 +224,19 @@ class ScanOssResultParserTest : WordSpec({ sourceLocation.endLine shouldBe 24 } } + + "exclude identified snippets from snippet findings" { + // The scanoss-identified-snippet.json contains two snippets, but one is identified. + // Only unidentified snippets should be included in the SnippetFindings. + val results = readResource("/scanoss-identified-snippet.json").let { + JsonUtils.toScanFileResultsFromObject(JsonUtils.toJsonObject(it)) + } + + val time = Instant.now() + val summary = generateSummary(time, time, results) + + // Should have only one finding because the identified snippet is excluded + summary.snippetFindings should haveSize(1) + } } }) diff --git a/plugins/scanners/scanoss/src/test/resources/scanoss-identified-snippet.json b/plugins/scanners/scanoss/src/test/resources/scanoss-identified-snippet.json new file mode 100644 index 0000000000000..f1e34a76d8264 --- /dev/null +++ b/plugins/scanners/scanoss/src/test/resources/scanoss-identified-snippet.json @@ -0,0 +1,133 @@ +{ + "main.c": [ + { + "id": "snippet", + "lines": "14-22", + "oss_lines": "34-42", + "matched": "19%", + "file_hash": "4597ef1de00849bb96d42e78f2cfc3a7", + "source_hash": "f5aef06745de4d711838ea21198f2fc1", + "quality": [ + { + "score": "4/5", + "source": "best_practices" + } + ], + "cryptography": [], + "purl": [ + "pkg:sourceforge/check" + ], + "vendor": "check", + "component": "check", + "version": "0.8.1", + "latest": "0.8.1", + "url": "https://sourceforge.net/projects/check", + "status": "identified", + "release_date": "2002-03-02", + "file": "src/check_error.c", + "url_hash": "d81953e1dca4c498140c44f5d6fa92d6", + "licenses": [ + { + "name": "LGPL-2.1-or-later", + "patent_hints": "yes", + "copyleft": "yes", + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/LGPL-2.1-or-later.txt", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "osadl_updated": "2025-02-10T14:26:00+0000", + "source": "scancode", + "url": "https://spdx.org/licenses/LGPL-2.1-or-later.html" + }, + { + "name": "LGPL-2.1-or-later", + "patent_hints": "yes", + "copyleft": "yes", + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/LGPL-2.1-or-later.txt", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "osadl_updated": "2025-02-10T14:26:00+0000", + "source": "file_header", + "url": "https://spdx.org/licenses/LGPL-2.1-or-later.html" + } + ], + "url_stats": {}, + "dependencies": [], + "copyrights": [ + { + "name": "Copyright (C) 2001; 2002 Arien Malec", + "source": "file_header" + }, + { + "name": "Copyright (c) 2001; 2002 Arien Malec", + "source": "scancode" + } + ], + "vulnerabilities": [], + "server": { + "version": "5.4.10", + "kb_version": { + "monthly": "25.04", + "daily": "25.05.07" + }, + "hostname": "d2", + "flags": "16384", + "elapsed": "0.107563s" + } + } + ], + "hung_task.c": [ + { + "component": "proton_bluecross", + "file": "kernel/hung_task.c", + "file_hash": "581734935cfbe570d280a1265aaa2a6b", + "file_url": "https://api.scanoss.com/file_contents/581734935cfbe570d280a1265aaa2a6b", + "id": "snippet", + "latest": "17", + "licenses": [ + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/GPL-2.0-only.txt", + "copyleft": "yes", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "name": "GPL-2.0-only", + "osadl_updated": "2025-02-10T14:26:00+0000", + "patent_hints": "yes", + "source": "scancode", + "url": "https://spdx.org/licenses/GPL-2.0-only.html" + }, + { + "name": "GPL-2.0-only WITH Linux-syscall-note", + "source": "scancode", + "url": "https://spdx.org/licenses/GPL-2.0-only WITH Linux-syscall-note.html" + }, + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/GPL-2.0-only.txt", + "copyleft": "yes", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "name": "GPL-2.0-only", + "osadl_updated": "2025-02-10T14:26:00+0000", + "patent_hints": "yes", + "source": "scancode", + "url": "https://spdx.org/licenses/GPL-2.0-only.html" + } + ], + "lines": "12-150", + "matched": "35%", + "oss_lines": "10-148", + "purl": [ + "pkg:github/kdrag0n/proton_bluecross" + ], + "release_date": "2019-02-21", + "server": { + "kb_version": { + "daily": "25.03.27", + "monthly": "25.03" + }, + "version": "5.4.10" + }, + "source_hash": "45dd1e50621a8a32f88fbe0251a470ab", + "status": "pending", + "url": "https://github.com/kdrag0n/proton_bluecross", + "url_hash": "a9c1c67f0930dc42dbd40c29e565bcdd", + "vendor": "kdrag0n", + "version": "15" + } + ] +} From 0bbb508575d6fca0cc838bce980008c0478784c0 Mon Sep 17 00:00:00 2001 From: Agustin Isasmendi Date: Wed, 14 May 2025 18:05:34 +0200 Subject: [PATCH 9/9] fix(scanoss): Standardize SCANOSS naming in comments and documentation Change all instances of "ScanOSS" to "SCANOSS" for consistent branding. Signed-off-by: Agustin Isasmendi --- plugins/scanners/scanoss/src/main/kotlin/ScanOssConfig.kt | 4 ++-- .../scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt | 2 +- .../scanoss/src/test/kotlin/ScanOssScannerDirectoryTest.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/scanners/scanoss/src/main/kotlin/ScanOssConfig.kt b/plugins/scanners/scanoss/src/main/kotlin/ScanOssConfig.kt index b1af4522a9d0e..cf24794ccc78f 100644 --- a/plugins/scanners/scanoss/src/main/kotlin/ScanOssConfig.kt +++ b/plugins/scanners/scanoss/src/main/kotlin/ScanOssConfig.kt @@ -25,11 +25,11 @@ import org.ossreviewtoolkit.plugins.api.OrtPluginOption import org.ossreviewtoolkit.plugins.api.Secret data class ScanOssConfig( - /** The URL of the ScanOSS server. */ + /** The URL of the SCANOSS server. */ @OrtPluginOption(defaultValue = ScanApi.DEFAULT_BASE_URL) val apiUrl: String, - /** The API key used to authenticate with the ScanOSS server. */ + /** The API key used to authenticate with the SCANOSS server. */ @OrtPluginOption(defaultValue = "") val apiKey: Secret, diff --git a/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt b/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt index 2979110b4649e..c46917fe8aeaa 100644 --- a/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt +++ b/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt @@ -185,7 +185,7 @@ private fun createSnippetFindings(details: ScanFileDetails, localFilePath: Strin } /** - * Split [lineRanges] returned by ScanOSS such as "32-105,117-199" into [TextLocation]s for the given [file]. + * Split [lineRanges] returned by SCANOSS such as "32-105,117-199" into [TextLocation]s for the given [file]. */ private fun convertLines(file: String, lineRanges: String): List = lineRanges.split(',').map { lineRange -> diff --git a/plugins/scanners/scanoss/src/test/kotlin/ScanOssScannerDirectoryTest.kt b/plugins/scanners/scanoss/src/test/kotlin/ScanOssScannerDirectoryTest.kt index 18cb094ee1652..acbf4247a27b8 100644 --- a/plugins/scanners/scanoss/src/test/kotlin/ScanOssScannerDirectoryTest.kt +++ b/plugins/scanners/scanoss/src/test/kotlin/ScanOssScannerDirectoryTest.kt @@ -158,7 +158,7 @@ class ScanOssScannerDirectoryTest : StringSpec({ // [fingerprint data for the file] // --boundary-- - // Extract included filenames using a regex pattern from the ScanOSS HTTP POST. + // Extract included filenames using a regex pattern from the SCANOSS HTTP POST. // The pattern matches lines starting with "file=" followed by hash and size, then captures the filename. val filenamePattern = "file=.*?,.*?,(.+)".toRegex(RegexOption.MULTILINE) val includedFiles = requestBodies.flatMap { body ->