diff --git a/extension/core/src/main/kotlin/org/camunda/community/process_test_coverage/core/export/CoverageStateJsonExporter.kt b/extension/core/src/main/kotlin/org/camunda/community/process_test_coverage/core/export/CoverageStateJsonExporter.kt index 39721d4d..0110f5f8 100644 --- a/extension/core/src/main/kotlin/org/camunda/community/process_test_coverage/core/export/CoverageStateJsonExporter.kt +++ b/extension/core/src/main/kotlin/org/camunda/community/process_test_coverage/core/export/CoverageStateJsonExporter.kt @@ -25,4 +25,14 @@ object CoverageStateJsonExporter { fun readCoverageStateResult(json: String): CoverageStateResult = Gson().fromJson(json, CoverageStateResult::class.java) + @JvmStatic + fun combineCoverageStateResults(json1: String, json2: String): String { + val result1 = readCoverageStateResult(json1) + val result2 = readCoverageStateResult(json2) + return createCoverageStateResult( + result1.suites + result2.suites, + result1.models.plus(result2.models.filter { new -> !result1.models.map { model -> model.key }.contains(new.key) }) + ) + } + } \ No newline at end of file diff --git a/extension/core/src/main/kotlin/org/camunda/community/process_test_coverage/core/export/CoverageStateResult.kt b/extension/core/src/main/kotlin/org/camunda/community/process_test_coverage/core/export/CoverageStateResult.kt index 06dc8a49..fb3fa1e9 100644 --- a/extension/core/src/main/kotlin/org/camunda/community/process_test_coverage/core/export/CoverageStateResult.kt +++ b/extension/core/src/main/kotlin/org/camunda/community/process_test_coverage/core/export/CoverageStateResult.kt @@ -1,9 +1,16 @@ package org.camunda.community.process_test_coverage.core.export +import org.camunda.community.process_test_coverage.core.model.Coverage +import org.camunda.community.process_test_coverage.core.model.Event import org.camunda.community.process_test_coverage.core.model.Model import org.camunda.community.process_test_coverage.core.model.Suite -data class CoverageStateResult( +class CoverageStateResult( val suites: Collection, val models: Collection -) \ No newline at end of file +) : Coverage { + override fun getEvents() = suites.map { it.getEvents() }.flatten() + + override fun getEvents(modelKey: String) = suites.map { it.getEvents(modelKey) }.flatten() + +} \ No newline at end of file diff --git a/extension/report-aggregator-gradle-plugin/gradle.properties b/extension/report-aggregator-gradle-plugin/gradle.properties index cc352b2d..7c6131be 100644 --- a/extension/report-aggregator-gradle-plugin/gradle.properties +++ b/extension/report-aggregator-gradle-plugin/gradle.properties @@ -1,2 +1,2 @@ -kotlinVersion=1.7.21 +kotlinVersion=1.9.10 projectName=report-aggregator-gradle-plugin diff --git a/extension/report-aggregator-gradle-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/report/aggregator/ReportAggregatorPlugin.kt b/extension/report-aggregator-gradle-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/report/aggregator/ReportAggregatorPlugin.kt index db6e9141..9047ff9e 100644 --- a/extension/report-aggregator-gradle-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/report/aggregator/ReportAggregatorPlugin.kt +++ b/extension/report-aggregator-gradle-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/report/aggregator/ReportAggregatorPlugin.kt @@ -1,6 +1,8 @@ package org.camunda.community.process_test_coverage.report.aggregator -import org.camunda.community.process_test_coverage.core.export.CoverageStateJsonExporter +import org.camunda.community.process_test_coverage.core.export.CoverageStateJsonExporter.combineCoverageStateResults +import org.camunda.community.process_test_coverage.core.export.CoverageStateJsonExporter.createCoverageStateResult +import org.camunda.community.process_test_coverage.core.export.CoverageStateJsonExporter.readCoverageStateResult import org.camunda.community.process_test_coverage.core.export.CoverageStateResult import org.camunda.community.process_test_coverage.report.CoverageReportUtil import org.gradle.api.Plugin @@ -32,20 +34,15 @@ class ReportAggregatorPlugin : Plugin { project.logger.debug("Reading file ${it.path}") it.readText(Charsets.UTF_8) } - .map { CoverageStateJsonExporter.readCoverageStateResult(it) } - .reduceOrNull { result1, result2 -> CoverageStateResult( - result1.suites + result2.suites, - result1.models.plus(result2.models.filter { new -> !result1.models.map { model -> model.key }.contains(new.key) }) - ) - } + .reduceOrNull { result1, result2 -> combineCoverageStateResults(result1, result2) } ?.let { - println(outputDirectory) + val report = readCoverageStateResult(it) CoverageReportUtil.writeReport( - CoverageStateJsonExporter.createCoverageStateResult(it.suites, it.models), false, + createCoverageStateResult(report.suites, report.models), false, outputDirectory, "report.json" ) { result -> result } CoverageReportUtil.writeReport( - CoverageStateJsonExporter.createCoverageStateResult(it.suites, it.models), true, + createCoverageStateResult(report.suites, report.models), true, outputDirectory, "report.html", CoverageReportUtil::generateHtml) } ?: project.logger.warn("No coverage results found, skipping execution") } diff --git a/extension/report-aggregator-maven-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/report/aggregator/ReportAggregatorMojo.kt b/extension/report-aggregator-maven-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/report/aggregator/ReportAggregatorMojo.kt index b4daa4e2..ac6fb6bb 100644 --- a/extension/report-aggregator-maven-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/report/aggregator/ReportAggregatorMojo.kt +++ b/extension/report-aggregator-maven-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/report/aggregator/ReportAggregatorMojo.kt @@ -1,6 +1,5 @@ package org.camunda.community.process_test_coverage.report.aggregator -import org.apache.maven.MavenExecutionException import org.apache.maven.doxia.sink.Sink import org.apache.maven.plugin.AbstractMojo import org.apache.maven.plugins.annotations.LifecyclePhase @@ -8,9 +7,9 @@ import org.apache.maven.plugins.annotations.Mojo import org.apache.maven.plugins.annotations.Parameter import org.apache.maven.project.MavenProject import org.apache.maven.reporting.MavenReport +import org.camunda.community.process_test_coverage.core.export.CoverageStateJsonExporter.combineCoverageStateResults import org.camunda.community.process_test_coverage.core.export.CoverageStateJsonExporter.createCoverageStateResult import org.camunda.community.process_test_coverage.core.export.CoverageStateJsonExporter.readCoverageStateResult -import org.camunda.community.process_test_coverage.core.export.CoverageStateResult import org.camunda.community.process_test_coverage.report.CoverageReportUtil import org.codehaus.plexus.util.FileUtils import java.io.File @@ -95,16 +94,13 @@ class ReportAggregatorMojo : AbstractMojo(), MavenReport { log.debug("Reading file ${it.path}") FileUtils.fileRead(it) } - .map { readCoverageStateResult(it) } - .reduceOrNull { result1, result2 -> CoverageStateResult( - result1.suites + result2.suites, - result1.models.plus(result2.models.filter { new -> !result1.models.map { model -> model.key }.contains(new.key) }) - )} + .reduceOrNull { result1, result2 -> combineCoverageStateResults(result1, result2) } ?.let { - CoverageReportUtil.writeReport(createCoverageStateResult(it.suites, it.models), false, + val report = readCoverageStateResult(it) + CoverageReportUtil.writeReport(createCoverageStateResult(report.suites, report.models), false, outputDirectory.path, "report.json" ) { result -> result } - CoverageReportUtil.writeReport(createCoverageStateResult(it.suites, it.models), true, + CoverageReportUtil.writeReport(createCoverageStateResult(report.suites, report.models), true, outputDirectory.path, "report.html", CoverageReportUtil::generateHtml) } ?: log.warn("No coverage results found, skipping execution") } diff --git a/extension/sonar-process-test-coverage-plugin/pom.xml b/extension/sonar-process-test-coverage-plugin/pom.xml index b88bc175..a80ad14c 100644 --- a/extension/sonar-process-test-coverage-plugin/pom.xml +++ b/extension/sonar-process-test-coverage-plugin/pom.xml @@ -4,7 +4,7 @@ camunda-process-test-coverage-parent org.camunda.community.process_test_coverage - 2.0.1-SNAPSHOT + 2.1.1-SNAPSHOT Camunda Process Test Coverage Sonar Plugin sonar-camunda-process-test-coverage-plugin @@ -23,6 +23,10 @@ org.camunda.community.process_test_coverage camunda-process-test-coverage-core + + org.camunda.bpm.model + camunda-bpmn-model + diff --git a/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/BpmnLanguage.kt b/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/BpmnLanguage.kt new file mode 100644 index 00000000..d54ab0df --- /dev/null +++ b/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/BpmnLanguage.kt @@ -0,0 +1,14 @@ +package org.camunda.community.process_test_coverage.sonar + +import org.sonar.api.resources.AbstractLanguage + +class BpmnLanguage : AbstractLanguage(KEY, NAME) { + + companion object { + const val NAME = "BPMN" + const val KEY = "bpmn" + } + + override fun getFileSuffixes() = arrayOf(".bpmn") + +} \ No newline at end of file diff --git a/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/BpmnQualityProfile.kt b/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/BpmnQualityProfile.kt new file mode 100644 index 00000000..ea335e4c --- /dev/null +++ b/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/BpmnQualityProfile.kt @@ -0,0 +1,12 @@ +package org.camunda.community.process_test_coverage.sonar + +import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition + + +class BpmnQualityProfile : BuiltInQualityProfilesDefinition { + override fun define(context: BuiltInQualityProfilesDefinition.Context) { + val profile = context.createBuiltInQualityProfile("BPMN Rules", BpmnLanguage.KEY) + profile.setDefault(true) + profile.done() + } +} \ No newline at end of file diff --git a/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/ProcessTestCoveragePlugin.kt b/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/ProcessTestCoveragePlugin.kt index 5d9cd765..5bda6710 100644 --- a/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/ProcessTestCoveragePlugin.kt +++ b/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/ProcessTestCoveragePlugin.kt @@ -8,8 +8,9 @@ import org.sonar.api.resources.Qualifiers class ProcessTestCoveragePlugin : Plugin { override fun define(context: Plugin.Context) { + context.addExtensions(BpmnLanguage::class.java, BpmnQualityProfile::class.java) context.addExtension(ProcessTestCoverageMetrics::class.java) - context.addExtension(ProcessTestCoverageSensor::class.java) + context.addExtensions(ProcessTestCoverageSensor::class.java, ProcessTestCoverageProjectSensor::class.java) context.addExtension( PropertyDefinition.builder(ReportPathsProvider.REPORT_PATHS_PROPERTY_KEY) .onQualifiers(Qualifiers.PROJECT) diff --git a/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/ProcessTestCoverageProjectSensor.kt b/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/ProcessTestCoverageProjectSensor.kt new file mode 100644 index 00000000..8e0ecc7d --- /dev/null +++ b/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/ProcessTestCoverageProjectSensor.kt @@ -0,0 +1,52 @@ +package org.camunda.community.process_test_coverage.sonar + +import org.camunda.community.process_test_coverage.core.export.CoverageStateJsonExporter.combineCoverageStateResults +import org.camunda.community.process_test_coverage.core.export.CoverageStateJsonExporter.readCoverageStateResult +import org.sonar.api.batch.sensor.SensorContext +import org.sonar.api.batch.sensor.SensorDescriptor +import org.sonar.api.scanner.sensor.ProjectSensor +import org.sonar.api.utils.log.Loggers +import java.nio.file.Files + + +class ProcessTestCoverageProjectSensor : ProjectSensor { + + companion object { + private val LOG = Loggers.get(ProcessTestCoverageProjectSensor::class.java) + } + + override fun describe(descriptor: SensorDescriptor) { + descriptor.name("Process Test Coverage Report Importer") + } + + override fun execute(context: SensorContext) { + val reportPathsProvider = ReportPathsProvider(context) + val importer = ReportImporter(context) + importReports(reportPathsProvider, importer) + } + + private fun importReports(reportPathsProvider: ReportPathsProvider, importer: ReportImporter) { + val reportPaths = reportPathsProvider.getPaths() + if (reportPaths.isEmpty()) { + LOG.info("No report imported, no coverage information will be imported by Process Test Coverage Report Importer") + return + } + LOG.info( + "Importing {} report(s). Turn your logs in debug mode in order to see the exhaustive list.", + reportPaths.size + ) + try { + reportPaths + .map { + LOG.debug("Reading report '{}'", it) + Files.readAllBytes(it).decodeToString() + } + .reduceOrNull { result1, result2 -> combineCoverageStateResults(result1, result2) } + ?.let { importer.importProjectCoverage(readCoverageStateResult(it)) } + ?: LOG.warn("No coverage results found, skipping analysis") + } catch (e: Exception) { + LOG.error("Coverage reports could not be read/imported. Error: {}", e) + } + } + +} \ No newline at end of file diff --git a/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/ProcessTestCoverageSensor.kt b/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/ProcessTestCoverageSensor.kt index 933875fc..3660a749 100644 --- a/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/ProcessTestCoverageSensor.kt +++ b/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/ProcessTestCoverageSensor.kt @@ -1,6 +1,7 @@ package org.camunda.community.process_test_coverage.sonar -import org.camunda.community.process_test_coverage.core.export.CoverageStateJsonExporter +import org.camunda.community.process_test_coverage.core.export.CoverageStateJsonExporter.combineCoverageStateResults +import org.camunda.community.process_test_coverage.core.export.CoverageStateJsonExporter.readCoverageStateResult import org.sonar.api.batch.sensor.Sensor import org.sonar.api.batch.sensor.SensorContext import org.sonar.api.batch.sensor.SensorDescriptor @@ -34,14 +35,17 @@ class ProcessTestCoverageSensor : Sensor { "Importing {} report(s). Turn your logs in debug mode in order to see the exhaustive list.", reportPaths.size ) - for (reportPath in reportPaths) { - LOG.debug("Reading report '{}'", reportPath) - try { - val result = CoverageStateJsonExporter.readCoverageStateResult(Files.readAllBytes(reportPath).decodeToString()) - importer.importCoverage(result) - } catch (e: Exception) { - LOG.error("Coverage report '{}' could not be read/imported. Error: {}", reportPath, e) - } + try { + reportPaths + .map { + LOG.debug("Reading report '{}'", it) + Files.readAllBytes(it).decodeToString() + } + .reduceOrNull { result1, result2 -> combineCoverageStateResults(result1, result2) } + ?.let { importer.importCoverage(readCoverageStateResult(it)) } + ?: LOG.warn("No coverage results found, skipping analysis") + } catch (e: Exception) { + LOG.error("Coverage reports could not be read/imported. Error: {}", e) } } diff --git a/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/ReportImporter.kt b/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/ReportImporter.kt index d5ff2a34..6ea4dea4 100644 --- a/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/ReportImporter.kt +++ b/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/ReportImporter.kt @@ -1,8 +1,16 @@ package org.camunda.community.process_test_coverage.sonar +import org.camunda.bpm.model.bpmn.Bpmn +import org.camunda.bpm.model.bpmn.BpmnModelInstance +import org.camunda.bpm.model.bpmn.instance.* +import org.camunda.bpm.model.xml.instance.ModelElementInstance import org.camunda.community.process_test_coverage.core.export.CoverageStateResult +import org.camunda.community.process_test_coverage.core.model.Model +import org.sonar.api.batch.fs.InputFile import org.sonar.api.batch.sensor.SensorContext import org.sonar.api.utils.log.Loggers +import java.io.ByteArrayInputStream +import java.util.stream.Collectors class ReportImporter(private val ctx: SensorContext) { @@ -12,28 +20,80 @@ class ReportImporter(private val ctx: SensorContext) { } fun importCoverage(result: CoverageStateResult) { - if (result.suites.size == 1) { - result.suites.first().let { - - val lastDot = it.name.lastIndexOf('.') - val className = it.name.substring(lastDot + 1) - val packageName = it.name.substring(0, lastDot) - val path = "**/${packageName.replace('.', '/')}/$className.*" - val inputFile = ctx.fileSystem().inputFile(ctx.fileSystem().predicates().matchesPathPattern(path)) - - inputFile?.let { file -> + val resultsMap = result.models.associateBy { + Bpmn.readModelFromStream(it.xml.byteInputStream()).processDefinitionKey() + } + ctx.fileSystem().inputFiles(ctx.fileSystem().predicates().hasLanguage(BpmnLanguage.KEY)) + .associateBy { + Bpmn.readModelFromStream(it.inputStream()).processDefinitionKey() + } + .forEach { + LOG.info("Calculating coverage for process {} in file {}", it.key, it.value.filename()) + val coverage = resultsMap[it.key]?.let { model -> result.calculateCoverage(model) } ?: 0.0 + LOG.info("Coverage for process {} is {}", it.key, coverage) ctx.newMeasure() - .on(file) - .forMetric(ProcessTestCoverageMetrics.PROCESS_TEST_COVERAGE) - .withValue(it.calculateCoverage(result.models).asPercent()) - .save() + .on(it.value) + .forMetric(ProcessTestCoverageMetrics.PROCESS_TEST_COVERAGE) + .withValue(coverage.asPercent()) + .save() } - } + } + + fun importProjectCoverage(result: CoverageStateResult) { + val models = ctx.fileSystem().inputFiles(ctx.fileSystem().predicates().hasLanguage(BpmnLanguage.KEY)) + .map { readModel(it) } + val totalElementCount = models.sumOf { it.totalElementCount } + val coveredElementCount = models.sumOf { result.getEventsDistinct(modelKey = it.key).size } + ctx.newMeasure() + .on(ctx.project()) + .forMetric(ProcessTestCoverageMetrics.PROCESS_TEST_COVERAGE) + .withValue((coveredElementCount.toDouble() / totalElementCount.toDouble()).asPercent()) + .save() + } + + private fun readModel(file: InputFile): Model { + val modelInstance = Bpmn.readModelFromStream(file.inputStream()) + val key = modelInstance.processDefinitionKey() + val definitionFlowNodes = getExecutableFlowNodes(modelInstance.getModelElementsByType(FlowNode::class.java), key) + val definitionSequenceFlows = getExecutableSequenceNodes(modelInstance.getModelElementsByType(SequenceFlow::class.java), definitionFlowNodes) + + return Model( + key, + definitionFlowNodes.size + definitionSequenceFlows.size, + "unknown", + Bpmn.convertToString(modelInstance) + ) + } + + private fun getExecutableFlowNodes(flowNodes: Collection, processId: String): Set { + return flowNodes.stream() + .filter { node: FlowNode? -> isExecutable(node, processId) } + .collect(Collectors.toSet()) + } + + private fun getExecutableSequenceNodes(sequenceFlows: Collection, definitionFlowNodes: Set): Set { + return sequenceFlows.stream() + .filter { s: SequenceFlow -> definitionFlowNodes.contains(s.source) } + .collect(Collectors.toSet()) + } + + private fun isExecutable(node: ModelElementInstance?, processId: String): Boolean { + if (node == null) { + return false + } + return if (node is Process) { + node.isExecutable && node.id == processId + } else if (node is IntermediateThrowEvent) { + node.eventDefinitions.none { it is LinkEventDefinition } } else { - LOG.warn("Cannot import coverage results for more than one suite") + isExecutable(node.parentElement, processId) } } + private fun BpmnModelInstance.processDefinitionKey() = + getModelElementsByType(Process::class.java).firstOrNull { process -> process.isExecutable }?.id + ?: throw IllegalArgumentException("No executable process found") + private fun Double.asPercent() = this * 100 } diff --git a/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/ReportPathsProvider.kt b/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/ReportPathsProvider.kt index e1e15036..418d0d27 100644 --- a/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/ReportPathsProvider.kt +++ b/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/ReportPathsProvider.kt @@ -12,7 +12,7 @@ class ReportPathsProvider( companion object { private val LOG: Logger = Loggers.get(ReportPathsProvider::class.java) - private val DEFAULT_PATHS = arrayOf("target/process-test-coverage/**/report.json") + private val DEFAULT_PATHS = arrayOf("**/process-test-coverage/**/report.json") const val REPORT_PATHS_PROPERTY_KEY = "sonar.process-test-coverage.jsonReportPaths" } @@ -24,6 +24,7 @@ class ReportPathsProvider( val reportPaths: MutableSet = HashSet() if (patternPathList.isNotEmpty()) { for (patternPath in patternPathList) { + LOG.info("Scanning {} with pattern {}", baseDir, patternPath) val paths: List = WildcardPatternFileScanner.scan(baseDir, patternPath) if (paths.isEmpty() && patternPathList.size > 1) { LOG.info("Coverage report doesn't exist for pattern: '{}'", patternPath) diff --git a/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/WildcardPatternFileScanner.kt b/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/WildcardPatternFileScanner.kt index fb8a212c..509c5ac1 100644 --- a/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/WildcardPatternFileScanner.kt +++ b/extension/sonar-process-test-coverage-plugin/src/main/kotlin/org/camunda/community/process_test_coverage/sonar/WildcardPatternFileScanner.kt @@ -66,13 +66,13 @@ object WildcardPatternFileScanner { return paths } catch (e: IOException) { LOG.error( - "Failed to get Process Test Converage report paths: Scanning '" + baseDirectory + "' with pattern '" + unixLikePatternPath + "'" + + "Failed to get Process Test Coverage report paths: Scanning '" + baseDirectory + "' with pattern '" + unixLikePatternPath + "'" + " threw a " + e.javaClass.simpleName + ": " + e.message ) return emptyList() } catch (e: RuntimeException) { LOG.error( - ("Failed to get Process Test Converage report paths: Scanning '" + baseDirectory + "' with pattern '" + unixLikePatternPath + "'" + + ("Failed to get Process Test Coverage report paths: Scanning '" + baseDirectory + "' with pattern '" + unixLikePatternPath + "'" + " threw a " + e.javaClass.simpleName + ": " + e.message) ) return emptyList()