-
Notifications
You must be signed in to change notification settings - Fork 312
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
Migrate the Opossum reporter to KxS #9484
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,49 +19,37 @@ | |
|
||
package org.ossreviewtoolkit.plugins.reporters.opossum | ||
|
||
import com.fasterxml.jackson.databind.json.JsonMapper | ||
|
||
import io.kotest.core.TestConfiguration | ||
import io.kotest.assertions.json.shouldEqualSpecifiedJsonIgnoringOrder | ||
import io.kotest.core.spec.style.WordSpec | ||
import io.kotest.engine.spec.tempdir | ||
import io.kotest.matchers.sequences.shouldContain | ||
import io.kotest.matchers.shouldBe | ||
import io.kotest.matchers.string.shouldContain | ||
|
||
import org.ossreviewtoolkit.model.OrtResult | ||
import java.time.LocalDateTime | ||
|
||
import org.ossreviewtoolkit.reporter.ReporterInput | ||
import org.ossreviewtoolkit.utils.common.normalizeLineBreaks | ||
import org.ossreviewtoolkit.utils.common.unpackZip | ||
import org.ossreviewtoolkit.utils.test.getAssetFile | ||
import org.ossreviewtoolkit.utils.test.patchActualResult | ||
import org.ossreviewtoolkit.utils.test.patchExpectedResult | ||
import org.ossreviewtoolkit.utils.test.readOrtResult | ||
|
||
class OpossumReporterFunTest : WordSpec({ | ||
"generateReport()" should { | ||
val ortResult = readOrtResult("src/funTest/assets/reporter-test-input.yml") | ||
val reportStr = generateReport(ortResult).normalizeLineBreaks() | ||
val replacements = mapOf( | ||
"\"fileCreationDate\":\"[^\"]+\"" to "\"fileCreationDate\":\"${LocalDateTime.MIN}\"", | ||
"\"[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}\"" to "\"00000000-0000-0000-0000-000000000000\"" | ||
) | ||
|
||
"create '.opossum' output containing an 'input.json' with expected string" { | ||
reportStr shouldContain "fileCreationDate" | ||
} | ||
"The generated report" should { | ||
"match the expected result" { | ||
val ortResult = readOrtResult("src/funTest/assets/reporter-test-input.yml") | ||
val input = ReporterInput(ortResult) | ||
|
||
val outputDir = tempdir() | ||
val expectedFile = getAssetFile("reporter-test-output.json") | ||
|
||
"create a parseable result and contain some expected values" { | ||
with(JsonMapper().readTree(reportStr)) { | ||
isObject shouldBe true | ||
get("metadata").get("projectId").asText() shouldBe "0" | ||
get("attributionBreakpoints").size() shouldBe 4 | ||
get("externalAttributionSources").size() shouldBe 7 | ||
get("resourcesToAttributions").fieldNames().asSequence() shouldContain | ||
"/analyzer/src/funTest/assets/projects/synthetic/gradle/lib/build.gradle/" + | ||
"compile/org.apache.commons/[email protected]/dependencies/org.apache.commons/[email protected]" | ||
} | ||
OpossumReporterFactory.create().generateReport(input, outputDir).single().getOrThrow().unpackZip(outputDir) | ||
|
||
val actualResult = patchActualResult(outputDir.resolve("input.json").readText(), custom = replacements) | ||
actualResult shouldEqualSpecifiedJsonIgnoringOrder patchExpectedResult(expectedFile) | ||
} | ||
} | ||
}) | ||
|
||
private fun TestConfiguration.generateReport(ortResult: OrtResult): String { | ||
val input = ReporterInput(ortResult) | ||
val outputDir = tempdir() | ||
|
||
OpossumReporterFactory.create().generateReport(input, outputDir).single().getOrThrow().unpackZip(outputDir) | ||
|
||
return outputDir.resolve("input.json").readText() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
/* | ||
* Copyright (C) 2024 The ORT Project Authors (see <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>) | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* License-Filename: LICENSE | ||
*/ | ||
|
||
package org.ossreviewtoolkit.plugins.reporters.opossum | ||
|
||
import io.ks3.java.typealiases.LocalDateTimeAsString | ||
import io.ks3.java.typealiases.UuidAsString | ||
|
||
import java.io.File | ||
import java.time.LocalDateTime | ||
|
||
import kotlinx.serialization.KSerializer | ||
import kotlinx.serialization.Serializable | ||
import kotlinx.serialization.builtins.MapSerializer | ||
import kotlinx.serialization.builtins.serializer | ||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor | ||
import kotlinx.serialization.encoding.Decoder | ||
import kotlinx.serialization.encoding.Encoder | ||
import kotlinx.serialization.json.Json | ||
import kotlinx.serialization.json.encodeToStream | ||
|
||
import org.ossreviewtoolkit.model.Identifier | ||
import org.ossreviewtoolkit.model.utils.getPurlType | ||
import org.ossreviewtoolkit.utils.spdx.SpdxExpression | ||
|
||
internal val JSON = Json { | ||
explicitNulls = false | ||
encodeDefaults = true | ||
} | ||
|
||
internal fun File.writeReport(opossumInput: OpossumInput): File = | ||
apply { outputStream().use { JSON.encodeToStream(opossumInput, it) } } | ||
|
||
@Serializable | ||
internal data class OpossumInput( | ||
val metadata: OpossumInputMetadata = OpossumInputMetadata(), | ||
val resources: OpossumResources, | ||
val externalAttributions: Map<UuidAsString, OpossumSignalFlat>, | ||
val resourcesToAttributions: Map<String, Set<UuidAsString>>, | ||
val attributionBreakpoints: Set<String>, | ||
val filesWithChildren: Set<String>, | ||
val frequentLicenses: Set<OpossumFrequentLicense>, | ||
val baseUrlsForSources: Map<String, String>, | ||
val externalAttributionSources: Map<String, OpossumExternalAttributionSource> | ||
) { | ||
fun getSignalsForFile(file: String): List<OpossumSignalFlat> = | ||
Check warning Code scanning / QDJVMC Unused symbol Warning
Function "getSignalsForFile" is never used
|
||
resourcesToAttributions[file].orEmpty().mapNotNull { uuid -> externalAttributions[uuid] } | ||
} | ||
|
||
@Serializable | ||
internal data class OpossumInputMetadata( | ||
val projectId: String = "0", | ||
val fileCreationDate: LocalDateTimeAsString = LocalDateTime.now() | ||
) | ||
|
||
@Serializable(OpossumResourcesSerializer::class) | ||
internal data class OpossumResources( | ||
val tree: MutableMap<String, OpossumResources> = mutableMapOf() | ||
) { | ||
fun addResource(pathPieces: List<String>) { | ||
Check notice Code scanning / QDJVMC Class member can have 'private' visibility Note
Function 'addResource' could be private
|
||
if (pathPieces.isEmpty()) { | ||
return | ||
} | ||
|
||
val head = pathPieces.first() | ||
val tail = pathPieces.drop(1) | ||
|
||
if (head !in tree) { | ||
tree[head] = OpossumResources() | ||
} | ||
|
||
tree.getValue(head).addResource(tail) | ||
} | ||
|
||
fun addResource(path: String) { | ||
val pathPieces = path.split("/").filter { it.isNotEmpty() } | ||
|
||
addResource(pathPieces) | ||
} | ||
|
||
fun isFile() = tree.isEmpty() | ||
|
||
fun isPathAFile(path: String): Boolean { | ||
val pathPieces = path.split("/").filter { it.isNotEmpty() } | ||
|
||
return isPathAFile(pathPieces) | ||
} | ||
|
||
fun isPathAFile(pathPieces: List<String>): Boolean { | ||
Check notice Code scanning / QDJVMC Class member can have 'private' visibility Note
Function 'isPathAFile' could be private
|
||
if (pathPieces.isEmpty()) { | ||
return isFile() | ||
} | ||
|
||
val head = pathPieces.first() | ||
val tail = pathPieces.drop(1) | ||
|
||
return head !in tree || tree.getValue(head).isPathAFile(tail) | ||
} | ||
|
||
fun toFileList(): Set<String> = | ||
Check notice on line 116 in plugins/reporters/opossum/src/main/kotlin/OpossumModel.kt GitHub Actions / qodana-scanClass member can have 'private' visibility
|
||
Check notice Code scanning / QDJVMC Class member can have 'private' visibility Note
Function 'toFileList' could be private
Check warning Code scanning / QDJVMC Unused symbol Warning
Function "toFileList" is never used
|
||
tree.flatMapTo(mutableSetOf()) { (key, value) -> | ||
value.toFileList().map { resolvePath(key, it, isDirectory = false) } | ||
}.plus("/") | ||
} | ||
|
||
private object OpossumResourcesSerializer : KSerializer<OpossumResources> { | ||
override val descriptor = buildClassSerialDescriptor("Resource") | ||
|
||
override fun serialize(encoder: Encoder, value: OpossumResources) { | ||
if (value.isFile()) { | ||
encoder.encodeInt(1) | ||
} else { | ||
encoder.encodeSerializableValue(MapSerializer(String.serializer(), this), value.tree) | ||
} | ||
} | ||
|
||
override fun deserialize(decoder: Decoder): OpossumResources { | ||
throw NotImplementedError("Deserialization of OpossumResources is not supported.") | ||
} | ||
} | ||
|
||
@Serializable | ||
internal data class OpossumSignalFlat( | ||
val source: OpossumSignalSource, | ||
val attributionConfidence: Int = 80, | ||
val packageType: String?, | ||
val packageNamespace: String?, | ||
val packageName: String?, | ||
val packageVersion: String?, | ||
val copyright: String?, | ||
val licenseName: String?, | ||
val url: String?, | ||
val preSelected: Boolean, | ||
val followUp: OpossumFollowUp?, | ||
val excludeFromNotice: Boolean, | ||
val comment: String? | ||
) { | ||
companion object { | ||
fun create(signal: OpossumSignal): OpossumSignalFlat = | ||
OpossumSignalFlat( | ||
source = signal.base.source, | ||
attributionConfidence = signal.attributionConfidence, | ||
packageType = signal.base.packageType, | ||
packageNamespace = signal.base.packageNamespace, | ||
packageName = signal.base.packageName, | ||
packageVersion = signal.base.packageVersion, | ||
copyright = signal.base.copyright, | ||
licenseName = signal.base.licenseName, | ||
url = signal.base.url, | ||
preSelected = signal.base.preSelected, | ||
followUp = signal.followUp, | ||
excludeFromNotice = signal.excludeFromNotice, | ||
comment = signal.base.comment | ||
) | ||
} | ||
|
||
data class OpossumSignal( | ||
val base: OpossumSignalBase, | ||
val attributionConfidence: Int = 80, | ||
val followUp: OpossumFollowUp?, | ||
val excludeFromNotice: Boolean | ||
) { | ||
companion object { | ||
@Suppress("LongParameterList") | ||
fun create( | ||
source: String, | ||
id: Identifier? = null, | ||
url: String? = null, | ||
license: SpdxExpression? = null, | ||
copyright: String? = null, | ||
comment: String? = null, | ||
preSelected: Boolean = false, | ||
followUp: Boolean = false, | ||
excludeFromNotice: Boolean = false | ||
): OpossumSignal = | ||
OpossumSignal( | ||
base = OpossumSignalBase( | ||
source = OpossumSignalSource(name = source), | ||
packageType = id?.getPurlType().toString(), | ||
packageNamespace = id?.namespace, | ||
packageName = id?.name, | ||
packageVersion = id?.version, | ||
copyright = copyright, | ||
licenseName = license?.toString(), | ||
url = url, | ||
preSelected = preSelected, | ||
comment = comment | ||
), | ||
followUp = OpossumFollowUp.FOLLOW_UP.takeIf { followUp }, | ||
excludeFromNotice = excludeFromNotice | ||
) | ||
} | ||
|
||
data class OpossumSignalBase( | ||
val source: OpossumSignalSource, | ||
val packageType: String?, | ||
val packageNamespace: String?, | ||
val packageName: String?, | ||
val packageVersion: String?, | ||
val copyright: String?, | ||
val licenseName: String?, | ||
val url: String?, | ||
val preSelected: Boolean, | ||
val comment: String? | ||
) | ||
} | ||
} | ||
|
||
@Serializable | ||
internal data class OpossumSignalSource( | ||
val name: String, | ||
val documentConfidence: Int = 80 | ||
) | ||
|
||
internal enum class OpossumFollowUp { | ||
FOLLOW_UP | ||
} | ||
|
||
@Serializable | ||
internal data class OpossumFrequentLicense( | ||
val shortName: String, | ||
val fullName: String?, | ||
val defaultText: String? | ||
) : Comparable<OpossumFrequentLicense> { | ||
override fun compareTo(other: OpossumFrequentLicense) = | ||
compareValuesBy( | ||
this, | ||
other, | ||
{ it.shortName }, | ||
{ it.fullName }, | ||
{ it.defaultText } | ||
) | ||
} | ||
|
||
@Serializable | ||
internal data class OpossumExternalAttributionSource( | ||
val name: String, | ||
val priority: Int | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commit message nits:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The commit message body still uses simple present instead of imperative mood.