Skip to content

Commit d536a3e

Browse files
committed
feat(scanner): Merge duplicate scan results that share a provenance
When the SpdxDocumentFile package manager is used, the *project* and all contained *packages* often resolve to the **same VCS provenance** (e.g. the root of the Git repository). Before this change ORT stored two separate `ScanResult`s for such a provenance – one keyed to the project, one keyed to the package. That caused two follow-on problems: * Both results appeared in the `OrtResult`, so evaluators saw **duplicate findings** for the *same* source tree. * Because projects and packages are handled by different rules the package result was additionally **padded with a `SpdxConstants.NONE` finding** whenever `includeFilesWithoutFindings` was enabled. The evaluator therefore compared *real* license findings from the project result with `NONE` from the package result and failed with a violation. This patch * merges `ScannerRun`, and * performs the "pad with NONE" step only **after** deduplication, so every path is represented exactly once. As a consequence the evaluator now receives one consistent set of license findings per provenance / scanner, eliminating the false mismatch. Signed-off-by: Jonatan Männchen <jonatan@maennchen.ch>
1 parent e2dd087 commit d536a3e

File tree

2 files changed

+59
-23
lines changed

2 files changed

+59
-23
lines changed

scanner/src/funTest/kotlin/scanners/ScannerIntegrationFunTest.kt

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.ossreviewtoolkit.scanner.scanners
2121

2222
import io.kotest.core.spec.style.WordSpec
23+
import io.kotest.matchers.collections.shouldHaveSize
2324
import io.kotest.matchers.shouldBe
2425

2526
import java.io.File
@@ -34,6 +35,7 @@ import org.ossreviewtoolkit.model.Package
3435
import org.ossreviewtoolkit.model.PackageReference
3536
import org.ossreviewtoolkit.model.PackageType
3637
import org.ossreviewtoolkit.model.Project
38+
import org.ossreviewtoolkit.model.Repository
3739
import org.ossreviewtoolkit.model.ScanSummary
3840
import org.ossreviewtoolkit.model.Scope
3941
import org.ossreviewtoolkit.model.TextLocation
@@ -100,6 +102,15 @@ class ScannerIntegrationFunTest : WordSpec({
100102
patchExpectedResult(expectedResult)
101103
}
102104
}
105+
106+
"Scanning a project with the same provenance as packages" should {
107+
"not have duplicated scan results" {
108+
val analyzerResult = createAnalyzerResultWithProject(project0, pkg0)
109+
val ortResult = createScanner().scan(analyzerResult, skipExcluded = false, emptyMap())
110+
111+
ortResult.getScanResultsForId(project0.id) shouldHaveSize 1
112+
}
113+
}
103114
})
104115

105116
internal fun createScanner(scannerWrappers: Map<PackageType, List<ScannerWrapper>>? = null): Scanner {
@@ -128,25 +139,38 @@ internal fun createScanner(scannerWrappers: Map<PackageType, List<ScannerWrapper
128139
}
129140

130141
private fun createAnalyzerResult(vararg packages: Package): OrtResult {
142+
val project = Project.EMPTY.copy(
143+
id = createId("project")
144+
)
145+
146+
return createAnalyzerResultWithProject(project, *packages)
147+
}
148+
149+
private fun createAnalyzerResultWithProject(project: Project, vararg packages: Package): OrtResult {
131150
val scope = Scope(
132151
name = "deps",
133152
dependencies = packages.mapTo(mutableSetOf()) { PackageReference(it.id) }
134153
)
135154

136-
val project = Project.EMPTY.copy(
137-
id = createId("project"),
155+
val projectWithScope = project.copy(
138156
scopeDependencies = setOf(scope)
139157
)
140158

141159
val analyzerRun = AnalyzerRun.EMPTY.copy(
142160
result = AnalyzerResult.EMPTY.copy(
143-
projects = setOf(project),
161+
projects = setOf(projectWithScope),
144162
packages = packages.toSet()
145163
),
146164
config = AnalyzerConfiguration(enabledPackageManagers = emptyList())
147165
)
148166

149-
return OrtResult.EMPTY.copy(analyzer = analyzerRun)
167+
return OrtResult.EMPTY.copy(
168+
analyzer = analyzerRun,
169+
repository = Repository.EMPTY.copy(
170+
vcsProcessed = projectWithScope.vcsProcessed,
171+
vcs = projectWithScope.vcs
172+
)
173+
)
150174
}
151175

152176
private fun createId(name: String): Identifier = Identifier("Dummy::$name:1.0.0")
@@ -158,6 +182,23 @@ private fun createPackage(name: String, vcs: VcsInfo): Package =
158182
vcsProcessed = vcs.normalize()
159183
)
160184

185+
private fun createProject(name: String, vcs: VcsInfo): Project =
186+
Project.EMPTY.copy(
187+
id = createId(name),
188+
vcs = vcs,
189+
vcsProcessed = vcs.normalize()
190+
)
191+
192+
private val project0 = createProject(
193+
name = "project",
194+
vcs = VcsInfo(
195+
type = VcsType.GIT,
196+
url = "https://github.com/oss-review-toolkit/ort-test-data-scanner.git",
197+
revision = "97d57bb4795bc41f496e1a8e2c7751cefc7da7ec",
198+
path = ""
199+
)
200+
)
201+
161202
// A package with an empty VCS path.
162203
private val pkg0 = createPackage(
163204
name = "pkg0",

scanner/src/main/kotlin/Scanner.kt

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,6 @@ class Scanner(
107107
)
108108

109109
suspend fun scan(ortResult: OrtResult, skipExcluded: Boolean, labels: Map<String, String>): OrtResult {
110-
val startTime = Instant.now()
111-
112110
val projectPackages = ortResult.getProjects(skipExcluded).mapTo(mutableSetOf()) { it.toPackage() }
113111
val projectResults = scan(
114112
projectPackages,
@@ -134,8 +132,6 @@ class Scanner(
134132
)
135133
)
136134

137-
val endTime = Instant.now()
138-
139135
val toolVersions = mutableMapOf<String, String>()
140136

141137
scannerWrappers.values.flatten().forEach { scanner ->
@@ -144,19 +140,14 @@ class Scanner(
144140
}
145141
}
146142

147-
val scannerRun = ScannerRun(
148-
startTime = startTime,
149-
endTime = endTime,
150-
environment = Environment(toolVersions = toolVersions),
151-
config = scannerConfig,
152-
provenances = projectResults.provenances + packageResults.provenances,
153-
scanResults = projectResults.scanResults + packageResults.scanResults,
154-
issues = projectResults.issues + packageResults.issues,
155-
files = projectResults.files + packageResults.files,
156-
scanners = projectResults.scanners + packageResults.scanners
143+
val scannerRun = (projectResults + packageResults).copy(
144+
environment = Environment(toolVersions = toolVersions)
157145
)
158146

159-
return ortResult.copy(scanner = scannerRun)
147+
val paddedScannerRun = scannerRun.takeUnless { scannerConfig.includeFilesWithoutFindings }
148+
?: scannerRun.padNoneLicenseFindings()
149+
150+
return ortResult.copy(scanner = paddedScannerRun)
160151
}
161152

162153
internal suspend fun scan(packages: Set<Package>, context: ScanContext): ScannerRun {
@@ -170,6 +161,8 @@ class Scanner(
170161

171162
val controller = ScanController(packages, scannerWrappers, scannerConfig)
172163

164+
val startTime = Instant.now()
165+
173166
resolvePackageProvenances(controller)
174167
resolveNestedProvenances(controller)
175168

@@ -182,6 +175,8 @@ class Scanner(
182175
createFileLists(controller)
183176
createMissingArchives(controller)
184177

178+
val endTime = Instant.now()
179+
185180
val provenances = packages.mapTo(mutableSetOf()) { pkg ->
186181
val packageProvenance = controller.getPackageProvenance(pkg.id)
187182

@@ -216,17 +211,17 @@ class Scanner(
216211

217212
val issues = controller.getIssues()
218213

219-
val scannerRun = ScannerRun.EMPTY.copy(
214+
return ScannerRun(
215+
startTime = startTime,
216+
endTime = endTime,
217+
environment = Environment(),
220218
config = scannerConfig,
221219
provenances = provenances,
222220
scanResults = scanResults,
223221
files = files,
224222
scanners = scanners,
225223
issues = issues
226224
)
227-
228-
return scannerRun.takeUnless { scannerConfig.includeFilesWithoutFindings }
229-
?: scannerRun.padNoneLicenseFindings()
230225
}
231226

232227
private suspend fun resolvePackageProvenances(controller: ScanController) {

0 commit comments

Comments
 (0)