Skip to content

Commit ac41769

Browse files
maennchensschuberth
authored andcommitted
feat(model): Guard duplicate scan results / file lists per provenance
* `ScannerRun.init` - Add a `require` check that no two `ScanResult`s share the same provenance and scanner. - Add a similar check that no two `FileList`s share the same provenance. The offending provenance is rendered via `toYaml()` to aid debugging. * `ScannerRunTest` - Introduce two unit tests that assert an `IllegalArgumentException` is thrown when duplicates are present for scan results or file lists, respectively. * `FreeMarkerTemplateProcessorTest` - Provide distinct vcsInfo to tests. These runtime checks make improper scan runs surface early and provide clear error messages. Signed-off-by: Jonatan Männchen <[email protected]>
1 parent b58c8ba commit ac41769

File tree

4 files changed

+190
-12
lines changed

4 files changed

+190
-12
lines changed

model/src/main/kotlin/ScannerRun.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,20 @@ data class ScannerRun(
135135
}
136136
}
137137

138+
scanResults.getDuplicates { it.provenance to it.scanner }.keys.also { duplicates ->
139+
require(duplicates.isEmpty()) {
140+
val (dupProvenance, dupScanner) = duplicates.first()
141+
142+
buildString {
143+
appendLine("Found multiple scan results for the same provenance and scanner.")
144+
appendLine("Scanner:")
145+
appendLine(dupScanner.toYaml())
146+
appendLine("Provenance:")
147+
append(dupProvenance.toYaml())
148+
}
149+
}
150+
}
151+
138152
provenances.getDuplicates { it.id }.keys.also { idsForDuplicateProvenanceResolutionResults ->
139153
require(idsForDuplicateProvenanceResolutionResults.isEmpty()) {
140154
"Found multiple provenance resolution results for the following ids: " +
@@ -173,6 +187,13 @@ data class ScannerRun(
173187
}
174188
}
175189
}
190+
191+
files.getDuplicates { it.provenance }.keys.also { duplicateProvenances ->
192+
require(duplicateProvenances.isEmpty()) {
193+
"Found multiple file lists for the same provenance:\n" +
194+
duplicateProvenances.first().toYaml()
195+
}
196+
}
176197
}
177198

178199
private val provenancesById: Map<Identifier, ProvenanceResolutionResult> by lazy {

model/src/test/kotlin/ScannerRunTest.kt

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,154 @@ import org.ossreviewtoolkit.model.utils.clearVcsPath
3636
import org.ossreviewtoolkit.utils.ort.Environment
3737

3838
class ScannerRunTest : WordSpec({
39+
"init" should {
40+
"error on duplicate provenance and scanner scan results" {
41+
val provenance = RepositoryProvenance(
42+
VcsInfo(type = VcsType.GIT, url = "https://github.com/example.git", revision = "revision"),
43+
"revision"
44+
)
45+
val otherProvenance = RepositoryProvenance(
46+
VcsInfo(type = VcsType.GIT, url = "https://github.com/example.git", revision = "other_revision"),
47+
"other_revision"
48+
)
49+
val provenances = setOf(
50+
ProvenanceResolutionResult(
51+
id = Identifier("maven::example:1.0"),
52+
packageProvenance = provenance
53+
),
54+
ProvenanceResolutionResult(
55+
id = Identifier("maven::other_example:1.0"),
56+
packageProvenance = otherProvenance
57+
)
58+
)
59+
60+
val scanner = ScannerDetails("scanner", "1.0.0", "configuration")
61+
val otherScanner = ScannerDetails("other-scanner", "1.0.0", "configuration")
62+
63+
// Shared provenance and scanner.
64+
shouldThrow<IllegalArgumentException> {
65+
ScannerRun.EMPTY.copy(
66+
provenances = provenances,
67+
scanResults = setOf(
68+
ScanResult(
69+
provenance = provenance,
70+
scanner = scanner,
71+
summary = ScanSummary.EMPTY.copy(
72+
licenseFindings = setOf(
73+
LicenseFinding("MIT", TextLocation("file1.txt", 1, 1))
74+
)
75+
)
76+
),
77+
ScanResult(
78+
provenance = provenance,
79+
scanner = scanner,
80+
summary = ScanSummary.EMPTY.copy(
81+
licenseFindings = setOf(
82+
LicenseFinding("MIT", TextLocation("file2.txt", 1, 1))
83+
)
84+
)
85+
)
86+
)
87+
)
88+
}.message shouldBe buildString {
89+
appendLine("Found multiple scan results for the same provenance and scanner.")
90+
appendLine("Scanner:")
91+
appendLine(scanner.toYaml())
92+
appendLine("Provenance:")
93+
append(provenance.toYaml())
94+
}
95+
96+
// Shared provenance and different scanners.
97+
ScannerRun.EMPTY.copy(
98+
provenances = provenances,
99+
scanResults = setOf(
100+
ScanResult(
101+
provenance = provenance,
102+
scanner = scanner,
103+
summary = ScanSummary.EMPTY.copy(
104+
licenseFindings = setOf(
105+
LicenseFinding("MIT", TextLocation("file1.txt", 1, 1))
106+
)
107+
)
108+
),
109+
ScanResult(
110+
provenance = provenance,
111+
scanner = otherScanner,
112+
summary = ScanSummary.EMPTY.copy(
113+
licenseFindings = setOf(
114+
LicenseFinding("MIT", TextLocation("file2.txt", 1, 1))
115+
)
116+
)
117+
)
118+
)
119+
)
120+
121+
// Different provenance and shared scanner.
122+
ScannerRun.EMPTY.copy(
123+
provenances = provenances,
124+
scanResults = setOf(
125+
ScanResult(
126+
provenance = provenance,
127+
scanner = scanner,
128+
summary = ScanSummary.EMPTY.copy(
129+
licenseFindings = setOf(
130+
LicenseFinding("MIT", TextLocation("file1.txt", 1, 1))
131+
)
132+
)
133+
),
134+
ScanResult(
135+
provenance = otherProvenance,
136+
scanner = scanner,
137+
summary = ScanSummary.EMPTY.copy(
138+
licenseFindings = setOf(
139+
LicenseFinding("MIT", TextLocation("file2.txt", 1, 1))
140+
)
141+
)
142+
)
143+
)
144+
)
145+
}
146+
147+
"error on duplicate provenance file lists" {
148+
val provenance = RepositoryProvenance(
149+
VcsInfo(type = VcsType.GIT, url = "https://github.com/example.git", revision = "revision"),
150+
"revision"
151+
)
152+
153+
shouldThrow<IllegalArgumentException> {
154+
ScannerRun.EMPTY.copy(
155+
provenances = setOf(
156+
ProvenanceResolutionResult(
157+
id = Identifier("maven::other_example:1.0"),
158+
packageProvenance = provenance
159+
)
160+
),
161+
files = setOf(
162+
FileList(
163+
provenance = provenance,
164+
files = setOf(
165+
Entry(
166+
path = "vcs/path/file1.txt",
167+
sha1 = "1111111111111111111111111111111111111111"
168+
)
169+
)
170+
),
171+
FileList(
172+
provenance = provenance,
173+
files = setOf(
174+
Entry(
175+
path = "some/dir/file2.txt",
176+
sha1 = "2222222222222222222222222222222222222222"
177+
)
178+
)
179+
)
180+
)
181+
)
182+
}.message shouldBe "Found multiple file lists for the same provenance:\n" +
183+
provenance.toYaml()
184+
}
185+
}
186+
39187
"getFileList()" should {
40188
"filter by VCS path and merge sub-repository lists as expected" {
41189
val id = Identifier("a:b:c:1.0.0")

plugins/reporters/freemarker/src/test/kotlin/FreeMarkerTemplateProcessorTest.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,14 @@ private fun scanResults(vcsInfo: VcsInfo, findingsPaths: Collection<String>): Li
8989
)
9090
}
9191

92-
private val PROJECT_VCS_INFO = VcsInfo(
92+
private val PROJECT_ROOT_VCS_INFO = VcsInfo(
9393
type = VcsType.GIT_REPO,
9494
url = "ssh://git@host/manifests/repo?manifest=path/to/manifest.xml",
9595
revision = "deadbeaf44444444333333332222222211111111"
9696
)
97+
private val PROJECT_SUB_VCS_INFO = PROJECT_ROOT_VCS_INFO.copy(
98+
path = "sub-dir"
99+
)
97100
private val NESTED_VCS_INFO = VcsInfo(
98101
type = VcsType.GIT,
99102
url = "ssh://git@host/project/repo",
@@ -107,7 +110,7 @@ private val idNestedProject = Identifier("SpdxDocumentFile:@ort:project-in-neste
107110

108111
private val ORT_RESULT = OrtResult(
109112
repository = Repository(
110-
vcs = PROJECT_VCS_INFO,
113+
vcs = PROJECT_ROOT_VCS_INFO,
111114
config = RepositoryConfiguration(),
112115
nestedRepositories = mapOf("nested-vcs-dir" to NESTED_VCS_INFO)
113116
),
@@ -117,12 +120,12 @@ private val ORT_RESULT = OrtResult(
117120
Project.EMPTY.copy(
118121
id = idRootProject,
119122
definitionFilePath = "package.json",
120-
vcsProcessed = PROJECT_VCS_INFO
123+
vcsProcessed = PROJECT_ROOT_VCS_INFO
121124
),
122125
Project.EMPTY.copy(
123126
id = idSubProject,
124127
definitionFilePath = "sub-dir/project.spdx.yml",
125-
vcsProcessed = PROJECT_VCS_INFO
128+
vcsProcessed = PROJECT_ROOT_VCS_INFO
126129
),
127130
Project.EMPTY.copy(
128131
id = idNestedProject,
@@ -134,15 +137,15 @@ private val ORT_RESULT = OrtResult(
134137
),
135138
scanner = scannerRunOf(
136139
idRootProject to scanResults(
137-
vcsInfo = PROJECT_VCS_INFO,
140+
vcsInfo = PROJECT_ROOT_VCS_INFO,
138141
findingsPaths = listOf(
139142
"src/main.js",
140143
"sub-dir/src/main.cpp",
141144
"nested-vcs-dir/src/main.cpp"
142145
)
143146
),
144147
idSubProject to scanResults(
145-
vcsInfo = PROJECT_VCS_INFO,
148+
vcsInfo = PROJECT_SUB_VCS_INFO,
146149
findingsPaths = listOf(
147150
"sub-dir/src/main.cpp"
148151
)

utils/test/src/main/kotlin/Utils.kt

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,18 @@ fun scannerRunOf(vararg pkgScanResults: Pair<Identifier, List<ScanResult>>): Sca
151151
}
152152
}
153153

154-
val scanResults = pkgScanResultsWithKnownProvenance.values.flatten().mapTo(mutableSetOf()) { scanResult ->
155-
scanResult.copy(
156-
provenance = (scanResult.provenance as? RepositoryProvenance)?.clearVcsPath()?.alignRevisions()
157-
?: scanResult.provenance
158-
)
159-
}
154+
val scanResults = pkgScanResultsWithKnownProvenance.values.flatten()
155+
.map { scanResult ->
156+
scanResult.copy(
157+
provenance = (scanResult.provenance as? RepositoryProvenance)?.clearVcsPath()?.alignRevisions()
158+
?: scanResult.provenance
159+
)
160+
}
161+
.groupBy { it.provenance to it.scanner }
162+
.values
163+
.mapTo(mutableSetOf()) { scanResults ->
164+
scanResults.reduce { acc, next -> acc + next }
165+
}
160166

161167
val filePathsByProvenance = scanResults.mapNotNull { scanResult ->
162168
val provenance = scanResult.provenance as? KnownProvenance ?: return@mapNotNull null

0 commit comments

Comments
 (0)