Skip to content

Commit 56c12eb

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 1901be6 commit 56c12eb

File tree

2 files changed

+58
-22
lines changed

2 files changed

+58
-22
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: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,6 @@ class Scanner(
101101
)
102102

103103
suspend fun scan(ortResult: OrtResult, skipExcluded: Boolean, labels: Map<String, String>): OrtResult {
104-
val startTime = Instant.now()
105-
106104
val projectPackages = ortResult.getProjects(skipExcluded).mapTo(mutableSetOf()) { it.toPackage() }
107105
val projectResults = scan(
108106
projectPackages,
@@ -128,8 +126,6 @@ class Scanner(
128126
)
129127
)
130128

131-
val endTime = Instant.now()
132-
133129
val toolVersions = mutableMapOf<String, String>()
134130

135131
scannerWrappers.values.flatten().forEach { scanner ->
@@ -138,22 +134,23 @@ class Scanner(
138134
}
139135
}
140136

141-
val scannerRun = ScannerRun(
142-
startTime = startTime,
143-
endTime = endTime,
137+
val scannerRun = (projectResults + packageResults).copy(
144138
environment = Environment(toolVersions = toolVersions),
145-
config = scannerConfig,
146-
provenances = projectResults.provenances + packageResults.provenances,
147-
scanResults = projectResults.scanResults + packageResults.scanResults,
148-
issues = projectResults.issues + packageResults.issues,
149-
files = projectResults.files + packageResults.files,
150-
scanners = projectResults.scanners + packageResults.scanners
139+
config = scannerConfig
151140
)
152141

153-
return ortResult.copy(scanner = scannerRun)
142+
val paddedScannerRun = if (scannerConfig.includeFilesWithoutFindings) {
143+
scannerRun.padNoneLicenseFindings()
144+
} else {
145+
scannerRun
146+
}
147+
148+
return ortResult.copy(scanner = paddedScannerRun)
154149
}
155150

156151
suspend fun scan(packages: Set<Package>, context: ScanContext): ScannerRun {
152+
val startTime = Instant.now()
153+
157154
val scannerWrappers = scannerWrappers[context.packageType]
158155
if (scannerWrappers.isNullOrEmpty()) {
159156
logger.info { "Skipping ${context.packageType} scan as no according scanner is configured." }
@@ -210,7 +207,10 @@ class Scanner(
210207

211208
val issues = controller.getIssues()
212209

210+
val endTime = Instant.now()
213211
val scannerRun = ScannerRun.EMPTY.copy(
212+
startTime = startTime,
213+
endTime = endTime,
214214
config = scannerConfig,
215215
provenances = provenances,
216216
scanResults = scanResults,
@@ -219,10 +219,6 @@ class Scanner(
219219
issues = issues
220220
)
221221

222-
if (scannerConfig.includeFilesWithoutFindings) {
223-
return scannerRun.padNoneLicenseFindings()
224-
}
225-
226222
return scannerRun
227223
}
228224

0 commit comments

Comments
 (0)