forked from oppia/oppia-android
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix part of oppia#5343: Generate Code Coverage Report in HTML and MAR…
…KDOWN formats (oppia#5443) <!-- READ ME FIRST: Please fill in the explanation section below and check off every point from the Essential Checklist! --> ## Explanation <!-- - Explain what your PR does. If this PR fixes an existing bug, please include - "Fixes #bugnum:" in the explanation so that GitHub can auto-close the issue - when this PR is merged. --> Fixes part of oppia#5343 ### Project [PR 1.5 of Project 4.1] ### Changes Made This PR introduces the `CoverageReporter` utility to generate the code coverage report. The main features and changes include: - **New Utility:** - `CoverageReporter` to generate code coverage reports. - **Command Line Arguments:** - Now support output formats: `HTML` or `MARKDOWN`. - **Report Generation:** - Generates coverage reports from the generated proto for the requested format. - Outputs are generated and stored in the `coverage_reports` folder relative to the `repoRoot`. - **Gitignore Update:** - The `coverage_reports` folder is added to `.gitignore` to ensure it is not added to the repository. ## Essential Checklist <!-- Please tick the relevant boxes by putting an "x" in them. --> - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). ## For UI-specific PRs only <!-- Delete these section if this PR does not include UI-related changes. --> If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing --------- Co-authored-by: Ben Henning <[email protected]>
- Loading branch information
1 parent
c5de68b
commit f53c7e5
Showing
9 changed files
with
2,230 additions
and
271 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,3 +25,4 @@ bazel-* | |
.bazelproject | ||
.aswb | ||
*.pb | ||
coverage_reports |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
236 changes: 236 additions & 0 deletions
236
scripts/src/java/org/oppia/android/scripts/coverage/CoverageReporter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
package org.oppia.android.scripts.coverage | ||
|
||
import org.oppia.android.scripts.proto.Coverage | ||
import org.oppia.android.scripts.proto.CoverageReport | ||
import java.io.File | ||
|
||
/** | ||
* Class responsible for generating rich text coverage report. | ||
* | ||
* @param repoRoot the root directory of the repository | ||
* @param coverageReportList the list of coverage data proto | ||
* @param reportFormat the format in which the report will be generated | ||
*/ | ||
class CoverageReporter( | ||
private val repoRoot: String, | ||
private val coverageReportList: List<CoverageReport>, | ||
private val reportFormat: ReportFormat, | ||
) { | ||
private val computedCoverageRatio = computeCoverageRatio() | ||
private val formattedCoveragePercentage = "%.2f".format(computedCoverageRatio * 100) | ||
|
||
private val filePath = coverageReportList.firstOrNull()?.filePath ?: "Unknown" | ||
|
||
private val totalLinesFound = coverageReportList.getOrNull(0)?.linesFound ?: 0 | ||
private val totalLinesHit = coverageReportList.getOrNull(0)?.linesHit ?: 0 | ||
|
||
/** | ||
* Generates a rich text report for the analysed coverage data based on the specified format. | ||
* It supports Markdown and HTML formats. | ||
* | ||
* @return a pair where the first value is the computed coverage ratio represented in [0, 1] | ||
* and the second value is the generated report text | ||
*/ | ||
fun generateRichTextReport(): Pair<Float, String> { | ||
println("report format: $reportFormat") | ||
return when (reportFormat) { | ||
ReportFormat.MARKDOWN -> generateMarkdownReport() | ||
ReportFormat.HTML -> generateHtmlReport() | ||
} | ||
} | ||
|
||
private fun generateMarkdownReport(): Pair<Float, String> { | ||
val markdownContent = | ||
""" | ||
## Coverage Report | ||
- **Covered File:** $filePath | ||
- **Coverage percentage:** $formattedCoveragePercentage% covered | ||
- **Line coverage:** $totalLinesHit / $totalLinesFound lines covered | ||
""".trimIndent() | ||
|
||
println("\n$markdownContent") | ||
|
||
return Pair(computedCoverageRatio, markdownContent) | ||
} | ||
|
||
private fun generateHtmlReport(): Pair<Float, String> { | ||
var htmlContent = | ||
""" | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Coverage Report</title> | ||
<style> | ||
body { | ||
font-family: Arial, sans-serif; | ||
line-height: 1.6; | ||
padding: 20px; | ||
} | ||
table { | ||
width: 100%; | ||
border-collapse: collapse; | ||
margin-bottom: 20px; | ||
} | ||
th, td { | ||
padding: 8px; | ||
margin-left: 20px; | ||
text-align: left; | ||
border-bottom: 1px solid #fdfdfd; | ||
} | ||
.line-number-col { | ||
width: 2%; | ||
} | ||
.line-number-row { | ||
border-right: 1px dashed #000000 | ||
} | ||
.source-code-col { | ||
width: 98%; | ||
} | ||
.covered-line, .not-covered-line, .uncovered-line { | ||
white-space: pre-wrap; | ||
word-wrap: break-word; | ||
box-sizing: border-box; | ||
border-radius: 4px; | ||
padding: 2px 8px 2px 4px; | ||
display: inline-block; | ||
} | ||
.covered-line { | ||
background-color: #c8e6c9; /* Light green */ | ||
} | ||
.not-covered-line { | ||
background-color: #ffcdd2; /* Light red */ | ||
} | ||
.uncovered-line { | ||
background-color: #f1f1f1; /* light gray */ | ||
} | ||
.coverage-summary { | ||
margin-bottom: 20px; | ||
} | ||
h2 { | ||
text-align: center; | ||
} | ||
ul { | ||
list-style-type: none; | ||
padding: 0; | ||
text-align: center; | ||
} | ||
.summary-box { | ||
background-color: #f0f0f0; | ||
border: 1px solid #ccc; | ||
border-radius: 8px; | ||
padding: 10px; | ||
margin-bottom: 20px; | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: flex-start; | ||
} | ||
.summary-left { | ||
text-align: left; | ||
} | ||
.summary-right { | ||
text-align: right; | ||
} | ||
.legend { | ||
display: flex; | ||
align-items: center; | ||
} | ||
.legend-item { | ||
width: 20px; | ||
height: 10px; | ||
margin-right: 5px; | ||
border-radius: 2px; | ||
display: inline-block; | ||
} | ||
.legend .covered { | ||
background-color: #c8e6c9; /* Light green */ | ||
} | ||
.legend .not-covered { | ||
margin-left: 4px; | ||
background-color: #ffcdd2; /* Light red */ | ||
} | ||
@media screen and (max-width: 768px) { | ||
body { | ||
padding: 10px; | ||
} | ||
table { | ||
width: auto; | ||
} | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<h2>Coverage Report</h2> | ||
<div class="summary-box"> | ||
<div class="summary-left"> | ||
<strong>Covered File:</strong> $filePath <br> | ||
<div class="legend"> | ||
<div class="legend-item covered"></div> | ||
<span>Covered</span> | ||
<div class="legend-item not-covered"></div> | ||
<span>Uncovered</span> | ||
</div> | ||
</div> | ||
<div class="summary-right"> | ||
<div><strong>Coverage percentage:</strong> $formattedCoveragePercentage%</div> | ||
<div><strong>Line coverage:</strong> $totalLinesHit / $totalLinesFound covered</div> | ||
</div> | ||
</div> | ||
<table> | ||
<thead> | ||
<tr> | ||
<th class="line-number-col">Line No</th> | ||
<th class="source-code-col">Source Code</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
""".trimIndent() | ||
|
||
val fileContent = File(repoRoot, filePath).readLines() | ||
val coverageMap = coverageReportList | ||
.firstOrNull()?.coveredLineList?.associateBy { it.lineNumber } | ||
|
||
fileContent.forEachIndexed { index, line -> | ||
val lineNumber = index + 1 | ||
val lineClass = when (coverageMap?.get(lineNumber)?.coverage) { | ||
Coverage.FULL -> "covered-line" | ||
Coverage.NONE -> "not-covered-line" | ||
else -> "uncovered-line" | ||
} | ||
htmlContent += """ | ||
<tr> | ||
<td class="line-number-row">${lineNumber.toString().padStart(4, ' ')}</td> | ||
<td class="$lineClass">$line</td> | ||
</tr> | ||
""".trimIndent() | ||
} | ||
|
||
htmlContent += """ | ||
</tbody> | ||
</table> | ||
</body> | ||
</html> | ||
""".trimIndent() | ||
|
||
return Pair(computedCoverageRatio, htmlContent) | ||
} | ||
|
||
private fun computeCoverageRatio(): Float { | ||
val report = coverageReportList.getOrNull(0) | ||
return if (report != null && report.linesFound != 0) { | ||
report.linesHit.toFloat() / report.linesFound.toFloat() | ||
} else { | ||
0f | ||
} | ||
} | ||
} | ||
|
||
/** Represents the different types of formats available to generate code coverage reports. */ | ||
enum class ReportFormat { | ||
/** Indicates that the report should be formatted in .md format. */ | ||
MARKDOWN, | ||
/** Indicates that the report should be formatted in .html format. */ | ||
HTML | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.