Skip to content

Commit

Permalink
feature: Add PHPUnit xml format as discoverable format (#152)
Browse files Browse the repository at this point in the history
* feature: Add PHPUnit xml format as discoverable format

Discover PHP Unit xml if report file not given
  • Loading branch information
franciscodua authored Feb 11, 2020
1 parent 87a9c77 commit d27c46e
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 47 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ This command assumes the coverage reports follow a name convention:
* Clover → clover.xml
* DotCover → dotcover.xml
* OpenCover → opencover.xml
* PHPUnit XML → coverage-xml/index.xml

Otherwise, you must define the report's location with the flag `-r`.

Expand Down
135 changes: 88 additions & 47 deletions src/main/scala/com/codacy/rules/ReportRules.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,46 +39,15 @@ class ReportRules(config: Configuration, coverageServices: => CoverageServices)
} else config

files
.map {
case file if !file.exists =>
Left(s"File ${file.getAbsolutePath} does not exist.")
case file if !file.canRead =>
Left(s"Missing read permissions for report file: ${file.getAbsolutePath}")
case file =>
logger.info(s"Parsing coverage data from: ${file.getAbsolutePath} ...")

CoverageParser
.parse(rootProjectDir, file)
.map(transform(_)(finalConfig))
.flatMap { report =>
if (report.fileReports.isEmpty)
Left("The provided coverage report generated an empty result.")
else {
val codacyReportFilename =
s"${file.getAbsoluteFile.getParent}${File.separator}codacy-coverage.json"
logger.debug(s"Saving parsed report to $codacyReportFilename")
val codacyReportFile = new File(codacyReportFilename)

logger.debug(report.toString)
FileHelper.writeJsonToFile(codacyReportFile, report)

logUploadedFileInfo(codacyReportFile)

val language = guessReportLanguage(finalConfig.languageOpt, report)

language.map(
languageStr =>
coverageServices.sendReport(commitUUID, languageStr, report, finalConfig.partial) match {
case SuccessfulResponse(value) =>
logger.info(s"Coverage data uploaded. ${value.success}")
Right(())
case failed: FailedResponse =>
val message = handleFailedResponse(failed)
Left(s"Failed to upload report: $message")
}
)
}
}
.map { file =>
logger.info(s"Parsing coverage data from: ${file.getAbsolutePath} ...")
for {
_ <- validateFileAccess(file)
report <- CoverageParser.parse(rootProjectDir, file).map(transform(_)(finalConfig))
_ <- storeReport(report, file)
language <- guessReportLanguage(finalConfig.languageOpt, report)
success <- sendReport(report, language, finalConfig, commitUUID, file)
} yield { success }
}
.collectFirst {
case Left(l) => Left(l)
Expand All @@ -88,6 +57,70 @@ class ReportRules(config: Configuration, coverageServices: => CoverageServices)
}
}

private[rules] def validateFileAccess(file: File) = {
file match {
case file if !file.exists =>
Left(s"File ${file.getAbsolutePath} does not exist.")
case file if !file.canRead =>
Left(s"Missing read permissions for report file: ${file.getAbsolutePath}")
case _ =>
Right(())
}
}

/**
* Store Report
*
* Store the parsed report for troubleshooting purposes
* @param report coverage report to be stored
* @param file report file
* @return either an error message or nothing
*/
private[rules] def storeReport(report: CoverageReport, file: File) = {
if (report.fileReports.isEmpty)
Left(s"The provided coverage report ${file.getAbsolutePath} generated an empty result.")
else {
val codacyReportFilename =
s"${file.getAbsoluteFile.getParent}${File.separator}codacy-coverage.json"
logger.debug(s"Saving parsed report to $codacyReportFilename")
val codacyReportFile = new File(codacyReportFilename)

logger.debug(report.toString)
FileHelper.writeJsonToFile(codacyReportFile, report)

logUploadedFileInfo(codacyReportFile)
Right(codacyReportFilename)
}
}

/**
* Send Report
*
* Send the parsed report to coverage services with the given language and commitUUID
* @param report coverage report to be sent
* @param language language detected in files or specified by user input
* @param config configuration
* @param commitUUID unique id of commit being reported
* @param file report file
* @return either an error message or nothing
*/
private def sendReport(
report: CoverageReport,
language: String,
config: ReportConfig,
commitUUID: String,
file: File
) = {
coverageServices.sendReport(commitUUID, language, report, config.partial) match {
case SuccessfulResponse(value) =>
logger.info(s"Coverage data uploaded. ${value.success}")
Right(())
case failed: FailedResponse =>
val message = handleFailedResponse(failed)
Left(s"Failed to upload report ${file.getAbsolutePath}: $message")
}
}

/**
* Guess report language
*
Expand Down Expand Up @@ -130,16 +163,24 @@ class ReportRules(config: Configuration, coverageServices: => CoverageServices)
val CloverRegex = """(clover\.xml)""".r
val DotcoverRegex = """(dotcover\.xml)""".r
val OpencoverRegex = """(opencover\.xml)""".r
val PhpUnitRegex = """(index\.xml)""".r

val phpUnitCoverageFolder = "coverage-xml"

files match {
case value if value.isEmpty =>
val foundFiles = pathIterator
.filter(_.getName match {
case JacocoRegex(_) | CoberturaRegex(_) | LCOVRegex(_) | CloverRegex(_) | DotcoverRegex(_) |
OpencoverRegex(_) =>
true
case _ => false
})
.filter(
file =>
file.getName match {
case JacocoRegex(_) | CoberturaRegex(_) | LCOVRegex(_) | CloverRegex(_) | DotcoverRegex(_) |
OpencoverRegex(_) =>
true
// index.xml is a common name, so we just consider it if it's inside the coverage-xml folder
case PhpUnitRegex(_) => file.getParent == phpUnitCoverageFolder
case _ => false
}
)
.toList
if (foundFiles.isEmpty)
Left("Can't guess any report due to no matching! Try to specify the report with -r")
Expand Down Expand Up @@ -198,7 +239,7 @@ class ReportRules(config: Configuration, coverageServices: => CoverageServices)
}
}

private def withCommitUUID[T](config: BaseConfig)(block: (String) => Either[String, T]): Either[String, T] = {
private def withCommitUUID[T](config: BaseConfig)(block: String => Either[String, T]): Either[String, T] = {
val maybeCommitUUID = config.commitUUID.map(Right(_)).getOrElse {
val envVars = sys.env.filter { case (_, value) => value.trim.nonEmpty }
CommitUUIDProvider.getFromAll(envVars)
Expand Down
67 changes: 67 additions & 0 deletions src/test/scala/com/codacy/rules/ReportRulesSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,72 @@ class ReportRulesSpec extends WordSpec with Matchers with PrivateMethodTester wi
reportEither should be('right)
reportEither.right.value should be(files)
}

"only provide phpunit report file inside coverage-xml" in {
val fileIterator = Iterator(new File("index.xml"), new File("coverage-xml", "index.xml"))
val reportEither = components.reportRules.guessReportFiles(List.empty, fileIterator)

reportEither should be('right)
reportEither.right.value should be(List(new File("coverage-xml", "index.xml")))
}
}

"validateFileAccess" should {
"not validate file" when {
"file does not exist" in {
val file = new File("not-exist.xml")
val result = components.reportRules.validateFileAccess(file)

result should be('left)
}

"file does not have read access" in {
val file = File.createTempFile("validateFileAccess", "read-access")
file.createNewFile()
file.setReadable(false)
file.deleteOnExit()

val result = components.reportRules.validateFileAccess(file)
result should be('left)
}
}

"validate file" in {
val file = File.createTempFile("validateFileAccess", "valid")
file.createNewFile()
file.deleteOnExit()

val result = components.reportRules.validateFileAccess(file)
result should be('right)
}
}

"storeReport" should {
"not store report" in {
val emptyReport = CoverageReport(0, Seq.empty[CoverageFileReport])
val tempFile = File.createTempFile("storeReport", "not-store")
val result = components.reportRules.storeReport(emptyReport, tempFile)

result should be('left)
}

"report is stored" when {
def storeValidReport() = {
val emptyReport = CoverageReport(0, List(CoverageFileReport("file-name", 0, Map())))
val tempFile = File.createTempFile("storeReport", "not-store")
components.reportRules.storeReport(emptyReport, tempFile)
}

"store is successful" in {
val result = storeValidReport()
result should be('right)
}

"store report" in {
val result = storeValidReport()
val resultFile = new File(result.right.value)
resultFile.exists should be(true)
}
}
}
}

0 comments on commit d27c46e

Please sign in to comment.