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 8c3c546f026..6a302d0eb9f 100644 --- a/scripts/src/java/org/oppia/android/scripts/common/BazelClient.kt +++ b/scripts/src/java/org/oppia/android/scripts/common/BazelClient.kt @@ -142,7 +142,8 @@ class BazelClient(private val rootDirectory: File, private val commandExecutor: * or null if the coverage data file could not be parsed */ fun runCoverageForTestTarget(bazelTestTarget: String): List? { - val computeInstrumentation = bazelTestTarget.split("/").let { "//${it[2]}/..." } + val instrumentation = bazelTestTarget.split(":")[0] + val computeInstrumentation = instrumentation.split("/").let { "//${it[2]}/..." } val coverageCommandOutputLines = executeBazelCommand( "coverage", bazelTestTarget, @@ -159,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("Parsed Coverage Data File: $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 70c0cf9baab..7e597a2ca3f 100644 --- a/scripts/src/java/org/oppia/android/scripts/coverage/CoverageReporter.kt +++ b/scripts/src/java/org/oppia/android/scripts/coverage/CoverageReporter.kt @@ -8,21 +8,21 @@ 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 coverageReport the coverage data proto * @param reportFormat the format in which the report will be generated */ class CoverageReporter( private val repoRoot: String, - private val coverageReportList: List, + private val coverageReport: 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 filePath = coverageReport.filePath - private val totalLinesFound = coverageReportList.getOrNull(0)?.linesFound ?: 0 - private val totalLinesHit = coverageReportList.getOrNull(0)?.linesHit ?: 0 + private val totalLinesFound = coverageReport.linesFound + private val totalLinesHit = coverageReport.linesHit /** * Generates a rich text report for the analysed coverage data based on the specified format. @@ -32,7 +32,7 @@ class CoverageReporter( * and the second value is the generated report text */ fun generateRichTextReport(): Pair { - println("report format: $reportFormat") + println("Report format: $reportFormat") return when (reportFormat) { ReportFormat.MARKDOWN -> generateMarkdownReport() ReportFormat.HTML -> generateHtmlReport() @@ -66,6 +66,7 @@ class CoverageReporter( @@ -189,12 +185,11 @@ class CoverageReporter( """.trimIndent() val fileContent = File(repoRoot, filePath).readLines() - val coverageMap = coverageReportList - .firstOrNull()?.coveredLineList?.associateBy { it.lineNumber } + val coverageMap = coverageReport.coveredLineList.associateBy { it.lineNumber } fileContent.forEachIndexed { index, line -> val lineNumber = index + 1 - val lineClass = when (coverageMap?.get(lineNumber)?.coverage) { + val lineClass = when (coverageMap.get(lineNumber)?.coverage) { Coverage.FULL -> "covered-line" Coverage.NONE -> "not-covered-line" else -> "uncovered-line" @@ -218,9 +213,8 @@ class CoverageReporter( } private fun computeCoverageRatio(): Float { - val report = coverageReportList.getOrNull(0) - return if (report != null && report.linesFound != 0) { - report.linesHit.toFloat() / report.linesFound.toFloat() + return if (coverageReport.linesFound != 0) { + coverageReport.linesHit.toFloat() / coverageReport.linesFound.toFloat() } else { 0f } 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 b27cb822dbb..7a61e0fa07b 100644 --- a/scripts/src/java/org/oppia/android/scripts/coverage/CoverageRunner.kt +++ b/scripts/src/java/org/oppia/android/scripts/coverage/CoverageRunner.kt @@ -1,11 +1,9 @@ package org.oppia.android.scripts.coverage -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.async import org.oppia.android.scripts.common.BazelClient import org.oppia.android.scripts.common.CommandExecutor import org.oppia.android.scripts.common.ScriptBackgroundCoroutineDispatcher +import org.oppia.android.scripts.proto.BazelTestTarget import org.oppia.android.scripts.proto.Coverage import org.oppia.android.scripts.proto.CoverageReport import org.oppia.android.scripts.proto.CoveredLine @@ -34,15 +32,13 @@ class CoverageRunner( * @param bazelTestTarget Bazel test target to analyze coverage * @return a deferred value that contains the coverage data */ - fun runWithCoverageAsync( + fun retrieveCoverageDataForTestTarget( bazelTestTarget: String - ): Deferred { - return CoroutineScope(scriptBgDispatcher).async { - val coverageResult = retrieveCoverageResult(bazelTestTarget) - ?: error("Failed to retrieve coverage result for $bazelTestTarget") + ): CoverageReport { + val coverageResult = retrieveCoverageResult(bazelTestTarget) + ?: error("Failed to retrieve coverage result for $bazelTestTarget") - coverageDataFileLines(coverageResult, bazelTestTarget) - } + return coverageDataFileLines(coverageResult, bazelTestTarget) } private fun retrieveCoverageResult( @@ -93,8 +89,12 @@ class CoverageRunner( val file = File(repoRoot, filePath) val fileSha1Hash = calculateSha1(file.absolutePath) + val bazelTestTargetName = BazelTestTarget.newBuilder() + .setTestTargetName(bazelTestTarget) + .build() + return CoverageReport.newBuilder() - .setBazelTestTarget(bazelTestTarget) + .addBazelTestTargets(bazelTestTargetName) .setFilePath(filePath) .setFileSha1Hash(fileSha1Hash) .addAllCoveredLine(coveredLines) 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 db5d381a711..ec44977a53c 100644 --- a/scripts/src/java/org/oppia/android/scripts/coverage/RunCoverage.kt +++ b/scripts/src/java/org/oppia/android/scripts/coverage/RunCoverage.kt @@ -1,11 +1,12 @@ package org.oppia.android.scripts.coverage -import kotlinx.coroutines.runBlocking import org.oppia.android.scripts.common.BazelClient import org.oppia.android.scripts.common.CommandExecutor import org.oppia.android.scripts.common.CommandExecutorImpl import org.oppia.android.scripts.common.ScriptBackgroundCoroutineDispatcher +import org.oppia.android.scripts.proto.Coverage import org.oppia.android.scripts.proto.CoverageReport +import org.oppia.android.scripts.proto.CoveredLine import org.oppia.android.scripts.proto.TestFileExemptions import java.io.File import java.util.concurrent.TimeUnit @@ -27,7 +28,7 @@ import java.util.concurrent.TimeUnit * utility/src/main/java/org/oppia/android/util/parser/math/MathModel.kt format=HTML * Example with custom process timeout: * bazel run //scripts:run_coverage -- $(pwd) - * utility/src/main/java/org/oppia/android/util/parser/math/MathModel.kt processTimeout=10 + * utility/src/main/java/org/oppia/android/util/parser/math/MathModel.kt processTimeout=15 * */ fun main(vararg args: String) { @@ -46,8 +47,8 @@ fun main(vararg args: String) { val reportOutputPath = getReportOutputPath(repoRoot, filePath, reportFormat) - if (!File(repoRoot, filePath).exists()) { - error("File doesn't exist: $filePath.") + check(File(repoRoot, filePath).exists()) { + "File doesn't exist: $filePath." } ScriptBackgroundCoroutineDispatcher().use { scriptBgDispatcher -> @@ -110,22 +111,21 @@ class RunCoverage( if (filePath in testFileExemptionList) { println("This file is exempted from having a test file; skipping coverage check.") - return - } - - val testFilePaths = findTestFile(repoRoot, filePath) - if (testFilePaths.isEmpty()) { - error("No appropriate test file found for $filePath") - } + } else { + val testFilePaths = findTestFiles(repoRoot, filePath) + check(testFilePaths.isNotEmpty()) { + "No appropriate test file found for $filePath" + } - val testTargets = bazelClient.retrieveBazelTargets(testFilePaths) + val testTargets = bazelClient.retrieveBazelTargets(testFilePaths) - val coverageReports = testTargets.mapNotNull { testTarget -> - runCoverageForTarget(testTarget) - } + val coverageReports = testTargets.map { testTarget -> + CoverageRunner(rootDirectory, scriptBgDispatcher, commandExecutor) + .retrieveCoverageDataForTestTarget(testTarget.removeSuffix(".kt")) + } - coverageReports.takeIf { it.isNotEmpty() }?.run { - val reporter = CoverageReporter(repoRoot, this, reportFormat) + val aggregatedCoverageReport = calculateAggregateCoverageReport(coverageReports) + val reporter = CoverageReporter(repoRoot, aggregatedCoverageReport, reportFormat) val (computedCoverageRatio, reportText) = reporter.generateRichTextReport() File(reportOutputPath).apply { @@ -137,19 +137,50 @@ class RunCoverage( println("\nComputed Coverage Ratio is: $computedCoverageRatio") println("\nGenerated report at: $reportOutputPath\n") } - } ?: println("No coverage reports generated.") - } - private fun runCoverageForTarget(testTarget: String): CoverageReport { - return runBlocking { - CoverageRunner(rootDirectory, scriptBgDispatcher, commandExecutor) - .runWithCoverageAsync(testTarget.removeSuffix(".kt")) - .await() + println("COVERAGE ANALYSIS COMPLETED.") } } } -private fun findTestFile(repoRoot: String, filePath: String): List { +private fun calculateAggregateCoverageReport( + coverageReports: List +): CoverageReport { + fun aggregateCoverage(coverages: List): Coverage { + return coverages.find { it == Coverage.FULL } ?: Coverage.NONE + } + + val groupedCoverageReports = coverageReports.groupBy { + Pair(it.filePath, it.fileSha1Hash) + } + + val (key, reports) = groupedCoverageReports.entries.single() + val (filePath, fileSha1Hash) = key + + val allBazelTestTargets = reports.flatMap { it.bazelTestTargetsList } + val allCoveredLines = reports.flatMap { it.coveredLineList } + val groupedCoveredLines = allCoveredLines.groupBy { it.lineNumber } + val aggregatedCoveredLines = groupedCoveredLines.map { (lineNumber, coveredLines) -> + CoveredLine.newBuilder() + .setLineNumber(lineNumber) + .setCoverage(aggregateCoverage(coveredLines.map { it.coverage })) + .build() + } + + val totalLinesFound = aggregatedCoveredLines.size + val totalLinesHit = aggregatedCoveredLines.count { it.coverage == Coverage.FULL } + + return CoverageReport.newBuilder() + .addAllBazelTestTargets(allBazelTestTargets) + .setFilePath(filePath) + .setFileSha1Hash(fileSha1Hash) + .addAllCoveredLine(aggregatedCoveredLines) + .setLinesFound(totalLinesFound) + .setLinesHit(totalLinesHit) + .build() +} + +private fun findTestFiles(repoRoot: String, filePath: String): List { val possibleTestFilePaths = when { filePath.startsWith("scripts/") -> { listOf(filePath.replace("/java/", "/javatests/").replace(".kt", "Test.kt")) diff --git a/scripts/src/java/org/oppia/android/scripts/proto/coverage.proto b/scripts/src/java/org/oppia/android/scripts/proto/coverage.proto index 3b4af5c070e..1fe2cdb938b 100644 --- a/scripts/src/java/org/oppia/android/scripts/proto/coverage.proto +++ b/scripts/src/java/org/oppia/android/scripts/proto/coverage.proto @@ -9,7 +9,7 @@ option java_multiple_files = true; // Bazel coverage execution. message CoverageReport { // The test target for which the coverage report is generated. - string bazel_test_target = 1; + repeated BazelTestTarget bazel_test_targets = 1; // The relative path of the covered file. string file_path = 2; // SHA-1 hash of the file content at the time of report (to guard against changes). @@ -22,6 +22,12 @@ message CoverageReport { int32 lines_hit = 6; } +// Represents a single bazel test target corresponding to a test file. +message BazelTestTarget { + // The name of the bazel test target. + string test_target_name = 1; +} + // Information about a single line that was covered during the tests. message CoveredLine { // The line number of the covered line. @@ -33,8 +39,8 @@ message CoveredLine { enum Coverage { // Coverage status is unspecified. UNSPECIFIED = 0; - // The line, branch, or function is fully covered, ie. executed atleast once. + // The line is fully covered in a test, that is, it was executed at least once. FULL = 1; - // The line, branch, or function is not covered at all. + // The line is not covered at all, that is, it was never executed during a test. NONE = 2; } diff --git a/scripts/src/javatests/org/oppia/android/scripts/common/BazelClientTest.kt b/scripts/src/javatests/org/oppia/android/scripts/common/BazelClientTest.kt index ae4b2deee6f..296f44cac2c 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/common/BazelClientTest.kt +++ b/scripts/src/javatests/org/oppia/android/scripts/common/BazelClientTest.kt @@ -388,7 +388,7 @@ class BazelClientTest { """ package com.example - class TwoSum { + class AddNums { companion object { fun sumNumbers(a: Int, b: Int): Any { @@ -409,33 +409,35 @@ class BazelClientTest { import org.junit.Assert.assertEquals import org.junit.Test - class TwoSumTest { + class AddNumsTest { @Test fun testSumNumbers() { - assertEquals(TwoSum.sumNumbers(0, 1), 1) - assertEquals(TwoSum.sumNumbers(3, 4), 7) - assertEquals(TwoSum.sumNumbers(0, 0), "Both numbers are zero") + assertEquals(AddNums.sumNumbers(0, 1), 1) + assertEquals(AddNums.sumNumbers(3, 4), 7) + assertEquals(AddNums.sumNumbers(0, 0), "Both numbers are zero") } } """.trimIndent() testBazelWorkspace.addSourceAndTestFileWithContent( - filename = "TwoSum", - testFilename = "TwoSumTest", + filename = "AddNums", + testFilename = "AddNumsTest", sourceContent = sourceContent, testContent = testContent, sourceSubpackage = "coverage/main/java/com/example", testSubpackage = "coverage/test/java/com/example" ) - val result = bazelClient.runCoverageForTestTarget("//coverage/test/java/com/example:TwoSumTest") + val result = bazelClient.runCoverageForTestTarget( + "//coverage/test/java/com/example:AddNumsTest" + ) val expectedResult = listOf( - "SF:coverage/main/java/com/example/TwoSum.kt", - "FN:7,com/example/TwoSum${'$'}Companion::sumNumbers (II)Ljava/lang/Object;", - "FN:3,com/example/TwoSum:: ()V", - "FNDA:1,com/example/TwoSum${'$'}Companion::sumNumbers (II)Ljava/lang/Object;", - "FNDA:0,com/example/TwoSum:: ()V", + "SF:coverage/main/java/com/example/AddNums.kt", + "FN:7,com/example/AddNums${'$'}Companion::sumNumbers (II)Ljava/lang/Object;", + "FN:3,com/example/AddNums:: ()V", + "FNDA:1,com/example/AddNums${'$'}Companion::sumNumbers (II)Ljava/lang/Object;", + "FNDA:0,com/example/AddNums:: ()V", "FNF:2", "FNH:1", "BRDA:7,0,0,1", diff --git a/scripts/src/javatests/org/oppia/android/scripts/coverage/CoverageReporterTest.kt b/scripts/src/javatests/org/oppia/android/scripts/coverage/CoverageReporterTest.kt index 155885cce3d..cee105e2d41 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/coverage/CoverageReporterTest.kt +++ b/scripts/src/javatests/org/oppia/android/scripts/coverage/CoverageReporterTest.kt @@ -27,7 +27,7 @@ class CoverageReporterTest { fun testCoverageReporter_validData_generatesCorrectCoverageRatio() { reporter = CoverageReporter( tempFolder.root.absolutePath, - listOf(validCoverageReport), + validCoverageReport, ReportFormat.MARKDOWN ) val expectedCoverageRatio = 0.8F @@ -42,7 +42,7 @@ class CoverageReporterTest { val report = validCoverageReport.toBuilder().setLinesFound(0).build() reporter = CoverageReporter( tempFolder.root.absolutePath, - listOf(report), + report, ReportFormat.MARKDOWN ) val (coverageRatio, _) = reporter.generateRichTextReport() @@ -53,7 +53,7 @@ class CoverageReporterTest { fun testCoverageReporter_generateMarkdownReport_hasCorrectContentAndFormatting() { reporter = CoverageReporter( tempFolder.root.absolutePath, - listOf(validCoverageReport), + validCoverageReport, ReportFormat.MARKDOWN ) val (_, reportText) = reporter.generateRichTextReport() @@ -90,7 +90,7 @@ class CoverageReporterTest { reporter = CoverageReporter( tempFolder.root.absolutePath, - listOf(validCoverageReport), + validCoverageReport, ReportFormat.HTML ) val (_, reportText) = reporter.generateRichTextReport() @@ -106,6 +106,7 @@ class CoverageReporterTest { diff --git a/scripts/src/javatests/org/oppia/android/scripts/coverage/CoverageRunnerTest.kt b/scripts/src/javatests/org/oppia/android/scripts/coverage/CoverageRunnerTest.kt index 68fd8e7e3e6..2ffebe2f984 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/coverage/CoverageRunnerTest.kt +++ b/scripts/src/javatests/org/oppia/android/scripts/coverage/CoverageRunnerTest.kt @@ -1,7 +1,6 @@ package org.oppia.android.scripts.coverage import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Before import org.junit.Rule @@ -9,11 +8,13 @@ import org.junit.Test import org.junit.rules.TemporaryFolder import org.oppia.android.scripts.common.CommandExecutorImpl import org.oppia.android.scripts.common.ScriptBackgroundCoroutineDispatcher +import org.oppia.android.scripts.proto.BazelTestTarget import org.oppia.android.scripts.proto.Coverage import org.oppia.android.scripts.proto.CoverageReport import org.oppia.android.scripts.proto.CoveredLine import org.oppia.android.scripts.testing.TestBazelWorkspace import org.oppia.android.testing.assertThrows +import java.io.File import java.util.concurrent.TimeUnit /** Tests for [CoverageRunner]. */ @@ -27,11 +28,50 @@ class CoverageRunnerTest { private lateinit var testBazelWorkspace: TestBazelWorkspace private lateinit var bazelTestTarget: String + private lateinit var sourceContent: String + private lateinit var testContent: String + @Before fun setUp() { coverageRunner = CoverageRunner(tempFolder.root, scriptBgDispatcher, longCommandExecutor) bazelTestTarget = "//:testTarget" testBazelWorkspace = TestBazelWorkspace(tempFolder) + + sourceContent = + """ + package com.example + + class AddNums { + + companion object { + fun sumNumbers(a: Int, b: Int): Any { + return if (a == 0 && b == 0) { + "Both numbers are zero" + } else { + a + b + } + } + } + } + """.trimIndent() + + testContent = + """ + package com.example + + import org.junit.Assert.assertEquals + import org.junit.Test + + class AddNumsTest { + + @Test + fun testSumNumbers() { + assertEquals(AddNums.sumNumbers(0, 1), 1) + assertEquals(AddNums.sumNumbers(3, 4), 7) + assertEquals(AddNums.sumNumbers(0, 0), "Both numbers are zero") + } + } + """.trimIndent() } @After @@ -40,24 +80,20 @@ class CoverageRunnerTest { } @Test - fun testRunWithCoverageAsync_emptyDirectory_throwsException() { + fun testRetrieveCoverageDataForTestTarget_emptyDirectory_throwsException() { val exception = assertThrows() { - runBlocking { - coverageRunner.runWithCoverageAsync(bazelTestTarget).await() - } + coverageRunner.retrieveCoverageDataForTestTarget(bazelTestTarget) } assertThat(exception).hasMessageThat().contains("not invoked from within a workspace") } @Test - fun testRunWithCoverageAsync_invalidTestTarget_throwsException() { + fun testRetrieveCoverageDataForTestTarget_invalidTestTarget_throwsException() { testBazelWorkspace.initEmptyWorkspace() val exception = assertThrows() { - runBlocking { - coverageRunner.runWithCoverageAsync(bazelTestTarget).await() - } + coverageRunner.retrieveCoverageDataForTestTarget(bazelTestTarget) } assertThat(exception).hasMessageThat().contains("Expected non-zero exit code") @@ -65,64 +101,107 @@ class CoverageRunnerTest { } @Test - fun testRunWithCoverageAsync_validSampleTestTarget_returnsCoverageData() { + fun testRetrieveCoverageDataForTestTarget_withIncorrectPackageStructure_throwsException() { testBazelWorkspace.initEmptyWorkspace() + testBazelWorkspace.addSourceAndTestFileWithContent( + filename = "AddNums", + testFilename = "AddNumsTest", + sourceContent = sourceContent, + testContent = testContent, + sourceSubpackage = "coverage/example", + testSubpackage = "coverage/example" + ) - val sourceContent = - """ - package com.example - - class TwoSum { - - companion object { - fun sumNumbers(a: Int, b: Int): Any { - return if (a == 0 && b == 0) { - "Both numbers are zero" - } else { - a + b - } - } - } - } - """.trimIndent() + val exception = assertThrows() { + coverageRunner.retrieveCoverageDataForTestTarget( + "//coverage/example:AddNumsTest" + ) + } - val testContent = + assertThat(exception).hasMessageThat().contains("Failed to retrieve coverage result") + } + + @Test + fun testRetrieveCoverageDataForTestTarget_withNoDepsToSourceFile_throwsException() { + testBazelWorkspace.initEmptyWorkspace() + testBazelWorkspace.addSourceAndTestFileWithContent( + filename = "AddNums", + testFilename = "AddNumsTest", + sourceContent = sourceContent, + testContent = testContent, + sourceSubpackage = "coverage/main/java/com/example", + testSubpackage = "coverage/test/java/com/example" + ) + + val subTestFile = tempFolder.newFile("coverage/test/java/com/example/SubNumsTest.kt") + subTestFile.writeText( """ package com.example import org.junit.Assert.assertEquals import org.junit.Test + import com.example.AddNums - class TwoSumTest { + class SubNumsTest { @Test - fun testSumNumbers() { - assertEquals(TwoSum.sumNumbers(0, 1), 1) - assertEquals(TwoSum.sumNumbers(3, 4), 7) - assertEquals(TwoSum.sumNumbers(0, 0), "Both numbers are zero") + fun testSubNumbers() { + assertEquals(AddNums.sumNumbers(0, 1), 1) + assertEquals(AddNums.sumNumbers(3, 4), 7) + assertEquals(AddNums.sumNumbers(0, 0), "Both numbers are zero") } } """.trimIndent() + ) + + val testBuildFile = File(tempFolder.root, "coverage/test/java/com/example/BUILD.bazel") + testBuildFile.appendText( + """ + kt_jvm_test( + name = "SubNumsTest", + srcs = ["SubNumsTest.kt"], + deps = [ + "//coverage/main/java/com/example:addnums", + "@maven//:junit_junit", + ], + visibility = ["//visibility:public"], + test_class = "com.example.SubNumsTest", + ) + """.trimIndent() + ) + val exception = assertThrows() { + coverageRunner.retrieveCoverageDataForTestTarget( + "//coverage/test/java/com/example:SubNumsTest" + ) + } + + assertThat(exception).hasMessageThat().contains("Coverage data not found") + } + + @Test + fun testRetrieveCoverageDataForTestTarget_validSampleTestTarget_returnsCoverageData() { + testBazelWorkspace.initEmptyWorkspace() testBazelWorkspace.addSourceAndTestFileWithContent( - filename = "TwoSum", - testFilename = "TwoSumTest", + filename = "AddNums", + testFilename = "AddNumsTest", sourceContent = sourceContent, testContent = testContent, sourceSubpackage = "coverage/main/java/com/example", testSubpackage = "coverage/test/java/com/example" ) - val result = runBlocking { - coverageRunner.runWithCoverageAsync( - "//coverage/test/java/com/example:TwoSumTest" - ).await() - } + val result = coverageRunner.retrieveCoverageDataForTestTarget( + "//coverage/test/java/com/example:AddNumsTest" + ) val expectedResult = CoverageReport.newBuilder() - .setBazelTestTarget("//coverage/test/java/com/example:TwoSumTest") - .setFilePath("coverage/main/java/com/example/TwoSum.kt") - .setFileSha1Hash("1020b8f405555b3f4537fd07b912d3fb9ffa3354") + .addBazelTestTargets( + BazelTestTarget.newBuilder() + .setTestTargetName("//coverage/test/java/com/example:AddNumsTest") + ) + .setFilePath("coverage/main/java/com/example/AddNums.kt") + .setFileSha1Hash("cdb04b7e8a1c6a7adaf5807244b1a524b4f4bb44") .addCoveredLine( CoveredLine.newBuilder() .setLineNumber(3) diff --git a/scripts/src/javatests/org/oppia/android/scripts/coverage/RunCoverageTest.kt b/scripts/src/javatests/org/oppia/android/scripts/coverage/RunCoverageTest.kt index a61a580373b..1fbd57dc0c9 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/coverage/RunCoverageTest.kt +++ b/scripts/src/javatests/org/oppia/android/scripts/coverage/RunCoverageTest.kt @@ -44,7 +44,7 @@ class RunCoverageTest { """ package com.example - class TwoSum { + class AddNums { companion object { fun sumNumbers(a: Int, b: Int): Any { return if (a == 0 && b == 0) { @@ -64,12 +64,12 @@ class RunCoverageTest { import org.junit.Assert.assertEquals import org.junit.Test - class TwoSumTest { + class AddNumsTest { @Test fun testSumNumbers() { - assertEquals(TwoSum.sumNumbers(0, 1), 1) - assertEquals(TwoSum.sumNumbers(3, 4), 7) - assertEquals(TwoSum.sumNumbers(0, 0), "Both numbers are zero") + assertEquals(AddNums.sumNumbers(0, 1), 1) + assertEquals(AddNums.sumNumbers(3, 4), 7) + assertEquals(AddNums.sumNumbers(0, 0), "Both numbers are zero") } } """.trimIndent() @@ -115,12 +115,12 @@ class RunCoverageTest { @Test fun testRunCoverage_ignoreCaseMarkdownArgument_returnsCoverageData() { - val filePath = "coverage/main/java/com/example/TwoSum.kt" + val filePath = "coverage/main/java/com/example/AddNums.kt" testBazelWorkspace.initEmptyWorkspace() testBazelWorkspace.addSourceAndTestFileWithContent( - filename = "TwoSum", - testFilename = "TwoSumTest", + filename = "AddNums", + testFilename = "AddNumsTest", sourceContent = sourceContent, testContent = testContent, sourceSubpackage = "coverage/main/java/com/example", @@ -142,12 +142,12 @@ class RunCoverageTest { @Test fun testRunCoverage_ignoreCaseHtmlArgument_returnsCoverageData() { - val filePath = "coverage/main/java/com/example/TwoSum.kt" + val filePath = "coverage/main/java/com/example/AddNums.kt" testBazelWorkspace.initEmptyWorkspace() testBazelWorkspace.addSourceAndTestFileWithContent( - filename = "TwoSum", - testFilename = "TwoSumTest", + filename = "AddNums", + testFilename = "AddNumsTest", sourceContent = sourceContent, testContent = testContent, sourceSubpackage = "coverage/main/java/com/example", @@ -169,12 +169,12 @@ class RunCoverageTest { @Test fun testRunCoverage_reorderedArguments_returnsCoverageData() { - val filePath = "coverage/main/java/com/example/TwoSum.kt" + val filePath = "coverage/main/java/com/example/AddNums.kt" testBazelWorkspace.initEmptyWorkspace() testBazelWorkspace.addSourceAndTestFileWithContent( - filename = "TwoSum", - testFilename = "TwoSumTest", + filename = "AddNums", + testFilename = "AddNumsTest", sourceContent = sourceContent, testContent = testContent, sourceSubpackage = "coverage/main/java/com/example", @@ -215,12 +215,12 @@ class RunCoverageTest { @Test fun testRunCoverage_sampleTestsDefaultFormat_returnsCoverageData() { - val filePath = "coverage/main/java/com/example/TwoSum.kt" + val filePath = "coverage/main/java/com/example/AddNums.kt" testBazelWorkspace.initEmptyWorkspace() testBazelWorkspace.addSourceAndTestFileWithContent( - filename = "TwoSum", - testFilename = "TwoSumTest", + filename = "AddNums", + testFilename = "AddNumsTest", sourceContent = sourceContent, testContent = testContent, sourceSubpackage = "coverage/main/java/com/example", @@ -244,12 +244,12 @@ class RunCoverageTest { @Test fun testRunCoverage_sampleTestsMarkdownFormat_returnsCoverageData() { - val filePath = "coverage/main/java/com/example/TwoSum.kt" + val filePath = "coverage/main/java/com/example/AddNums.kt" testBazelWorkspace.initEmptyWorkspace() testBazelWorkspace.addSourceAndTestFileWithContent( - filename = "TwoSum", - testFilename = "TwoSumTest", + filename = "AddNums", + testFilename = "AddNumsTest", sourceContent = sourceContent, testContent = testContent, sourceSubpackage = "coverage/main/java/com/example", @@ -273,12 +273,12 @@ class RunCoverageTest { @Test fun testRunCoverage_scriptTestsMarkdownFormat_returnsCoverageData() { - val filePath = "scripts/java/com/example/TwoSum.kt" + val filePath = "scripts/java/com/example/AddNums.kt" testBazelWorkspace.initEmptyWorkspace() testBazelWorkspace.addSourceAndTestFileWithContent( - filename = "TwoSum", - testFilename = "TwoSumTest", + filename = "AddNums", + testFilename = "AddNumsTest", sourceContent = sourceContent, testContent = testContent, sourceSubpackage = "scripts/java/com/example", @@ -302,12 +302,12 @@ class RunCoverageTest { @Test fun testRunCoverage_appTestsMarkdownFormat_returnsCoverageData() { - val filePath = "app/main/java/com/example/TwoSum.kt" + val filePath = "app/main/java/com/example/AddNums.kt" testBazelWorkspace.initEmptyWorkspace() testBazelWorkspace.addSourceAndTestFileWithContent( - filename = "TwoSum", - testFilename = "TwoSumTest", + filename = "AddNums", + testFilename = "AddNumsTest", sourceContent = sourceContent, testContent = testContent, sourceSubpackage = "app/main/java/com/example", @@ -331,7 +331,7 @@ class RunCoverageTest { @Test fun testRunCoverage_localTestsMarkdownFormat_returnsCoverageData() { - val filePath = "app/main/java/com/example/TwoSum.kt" + val filePath = "app/main/java/com/example/AddNums.kt" testBazelWorkspace.initEmptyWorkspace() val testContentLocal = @@ -341,20 +341,20 @@ class RunCoverageTest { import org.junit.Assert.assertEquals import org.junit.Test - class TwoSumLocalTest { + class AddNumsLocalTest { @Test fun testSumNumbers() { - assertEquals(TwoSum.sumNumbers(0, 1), 1) - assertEquals(TwoSum.sumNumbers(3, 4), 7) - assertEquals(TwoSum.sumNumbers(0, 0), "Both numbers are zero") + assertEquals(AddNums.sumNumbers(0, 1), 1) + assertEquals(AddNums.sumNumbers(3, 4), 7) + assertEquals(AddNums.sumNumbers(0, 0), "Both numbers are zero") } } """.trimIndent() testBazelWorkspace.addSourceAndTestFileWithContent( - filename = "TwoSum", - testFilename = "TwoSumLocalTest", + filename = "AddNums", + testFilename = "AddNumsLocalTest", sourceContent = sourceContent, testContent = testContentLocal, sourceSubpackage = "app/main/java/com/example", @@ -378,12 +378,12 @@ class RunCoverageTest { @Test fun testRunCoverage_sharedTestsMarkdownFormat_returnsCoverageData() { - val filePath = "app/main/java/com/example/TwoSum.kt" + val filePath = "app/main/java/com/example/AddNums.kt" testBazelWorkspace.initEmptyWorkspace() testBazelWorkspace.addSourceAndTestFileWithContent( - filename = "TwoSum", - testFilename = "TwoSumTest", + filename = "AddNums", + testFilename = "AddNumsTest", sourceContent = sourceContent, testContent = testContent, sourceSubpackage = "app/main/java/com/example", @@ -407,9 +407,26 @@ class RunCoverageTest { @Test fun testRunCoverage_sharedAndLocalTestsMarkdownFormat_returnsCoverageData() { - val filePath = "app/main/java/com/example/TwoSum.kt" + val filePath = "app/main/java/com/example/AddNums.kt" testBazelWorkspace.initEmptyWorkspace() + + val testContentShared = + """ + package com.example + + import org.junit.Assert.assertEquals + import org.junit.Test + + class AddNumsTest { + + @Test + fun testSumNumbers() { + assertEquals(AddNums.sumNumbers(0, 1), 1) + } + } + """.trimIndent() + val testContentLocal = """ package com.example @@ -417,21 +434,20 @@ class RunCoverageTest { import org.junit.Assert.assertEquals import org.junit.Test - class TwoSumLocalTest { + class AddNumsLocalTest { @Test fun testSumNumbers() { - assertEquals(TwoSum.sumNumbers(0, 1), 1) - assertEquals(TwoSum.sumNumbers(3, 4), 7) - assertEquals(TwoSum.sumNumbers(0, 0), "Both numbers are zero") + assertEquals(AddNums.sumNumbers(0, 1), 1) + assertEquals(AddNums.sumNumbers(3, 4), 7) } } """.trimIndent() testBazelWorkspace.addMultiLevelSourceAndTestFileWithContent( - filename = "TwoSum", + filename = "AddNums", sourceContent = sourceContent, - testContentShared = testContent, + testContentShared = testContentShared, testContentLocal = testContentLocal, subpackage = "app" ) @@ -446,19 +462,97 @@ class RunCoverageTest { ).execute() val outputReportText = File(markdownOutputPath).readText() - val expectedResult = getExpectedMarkdownText(filePath) + val expectedResult = + """ + ## Coverage Report + + - **Covered File:** $filePath + - **Coverage percentage:** 50.00% covered + - **Line coverage:** 2 / 4 lines covered + """.trimIndent() + + assertThat(outputReportText).isEqualTo(expectedResult) + } + + @Test + fun testRunCoverage_withMultipleTestsForFile_analysingSameFile() { + val filePath = "app/main/java/com/example/AddNums.kt" + + testBazelWorkspace.initEmptyWorkspace() + + val testContent1 = + """ + package com.example + + import org.junit.Assert.assertEquals + import org.junit.Test + + class AddNumsTest { + + @Test + fun testSumNumbers() { + assertEquals(AddNums.sumNumbers(0, 1), 1) + } + } + """.trimIndent() + + val testContent2 = + """ + package com.example + + import org.junit.Assert.assertEquals + import org.junit.Test + + class AddNumsLocalTest { + + @Test + fun testSumNumbers() { + assertEquals(AddNums.sumNumbers(0, 1), 1) + assertEquals(AddNums.sumNumbers(3, 4), 7) + } + } + """.trimIndent() + + testBazelWorkspace.addMultiLevelSourceAndTestFileWithContent( + filename = "AddNums", + sourceContent = sourceContent, + testContentShared = testContent1, + testContentLocal = testContent2, + subpackage = "app" + ) + + // Both the test files will correspond to one single source file + // therefore no error would be thrown while aggregating the coverage reports. + RunCoverage( + "${tempFolder.root}", + filePath, + ReportFormat.MARKDOWN, + markdownOutputPath, + longCommandExecutor, + scriptBgDispatcher + ).execute() + + val outputReportText = File(markdownOutputPath).readText() + val expectedResult = + """ + ## Coverage Report + + - **Covered File:** $filePath + - **Coverage percentage:** 50.00% covered + - **Line coverage:** 2 / 4 lines covered + """.trimIndent() assertThat(outputReportText).isEqualTo(expectedResult) } @Test fun testRunCoverage_sampleTestsHtmlFormat_returnsCoverageData() { - val filePath = "coverage/main/java/com/example/TwoSum.kt" + val filePath = "coverage/main/java/com/example/AddNums.kt" testBazelWorkspace.initEmptyWorkspace() testBazelWorkspace.addSourceAndTestFileWithContent( - filename = "TwoSum", - testFilename = "TwoSumTest", + filename = "AddNums", + testFilename = "AddNumsTest", sourceContent = sourceContent, testContent = testContent, sourceSubpackage = "coverage/main/java/com/example", @@ -482,12 +576,12 @@ class RunCoverageTest { @Test fun testRunCoverage_scriptTestsHtmlFormat_returnsCoverageData() { - val filePath = "scripts/java/com/example/TwoSum.kt" + val filePath = "scripts/java/com/example/AddNums.kt" testBazelWorkspace.initEmptyWorkspace() testBazelWorkspace.addSourceAndTestFileWithContent( - filename = "TwoSum", - testFilename = "TwoSumTest", + filename = "AddNums", + testFilename = "AddNumsTest", sourceContent = sourceContent, testContent = testContent, sourceSubpackage = "scripts/java/com/example", @@ -511,12 +605,12 @@ class RunCoverageTest { @Test fun testRunCoverage_appTestsHtmlFormat_returnsCoverageData() { - val filePath = "app/main/java/com/example/TwoSum.kt" + val filePath = "app/main/java/com/example/AddNums.kt" testBazelWorkspace.initEmptyWorkspace() testBazelWorkspace.addSourceAndTestFileWithContent( - filename = "TwoSum", - testFilename = "TwoSumTest", + filename = "AddNums", + testFilename = "AddNumsTest", sourceContent = sourceContent, testContent = testContent, sourceSubpackage = "app/main/java/com/example", @@ -540,7 +634,7 @@ class RunCoverageTest { @Test fun testRunCoverage_localTestsHtmlFormat_returnsCoverageData() { - val filePath = "app/main/java/com/example/TwoSum.kt" + val filePath = "app/main/java/com/example/AddNums.kt" testBazelWorkspace.initEmptyWorkspace() val testContentLocal = @@ -550,20 +644,20 @@ class RunCoverageTest { import org.junit.Assert.assertEquals import org.junit.Test - class TwoSumLocalTest { + class AddNumsLocalTest { @Test fun testSumNumbers() { - assertEquals(TwoSum.sumNumbers(0, 1), 1) - assertEquals(TwoSum.sumNumbers(3, 4), 7) - assertEquals(TwoSum.sumNumbers(0, 0), "Both numbers are zero") + assertEquals(AddNums.sumNumbers(0, 1), 1) + assertEquals(AddNums.sumNumbers(3, 4), 7) + assertEquals(AddNums.sumNumbers(0, 0), "Both numbers are zero") } } """.trimIndent() testBazelWorkspace.addSourceAndTestFileWithContent( - filename = "TwoSum", - testFilename = "TwoSumLocalTest", + filename = "AddNums", + testFilename = "AddNumsLocalTest", sourceContent = sourceContent, testContent = testContentLocal, sourceSubpackage = "app/main/java/com/example", @@ -587,12 +681,12 @@ class RunCoverageTest { @Test fun testRunCoverage_sharedTestsHtmlFormat_returnsCoverageData() { - val filePath = "app/main/java/com/example/TwoSum.kt" + val filePath = "app/main/java/com/example/AddNums.kt" testBazelWorkspace.initEmptyWorkspace() testBazelWorkspace.addSourceAndTestFileWithContent( - filename = "TwoSum", - testFilename = "TwoSumTest", + filename = "AddNums", + testFilename = "AddNumsTest", sourceContent = sourceContent, testContent = testContent, sourceSubpackage = "app/main/java/com/example", @@ -616,9 +710,26 @@ class RunCoverageTest { @Test fun testRunCoverage_sharedAndLocalTestsHtmlFormat_returnsCoverageData() { - val filePath = "app/main/java/com/example/TwoSum.kt" + val filePath = "app/main/java/com/example/AddNums.kt" testBazelWorkspace.initEmptyWorkspace() + + val testContentShared = + """ + package com.example + + import org.junit.Assert.assertEquals + import org.junit.Test + + class AddNumsTest { + + @Test + fun testSumNumbers() { + assertEquals(AddNums.sumNumbers(0, 1), 1) + } + } + """.trimIndent() + val testContentLocal = """ package com.example @@ -626,28 +737,27 @@ class RunCoverageTest { import org.junit.Assert.assertEquals import org.junit.Test - class TwoSumLocalTest { + class AddNumsLocalTest { @Test fun testSumNumbers() { - assertEquals(TwoSum.sumNumbers(0, 1), 1) - assertEquals(TwoSum.sumNumbers(3, 4), 7) - assertEquals(TwoSum.sumNumbers(0, 0), "Both numbers are zero") + assertEquals(AddNums.sumNumbers(0, 1), 1) + assertEquals(AddNums.sumNumbers(3, 4), 7) } } """.trimIndent() testBazelWorkspace.addMultiLevelSourceAndTestFileWithContent( - filename = "TwoSum", + filename = "AddNums", sourceContent = sourceContent, - testContentShared = testContent, + testContentShared = testContentShared, testContentLocal = testContentLocal, subpackage = "app" ) RunCoverage( "${tempFolder.root}", - "app/main/java/com/example/TwoSum.kt", + "app/main/java/com/example/AddNums.kt", ReportFormat.HTML, htmlOutputPath, longCommandExecutor, @@ -655,7 +765,176 @@ class RunCoverageTest { ).execute() val outputReportText = File(htmlOutputPath).readText() - val expectedResult = getExpectedHtmlText(filePath) + val expectedResult = + """ + + + + + + Coverage Report + + + +

Coverage Report

+
+
+ Covered File: $filePath
+
+
+ Covered +
+ Uncovered +
+
+
+
Coverage percentage: 50.00%
+
Line coverage: 2 / 4 covered
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Line NoSource Code
1package com.example
2
3class AddNums {
4 companion object {
5 fun sumNumbers(a: Int, b: Int): Any {
6 return if (a == 0 && b == 0) {
7 "Both numbers are zero"
8 } else {
9 a + b
10 }
11 }
12 }
13}
+ + + """.trimIndent() assertThat(outputReportText).isEqualTo(expectedResult) } @@ -685,6 +964,7 @@ class RunCoverageTest { @@ -812,7 +1087,7 @@ class RunCoverageTest { 3 - class TwoSum { + class AddNums { 4 companion object {