Skip to content

Commit

Permalink
refactor(spdx-utils): Split the large SpdxExpressionTest
Browse files Browse the repository at this point in the history
Improve the overview before adding more tests.

Signed-off-by: Sebastian Schuberth <sebastian@doubleopen.org>
  • Loading branch information
sschuberth committed Nov 6, 2024
1 parent 757d38d commit 94a8708
Show file tree
Hide file tree
Showing 3 changed files with 382 additions and 330 deletions.
37 changes: 37 additions & 0 deletions utils/spdx/src/test/kotlin/SpdxCompoundExpressionTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2017 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.utils.spdx

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.WordSpec

class SpdxCompoundExpressionTest : WordSpec({
"Creating a compound expression" should {
"fail if the expression has less than two children" {
shouldThrow<IllegalArgumentException> {
SpdxCompoundExpression(SpdxOperator.AND, emptyList())
}

shouldThrow<IllegalArgumentException> {
SpdxCompoundExpression(SpdxOperator.AND, listOf(SpdxLicenseIdExpression("license")))
}
}
}
})
345 changes: 345 additions & 0 deletions utils/spdx/src/test/kotlin/SpdxExpressionChoiceTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,345 @@
/*
* Copyright (C) 2017 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.utils.spdx

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.WordSpec
import io.kotest.matchers.collections.containExactlyInAnyOrder
import io.kotest.matchers.should
import io.kotest.matchers.shouldBe

class SpdxExpressionChoiceTest : WordSpec({
"applyChoice()" should {
"return the choice for a simple expression" {
val expression = "a".toSpdx()
val choice = "a".toSpdx()

val result = expression.applyChoice(choice)

result shouldBe "a".toSpdx()
}

"throw an exception if the user chose a wrong license for a simple expression" {
val expression = "a".toSpdx()
val choice = "b".toSpdx()

shouldThrow<InvalidLicenseChoiceException> { expression.applyChoice(choice) }
}

"return the new expression if only a part of the expression is matched by the subExpression" {
val expression = "a OR b OR c".toSpdx()
val choice = "b".toSpdx()
val subExpression = "a OR b".toSpdx()

val result = expression.applyChoice(choice, subExpression)

result shouldBe "b OR c".toSpdx()
}

"work with choices that itself are a choice" {
val expression = "a OR b OR c OR d".toSpdx()
val choice = "a OR b".toSpdx()
val subExpression = "a OR b OR c".toSpdx()

val result = expression.applyChoice(choice, subExpression)

result shouldBe "a OR b OR d".toSpdx()
}

"apply the choice if the expression contains multiple choices" {
val expression = "a OR b OR c".toSpdx()
val choice = "b".toSpdx()

val result = expression.applyChoice(choice)

result shouldBe "b".toSpdx()
}

"throw an exception if the chosen license is not a valid option" {
val expression = "a OR b".toSpdx()
val choice = "c".toSpdx()

shouldThrow<InvalidLicenseChoiceException> { expression.applyChoice(choice) }
}

"apply the choice even if not literally contained in the expression" {
val expression = "(a OR b) AND c".toSpdx()
val choice = "a AND c".toSpdx()

val result = expression.applyChoice(choice)

result shouldBe "a AND c".toSpdx()
}

"return the reduced subExpression if the choice was valid" {
val expression = "(a OR b) AND c AND (d OR e)".toSpdx()
val choice = "a AND c AND d".toSpdx()
val subExpression = "a AND c AND d OR a AND c AND e".toSpdx()

val result = expression.applyChoice(choice, subExpression)

result shouldBe "a AND c AND d OR b AND c AND d OR b AND c AND e".toSpdx()
}

"throw an exception if the subExpression does not match the simple expression" {
val expression = "a".toSpdx()
val choice = "x".toSpdx()
val subExpression = "x OR y".toSpdx()

shouldThrow<InvalidSubExpressionException> { expression.applyChoice(choice, subExpression) }
}

"throw an exception if the subExpression does not match the expression" {
val expression = "a OR b OR c".toSpdx()
val choice = "x".toSpdx()
val subExpression = "x OR y OR z".toSpdx()

shouldThrow<InvalidSubExpressionException> { expression.applyChoice(choice, subExpression) }
}

"throw an exception if the subExpression does not match" {
val expression = "(a OR b) AND c AND (d OR e)".toSpdx()
val choice = "a AND c AND d".toSpdx()
val subExpression = "(a AND c AND d) OR (x AND y AND z)".toSpdx()

shouldThrow<InvalidSubExpressionException> { expression.applyChoice(choice, subExpression) }
}
}

"applyChoices()" should {
"return the correct result if a single choice is applied" {
val expression = "a OR b OR c OR d".toSpdx()

val choices = listOf(SpdxLicenseChoice(expression, "a".toSpdx()))

val result = expression.applyChoices(choices)

result shouldBe "a".toSpdx()
}

"return the correct result if multiple simple choices are applied" {
val expression = "(a OR b) AND (c OR d)".toSpdx()

val choices = listOf(
SpdxLicenseChoice("a OR b".toSpdx(), "a".toSpdx()),
SpdxLicenseChoice("c OR d".toSpdx(), "c".toSpdx())
)

val result = expression.applyChoices(choices)

result shouldBe "a AND c".toSpdx()
}

"ignore invalid sub-expressions and return the correct result for valid choices" {
val expression = "a OR b OR c OR d".toSpdx()

val choices = listOf(
SpdxLicenseChoice("a OR b".toSpdx(), "b".toSpdx()), // b OR c OR d
SpdxLicenseChoice("a OR c".toSpdx(), "a".toSpdx()) // not applied
)

val result = expression.applyChoices(choices)

result shouldBe "b OR c OR d".toSpdx()
}

"apply the second choice to the effective license after the first choice" {
val expression = "a OR b OR c OR d".toSpdx()

val choices = listOf(
SpdxLicenseChoice("a OR b".toSpdx(), "b".toSpdx()), // b OR c OR d
SpdxLicenseChoice("b OR c".toSpdx(), "b".toSpdx()) // b OR d
)

val result = expression.applyChoices(choices)

result shouldBe "b OR d".toSpdx()
}

"apply a single choice to multiple expressions" {
val expression = "(a OR b) AND (c OR d) AND (a OR e)".toSpdx()

val choices = listOf(
SpdxLicenseChoice("a OR b".toSpdx(), "a".toSpdx()),
SpdxLicenseChoice("a OR e".toSpdx(), "a".toSpdx())
)

val result = expression.applyChoices(choices)

result shouldBe "a AND (c OR d) AND a".toSpdx()
}

"given expressions should match semantically equivalent license expressions" {
val expression = "a OR b".toSpdx()

val choices = listOf(
SpdxLicenseChoice("b OR a".toSpdx(), "a".toSpdx())
)

val result = expression.applyChoices(choices)

result shouldBe "a".toSpdx()
}
}

"isSubExpression()" should {
"return true for the same simple expression" {
val mit = "MIT".toSpdx() as SpdxSimpleExpression

mit.isSubExpression(mit) shouldBe true
}

"return true for the same single expression" {
val mit = "GPL-2.0-only WITH Classpath-exception-2.0".toSpdx() as SpdxSingleLicenseExpression

mit.isSubExpression(mit) shouldBe true
}

"return true for the same compound expression" {
val mit = "CDDL-1.1 OR GPL-2.0-only".toSpdx() as SpdxCompoundExpression

mit.isSubExpression(mit) shouldBe true
}

"work correctly for compound expressions with exceptions" {
val gplWithException = "CDDL-1.1 OR GPL-2.0-only WITH Classpath-exception-2.0".toSpdx()
val gpl = "CDDL-1.1 OR GPL-2.0-only".toSpdx()

gplWithException.isSubExpression(gpl) shouldBe false
}

"work correctly for nested compound expressions" {
val expression = "(CDDL-1.1 OR GPL-2.0-only) AND (CDDL-1.1 OR GPL-2.0-only WITH Classpath-exception-2.0)"
.toSpdx()
val subExpression = "CDDL-1.1 OR GPL-2.0-only".toSpdx()

expression.isSubExpression(subExpression) shouldBe true
}
}

"isValidChoice()" should {
"return true if a choice is valid" {
val spdxExpression = "(a OR b) AND c AND (d OR e)".toSpdx()

spdxExpression.isValidChoice("a AND c AND d".toSpdx()) shouldBe true
spdxExpression.isValidChoice("a AND d AND c".toSpdx()) shouldBe true
spdxExpression.isValidChoice("c AND a AND d".toSpdx()) shouldBe true
spdxExpression.isValidChoice("c AND d AND a".toSpdx()) shouldBe true
spdxExpression.isValidChoice("d AND a AND c".toSpdx()) shouldBe true
spdxExpression.isValidChoice("d AND c AND a".toSpdx()) shouldBe true

spdxExpression.isValidChoice("a AND c AND e".toSpdx()) shouldBe true
spdxExpression.isValidChoice("b AND c AND d".toSpdx()) shouldBe true
spdxExpression.isValidChoice("b AND c AND e".toSpdx()) shouldBe true
}

"return false if a choice is invalid" {
val spdxExpression = "(a OR b) AND c AND (d OR e)".toSpdx()

spdxExpression.isValidChoice("a".toSpdx()) shouldBe false
spdxExpression.isValidChoice("a AND b".toSpdx()) shouldBe false
spdxExpression.isValidChoice("a AND c".toSpdx()) shouldBe false
spdxExpression.isValidChoice("a AND d".toSpdx()) shouldBe false
spdxExpression.isValidChoice("a AND e".toSpdx()) shouldBe false
spdxExpression.isValidChoice("a AND b AND c".toSpdx()) shouldBe false
spdxExpression.isValidChoice("a AND b AND d".toSpdx()) shouldBe false
spdxExpression.isValidChoice("a AND b AND c AND d".toSpdx()) shouldBe false
}

"return true for a simplified choice for a complex expression" {
val license = "(MIT OR GPL-2.0-only) AND (MIT OR BSD-3-Clause OR GPL-1.0-or-later) AND " +
"(MIT OR BSD-3-Clause OR GPL-2.0-only)"

license.toSpdx().isValidChoice("MIT".toSpdx()) shouldBe true
}
}

"offersChoice()" should {
"return true if the expression contains the OR operator" {
"a OR b".toSpdx().offersChoice() shouldBe true
"a AND b OR c".toSpdx().offersChoice() shouldBe true
"a OR b AND c".toSpdx().offersChoice() shouldBe true
"a AND b AND c OR d".toSpdx().offersChoice() shouldBe true
}

"return false if the expression does not contain the OR operator" {
"a".toSpdx().offersChoice() shouldBe false
"a AND b".toSpdx().offersChoice() shouldBe false
"a AND b AND c".toSpdx().offersChoice() shouldBe false
}
}

"validChoices()" should {
"return the original terms of an expression in DNF" {
val choices = "(a AND b) OR (c AND d) OR (e AND f)".toSpdx().validChoices()

choices.map { it.toString() } should containExactlyInAnyOrder(
"a AND b",
"c AND d",
"e AND f"
)
}

"return the distribution of all terms of an expression in CNF" {
val choices = "(a OR b) AND (c OR d)".toSpdx().validChoices()

choices.map { it.toString() } should containExactlyInAnyOrder(
"a AND c",
"a AND d",
"b AND c",
"b AND d"
)
}

"return the valid choices for a complex expression" {
val choices = "(a OR b) AND c AND (d OR e)".toSpdx().validChoices()

choices.map { it.toString() } should containExactlyInAnyOrder(
"a AND c AND d",
"a AND c AND e",
"b AND c AND d",
"b AND c AND e"
)
}

"return the valid choices for a nested expression" {
val choices = "(a OR (b AND (c OR d))) AND (e OR f)".toSpdx().validChoices()

choices.map { it.toString() } should containExactlyInAnyOrder(
"a AND e",
"a AND f",
"b AND c AND e",
"b AND c AND f",
"b AND d AND e",
"b AND d AND f"
)
}

"be explicit about the choices even if they could be simplified" {
val choices = "(a OR b) AND (a OR b)".toSpdx().validChoices()

choices.map { it.toString() } should containExactlyInAnyOrder(
"a AND a",
"b AND a",
"b AND b"
)
}
}
})
Loading

0 comments on commit 94a8708

Please sign in to comment.