Skip to content

Commit 038961f

Browse files
committedFeb 11, 2025·
test(model): Add two tests for a recently fixed performance issue
Add two tests for the issue fixed by [1]. The issue was caused by code which runs `.reduceOrNull(SpdxExpression::and)` on a collection of `SpdxExpression`s which has been removed in several places [1]. While it's not possible to prevent the re-introduction of such code, test two of the callers so we can be sure that computing the effective license and creating the compund expression is releatively fast. [1]: 1751f28 Signed-off-by: Frank Viernau <x9fviern@zeiss.com>
1 parent f75a87d commit 038961f

File tree

1 file changed

+135
-1
lines changed

1 file changed

+135
-1
lines changed
 

‎model/src/test/kotlin/licenses/ResolvedLicenseInfoTest.kt

+135-1
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,28 @@ import io.kotest.matchers.shouldBe
2525

2626
import io.mockk.mockk
2727

28+
import kotlin.time.Duration.Companion.seconds
29+
30+
import kotlinx.coroutines.DelicateCoroutinesApi
31+
import kotlinx.coroutines.GlobalScope
32+
import kotlinx.coroutines.async
33+
2834
import org.ossreviewtoolkit.model.Identifier
35+
import org.ossreviewtoolkit.model.LicenseFinding
2936
import org.ossreviewtoolkit.model.LicenseSource
37+
import org.ossreviewtoolkit.model.TextLocation
38+
import org.ossreviewtoolkit.model.UnknownProvenance
39+
import org.ossreviewtoolkit.model.config.CopyrightGarbage
40+
import org.ossreviewtoolkit.model.config.LicenseFilePatterns
41+
import org.ossreviewtoolkit.model.licenses.LicenseView.Companion.CONCLUDED_OR_DECLARED_AND_DETECTED
42+
import org.ossreviewtoolkit.utils.ort.ProcessedDeclaredLicense
43+
import org.ossreviewtoolkit.utils.spdx.SpdxExpression
44+
import org.ossreviewtoolkit.utils.spdx.SpdxLicense
3045
import org.ossreviewtoolkit.utils.spdx.SpdxLicenseChoice
3146
import org.ossreviewtoolkit.utils.spdx.SpdxSingleLicenseExpression
3247
import org.ossreviewtoolkit.utils.spdx.toSpdx
3348

49+
@DelicateCoroutinesApi
3450
class ResolvedLicenseInfoTest : WordSpec({
3551
"effectiveLicense()" should {
3652
"apply choices for LicenseView.ALL on all resolved licenses" {
@@ -81,7 +97,7 @@ class ResolvedLicenseInfoTest : WordSpec({
8197
)
8298

8399
val effectiveLicense = RESOLVED_LICENSE_INFO.effectiveLicense(
84-
LicenseView.CONCLUDED_OR_DECLARED_AND_DETECTED,
100+
CONCLUDED_OR_DECLARED_AND_DETECTED,
85101
choices
86102
)
87103

@@ -119,6 +135,28 @@ class ResolvedLicenseInfoTest : WordSpec({
119135

120136
effectiveLicense shouldBe "$APACHE and ($MIT or $GPL) and $BSD".toSpdx()
121137
}
138+
139+
"execute in reasonable time for large license info with several OR operators".config(
140+
blockingTest = true,
141+
timeout = 1.seconds
142+
) {
143+
runCancellable {
144+
COMPUTATION_HEAVY_RESOLVED_LICENSE_INFO.effectiveLicense(CONCLUDED_OR_DECLARED_AND_DETECTED)
145+
}
146+
}
147+
}
148+
149+
"toCompoundExpression()" should {
150+
"execute in reasonable time for large license info with several OR operators".config(
151+
blockingTest = true,
152+
timeout = 1.seconds
153+
) {
154+
// Run non-cancellable operation in such a way that cancellation does not wait for completion, see also
155+
// https://github.com/Kotlin/kotlinx.coroutines/issues/1449#issuecomment-522907869.
156+
runCancellable {
157+
COMPUTATION_HEAVY_RESOLVED_LICENSE_INFO.toCompoundExpression()
158+
}
159+
}
122160
}
123161

124162
"applyChoices(licenseChoices)" should {
@@ -187,3 +225,99 @@ private val RESOLVED_LICENSE_INFO: ResolvedLicenseInfo by lazy {
187225
unmatchedCopyrights = emptyMap()
188226
)
189227
}
228+
229+
/**
230+
* A (detected) resolved license info found in a real world scan, which makes several class members, for example
231+
* effectiveLicense(), rather computation-heavy.
232+
*/
233+
private val COMPUTATION_HEAVY_RESOLVED_LICENSE_INFO: ResolvedLicenseInfo by lazy {
234+
val licensesWithoutChoice = SpdxLicense.values()
235+
.toList()
236+
.subList(0, 200)
237+
.map { SpdxExpression.parse(it.id) }
238+
239+
// expressions take from a real world scan with swapped identifiers.
240+
val licensesWithChoice = listOf(
241+
"AAL OR Abstyles",
242+
"AdaCore-doc OR Adobe-2006",
243+
"Adobe-Display-PostScript OR Adobe-Glyph",
244+
"Adobe-Display-PostScript OR AAL",
245+
"Adobe-Utopia OR Adobe-2006",
246+
"Adobe-Display-PostScript OR Adobe-2006",
247+
"(Adobe-2006 OR AdaCore-doc) AND ADSL AND AFL-1.1 AND AFL-1.2 AND AFL-2.0 AND AFL-2.1",
248+
"AFL-3.0 OR Afmparse",
249+
"AGPL-1.0 OR Abstyles",
250+
"AFL-3.0 OR AGPL-1.0-only",
251+
"AGPL-1.0-or-later OR AGPL-3.0",
252+
"AAL OR Adobe-Glyph",
253+
"(AGPL-3.0-only OR AAL) AND AAL",
254+
"AGPL-3.0-or-later OR AGPL-1.0-or-later",
255+
"AAL OR AGPL-1.0-or-later",
256+
"AGPL-3.0-only OR Adobe-2006",
257+
"Adobe-Display-PostScript OR Aladdin",
258+
"AMDPLPA OR AFL-3.0",
259+
"AMD-newlib OR AAL",
260+
"AML OR AML OR AML OR AML OR AML OR AML",
261+
"AML-glslang OR AAL",
262+
"AMPAS OR ANTLR-PD",
263+
"AAL OR ANTLR-PD-fallback",
264+
"any-OSI OR AML-glslang",
265+
"Adobe-2006 OR AML-glslang"
266+
).map { SpdxExpression.parse(it) }
267+
268+
val licenseFindings = (licensesWithoutChoice + licensesWithChoice).mapTo(mutableSetOf()) { license ->
269+
LicenseFinding(
270+
license = license,
271+
location = TextLocation(
272+
path = "path",
273+
startLine = 1,
274+
endLine = 2
275+
)
276+
)
277+
}
278+
279+
val licenseInfo = LicenseInfo(
280+
id = Identifier.EMPTY,
281+
declaredLicenseInfo = DeclaredLicenseInfo(
282+
authors = emptySet(),
283+
licenses = emptySet(),
284+
appliedCurations = emptyList(),
285+
processed = ProcessedDeclaredLicense(SpdxExpression.parse("NONE"))
286+
),
287+
detectedLicenseInfo = DetectedLicenseInfo(
288+
findings = listOf(
289+
Findings(
290+
provenance = UnknownProvenance,
291+
licenses = licenseFindings,
292+
copyrights = emptySet(),
293+
licenseFindingCurations = emptyList(),
294+
pathExcludes = emptyList(),
295+
relativeFindingsPath = ""
296+
)
297+
)
298+
),
299+
concludedLicenseInfo = ConcludedLicenseInfo(
300+
concludedLicense = null,
301+
appliedCurations = emptyList()
302+
)
303+
)
304+
305+
val resolver = LicenseInfoResolver(
306+
provider = SimpleLicenseInfoProvider(listOf(licenseInfo)),
307+
copyrightGarbage = CopyrightGarbage(emptySet()),
308+
archiver = null,
309+
licenseFilePatterns = LicenseFilePatterns.DEFAULT,
310+
addAuthorsToCopyrights = false
311+
)
312+
313+
resolver.resolveLicenseInfo(licenseInfo.id)
314+
}
315+
316+
@DelicateCoroutinesApi
317+
private suspend fun runCancellable(nonCancellableBlock: () -> Unit) {
318+
// Run non-cancellable operation in such a way that cancellation does not wait for completion, see also
319+
// https://github.com/Kotlin/kotlinx.coroutines/issues/1449#issuecomment-522907869.
320+
GlobalScope.async {
321+
nonCancellableBlock()
322+
}.await()
323+
}

0 commit comments

Comments
 (0)
Please sign in to comment.