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

JUnit report sensor #143

Merged
merged 32 commits into from
Dec 28, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
40f6c3b
WIP Unit tests sensor.
mwz Dec 19, 2018
00f8a1e
Tweak debug messages.
mwz Dec 20, 2018
657983a
Save test metrics.
mwz Dec 20, 2018
f019abe
Renamed the sensor; improved logging.
mwz Dec 20, 2018
a21ffd5
Update log tests.
mwz Dec 20, 2018
6a57a7a
Address some PR comments.
mwz Dec 21, 2018
ce6ecf8
Minor refactoring of file resolution.
mwz Dec 21, 2018
06964ad
Improved logging.
mwz Dec 21, 2018
34dfb6b
Improve logging.
mwz Dec 21, 2018
bf3686c
Update logic for filtering junit report files.
mwz Dec 21, 2018
ca4a468
Refactor utils.
mwz Dec 22, 2018
7c5e8cd
Refactor utils.
mwz Dec 22, 2018
51aa893
Add config syntax tests.
mwz Dec 22, 2018
6644a75
Add some sensor tests.
mwz Dec 22, 2018
c8d9884
Add file system syntax tests.
mwz Dec 22, 2018
4903118
Add sensor context syntax tests.
mwz Dec 23, 2018
79634e0
Add a syntax package object.
mwz Dec 23, 2018
76ba759
Refactor syntax into implicit classes.
mwz Dec 23, 2018
dd0446f
Refactor option conversions.
mwz Dec 23, 2018
08cf5b6
Add a new file system test.
mwz Dec 23, 2018
1ec5cc5
More sensor tests.
mwz Dec 23, 2018
50e6a5d
Report parser tests.
mwz Dec 23, 2018
8d4f1c8
Remove a trailing comma.
mwz Dec 24, 2018
3a5f129
Change logging level.
mwz Dec 24, 2018
bd2e976
Add sonar.tests property to the example set projects.
mwz Dec 24, 2018
e58775f
Make tweaks to filtering of junit files.
mwz Dec 24, 2018
5c4d653
Make parsing junit reports safer.
mwz Dec 24, 2018
4607cef
Update example mvn projects.
mwz Dec 24, 2018
036d17e
Update example grade projects.
mwz Dec 24, 2018
2df6b1c
Use getStringArray to get a list of paths from the config.
mwz Dec 24, 2018
b1411d7
Drop the ’sonar.junit.disable’ property (the sensor isn’t executed wi…
mwz Dec 25, 2018
e6c9f0f
Rename defaultPath to DefaultPaths.
mwz Dec 28, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/main/scala/com/mwz/sonar/scala/ScalaPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ final class ScalaPlugin extends Plugin {
classOf[scoverage.ScoverageMetrics],
classOf[scoverage.ScoverageReportParser],
classOf[scoverage.ScoverageSensor],
// Unit tests.
classOf[junit.JUnitSensor],
// JUnit.
classOf[junit.JUnitReportParser],
classOf[junit.JUnitSensor]
)
}
}
33 changes: 19 additions & 14 deletions src/main/scala/com/mwz/sonar/scala/junit/JUnitReportParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ import scala.collection.JavaConverters._
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]
}

Expand All @@ -43,16 +48,15 @@ final class JUnitReportParser(fileSystem: FileSystem) extends JUnitReportParserA

// Parse report files.
val unitTestReports: List[JUnitReport] = parseReportFiles(reports)

log.debug("Unit test reports:")
log.debug(unitTestReports.mkString(", "))
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-"
* 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 =>
Expand Down Expand Up @@ -93,24 +97,25 @@ final class JUnitReportParser(fileSystem: FileSystem) extends JUnitReportParserA
): Map[InputFile, JUnitReport] = {
reports
.groupBy(_.name)
.map {
.flatMap {
case (name, reports) =>
val filePath: String = name.replace(".", "/")
val path: String = name.replace(".", "/")
val files: List[Path] = tests.map(_.resolve(s"$path.scala"))
val predicates: List[FilePredicate] =
tests.map { path =>
fileSystem.predicates.hasPath(path.resolve(s"$filePath.scala").toString)
}
val files: Iterable[InputFile] =
files.map(f => fileSystem.predicates.hasPath(f.toString))

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

// Collect the first file here.
files.headOption.flatMap(file => reports.headOption.map((file, _)))
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, _)))
}
.collect { case Some(e) => e } // unNone
.toMap
}
}
68 changes: 44 additions & 24 deletions src/main/scala/com/mwz/sonar/scala/junit/JUnitSensor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,17 @@ import org.sonar.api.batch.fs.{FileSystem, InputFile}
import org.sonar.api.batch.sensor.{Sensor, SensorContext, SensorDescriptor}
import org.sonar.api.config.Configuration
import org.sonar.api.measures.CoreMetrics
import org.sonar.api.scan.filesystem.PathResolver

import scala.util.Try
import scala.collection.JavaConverters._

/**
* Scala JUnit sensor.
* Parses JUnit XML reports and saves test metrics.
*/
final class JUnitSensor(
untTestsReportParser: JUnitReportParserAPI,
config: Configuration,
fs: FileSystem,
pathResolver: PathResolver
fs: FileSystem // TODO: Is the injected fileSystem different from context.fileSystem?
) extends Sensor {
import JUnitSensor._ // scalastyle:ignore org.scalastyle.scalariform.ImportGroupingChecker

Expand All @@ -46,39 +48,52 @@ final class JUnitSensor(
descriptor
.name(SensorName)
.onlyOnLanguage(Scala.LanguageKey)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this sensor only works for test files.
Thus, you may add
.onlyOnFileType(InputFile.Type.TEST)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes definitely

.onlyOnFileType(InputFile.Type.TEST)
// TODO: Add a flag to disable the sensor.
Copy link
Contributor

@BalmungSan BalmungSan Dec 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// TODO: Add a flag to disable the sensor.

👍

}

override def execute(context: SensorContext): Unit = {
log.info("Initializing the Scala JUnit sensor.")

// Get the test paths.
val tests: List[Path] = fromConfig(config, TestsPropertyKey, DefaultTests)
log.debug(s"The source prefixes are: ${tests.mkString("[", ",", "]")}.")

// Get the junit report paths.
val reports: List[Path] = fromConfig(config, ReportsPropertyKey, DefaultReportPaths)
log.debug(s"The JUnit report paths are: ${reports.mkString("[", ",", "]")}.")

val inputFiles = context.fileSystem
.inputFiles(
context.fileSystem.predicates.and(
context.fileSystem.predicates.hasLanguage(Scala.LanguageKey),
context.fileSystem.predicates.hasType(InputFile.Type.TEST)
// Get test input files
val inputFiles: Iterable[InputFile] =
context.fileSystem
.inputFiles(
context.fileSystem.predicates.and(
context.fileSystem.predicates.hasLanguage(Scala.LanguageKey),
context.fileSystem.predicates.hasType(InputFile.Type.TEST)
)
)
)
.asScala

log.debug("Input test files:")
inputFiles.forEach(f => log.debug(f.toString))
if (inputFiles.nonEmpty)
log.debug(s"Input test files: \n${inputFiles.mkString(", ")}")
else
log.warn(s"No test files found for module ${context.module.key}.")

val directories: List[File] =
reports.flatMap(path => Try(pathResolver.relativeFile(fs.baseDir, path.toString)).toOption)
// TODO: Is the injected fileSystem different from context.fileSystem?
// Resolve test directories.
val testDirectories: List[File] = resolve(fs, tests)
if (testDirectories.isEmpty)
log.error(s"The following test directories were not found: ${reports.mkString(", ")}.")

if (directories.isEmpty)
log.warn(s"JUnit test report path(s) not found for ${reports.mkString(", ")}.")
else {
val parsedReports: Map[InputFile, JUnitReport] = untTestsReportParser.parse(tests, directories)
log.debug("Parsed reports:")
log.debug(parsedReports.mkString(", "))
// Resolve JUnit report directories.
val reportDirectories: List[File] = resolve(fs, reports)
if (reportDirectories.isEmpty)
log.error(s"The following JUnit test report path(s) were not found : ${reports.mkString(", ")}.")

// Save test metrics for each file.
save(context, parsedReports)
}
// Parse the reports.
val parsedReports: Map[InputFile, JUnitReport] = untTestsReportParser.parse(tests, reportDirectories)

// Save test metrics for each file.
save(context, parsedReports)
}

/**
Expand All @@ -102,6 +117,11 @@ final class JUnitSensor(
(report.time * 1000).longValue
)
}

if (reports.nonEmpty)
log.debug(s"Parsed reports:\n${reports.mkString(", ")}")
else
log.info("No test metrics were saved by this sensor.")
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/com/mwz/sonar/scala/util/Log.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ object Log {
def apply[T](clazz: Class[T], module: String): Log = Log(clazz, Some(module))
def apply[T](clazz: Class[T], module: Option[String] = None): Log = {
val log: Logger = Loggers.get(clazz)
val prefix: String = "sonar-scala" + module.map("-" + _).getOrElse("")
val prefix: String = "sonar-scala" + module.fold("")("-" + _)
new Log {
override def debug(s: String): Unit = log.debug(s"[$prefix] $s")
override def info(s: String): Unit = log.info(s"[$prefix] $s")
Expand Down
12 changes: 11 additions & 1 deletion src/main/scala/com/mwz/sonar/scala/util/util.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package com.mwz.sonar.scala
package util

import java.io.File
import java.nio.file.{Path, Paths}
import java.util.Optional

Expand All @@ -27,7 +28,8 @@ import org.sonar.api.batch.measure.Metric
import org.sonar.api.batch.sensor.SensorContext
import org.sonar.api.config.Configuration

import scala.language.implicitConversions
import scala.language.{higherKinds, implicitConversions}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where/Why exactly do we need higherKinds here ?

import scala.util.Try

/**
* Scala.Option <-> Java.Optional conversions.
Expand Down Expand Up @@ -94,6 +96,14 @@ object PathUtils {
val currentWorkdirAbsolutePath = PathUtils.cwd
currentWorkdirAbsolutePath.relativize(moduleAbsolutePath)
}

/**
* Resolve a list of paths relative to the given file system.
*/
def resolve(fs: FileSystem, toResolve: List[Path]): List[File] =
toResolve.flatMap { path =>
Try(fs.resolvePath(path.toString)).toOption
}
}

object MetricUtils {
Expand Down
5 changes: 5 additions & 0 deletions src/test/scala/com/mwz/sonar/scala/ScalaPluginSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,9 @@ class ScalaPluginSpec extends FlatSpec with Matchers {
assert(context.getExtensions.contains(classOf[scoverage.ScoverageReportParser]))
assert(context.getExtensions.contains(classOf[scoverage.ScoverageSensor]))
}

it should "provide junit sensor" in {
assert(context.getExtensions.contains(classOf[junit.JUnitReportParser]))
assert(context.getExtensions.contains(classOf[junit.JUnitSensor]))
}
}