Skip to content

Commit 6ca7dd6

Browse files
nnobelismnonnenmacher
authored andcommitted
feat(model): Apply includes to the OrtResult
The includes are applied trivially: if no include is defined, everything is included. If an include is defined for a path, only the project correponding to this path is included (= not excluded) in the results. Scope includes are not supported. Signed-off-by: Nicolas Nobelis <[email protected]>
1 parent 11f4c9c commit 6ca7dd6

File tree

3 files changed

+337
-1
lines changed

3 files changed

+337
-1
lines changed

model/src/main/kotlin/OrtResult.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import org.apache.logging.log4j.kotlin.loggerOf
3030
import org.ossreviewtoolkit.model.DependencyNavigator.Companion.MATCH_SUB_PROJECTS
3131
import org.ossreviewtoolkit.model.ResolvedPackageCurations.Companion.REPOSITORY_CONFIGURATION_PROVIDER_ID
3232
import org.ossreviewtoolkit.model.config.Excludes
33+
import org.ossreviewtoolkit.model.config.Includes
3334
import org.ossreviewtoolkit.model.config.IssueResolution
3435
import org.ossreviewtoolkit.model.config.LicenseFindingCuration
3536
import org.ossreviewtoolkit.model.config.PackageConfiguration
@@ -189,9 +190,19 @@ data class OrtResult(
189190
{ project -> project.id },
190191
{ project ->
191192
val pathExcludes = getExcludes().findPathExcludes(project, this)
193+
val pathIncludes = getIncludes().findPathIncludes(project, this)
194+
195+
val isExcluded = if (getIncludes() == Includes.EMPTY) {
196+
// No includes are defined. It is excluded if it has path excludes.
197+
pathExcludes.isNotEmpty()
198+
} else {
199+
// Some includes are defined. It is excluded if it has no path includes or has path excludes.
200+
pathIncludes.isEmpty() || pathExcludes.isNotEmpty()
201+
}
202+
192203
ProjectEntry(
193204
project = project,
194-
isExcluded = pathExcludes.isNotEmpty()
205+
isExcluded = isExcluded
195206
)
196207
}
197208
)
@@ -246,6 +257,9 @@ data class OrtResult(
246257
return dependencies
247258
}
248259

260+
@JsonIgnore
261+
fun getIncludes(): Includes = repository.config.includes
262+
249263
@JsonIgnore
250264
fun getExcludes(): Excludes = repository.config.excludes
251265

model/src/test/kotlin/OrtResultTest.kt

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import io.kotest.matchers.string.shouldMatch
3434
import io.kotest.matchers.types.beInstanceOf
3535

3636
import org.ossreviewtoolkit.model.config.Excludes
37+
import org.ossreviewtoolkit.model.config.Includes
3738
import org.ossreviewtoolkit.model.config.IssueResolution
3839
import org.ossreviewtoolkit.model.config.IssueResolutionReason
3940
import org.ossreviewtoolkit.model.config.PathExclude
@@ -42,6 +43,8 @@ import org.ossreviewtoolkit.model.config.RepositoryConfiguration
4243
import org.ossreviewtoolkit.model.config.Resolutions
4344
import org.ossreviewtoolkit.model.config.RuleViolationResolution
4445
import org.ossreviewtoolkit.model.config.RuleViolationResolutionReason
46+
import org.ossreviewtoolkit.model.config.config.PathInclude
47+
import org.ossreviewtoolkit.model.config.config.PathIncludeReason
4548
import org.ossreviewtoolkit.utils.test.readOrtResult
4649

4750
class OrtResultTest : WordSpec({
@@ -247,6 +250,80 @@ class OrtResultTest : WordSpec({
247250
openIssues.map { it.message } shouldHaveSingleElement "Included issue"
248251
}
249252

253+
"omit issues of non-included projects" {
254+
val ortResult = OrtResult.EMPTY.copy(
255+
repository = Repository.EMPTY.copy(
256+
config = RepositoryConfiguration(
257+
includes = Includes(
258+
paths = listOf(
259+
PathInclude(
260+
pattern = "included/pom.xml",
261+
reason = PathIncludeReason.SOURCE_OF
262+
)
263+
)
264+
)
265+
)
266+
),
267+
analyzer = AnalyzerRun.EMPTY.copy(
268+
result = AnalyzerResult.EMPTY.copy(
269+
projects = setOf(
270+
Project.EMPTY.copy(
271+
id = Identifier("Maven:org.oss-review-toolkit:excluded:1.0"),
272+
definitionFilePath = "excluded/pom.xml",
273+
declaredLicenses = emptySet()
274+
)
275+
),
276+
issues = mapOf(
277+
Identifier("Maven:org.oss-review-toolkit:excluded:1.0") to
278+
listOf(Issue(message = "Excluded issue", source = "")),
279+
Identifier("Maven:org.oss-review-toolkit:included:1.0") to
280+
listOf(Issue(message = "Included issue", source = ""))
281+
)
282+
)
283+
)
284+
)
285+
286+
val openIssues = ortResult.getOpenIssues()
287+
288+
openIssues.map { it.message } shouldHaveSingleElement "Included issue"
289+
}
290+
291+
"include issues of included projects" {
292+
val ortResult = OrtResult.EMPTY.copy(
293+
repository = Repository.EMPTY.copy(
294+
config = RepositoryConfiguration(
295+
includes = Includes(
296+
paths = listOf(
297+
PathInclude(
298+
pattern = "included/pom.xml",
299+
reason = PathIncludeReason.SOURCE_OF
300+
)
301+
)
302+
)
303+
)
304+
),
305+
analyzer = AnalyzerRun.EMPTY.copy(
306+
result = AnalyzerResult.EMPTY.copy(
307+
projects = setOf(
308+
Project.EMPTY.copy(
309+
id = Identifier("Maven:org.oss-review-toolkit:included:1.0"),
310+
definitionFilePath = "included/pom.xml",
311+
declaredLicenses = emptySet()
312+
)
313+
),
314+
issues = mapOf(
315+
Identifier("Maven:org.oss-review-toolkit:included:1.0") to
316+
listOf(Issue(message = "Included issue", source = ""))
317+
)
318+
)
319+
)
320+
)
321+
322+
val openIssues = ortResult.getOpenIssues()
323+
324+
openIssues.map { it.message } shouldHaveSingleElement "Included issue"
325+
}
326+
250327
"omit scan issues with excluded affected path" {
251328
val projectId = Identifier("Maven:org.oss-review-toolkit:example-project:1.0")
252329
val vcs = VcsInfo(
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/*
2+
* Copyright (C) 2025 The ORT Project Authors (see <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
* License-Filename: LICENSE
18+
*/
19+
20+
@file:Suppress("MaxLineLength")
21+
22+
package org.ossreviewtoolkit.model.config
23+
24+
import io.kotest.core.spec.style.WordSpec
25+
import io.kotest.core.test.TestCase
26+
import io.kotest.matchers.collections.beEmpty
27+
import io.kotest.matchers.should
28+
import io.kotest.matchers.shouldBe
29+
30+
import org.ossreviewtoolkit.model.AnalyzerRun
31+
import org.ossreviewtoolkit.model.CuratedPackage
32+
import org.ossreviewtoolkit.model.Identifier
33+
import org.ossreviewtoolkit.model.OrtResult
34+
import org.ossreviewtoolkit.model.Package
35+
import org.ossreviewtoolkit.model.PackageReference
36+
import org.ossreviewtoolkit.model.Project
37+
import org.ossreviewtoolkit.model.Repository
38+
import org.ossreviewtoolkit.model.Scope
39+
import org.ossreviewtoolkit.model.config.config.PathInclude
40+
import org.ossreviewtoolkit.model.config.config.PathIncludeReason
41+
42+
class IncludesTest : WordSpec() {
43+
private val packageId = Identifier("type", "namespace", "name", "version")
44+
45+
private val pkg = CuratedPackage(metadata = org.ossreviewtoolkit.model.Package.EMPTY.copy(id = packageId))
46+
47+
private val projectId1 = packageId.copy(name = "project1")
48+
private val projectId2 = packageId.copy(name = "project2")
49+
private val project1 = Project.EMPTY.copy(id = projectId1, definitionFilePath = "path1")
50+
private val project2 = Project.EMPTY.copy(id = projectId2, definitionFilePath = "path2")
51+
52+
private val pathInclude1 = PathInclude("path1", PathIncludeReason.SOURCE_OF, "")
53+
private val pathInclude2 = PathInclude("path2", PathIncludeReason.SOURCE_OF, "")
54+
private val pathInclude3 = PathInclude("path3", PathIncludeReason.SOURCE_OF, "")
55+
56+
private val scope1 = Scope("scope1", setOf(PackageReference(packageId)))
57+
private val scope2 = Scope("scope2", setOf(PackageReference(packageId)))
58+
private val scopeProject1 = Scope("scopeProject1", setOf(PackageReference(project1.id)))
59+
60+
private lateinit var ortResult: OrtResult
61+
62+
override suspend fun beforeEach(testCase: TestCase) {
63+
ortResult = OrtResult(
64+
repository = Repository.EMPTY,
65+
analyzer = AnalyzerRun.EMPTY
66+
)
67+
}
68+
69+
private fun setIncludes(paths: List<PathInclude> = emptyList()) {
70+
setIncludes(Includes(paths = paths))
71+
}
72+
73+
private fun setIncludes(includes: Includes) {
74+
val config = ortResult.repository.config.copy(includes = includes)
75+
ortResult = ortResult.replaceConfig(config)
76+
}
77+
78+
private fun setProjects(vararg projects: Project) {
79+
val packages = mutableSetOf<Package>()
80+
if (packageId in projects.flatMap { ortResult.dependencyNavigator.projectDependencies(it) }) packages += pkg.metadata
81+
val analyzerResult = ortResult.analyzer!!.result.copy(
82+
projects = projects.toSet(),
83+
packages = packages
84+
)
85+
ortResult = ortResult.copy(analyzer = ortResult.analyzer!!.copy(result = analyzerResult))
86+
}
87+
88+
init {
89+
"isExcluded" should {
90+
"return false for a package if no includes are defined" {
91+
setProjects(
92+
project1.copy(scopeDependencies = setOf(scope1))
93+
)
94+
95+
ortResult.isExcluded(packageId) shouldBe false
96+
}
97+
98+
"return true if some path includes are defined but all projects depending on a package are not included" {
99+
setProjects(
100+
project1.copy(scopeDependencies = setOf(scope1)),
101+
project2.copy(scopeDependencies = setOf(scope2))
102+
)
103+
104+
setIncludes(
105+
paths = listOf(PathInclude("someOtherPath", PathIncludeReason.SOURCE_OF, ""))
106+
)
107+
108+
ortResult.isExcluded(packageId) shouldBe true
109+
}
110+
111+
"return false if all projects depending on a package are included by path includes" {
112+
setProjects(
113+
project1.copy(scopeDependencies = setOf(scope1)),
114+
project2.copy(scopeDependencies = setOf(scope2))
115+
)
116+
117+
setIncludes(
118+
paths = listOf(pathInclude1, pathInclude2)
119+
)
120+
121+
ortResult.isExcluded(packageId) shouldBe false
122+
}
123+
124+
"return false if only part of the projects depending on a package are included by path includes" {
125+
setProjects(
126+
project1.copy(scopeDependencies = setOf(scope1)),
127+
project2.copy(scopeDependencies = setOf(scope2))
128+
)
129+
130+
setIncludes(
131+
paths = listOf(pathInclude1)
132+
)
133+
134+
ortResult.isExcluded(packageId) shouldBe false
135+
}
136+
137+
"return false if a project is included by path includes but not all dependencies on the project are included" {
138+
setProjects(
139+
project1,
140+
project2.copy(scopeDependencies = setOf(scopeProject1))
141+
)
142+
143+
setIncludes(
144+
paths = listOf(pathInclude2)
145+
)
146+
147+
ortResult.isExcluded(project1.id) shouldBe false
148+
}
149+
}
150+
151+
"isPackageExcluded" should {
152+
"return false if the package is not found" {
153+
setProjects(
154+
project1
155+
)
156+
157+
ortResult.isPackageExcluded(packageId) shouldBe false
158+
}
159+
160+
"return false if the package is neither excluded nor included" {
161+
setProjects(
162+
project1.copy(scopeDependencies = setOf(scope1))
163+
)
164+
165+
ortResult.isPackageExcluded(packageId) shouldBe false
166+
}
167+
168+
"return false if all projects depending on a package are included by path include" {
169+
setProjects(
170+
project1.copy(scopeDependencies = setOf(scope1)),
171+
project2.copy(scopeDependencies = setOf(scope2))
172+
)
173+
174+
setIncludes(
175+
paths = listOf(pathInclude1, pathInclude2)
176+
)
177+
178+
ortResult.isPackageExcluded(packageId) shouldBe false
179+
}
180+
181+
"return false if only part of the projects depending on a package are included by path includes" {
182+
setProjects(
183+
project1.copy(scopeDependencies = setOf(scope1)),
184+
project2.copy(scopeDependencies = setOf(scope2))
185+
)
186+
187+
setIncludes(
188+
paths = listOf(pathInclude1)
189+
)
190+
191+
ortResult.isPackageExcluded(packageId) shouldBe false
192+
}
193+
194+
"return true if some path include are defined but all projects depending on a package are not included" {
195+
setProjects(
196+
project1.copy(scopeDependencies = setOf(scope1)),
197+
project2.copy(scopeDependencies = setOf(scope2))
198+
)
199+
200+
setIncludes(
201+
paths = listOf(pathInclude3)
202+
)
203+
204+
ortResult.isPackageExcluded(packageId) shouldBe true
205+
}
206+
207+
"return false if there are no dependencies on the project" {
208+
setProjects(
209+
project1,
210+
project2
211+
)
212+
213+
ortResult.isPackageExcluded(project1.id) shouldBe false
214+
}
215+
}
216+
217+
"isProjectExcluded" should {
218+
"return false if the definition file path is matched by a path include" {
219+
setIncludes(Includes(paths = listOf(pathInclude1)))
220+
setProjects(project1)
221+
222+
ortResult.isProjectExcluded(project1.id) shouldBe false
223+
}
224+
225+
"return true if some path includes are defined but the definition file path is not matched by a path include" {
226+
setIncludes(Includes(paths = listOf(pathInclude2)))
227+
setProjects(project1)
228+
229+
ortResult.isProjectExcluded(project1.id) shouldBe true
230+
}
231+
232+
"return false if there are no path includes" {
233+
setProjects(project1)
234+
235+
ortResult.isProjectExcluded(project1.id) shouldBe false
236+
}
237+
}
238+
239+
"EMPTY" should {
240+
"not contain any inclusions" {
241+
Includes.EMPTY.paths should beEmpty()
242+
}
243+
}
244+
}
245+
}

0 commit comments

Comments
 (0)