Skip to content

Commit d780617

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 7c59584 commit d780617

File tree

2 files changed

+55
-23
lines changed

2 files changed

+55
-23
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: 11 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,22 +140,19 @@ 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 {
154+
val startTime = Instant.now()
155+
163156
val scannerWrappers = scannerWrappers[context.packageType]
164157
if (scannerWrappers.isNullOrEmpty()) {
165158
logger.info { "Skipping ${context.packageType} scan as no according scanner is configured." }
@@ -216,17 +209,16 @@ class Scanner(
216209

217210
val issues = controller.getIssues()
218211

219-
val scannerRun = ScannerRun.EMPTY.copy(
212+
return ScannerRun.EMPTY.copy(
213+
startTime = startTime,
214+
endTime = Instant.now(),
220215
config = scannerConfig,
221216
provenances = provenances,
222217
scanResults = scanResults,
223218
files = files,
224219
scanners = scanners,
225220
issues = issues
226221
)
227-
228-
return scannerRun.takeUnless { scannerConfig.includeFilesWithoutFindings }
229-
?: scannerRun.padNoneLicenseFindings()
230222
}
231223

232224
private suspend fun resolvePackageProvenances(controller: ScanController) {

0 commit comments

Comments
 (0)