Skip to content

Commit d5401bd

Browse files
lamppusschuberth
authored andcommitted
refactor(scanner): Don't create fake scan results when path scan fails
To resolve an issue where scan results from failed scans were reused even if the problem was resolved in newer scans, change the behavior of the path scanners so that they don't create empty scan results just to keep track of issues, and instead collect these kinds of issues to the `ScannerRun` issues map. Resolves #10054. Signed-off-by: Johanna Lamppu <[email protected]>
1 parent 7833a73 commit d5401bd

File tree

3 files changed

+126
-39
lines changed

3 files changed

+126
-39
lines changed

scanner/src/main/kotlin/ScanController.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,10 @@ internal class ScanController(
6565
private val nestedProvenanceResolutionIssues = mutableMapOf<Identifier, Issue>()
6666

6767
/**
68-
* A map of [Identifier]s associated with a list of [Issue]s that occurred during a scan besides the issues
68+
* A map of [Identifier]s associated with a set of [Issue]s that occurred during a scan besides the issues
6969
* created by the scanners themselves as part of the [ScanSummary].
7070
*/
71-
private val issues = mutableMapOf<Identifier, MutableList<Issue>>()
71+
private val issues = mutableMapOf<Identifier, MutableSet<Issue>>()
7272

7373
/**
7474
* A map of [KnownProvenance]s to their resolved [NestedProvenance]s.
@@ -124,7 +124,7 @@ internal class ScanController(
124124
fun getAllFileLists(): Map<KnownProvenance, FileList> = fileLists
125125

126126
fun addIssue(id: Identifier, issue: Issue) {
127-
issues.getOrPut(id) { mutableListOf() } += issue
127+
issues.getOrPut(id) { mutableSetOf() } += issue
128128
}
129129

130130
/**
@@ -197,6 +197,11 @@ internal class ScanController(
197197
*/
198198
fun getNestedProvenanceResolutionIssue(id: Identifier): Issue? = nestedProvenanceResolutionIssues[id]
199199

200+
/**
201+
* Get issues that are not part of the scan summaries.
202+
*/
203+
fun getIssues(): Map<Identifier, Set<Issue>> = issues
204+
200205
/**
201206
* Get the [NestedProvenance] for the provided [id], or null if no nested provenance for the [id] is available.
202207
*/

scanner/src/main/kotlin/Scanner.kt

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ import org.ossreviewtoolkit.model.Package
4242
import org.ossreviewtoolkit.model.PackageType
4343
import org.ossreviewtoolkit.model.ProvenanceResolutionResult
4444
import org.ossreviewtoolkit.model.ScanResult
45-
import org.ossreviewtoolkit.model.ScanSummary
4645
import org.ossreviewtoolkit.model.ScannerRun
4746
import org.ossreviewtoolkit.model.TextLocation
4847
import org.ossreviewtoolkit.model.VcsInfo
@@ -149,6 +148,7 @@ class Scanner(
149148
config = scannerConfig,
150149
provenances = projectResults.provenances + packageResults.provenances,
151150
scanResults = projectResults.scanResults + packageResults.scanResults,
151+
issues = projectResults.issues + packageResults.issues,
152152
files = projectResults.files + packageResults.files,
153153
scanners = projectResults.scanners + packageResults.scanners
154154
)
@@ -234,12 +234,15 @@ class Scanner(
234234
val scannerIds = scannerWrappers.mapTo(mutableSetOf()) { it.descriptor.id }
235235
val scanners = packages.associateBy({ it.id }) { scannerIds }
236236

237+
val issues = controller.getIssues()
238+
237239
return ScannerRun.EMPTY.copy(
238240
config = scannerConfig,
239241
provenances = provenances,
240242
scanResults = scanResults,
241243
files = files,
242-
scanners = scanners
244+
scanners = scanners,
245+
issues = issues
243246
)
244247
}
245248

@@ -452,7 +455,7 @@ class Scanner(
452455

453456
logger.info { "Scanning $provenance (${index + 1} of ${provenances.size})..." }
454457

455-
val scanResults = scanPath(provenance, scannersWithoutResults, context)
458+
val scanResults = scanPath(provenance, scannersWithoutResults, context, controller)
456459

457460
scanResults.forEach { (scanner, scanResult) ->
458461
val completedPackages = controller.getPackagesCompletedByProvenance(scanner, provenance)
@@ -573,7 +576,8 @@ class Scanner(
573576
private fun scanPath(
574577
provenance: KnownProvenance,
575578
scanners: List<PathScannerWrapper>,
576-
context: ScanContext
579+
context: ScanContext,
580+
controller: ScanController
577581
): Map<PathScannerWrapper, ScanResult> {
578582
val downloadDir = try {
579583
provenanceDownloader.download(provenance)
@@ -582,58 +586,54 @@ class Scanner(
582586
"Downloader", "Could not download provenance $provenance: ${e.collectMessages()}"
583587
)
584588

585-
val time = Instant.now()
586-
val summary = ScanSummary(
587-
startTime = time,
588-
endTime = time,
589-
issues = listOf(issue)
590-
)
591-
592-
return scanners.associateWith { scanner ->
593-
ScanResult(
594-
provenance = provenance,
595-
scanner = scanner.details,
596-
summary = summary
589+
controller.getIdsByProvenance().getValue(provenance).forEach { id ->
590+
controller.addIssue(
591+
id,
592+
issue
597593
)
598594
}
595+
596+
return emptyMap()
599597
}
600598

601-
val results = scanners.associateWith { scanner ->
599+
val results = scanners.mapNotNull { scanner ->
602600
logger.info { "Scan of $provenance with path scanner '${scanner.descriptor.displayName}' started." }
603601

604602
// Filter the scan context to hide the excludes from scanner with scan matcher.
605603
val filteredContext = if (scanner.matcher == null) context else context.copy(excludes = null)
606604

607605
val summary = runCatching {
608606
scanner.scanPath(downloadDir, filteredContext)
609-
}.getOrElse { e ->
607+
}.onFailure { e ->
610608
val issue = scanner.createAndLogIssue(
611609
"Failed to scan $provenance with path scanner '${scanner.descriptor.displayName}': " +
612610
e.collectMessages()
613611
)
614612

615-
val time = Instant.now()
616-
ScanSummary(
617-
startTime = time,
618-
endTime = time,
619-
issues = listOf(issue)
620-
)
621-
}
613+
controller.getIdsByProvenance().getValue(provenance).forEach { id ->
614+
controller.addIssue(
615+
id,
616+
issue
617+
)
618+
}
619+
}.getOrNull()
622620

623621
logger.info { "Scan of $provenance with path scanner '${scanner.descriptor.displayName}' finished." }
624622

625-
val summaryWithMappedLicenses = summary.copy(
626-
licenseFindings = summary.licenseFindings.mapTo(mutableSetOf()) {
627-
val licenseString = it.license.toString()
628-
it.copy(
629-
license = licenseString.mapLicense(scannerConfig.detectedLicenseMapping).toSpdx(),
630-
location = it.location.withRelativePath(downloadDir)
631-
)
632-
}
633-
)
623+
summary?.let {
624+
val summaryWithMappedLicenses = summary.copy(
625+
licenseFindings = summary.licenseFindings.mapTo(mutableSetOf()) { finding ->
626+
val licenseString = finding.license.toString()
627+
finding.copy(
628+
license = licenseString.mapLicense(scannerConfig.detectedLicenseMapping).toSpdx(),
629+
location = finding.location.withRelativePath(downloadDir)
630+
)
631+
}
632+
)
634633

635-
ScanResult(provenance, scanner.details, summaryWithMappedLicenses)
636-
}
634+
scanner to ScanResult(provenance, scanner.details, summaryWithMappedLicenses)
635+
}
636+
}.toMap()
637637

638638
downloadDir.safeDeleteRecursively()
639639

scanner/src/test/kotlin/ScannerTest.kt

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,15 @@ import io.kotest.assertions.throwables.shouldThrow
2323
import io.kotest.core.spec.style.WordSpec
2424
import io.kotest.matchers.collections.beEmpty
2525
import io.kotest.matchers.collections.containExactly
26+
import io.kotest.matchers.collections.shouldHaveSize
2627
import io.kotest.matchers.maps.beEmpty as beEmptyMap
2728
import io.kotest.matchers.maps.containExactly
29+
import io.kotest.matchers.maps.haveSize
2830
import io.kotest.matchers.nulls.shouldNotBeNull
2931
import io.kotest.matchers.should
3032
import io.kotest.matchers.shouldBe
3133
import io.kotest.matchers.shouldNot
34+
import io.kotest.matchers.string.shouldContain
3235

3336
import io.mockk.coEvery
3437
import io.mockk.every
@@ -39,6 +42,7 @@ import io.mockk.verify
3942
import java.io.File
4043
import java.io.IOException
4144

45+
import org.ossreviewtoolkit.downloader.DownloadException
4246
import org.ossreviewtoolkit.model.ArtifactProvenance
4347
import org.ossreviewtoolkit.model.Hash
4448
import org.ossreviewtoolkit.model.HashAlgorithm
@@ -351,6 +355,84 @@ class ScannerTest : WordSpec({
351355
provenanceDownloader.download(any())
352356
}
353357
}
358+
359+
"return an issue and no scan result if source download fails" {
360+
val pkg = Package.new(name = "repository").copy(
361+
vcsProcessed = VcsInfo.valid()
362+
)
363+
364+
val scannerWrapper = FakePathScannerWrapper()
365+
val provenanceDownloader = spyk(FakeProvenanceDownloader())
366+
367+
every { provenanceDownloader.download(any()) } throws DownloadException("Test download error")
368+
369+
val scanner = createScanner(
370+
provenanceDownloader = provenanceDownloader,
371+
packageScannerWrappers = listOf(scannerWrapper)
372+
)
373+
374+
val run = scanner.scan(setOf(pkg), createContext())
375+
376+
run.scanResults should beEmpty()
377+
run.issues should haveSize(1)
378+
run.issues[pkg.id].shouldNotBeNull().shouldHaveSize(1)
379+
380+
run.issues[pkg.id]?.first().shouldNotBeNull {
381+
source shouldBe "Downloader"
382+
message shouldContain "Test download error"
383+
}
384+
}
385+
386+
"return an issue and no scan result if scanning path fails" {
387+
val pkg = Package.new(name = "repository").copy(
388+
vcsProcessed = VcsInfo.valid()
389+
)
390+
391+
val scannerWrapper = spyk(FakePathScannerWrapper())
392+
val provenanceDownloader = FakeProvenanceDownloader()
393+
394+
every { scannerWrapper.scanPath(any(), any()) } throws Exception("Test scan failure")
395+
396+
val scanner = createScanner(
397+
provenanceDownloader = provenanceDownloader,
398+
packageScannerWrappers = listOf(scannerWrapper)
399+
)
400+
401+
val run = scanner.scan(setOf(pkg), createContext())
402+
403+
run.scanResults should beEmpty()
404+
run.issues should haveSize(1)
405+
run.issues[pkg.id].shouldNotBeNull().shouldHaveSize(1)
406+
407+
run.issues[pkg.id]?.first().shouldNotBeNull {
408+
source shouldBe "fake"
409+
message shouldContain "Test scan failure"
410+
}
411+
}
412+
413+
"not write any scan results to storage if a scan fails" {
414+
val pkg = Package.new(name = "repository").copy(
415+
vcsProcessed = VcsInfo.valid()
416+
)
417+
418+
val storageWriter = spyk(FakeProvenanceBasedStorageWriter())
419+
val scannerWrapper = spyk(FakePathScannerWrapper())
420+
val provenanceDownloader = FakeProvenanceDownloader()
421+
422+
every { scannerWrapper.scanPath(any(), any()) } throws Exception("Test scan failure")
423+
424+
val scanner = createScanner(
425+
provenanceDownloader = provenanceDownloader,
426+
packageScannerWrappers = listOf(scannerWrapper),
427+
storageWriters = listOf(storageWriter)
428+
)
429+
430+
scanner.scan(setOf(pkg), createContext())
431+
432+
verify(exactly = 0) {
433+
storageWriter.write(any())
434+
}
435+
}
354436
}
355437

356438
"scanning with a package based storage reader" should {

0 commit comments

Comments
 (0)