Skip to content

Commit 671db0d

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 6e3a8e6 commit 671db0d

File tree

2 files changed

+59
-20
lines changed

2 files changed

+59
-20
lines changed

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

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import org.ossreviewtoolkit.model.Package
3434
import org.ossreviewtoolkit.model.PackageReference
3535
import org.ossreviewtoolkit.model.PackageType
3636
import org.ossreviewtoolkit.model.Project
37+
import org.ossreviewtoolkit.model.Repository
3738
import org.ossreviewtoolkit.model.ScanSummary
3839
import org.ossreviewtoolkit.model.Scope
3940
import org.ossreviewtoolkit.model.TextLocation
@@ -100,6 +101,15 @@ class ScannerIntegrationFunTest : WordSpec({
100101
patchExpectedResult(expectedResult)
101102
}
102103
}
104+
105+
"Scanning a project with the same provenance as packages" should {
106+
"not have duplicated scan results" {
107+
val analyzerResult = createAnalyzerResultWithProject(project0, pkg0)
108+
val ortResult = createScanner().scan(analyzerResult, skipExcluded = false, emptyMap())
109+
110+
ortResult.getScanResultsForId(project0.id).size shouldBe 1
111+
}
112+
}
103113
})
104114

105115
internal fun createScanner(scannerWrappers: Map<PackageType, List<ScannerWrapper>>? = null): Scanner {
@@ -128,25 +138,38 @@ internal fun createScanner(scannerWrappers: Map<PackageType, List<ScannerWrapper
128138
}
129139

130140
private fun createAnalyzerResult(vararg packages: Package): OrtResult {
141+
val project = Project.EMPTY.copy(
142+
id = createId("project")
143+
)
144+
145+
return createAnalyzerResultWithProject(project, *packages)
146+
}
147+
148+
private fun createAnalyzerResultWithProject(project: Project, vararg packages: Package): OrtResult {
131149
val scope = Scope(
132150
name = "deps",
133151
dependencies = packages.mapTo(mutableSetOf()) { PackageReference(it.id) }
134152
)
135153

136-
val project = Project.EMPTY.copy(
137-
id = createId("project"),
154+
val projectWithScope = project.copy(
138155
scopeDependencies = setOf(scope)
139156
)
140157

141158
val analyzerRun = AnalyzerRun.EMPTY.copy(
142159
result = AnalyzerResult.EMPTY.copy(
143-
projects = setOf(project),
160+
projects = setOf(projectWithScope),
144161
packages = packages.toSet()
145162
),
146163
config = AnalyzerConfiguration(enabledPackageManagers = emptyList())
147164
)
148165

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

152175
private fun createId(name: String): Identifier = Identifier("Dummy::$name:1.0.0")
@@ -158,6 +181,23 @@ private fun createPackage(name: String, vcs: VcsInfo): Package =
158181
vcsProcessed = vcs.normalize()
159182
)
160183

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

scanner/src/main/kotlin/Scanner.kt

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,6 @@ class Scanner(
104104
)
105105

106106
suspend fun scan(ortResult: OrtResult, skipExcluded: Boolean, labels: Map<String, String>): OrtResult {
107-
val startTime = Instant.now()
108-
109107
val projectPackages = ortResult.getProjects(skipExcluded).mapTo(mutableSetOf()) { it.toPackage() }
110108
val projectResults = scan(
111109
projectPackages,
@@ -131,8 +129,6 @@ class Scanner(
131129
)
132130
)
133131

134-
val endTime = Instant.now()
135-
136132
val toolVersions = mutableMapOf<String, String>()
137133

138134
scannerWrappers.values.flatten().forEach { scanner ->
@@ -141,22 +137,23 @@ class Scanner(
141137
}
142138
}
143139

144-
val scannerRun = ScannerRun(
145-
startTime = startTime,
146-
endTime = endTime,
140+
val scannerRun = (projectResults + packageResults).copy(
147141
environment = Environment(toolVersions = toolVersions),
148-
config = scannerConfig,
149-
provenances = projectResults.provenances + packageResults.provenances,
150-
scanResults = projectResults.scanResults + packageResults.scanResults,
151-
issues = projectResults.issues + packageResults.issues,
152-
files = projectResults.files + packageResults.files,
153-
scanners = projectResults.scanners + packageResults.scanners
142+
config = scannerConfig
154143
)
155144

156-
return ortResult.copy(scanner = scannerRun)
145+
val paddedScannerRun = if (scannerConfig.includeFilesWithoutFindings) {
146+
scannerRun.padNoneLicenseFindings()
147+
} else {
148+
scannerRun
149+
}
150+
151+
return ortResult.copy(scanner = paddedScannerRun)
157152
}
158153

159154
suspend fun scan(packages: Set<Package>, context: ScanContext): ScannerRun {
155+
val startTime = Instant.now()
156+
160157
val scannerWrappers = scannerWrappers[context.packageType]
161158
if (scannerWrappers.isNullOrEmpty()) {
162159
logger.info { "Skipping ${context.packageType} scan as no according scanner is configured." }
@@ -213,7 +210,10 @@ class Scanner(
213210

214211
val issues = controller.getIssues()
215212

213+
val endTime = Instant.now()
216214
val scannerRun = ScannerRun.EMPTY.copy(
215+
startTime = startTime,
216+
endTime = endTime,
217217
config = scannerConfig,
218218
provenances = provenances,
219219
scanResults = scanResults,
@@ -222,8 +222,7 @@ class Scanner(
222222
issues = issues
223223
)
224224

225-
return scannerRun.takeUnless { scannerConfig.includeFilesWithoutFindings }
226-
?: scannerRun.padNoneLicenseFindings()
225+
return scannerRun
227226
}
228227

229228
private suspend fun resolvePackageProvenances(controller: ScanController) {

0 commit comments

Comments
 (0)