diff --git a/scripts/assets/test_file_exemptions.textproto b/scripts/assets/test_file_exemptions.textproto index 7876da19715..1b873b622a0 100644 --- a/scripts/assets/test_file_exemptions.textproto +++ b/scripts/assets/test_file_exemptions.textproto @@ -2,10 +2,6 @@ test_file_exemption { exempted_file_path: "utility/src/main/java/org/oppia/android/util/parser/math/MathModel.kt" override_min_coverage_percent_required: 388 } -test_file_exemption { - exempted_file_path: "utility/src/main/java/org/oppia/android/util/parser/math/MathModel2.kt" - override_min_coverage_percent_required: 24 -} test_file_exemption { exempted_file_path: "app/src/main/java/org/oppia/android/app/activity/ActivityComponent.kt" test_file_not_required: true diff --git a/scripts/src/java/org/oppia/android/scripts/common/BazelClient.kt b/scripts/src/java/org/oppia/android/scripts/common/BazelClient.kt index db9818bf60f..7e995563366 100644 --- a/scripts/src/java/org/oppia/android/scripts/common/BazelClient.kt +++ b/scripts/src/java/org/oppia/android/scripts/common/BazelClient.kt @@ -144,13 +144,11 @@ class BazelClient(private val rootDirectory: File, private val commandExecutor: fun runCoverageForTestTarget(bazelTestTarget: String): List? { val instrumentation = bazelTestTarget.split(":")[0] val computeInstrumentation = instrumentation.split("/").let { "//${it[2]}/..." } - println("In computing coverage for test in bazel client for $bazelTestTarget") val coverageCommandOutputLines = executeBazelCommand( "coverage", bazelTestTarget, "--instrumentation_filter=$computeInstrumentation" ) - println("After computing coverage for test in bazel client for $bazelTestTarget") return parseCoverageDataFilePath(coverageCommandOutputLines)?.let { path -> File(path).readLines() } @@ -162,7 +160,7 @@ class BazelClient(private val rootDirectory: File, private val commandExecutor: val match = regex.find(line) val extractedPath = match?.value?.substringAfterLast(",")?.trim() if (extractedPath != null) { - println("Raw Coverage Data: $extractedPath") +// println("Raw Coverage Data: $extractedPath") return extractedPath } } diff --git a/scripts/src/java/org/oppia/android/scripts/coverage/CoverageReporter.kt b/scripts/src/java/org/oppia/android/scripts/coverage/CoverageReporter.kt index ac1e30d8925..f6d5d16a97a 100644 --- a/scripts/src/java/org/oppia/android/scripts/coverage/CoverageReporter.kt +++ b/scripts/src/java/org/oppia/android/scripts/coverage/CoverageReporter.kt @@ -32,7 +32,19 @@ class CoverageReporter( * and the second value is the generated report text */ fun generateRichTextReport(): Pair { - println("Report format: $reportFormat") + logCoverageReport() + + // Rough + /* If HTML -> HTML report -> send reportText -> Done + * If MD -> generate report() -> send reportText ("file.kt, 23%, 2/4 lines, :x:") + * oh wait no... the last one is decided also based on exemption + * so after coverageCheckThreshold -> determine coverage status -> + * set coverage status to PASS or FAIL + * reportText += " > :tick: ; < :wrong:" + * + * In execute after awaitAll -> check MD -> generateMD report + * */ + return when (reportFormat) { ReportFormat.MARKDOWN -> generateMarkdownReport() ReportFormat.HTML -> generateHtmlReport() @@ -69,15 +81,38 @@ class CoverageReporter( *
  • Lines Coverage: 19/19 covered * * - */ - val markdownContent = - """ - ## Coverage Report + * + * Sample template for reference: + * |Covered File|Percentage|Line Coverage|Status| + |-------------|:----------:|:---------------:|:------:| + |[HomeActivity.kt](https://www.github.com)|53.00%|4/7 lines|:x:| - - **Covered File:** $filePath - - **Coverage percentage:** $formattedCoveragePercentage% covered - - **Line coverage:** $totalLinesHit / $totalLinesFound lines covered - """.trimIndent() + + # Coverage Report + + ## Failed Coverage + + | File Path | Coverage Percentage | Line Coverage | + |-------------------------------|----------------------|-------------------| + | src/main/file1.kt | 45.00% | 90/200 lines | + | src/main/file2.kt | 50.50% | 101/200 lines | + | src/main/really_long_file_name.kt | 60.00% | 120/200 lines | + +
    + Success Coverage + + | File Path | Coverage Percentage | Line Coverage |Status + |-------------------------------|:----------------------:|-------------------|:------:| + | src/main/file3.kt | 85.00% | 170/200 lines |:white_check_mark:| + | src/main/file4.kt | 90.50% | 181/200 lines |:white_check_mark:| + | src/main/file5.kt | 95.00% | 190/200 lines |:x:| + +
    + + */ + val markdownContent = "|$filePath" + + "|$formattedCoveragePercentage%" + + "|$totalLinesHit / $totalLinesFound" println("\n$markdownContent") @@ -249,6 +284,23 @@ class CoverageReporter( 0f } } + + private fun logCoverageReport() { + // TODO: (remove) as this looks un even in the output log + val logReportText = listOf( + "Covered File: $filePath", + "Coverage percentage: $formattedCoveragePercentage% covered", + "Line coverage: $totalLinesHit / $totalLinesFound lines covered" + ) + + val maxLength = logReportText.maxOf {it.length} + val horizontalBorder = "+-${"-".repeat(maxLength)}-+" + val reportText = logReportText.joinToString(separator = "\n") { line -> + "| ${line.padEnd(maxLength)} |" + } + + println("$horizontalBorder\n$reportText\n$horizontalBorder") + } } /** Represents the different types of formats available to generate code coverage reports. */ diff --git a/scripts/src/java/org/oppia/android/scripts/coverage/CoverageRunner.kt b/scripts/src/java/org/oppia/android/scripts/coverage/CoverageRunner.kt index 4c93a3092d7..17a44e2aaf1 100644 --- a/scripts/src/java/org/oppia/android/scripts/coverage/CoverageRunner.kt +++ b/scripts/src/java/org/oppia/android/scripts/coverage/CoverageRunner.kt @@ -40,11 +40,11 @@ class CoverageRunner( bazelTestTarget: String ): Deferred { return CoroutineScope(scriptBgDispatcher).async { - println("$bazelTestTarget start: ${LocalDateTime.now()} on thread: ${Thread.currentThread().name}") +// println("$bazelTestTarget start: ${LocalDateTime.now()} on thread: ${Thread.currentThread().name}") val coverageResult = retrieveCoverageResult(bazelTestTarget) ?: return@async generateFailedCoverageReport() - println("$bazelTestTarget end: ${LocalDateTime.now()} on thread: ${Thread.currentThread().name}") +// println("$bazelTestTarget end: ${LocalDateTime.now()} on thread: ${Thread.currentThread().name}") coverageDataFileLines(coverageResult, bazelTestTarget) } diff --git a/scripts/src/java/org/oppia/android/scripts/coverage/RunCoverage.kt b/scripts/src/java/org/oppia/android/scripts/coverage/RunCoverage.kt index eecb8b99d98..b35872f55d3 100644 --- a/scripts/src/java/org/oppia/android/scripts/coverage/RunCoverage.kt +++ b/scripts/src/java/org/oppia/android/scripts/coverage/RunCoverage.kt @@ -57,6 +57,7 @@ fun main(vararg args: String) { ?.uppercase() ?: "MARKDOWN" val reportFormat = when (format) { + // TODO: (default to HTML) as it would be much simpler for local development "HTML" -> ReportFormat.HTML "MARKDOWN" -> ReportFormat.MARKDOWN else -> throw IllegalArgumentException("Unsupported report format: $format") @@ -132,8 +133,10 @@ class RunCoverage( } }.awaitAll() - println("Coverage Results: $coverageResults") - println("COVERAGE ANALYSIS COMPLETED.") + if (reportFormat == ReportFormat.MARKDOWN) generateFinalMdReport(coverageResults) + +// println("Coverage Results: $coverageResults") + println("\nCOVERAGE ANALYSIS COMPLETED.") } private suspend fun runCoverageForFile(filePath: String): String { @@ -160,6 +163,7 @@ class RunCoverage( val coverageReports = deferredCoverageReports.awaitAll() // Check if the coverage reports are successfully generated else return failure message. + // TODO: (yet to decide) if this too needs to be set as coverage state -> FAIL. coverageReports.firstOrNull()?.let { if (!it.isGenerated) { return "Failed to generate coverage report for the file: $filePath.".also { @@ -170,13 +174,18 @@ class RunCoverage( val aggregatedCoverageReport = calculateAggregateCoverageReport(coverageReports) val reporter = CoverageReporter(repoRoot, aggregatedCoverageReport, reportFormat) - val (computedCoverageRatio, reportText) = reporter.generateRichTextReport() + var (computedCoverageRatio, reportText) = reporter.generateRichTextReport() val coverageCheckThreshold = exemption?.overrideMinCoveragePercentRequired ?: MIN_THRESHOLD - println("**************************Coverage threshold : $coverageCheckThreshold") - if (computedCoverageRatio*100 < coverageCheckThreshold) coverageCheckState = CoverageCheck.FAIL - println("***************Coverage check state: $coverageCheckState") +// println("**************************Coverage threshold : $coverageCheckThreshold") + if (computedCoverageRatio*100 < coverageCheckThreshold) { + coverageCheckState = CoverageCheck.FAIL + reportText += "|:x:|" + } else { + reportText += "|:white_check_mark:|" + } +// println("***************Coverage check state: $coverageCheckState") File(reportOutputPath).apply { parentFile?.mkdirs() @@ -184,7 +193,6 @@ class RunCoverage( } if (File(reportOutputPath).exists()) { - println("\nComputed Coverage Ratio is: $computedCoverageRatio") println("\nGenerated report at: $reportOutputPath\n") } @@ -198,6 +206,61 @@ class RunCoverage( } } +private fun generateFinalMdReport(coverageResults: List) { + /*val coverageTableHeader = "| Covered File | Percentage | Line Coverage | Status |\n" + + "|--------------|------------|---------------|--------|\n" + + println("Coverage table header: $coverageTableHeader") + + *//*val coverageFailures = coverageResults.map { result -> + result.split("|").fil{it} + }*//* + println(coverageResults[0].split("|")[4]) + println("Coverage Failures: $coverageResults")*/ + + val coverageTableHeader = "| Covered File | Percentage | Line Coverage | Status |\n" + + "|--------------|------------|---------------|--------|\n" + + val coverageFailures = coverageResults.filter { result -> + result.contains("|") && result.split("|")[4].trim() == ":x:" + } + + val coverageSuccesses = coverageResults.filter { result -> + result.contains("|") && result.split("|")[4].trim() == ":white_check_mark:" + } + + val exemptedCases = coverageResults.filterNot { it.contains("|") } + + val coverageFailuresRows = coverageFailures.joinToString(separator = "\n") + val coverageSuccessesRows = coverageSuccesses.joinToString(separator = "\n") + + val failureMarkdownTable = "## Coverage Report\n\n" + + "Total covered files: ${coverageResults.size}\n" + + "Coverage Status: FAIL\n" + + "Min Coverage Required: 10%\n\n" + // make use of MIN_THRESHOLD + coverageTableHeader + + coverageFailuresRows + + val successMarkdownTable = "
    \n" + + "Succeeded Coverages\n\n" + + coverageTableHeader + + coverageSuccessesRows + + "\n
    " + + val exemptedCasesList = exemptedCases.joinToString(separator = "\n") { "- $it" } + + val finalReportText = failureMarkdownTable + "\n\n" + successMarkdownTable + "\n\n" + "### Exempted Cases\n" + exemptedCasesList +/* + + println(""" + ## Coverage Report + $coverageResults + """.trimIndent()) +*/ + + println(finalReportText) +} + private fun calculateAggregateCoverageReport( coverageReports: List ): CoverageReport {