Skip to content

Commit

Permalink
Introduced Coverage Reporter script to generate HTML and MARKDOWN rep…
Browse files Browse the repository at this point in the history
…orts
  • Loading branch information
Rd4dev committed Jun 24, 2024
1 parent bc9a5c0 commit 7e10505
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 3 deletions.
14 changes: 14 additions & 0 deletions scripts/src/java/org/oppia/android/scripts/coverage/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ kt_jvm_library(
deps = [
"//scripts/src/java/org/oppia/android/scripts/common:bazel_client",
"//scripts/src/java/org/oppia/android/scripts/coverage:coverage_runner",
"//scripts/src/java/org/oppia/android/scripts/coverage:coverage_reporter",
"//scripts/src/java/org/oppia/android/scripts/proto:script_exemptions_java_proto",
],
)
Expand All @@ -30,3 +31,16 @@ kt_jvm_library(
"//scripts/src/java/org/oppia/android/scripts/proto:coverage_java_proto",
],
)

kt_jvm_library(
name = "coverage_reporter",
testonly = True,
srcs = [
"CoverageReporter.kt",
],
visibility = ["//scripts:oppia_script_binary_visibility"],
deps = [
"//scripts/src/java/org/oppia/android/scripts/common:bazel_client",
"//scripts/src/java/org/oppia/android/scripts/proto:coverage_java_proto",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package org.oppia.android.scripts.coverage

import org.oppia.android.scripts.proto.CoverageReport

class CoverageReporter(private val coverageReportList: List<CoverageReport>) {

fun generateRichTextReport(format: ReportFormat, computedCoverageRatio: Float): String {
return when (format) {
ReportFormat.MARKDOWN -> generateMarkdownReport(computedCoverageRatio)
ReportFormat.HTML -> generateHtmlReport()
}
}

private fun generateMarkdownReport(computedCoverageRatio: Float): String {
val computedCoveragePercentage = "%.2f".format(computedCoverageRatio)
val totalFiles = coverageReportList.size
val filePath = coverageReportList.firstOrNull()?.getCoveredFile(0)?.filePath ?: "Unknown"

val (totalLinesFound, totalLinesHit) = computeTotalsFor("lines")
val (totalFunctionsFound, totalFunctionsHit) = computeTotalsFor("functions")
val (totalBranchesFound, totalBranchesHit) = computeTotalsFor("branches")

return """
# Coverage Report
**Total coverage:**
- **Files covered:** $totalFiles
- **Covered File:** $filePath
- **Coverage percentage:** $computedCoveragePercentage% covered
- **Line coverage:** $totalLinesFound covered / $totalLinesHit found
- **Function coverage:** $totalFunctionsHit covered / $totalFunctionsFound found
- **Branch coverage:** $totalBranchesFound covered / $totalBranchesHit found
""".trimIndent()
}

private fun computeTotalsFor(type: String): Pair<Int, Int> {
var totalFound = 0
var totalHit = 0

coverageReportList.forEach { coverageReport ->
coverageReport.coveredFileList.forEach { coveredFile ->
when (type) {
"lines" -> {
totalFound += coveredFile.linesFound
totalHit += coveredFile.linesHit
}
"functions" -> {
totalFound += coveredFile.functionsFound
totalHit += coveredFile.functionsHit
}
"branches" -> {
totalFound += coveredFile.branchesFound
totalHit += coveredFile.branchesHit
}
}
}
}

return Pair(totalFound, totalHit)
}

private fun generateHtmlReport(): String {
// Placeholder for HTML report generation
return ""
}

fun computeCoverageRatio(): Float {
var totalFound = 0f
var totalHit = 0f

coverageReportList.forEach { coverageReport ->
coverageReport.coveredFileList.forEach { coveredFile ->
totalFound += (coveredFile.linesFound + coveredFile.functionsFound + coveredFile.branchesFound).toFloat()
totalHit += (coveredFile.linesHit + coveredFile.functionsHit + coveredFile.branchesHit).toFloat()
}
}

return if (totalFound > 0) (totalHit / totalFound * 100) else 0.0f
}
}

enum class ReportFormat {
MARKDOWN,
HTML
}
28 changes: 25 additions & 3 deletions scripts/src/java/org/oppia/android/scripts/coverage/RunCoverage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ import java.util.concurrent.TimeUnit
fun main(vararg args: String) {
val repoRoot = args[0]
val filePath = args[1]
val reportFormat = when (args.getOrNull(2)) {
"HTML" -> ReportFormat.HTML
"MARKDOWN", null -> ReportFormat.MARKDOWN // Default to MARKDOWN if not specified
else -> throw IllegalArgumentException("Unsupported report format: ${args[2]}")
}

ScriptBackgroundCoroutineDispatcher().use { scriptBgDispatcher ->
val processTimeout: Long = args.find { it.startsWith("processTimeout=") }
Expand All @@ -41,7 +46,7 @@ fun main(vararg args: String) {
scriptBgDispatcher, processTimeout = processTimeout, processTimeoutUnit = TimeUnit.MINUTES
)

println(RunCoverage(repoRoot, filePath, commandExecutor, scriptBgDispatcher).execute())
RunCoverage(repoRoot, filePath, reportFormat, commandExecutor, scriptBgDispatcher).execute()
}
}

Expand All @@ -56,6 +61,7 @@ fun main(vararg args: String) {
class RunCoverage(
private val repoRoot: String,
private val filePath: String,
private val reportFormat: ReportFormat,
private val commandExecutor: CommandExecutor,
private val scriptBgDispatcher: ScriptBackgroundCoroutineDispatcher
) {
Expand All @@ -64,6 +70,12 @@ class RunCoverage(
private val rootDirectory = File(repoRoot).absoluteFile
private val testFileExemptionTextProto = "scripts/assets/test_file_exemptions"

companion object {
// The minimum coverage percentage threshold for coverage analysis,
// The script will fail if the file has less than the minimum specified coverage.
const val MIN_COVERAGE_PERCENTAGE = 20
}

/**
* Executes coverage analysis for the specified file.
*
Expand All @@ -75,7 +87,6 @@ class RunCoverage(
* @return a list of lists containing coverage data for each requested test target, if
* the file is exempted from having a test file, an empty list is returned
*/

fun execute(): List<CoverageReport> {
val testFileExemptionList = loadTestFileExemptionsProto(testFileExemptionTextProto)
.getExemptedFilePathList()
Expand All @@ -88,13 +99,24 @@ class RunCoverage(
val testFilePaths = findTestFile(repoRoot, filePath)
val testTargets = bazelClient.retrieveBazelTargets(testFilePaths)

return testTargets.mapNotNull { testTarget ->
val coverageReports = testTargets.mapNotNull { testTarget ->
val coverageData = runCoverageForTarget(testTarget)
if (coverageData == null) {
println("Coverage data for $testTarget is null")
}
coverageData
}

if (coverageReports.isNotEmpty()) {
val reporter = CoverageReporter(coverageReports)
val coverageRatio = reporter.computeCoverageRatio()
val generatedReport = reporter.generateRichTextReport(reportFormat, coverageRatio)
println("Generated report: $generatedReport")
} else {
println("No coverage reports generated.")
}

return coverageReports
}

private fun runCoverageForTarget(testTarget: String): CoverageReport? {
Expand Down

0 comments on commit 7e10505

Please sign in to comment.