Skip to content
This repository has been archived by the owner on Jan 30, 2023. It is now read-only.

Commit

Permalink
JUnit report sensor (#143)
Browse files Browse the repository at this point in the history
  • Loading branch information
mwz committed Dec 28, 2018
1 parent f45a13c commit 9cd167e
Show file tree
Hide file tree
Showing 39 changed files with 1,027 additions and 66 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Expand Up @@ -45,7 +45,7 @@ libraryDependencies ++= List(
"org.scalastyle" %% "scalastyle" % "1.0.0",
"org.scala-lang.modules" %% "scala-xml" % "1.1.1",
"org.scalatest" %% "scalatest" % "3.0.5" % Test,
"org.mockito" % "mockito-core" % "2.23.4" % Test
"org.mockito" %% "mockito-scala" % "1.0.6" % Test
)

// Adding a resolver to the Artima maven repo, so sbt can download the Artima SuperSafe Scala compiler
Expand Down
2 changes: 1 addition & 1 deletion examples/gradle/multi-module/README.md
Expand Up @@ -8,7 +8,7 @@ It uses gradle-scoverage and sonarqube-gradle plugins and includes examples of h
To run the analysis execute the following command setting the `sonar.host.url` property to point to your SonarQube instance with installed sonar-scala plugin.

```bash
gradle --no-daemon -Dsonar.host.url=http://localhost clean reportScoverage sonarqube
gradle --no-daemon -Dsonar.host.url=http://localhost clean test reportScoverage sonarqube
```

For more configuration options please refer to SonarQube Gradle Scanner [documentation](https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner+for+Gradle).
2 changes: 2 additions & 0 deletions examples/gradle/multi-module/build.gradle
Expand Up @@ -31,6 +31,8 @@ allprojects {
sonarqube {
properties {
property "sonar.sources", "src/main/scala"
property "sonar.tests", "src/test/scala"
property "sonar.junit.reportPaths", "build/test-results/test"
property "sonar.scala.version", "2.12"
property "sonar.scala.scoverage.reportPath", "build/reports/scoverage/scoverage.xml"
}
Expand Down
2 changes: 1 addition & 1 deletion examples/gradle/single-module/README.md
Expand Up @@ -8,7 +8,7 @@ It uses gradle-scoverage and sonarqube-gradle plugins and includes examples of h
To run the analysis execute the following command setting the `sonar.host.url` property to point to your SonarQube instance with installed sonar-scala plugin.

```bash
gradle --no-daemon -Dsonar.host.url=http://localhost clean reportScoverage sonarqube
gradle --no-daemon -Dsonar.host.url=http://localhost clean test reportScoverage sonarqube
```

For more configuration options please refer to SonarQube Gradle Scanner [documentation](https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner+for+Gradle).
2 changes: 2 additions & 0 deletions examples/gradle/single-module/build.gradle
Expand Up @@ -25,6 +25,8 @@ sonarqube {
properties {
property "sonar.sourceEncoding", "UTF-8"
property "sonar.sources", "src/main/scala"
property "sonar.tests", "src/test/scala"
property "sonar.junit.reportPaths", "build/test-results/test"
property "sonar.scala.version", "2.12"
property "sonar.scala.scoverage.reportPath", "build/reports/scoverage/scoverage.xml"
}
Expand Down
3 changes: 3 additions & 0 deletions examples/mvn/multi-module/module1/pom.xml
Expand Up @@ -9,5 +9,8 @@
<artifactId>example-mvn-multi-module</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<properties>
<sonar.projectName>Module 1</sonar.projectName>
</properties>
<artifactId>mvn-multi-module-module1</artifactId>
</project>
3 changes: 3 additions & 0 deletions examples/mvn/multi-module/module2/pom.xml
Expand Up @@ -9,5 +9,8 @@
<artifactId>example-mvn-multi-module</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<properties>
<sonar.projectName>Module 2</sonar.projectName>
</properties>
<artifactId>mvn-multi-module-module2</artifactId>
</project>
4 changes: 2 additions & 2 deletions examples/mvn/multi-module/pom.xml
Expand Up @@ -23,12 +23,12 @@

<sonar.projectName>[example] Maven Multi Module</sonar.projectName>
<sonar.sources>src/main/scala</sonar.sources>
<sonar.tests>src/test/scala</sonar.tests>
<sonar.junit.reportPaths>target/surefire-reports</sonar.junit.reportPaths>
<sonar.sourceEncoding>UTF-8</sonar.sourceEncoding>
<sonar.scala.version>2.12</sonar.scala.version>
<sonar.scala.scoverage.reportPath>target/scoverage.xml</sonar.scala.scoverage.reportPath>
<sonar.modukes>module1,module2</sonar.modukes>
<module1.sonar.projectName>Module 1</module1.sonar.projectName>
<module2.sonar.projectName>Module 2</module2.sonar.projectName>
</properties>

<dependencies>
Expand Down
3 changes: 2 additions & 1 deletion examples/mvn/scala-java/pom.xml
Expand Up @@ -18,14 +18,15 @@
<sonar.projectName>[example] Maven Scala-Java</sonar.projectName>
<sonar.projectKey>example-mvn-scala-java</sonar.projectKey>
<sonar.sources>src/main/scala,src/main/java</sonar.sources>
<sonar.tests>src/test/scala,src/test/java</sonar.tests>
<sonar.junit.reportsPath>target/surefire-reports</sonar.junit.reportsPath>
<sonar.sourceEncoding>UTF-8</sonar.sourceEncoding>
<sonar.scala.version>2.12</sonar.scala.version>
<sonar.scala.scoverage.reportPath>target/scoverage.xml</sonar.scala.scoverage.reportPath>

<!-- Sonar Java properties -->
<sonar.java.binaries>target/scoverage-classes</sonar.java.binaries>
<sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin>
<sonar.junit.reportsPath>target/surefire-reports</sonar.junit.reportsPath>
<sonar.jacoco.reportPaths>target/jacoco.exec</sonar.jacoco.reportPaths>
<sonar.exclusions>src/test/**</sonar.exclusions>
</properties>
Expand Down
2 changes: 2 additions & 0 deletions examples/mvn/single-module/pom.xml
Expand Up @@ -18,6 +18,8 @@
<sonar.projectName>[example] Maven Single Module</sonar.projectName>
<sonar.projectKey>example-mvn-single-module</sonar.projectKey>
<sonar.sources>src/main/scala</sonar.sources>
<sonar.tests>src/test/scala</sonar.tests>
<sonar.junit.reportPaths>target/surefire-reports</sonar.junit.reportPaths>
<sonar.sourceEncoding>UTF-8</sonar.sourceEncoding>
<sonar.scala.version>2.12</sonar.scala.version>
<sonar.scala.scoverage.reportPath>target/scoverage.xml</sonar.scala.scoverage.reportPath>
Expand Down
1 change: 1 addition & 0 deletions examples/sbt/multi-module/sonar-project.properties
@@ -1,6 +1,7 @@
sonar.projectName=[example] SBT Multi Module
sonar.projectKey=example-sbt-multi-module
sonar.sources=src/main/scala
sonar.tests=src/test/scala
sonar.sourceEncoding=UTF-8
sonar.scala.version=2.12
sonar.scala.scoverage.reportPath=target/scala-2.12/scoverage-report/scoverage.xml
Expand Down
1 change: 1 addition & 0 deletions examples/sbt/single-module/sonar-project.properties
@@ -1,6 +1,7 @@
sonar.projectName=[example] SBT Single Module
sonar.projectKey=example-sbt-single-module
sonar.sources=src/main/scala
sonar.tests=src/test/scala
sonar.sourceEncoding=UTF-8
sonar.scala.version=2.12
sonar.scala.scoverage.reportPath=target/scala-2.12/scoverage-report/scoverage.xml
Expand Down
4 changes: 2 additions & 2 deletions examples/scan.sh
Expand Up @@ -19,12 +19,12 @@ sonar-scanner ${SONAR_SCANNER_DEFAULTS}
# Gradle single-module
echo -e "\nScanning Gradle single-module project."
cd $CWD/gradle/single-module
gradle --no-daemon ${SONAR_SCANNER_DEFAULTS} clean reportScoverage sonarqube
gradle --no-daemon ${SONAR_SCANNER_DEFAULTS} clean test reportScoverage sonarqube

# Gradle multi-module
echo -e "\nScanning Gradle multi-module project."
cd $CWD/gradle/multi-module
gradle --no-daemon ${SONAR_SCANNER_DEFAULTS} clean reportScoverage sonarqube
gradle --no-daemon ${SONAR_SCANNER_DEFAULTS} clean test reportScoverage sonarqube

# Maven single-module
echo -e "\nScanning Maven single-module project."
Expand Down
10 changes: 6 additions & 4 deletions src/main/scala/com/mwz/sonar/scala/ScalaPlugin.scala
Expand Up @@ -20,9 +20,8 @@ package com.mwz.sonar.scala

import java.nio.file.{Path, Paths}

import cats.kernel.Eq
import com.mwz.sonar.scala.util.JavaOptionals._
import com.mwz.sonar.scala.util.Log
import com.mwz.sonar.scala.util.syntax.Optionals._
import org.sonar.api.Plugin
import org.sonar.api.config.Configuration
import org.sonar.api.resources.AbstractLanguage
Expand Down Expand Up @@ -52,7 +51,7 @@ object Scala {
private val SourcesPropertyKey = "sonar.sources"
private val DefaultSourcesFolder = "src/main/scala"

private val logger = Log(classOf[Scala], "sonar-scala")
private val logger = Log(classOf[Scala])

def getScalaVersion(settings: Configuration): ScalaVersion = {
def parseVersion(s: String): Option[ScalaVersion] = s match {
Expand Down Expand Up @@ -122,7 +121,10 @@ final class ScalaPlugin extends Plugin {
// Scoverage.
classOf[scoverage.ScoverageMetrics],
classOf[scoverage.ScoverageReportParser],
classOf[scoverage.ScoverageSensor]
classOf[scoverage.ScoverageSensor],
// JUnit.
classOf[junit.JUnitReportParser],
classOf[junit.JUnitSensor]
)
}
}
Expand Up @@ -17,14 +17,13 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package com.mwz.sonar.scala
package junit

import java.io.File
import java.nio.file.{Files, Path}

trait WithFile {
def withFile(path: Path)(test: File => Any): Unit = {
val file = Files.createFile(path).toFile
try test(file)
finally Files.deleteIfExists(path)
}
}
private[junit] final case class JUnitReport(
name: String,
tests: Int,
errors: Int,
failures: Int,
skipped: Int,
time: Float
)
125 changes: 125 additions & 0 deletions src/main/scala/com/mwz/sonar/scala/junit/JUnitReportParser.scala
@@ -0,0 +1,125 @@
/*
* Sonar Scala Plugin
* Copyright (C) 2018 All contributors
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package com.mwz.sonar.scala
package junit

import java.io.File
import java.nio.file.Path

import com.mwz.sonar.scala.util.Log
import org.sonar.api.batch.ScannerSide
import org.sonar.api.batch.fs.{FilePredicate, FileSystem, InputFile}

import scala.collection.JavaConverters._
import scala.util.Try
import scala.xml.{Elem, XML}

trait JUnitReportParserAPI {

/**
* Parse JUnit report files from the given directory
* and return a map from input files to the parsed reports.
*/
def parse(tests: List[Path], directories: List[File]): Map[InputFile, JUnitReport]
}

@ScannerSide
final class JUnitReportParser(fileSystem: FileSystem) extends JUnitReportParserAPI {
private[this] val log = Log(classOf[JUnitReportParser], "junit")

def parse(tests: List[Path], directories: List[File]): Map[InputFile, JUnitReport] = {
// Get report files - xml files starting with "TEST-".
val reports: List[File] = reportFiles(directories)

// Parse report files.
val unitTestReports: List[JUnitReport] = parseReportFiles(reports)
if (unitTestReports.nonEmpty)
log.debug(s"JUnit test reports:\n${unitTestReports.mkString(", ")}")

// Convert package names into files.
resolveFiles(tests, unitTestReports)
}

/**
* Get report files - xml files starting with "TEST-".
*/
private[junit] def reportFiles(directories: List[File]): List[File] = {
val reportFiles: List[File] =
directories
.filter(_.isDirectory)
.flatMap { dir =>
// Sbt creates files without the "TEST-" prefix unlike the mvn surefire plugin.
// Also filter out the aggregate report starting with a "TESTS-" prefix.
dir.listFiles((_, name) => !name.startsWith("TESTS-") && name.endsWith(".xml"))
}

if (directories.isEmpty)
log.error(s"The paths ${directories.mkString(", ")} are not valid directories.")
else if (reportFiles.isEmpty)
log.error(s"No report files found in ${directories.mkString(", ")}.")

reportFiles
}

/**
* Parse report files.
*/
private[junit] def parseReportFiles(reports: List[File]): List[JUnitReport] =
reports.map { file =>
val xml: Elem = XML.loadFile(file)
JUnitReport(
name = xml \@ "name",
tests = Try((xml \@ "tests").toInt).toOption.getOrElse(0),
errors = Try((xml \@ "errors").toInt).toOption.getOrElse(0),
failures = Try((xml \@ "failures").toInt).toOption.getOrElse(0),
skipped = Try((xml \@ "skipped").toInt).toOption.getOrElse(0),
time = Try((xml \@ "time").toFloat).toOption.getOrElse(0)
)
}

/**
* Convert package names into files.
*/
private[junit] def resolveFiles(
tests: List[Path],
reports: List[JUnitReport]
): Map[InputFile, JUnitReport] =
reports
.groupBy(_.name)
.flatMap {
case (name, reports) =>
val path: String = name.replace(".", "/")
val files: List[Path] = tests.map(_.resolve(s"$path.scala"))
val predicates: List[FilePredicate] =
files.map(f => fileSystem.predicates.hasPath(f.toString))

val inputFiles: Iterable[InputFile] =
fileSystem
.inputFiles(
fileSystem.predicates.or(predicates.asJava)
)
.asScala

if (files.isEmpty)
log.error(s"The following files were not found: ${files.mkString(", ")}")

// Collect all of the input files.
inputFiles.flatMap(file => reports.headOption.map((file, _)))
}
}

0 comments on commit 9cd167e

Please sign in to comment.