From 8b78a992f85d583f211ff69eef155cf2999003c7 Mon Sep 17 00:00:00 2001 From: Michael Zinsmaier Date: Tue, 20 Oct 2015 00:23:42 +0200 Subject: [PATCH 1/7] Removed unused class The stub parser is not used anymore, removed it --- .../xml/StubScoverageReportParser.scala | 62 ------------------- 1 file changed, 62 deletions(-) delete mode 100644 plugin/src/main/scala/com/buransky/plugins/scoverage/xml/StubScoverageReportParser.scala diff --git a/plugin/src/main/scala/com/buransky/plugins/scoverage/xml/StubScoverageReportParser.scala b/plugin/src/main/scala/com/buransky/plugins/scoverage/xml/StubScoverageReportParser.scala deleted file mode 100644 index adcb548..0000000 --- a/plugin/src/main/scala/com/buransky/plugins/scoverage/xml/StubScoverageReportParser.scala +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Sonar Scoverage Plugin - * Copyright (C) 2013 Rado Buransky - * dev@sonar.codehaus.org - * - * 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 02 - */ -package com.buransky.plugins.scoverage.xml - -import com.buransky.plugins.scoverage._ -import com.buransky.plugins.scoverage.ProjectStatementCoverage -import com.buransky.plugins.scoverage.CoveredStatement -import com.buransky.plugins.scoverage.StatementPosition -import com.buransky.plugins.scoverage.FileStatementCoverage -import com.buransky.plugins.scoverage.DirectoryStatementCoverage -import scala.io.Source - -/** - * Stub with some dummy data so that we don't have to parse XML for testing. - * - * @author Rado Buransky - */ -class StubScoverageReportParser extends ScoverageReportParser { - def parse(reportFilePath: String): ProjectStatementCoverage = { - val errorCodeFile = FileStatementCoverage("ErrorCode.scala", 17, 13, - List(simpleStatement(10, 2), simpleStatement(11, 0), - simpleStatement(25, 1))) - - val graphFile = FileStatementCoverage("Graph.scala", 42, 0, - List(simpleStatement(33, 0), simpleStatement(3, 1), simpleStatement(1, 0), simpleStatement(2, 2))) - - val file2 = FileStatementCoverage("file2.scala", 2, 1, Nil) - val bbbDir = DirectoryStatementCoverage("bbb", Seq(file2)) - - val file1 = FileStatementCoverage("file1.scala", 100, 33, Nil) - val aaaDir = DirectoryStatementCoverage("aaa", Seq(file1, errorCodeFile, graphFile, bbbDir)) - - val project = ProjectStatementCoverage("project", Seq(aaaDir)) - - project - } - - def parse(source: Source): ProjectStatementCoverage = { - ProjectStatementCoverage("x", Nil) - } - - private def simpleStatement(line: Int, hitCount: Int): CoveredStatement = - CoveredStatement(StatementPosition(line, 0), StatementPosition(line, 0), hitCount) - -} From 6d1afc970a095ffd4ed3abb27e3cf9673ef06106 Mon Sep 17 00:00:00 2001 From: Michael Zinsmaier Date: Tue, 20 Oct 2015 21:43:24 +0200 Subject: [PATCH 2/7] rewrote PathUtil to work with file.getParentFile Justification: relying on getParentFile gets the path splitting working under windows and unix - adapted the tests to the new behavior - added a windows / unix switch in the tests to get them working on both systems PathUtils now converts: - the empty path to an empty List - an absolute path to a list folders (not including the drive name under windows) - a relative path to a list of folders --- .../plugins/scoverage/util/PathUtil.scala | 21 +++++++++---- .../plugins/scoverage/util/PathUtilSpec.scala | 31 ++++++++++++------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/plugin/src/main/scala/com/buransky/plugins/scoverage/util/PathUtil.scala b/plugin/src/main/scala/com/buransky/plugins/scoverage/util/PathUtil.scala index d4a8938..1981fcf 100644 --- a/plugin/src/main/scala/com/buransky/plugins/scoverage/util/PathUtil.scala +++ b/plugin/src/main/scala/com/buransky/plugins/scoverage/util/PathUtil.scala @@ -20,15 +20,24 @@ package com.buransky.plugins.scoverage.util import java.io.File +import scala.Iterator /** * File path helper. * * @author Rado Buransky */ object PathUtil { - def splitPath(filePath: String, separator: String = File.separator): List[String] = - filePath.split(separator.replaceAllLiterally("\\", "\\\\")).toList match { - case "" :: tail if tail.nonEmpty => separator :: tail - case other => other - } -} + + def splitPath(filePath: String): List[String] = { + new FileParentIterator(new File(filePath)).toList.reverse + } + + class FileParentIterator(private var f: File) extends Iterator[String] { + def hasNext: Boolean = f != null && !f.getName().isEmpty() + def next(): String = { + val name = f.getName() + f = f.getParentFile + name + } + } +} \ No newline at end of file diff --git a/plugin/src/test/scala/com/buransky/plugins/scoverage/util/PathUtilSpec.scala b/plugin/src/test/scala/com/buransky/plugins/scoverage/util/PathUtilSpec.scala index bae563d..4f095ef 100644 --- a/plugin/src/test/scala/com/buransky/plugins/scoverage/util/PathUtilSpec.scala +++ b/plugin/src/test/scala/com/buransky/plugins/scoverage/util/PathUtilSpec.scala @@ -24,23 +24,30 @@ import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner @RunWith(classOf[JUnitRunner]) -class UnixPathUtilSpec extends ParamPathUtilSpec("Unix", "/") - -@RunWith(classOf[JUnitRunner]) -class WindowsPathUtilSpec extends ParamPathUtilSpec("Windows", "\\") - -abstract class ParamPathUtilSpec(osName: String, separator: String) extends FlatSpec with Matchers { +class PathUtilSpec extends FlatSpec with Matchers { + + val osName = System.getProperty("os.name") + val separator = System.getProperty("file.separator") + behavior of s"splitPath for $osName" - - it should "work for empty path" in { - PathUtil.splitPath("", separator) should equal(List("")) + + it should "ignore the empty path" in { + PathUtil.splitPath("") should equal(List.empty[String]) } - it should "work with separator at the beginning" in { - PathUtil.splitPath(s"${separator}a", separator) should equal(List(separator, "a")) + it should "ignore a separator at the beginning" in { + PathUtil.splitPath(s"${separator}a") should equal(List("a")) } it should "work with separator in the middle" in { - PathUtil.splitPath(s"a${separator}b", separator) should equal(List("a", "b")) + PathUtil.splitPath(s"a${separator}b") should equal(List("a", "b")) + } + + it should "work with an OS dependent absolute path" in { + if (osName.startsWith("Windows")) { + PathUtil.splitPath("C:\\test\\2") should equal(List("test", "2")) + } else { + PathUtil.splitPath("/test/2") should equal(List("test", "2")) + } } } \ No newline at end of file From 1209dfaff0bd514e732d3bac2b942e325e766921 Mon Sep 17 00:00:00 2001 From: Michael Zinsmaier Date: Tue, 20 Oct 2015 22:19:19 +0200 Subject: [PATCH 3/7] Switched to source directory relative pathes, sanitizing reported pathes Justification: - sanitizing the reported file pathes to a common format makes the sensor simpler - it is a precondition for implementing proper directory coverage - tests run now under unix and windows, easier to develop The sonar.sources property can be read from the settings. By mapping all reported pathes (absolute / base dir relative / source dir relative) against the actual file tree it is possible to converte all of them to the same source dir relative format. For instance /home/src/main/scala/folder/test0.scala => folder/test0.scala folder/test1.scala => folder/test1.scala /src/main/scala/test2.scala => test2.scala ... --- .../scoverage/ScoverageReportParser.scala | 4 +- .../BruteForceSequenceMatcher.scala | 86 +++++++++++++++++++ .../scoverage/pathcleaner/PathSanitizer.scala | 34 ++++++++ .../scoverage/sensor/ScoverageSensor.scala | 28 ++++-- ...XmlScoverageReportConstructingParser.scala | 56 +++++++----- .../xml/XmlScoverageReportParser.scala | 5 +- .../sensor/ScoverageSensorSpec.scala | 10 +-- ...coverageReportConstructingParserSpec.scala | 31 +++++-- .../xml/XmlScoverageReportParserSpec.scala | 6 +- 9 files changed, 212 insertions(+), 48 deletions(-) create mode 100644 plugin/src/main/scala/com/buransky/plugins/scoverage/pathcleaner/BruteForceSequenceMatcher.scala create mode 100644 plugin/src/main/scala/com/buransky/plugins/scoverage/pathcleaner/PathSanitizer.scala diff --git a/plugin/src/main/scala/com/buransky/plugins/scoverage/ScoverageReportParser.scala b/plugin/src/main/scala/com/buransky/plugins/scoverage/ScoverageReportParser.scala index b3c84c6..72d40d0 100644 --- a/plugin/src/main/scala/com/buransky/plugins/scoverage/ScoverageReportParser.scala +++ b/plugin/src/main/scala/com/buransky/plugins/scoverage/ScoverageReportParser.scala @@ -19,13 +19,15 @@ */ package com.buransky.plugins.scoverage +import com.buransky.plugins.scoverage.pathcleaner.PathSanitizer + /** * Interface for Scoverage report parser. * * @author Rado Buransky */ trait ScoverageReportParser { - def parse(reportFilePath: String): ProjectStatementCoverage + def parse(reportFilePath: String, pathSanitizer: PathSanitizer): ProjectStatementCoverage } /** diff --git a/plugin/src/main/scala/com/buransky/plugins/scoverage/pathcleaner/BruteForceSequenceMatcher.scala b/plugin/src/main/scala/com/buransky/plugins/scoverage/pathcleaner/BruteForceSequenceMatcher.scala new file mode 100644 index 0000000..5acd4f7 --- /dev/null +++ b/plugin/src/main/scala/com/buransky/plugins/scoverage/pathcleaner/BruteForceSequenceMatcher.scala @@ -0,0 +1,86 @@ +/* + * Sonar Scoverage Plugin + * Copyright (C) 2013 Rado Buransky + * dev@sonar.codehaus.org + * + * 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 02 + */ +package com.buransky.plugins.scoverage.pathcleaner + +import java.io.File +import org.apache.commons.io.FileUtils +import org.apache.commons.io.FileUtils +import BruteForceSequenceMatcher._ +import com.buransky.plugins.scoverage.util.PathUtil +import scala.collection.JavaConversions._ +import org.sonar.api.utils.log.Loggers + +object BruteForceSequenceMatcher { + + val extensions = Array[String]("java", "scala") + + type PathSeq = Seq[String] +} + +/** + * Helper that allows to convert a report path into a source folder relative path by testing it against + * the tree of source files. + * + * Assumes that all report paths of a given report have a common root. Dependent of the scoverage + * report this root is either something outside the actual project (absolute path), the base dir of the project + * (report path relative to base dir) or some sub folder of the project. + * + * By reverse mapping a report path against the tree of all file children of the source folder the correct filesystem file + * can be found and the report path can be converted into a source dir relative path. * + * + * @author Michael Zinsmaier + */ +class BruteForceSequenceMatcher(baseDir: File, sourcePath: String) extends PathSanitizer { + + private val sourceDir = initSourceDir() + require(sourceDir.isAbsolute) + require(sourceDir.isDirectory) + + private val log = Loggers.get(classOf[BruteForceSequenceMatcher]) + private val sourcePathLength = PathUtil.splitPath(sourceDir.getAbsolutePath).size + private val filesMap = initFilesMap() + + + def getSourceRelativePath(reportPath: PathSeq): Option[PathSeq] = { + // match with file system map of files + val relPathOption = for { + absPathCandidates <- filesMap.get(reportPath.last) + path <- absPathCandidates.find(absPath => absPath.endsWith(reportPath)) + } yield path.drop(sourcePathLength) + + relPathOption + } + + // mock able helpers that allow us to remove the dependency to the real file system during tests + + private[pathcleaner] def initSourceDir(): File = { + val sourceDir = new File(baseDir, sourcePath) + sourceDir + } + + private[pathcleaner] def initFilesMap(): Map[String, Seq[PathSeq]] = { + val srcFiles = FileUtils.iterateFiles(sourceDir, extensions, true) + val paths = srcFiles.map(file => PathUtil.splitPath(file.getAbsolutePath)).toSeq + + // group them by filename, in case multiple files have the same name + paths.groupBy(path => path.last) + } + +} \ No newline at end of file diff --git a/plugin/src/main/scala/com/buransky/plugins/scoverage/pathcleaner/PathSanitizer.scala b/plugin/src/main/scala/com/buransky/plugins/scoverage/pathcleaner/PathSanitizer.scala new file mode 100644 index 0000000..d4a1b79 --- /dev/null +++ b/plugin/src/main/scala/com/buransky/plugins/scoverage/pathcleaner/PathSanitizer.scala @@ -0,0 +1,34 @@ +/* + * Sonar Scoverage Plugin + * Copyright (C) 2013 Rado Buransky + * dev@sonar.codehaus.org + * + * 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 02 + */ +package com.buransky.plugins.scoverage.pathcleaner + +/** + * @author Michael Zinsmaier + */ +trait PathSanitizer { + + /** tries to convert the given path such that it is relative to the + * projects/modules source directory. + * + * @return Some(source folder relative path) or None if the path cannot be converted + */ + def getSourceRelativePath(path: Seq[String]): Option[Seq[String]] + +} \ No newline at end of file diff --git a/plugin/src/main/scala/com/buransky/plugins/scoverage/sensor/ScoverageSensor.scala b/plugin/src/main/scala/com/buransky/plugins/scoverage/sensor/ScoverageSensor.scala index bad6f98..e488410 100644 --- a/plugin/src/main/scala/com/buransky/plugins/scoverage/sensor/ScoverageSensor.scala +++ b/plugin/src/main/scala/com/buransky/plugins/scoverage/sensor/ScoverageSensor.scala @@ -35,6 +35,8 @@ import org.sonar.api.scan.filesystem.PathResolver import org.sonar.api.utils.log.Loggers import scala.collection.JavaConversions._ +import com.buransky.plugins.scoverage.pathcleaner.BruteForceSequenceMatcher +import com.buransky.plugins.scoverage.pathcleaner.PathSanitizer /** * Main sensor for importing Scoverage report to Sonar. @@ -53,7 +55,16 @@ class ScoverageSensor(settings: Settings, pathResolver: PathResolver, fileSystem scoverageReportPath match { case Some(reportPath) => // Single-module project - processProject(scoverageReportParser.parse(reportPath), project, context) + val srcOption = Option(settings.getString(project.getName() + ".sonar.sources")) + val sonarSources = srcOption match { + case Some(src) => src + case None => { + log.warn(s"could not find settings key ${project.getName()}.sonar.sources assuming src/main/scala.") + "src/main/scala" + } + } + val pathSanitizer = createPathSanitizer(sonarSources) + processProject(scoverageReportParser.parse(reportPath, pathSanitizer), project, context, sonarSources) case None => // Multi-module project has report path set for each module individually @@ -63,6 +74,9 @@ class ScoverageSensor(settings: Settings, pathResolver: PathResolver, fileSystem override val toString = getClass.getSimpleName + protected def createPathSanitizer(sonarSources: String): PathSanitizer + = new BruteForceSequenceMatcher(fileSystem.baseDir(), sonarSources) + private lazy val scoverageReportPath: Option[String] = { settings.getString(SCOVERAGE_REPORT_PATH_PROPERTY) match { case null => None @@ -127,14 +141,14 @@ class ScoverageSensor(settings: Settings, pathResolver: PathResolver, fileSystem } } - private def processProject(projectCoverage: ProjectStatementCoverage, project: Project, context: SensorContext) { + private def processProject(projectCoverage: ProjectStatementCoverage, project: Project, context: SensorContext, sonarSources: String) { // Save measures saveMeasures(context, project, projectCoverage) log.info(LogUtil.f("Statement coverage for " + project.getKey + " is " + ("%1.2f" format projectCoverage.rate))) // Process children - processChildren(projectCoverage.children, context, "") + processChildren(projectCoverage.children, context, sonarSources) } private def processDirectory(directoryCoverage: DirectoryStatementCoverage, context: SensorContext, @@ -147,9 +161,8 @@ class ScoverageSensor(settings: Settings, pathResolver: PathResolver, fileSystem val path = appendFilePath(directory, fileCoverage.name) val p = fileSystem.predicates() - val pathPredicate = if (new io.File(path).isAbsolute) p.hasAbsolutePath(path) else p.matchesPathPattern("**/" + path) val files = fileSystem.inputFiles(p.and( - pathPredicate, + p.hasRelativePath(path), p.hasLanguage(scala.getKey), p.hasType(InputFile.Type.MAIN))).toList @@ -164,10 +177,7 @@ class ScoverageSensor(settings: Settings, pathResolver: PathResolver, fileSystem saveLineCoverage(fileCoverage.statements, scalaSourceFile, context) case None => { - fileSystem.inputFiles(p.all()).foreach { inputFile => - log.debug(inputFile.absolutePath()) - } - log.warn(s"File not found in file system! [$pathPredicate]") + log.warn(s"File not found in file system! [$path]") } } } diff --git a/plugin/src/main/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportConstructingParser.scala b/plugin/src/main/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportConstructingParser.scala index d597aa7..77390db 100644 --- a/plugin/src/main/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportConstructingParser.scala +++ b/plugin/src/main/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportConstructingParser.scala @@ -30,13 +30,14 @@ import scala.collection.mutable import scala.io.Source import scala.xml.parsing.ConstructingParser import scala.xml.{MetaData, NamespaceBinding, Text} +import com.buransky.plugins.scoverage.pathcleaner.PathSanitizer /** * Scoverage XML parser based on ConstructingParser provided by standard Scala library. * * @author Rado Buransky */ -class XmlScoverageReportConstructingParser(source: Source) extends ConstructingParser(source, false) { +class XmlScoverageReportConstructingParser(source: Source, pathSanitizer: PathSanitizer) extends ConstructingParser(source, false) { private val log = Loggers.get(classOf[XmlScoverageReportConstructingParser]) private val CLASS_ELEMENT = "class" @@ -163,7 +164,7 @@ class XmlScoverageReportConstructingParser(source: Source) extends ConstructingP val files = fileStatementCoverage(statementsInFile) // Transform file paths to chain of case classes - val chained = files.map(fsc => pathToChain(fsc._1, fsc._2)) + val chained = files.map(fsc => pathToChain(fsc._1, fsc._2)).flatten // Merge chains into one tree val root = DirOrFile("", Nil, None) @@ -173,31 +174,42 @@ class XmlScoverageReportConstructingParser(source: Source) extends ConstructingP root.toProjectStatementCoverage } - private def pathToChain(filePath: String, coverage: FileStatementCoverage): DirOrFile = { + private def pathToChain(filePath: String, coverage: FileStatementCoverage): Option[DirOrFile] = { + // helper + def convertToDirOrFile(relPath: Seq[String]) = { + // Get directories + val dirs = for (i <- 0 to relPath.length - 2) + yield DirOrFile(relPath(i), Nil, None) + + // Chain directories + for (i <- 0 to dirs.length - 2) + dirs(i).children = List(dirs(i + 1)) + + // Get file + val file = DirOrFile(relPath(relPath.length - 1).toString, Nil, Some(coverage)) + + if (dirs.isEmpty) { + // File in root dir + file + } else { + // Append file + dirs.last.children = List(file) + dirs.head + } + } + + // processing val path = PathUtil.splitPath(filePath) if (path.length < 1) throw new ScoverageException("Path cannot be empty!") - // Get directories - val dirs = for (i <- 0 to path.length - 2) - yield DirOrFile(path(i), Nil, None) - - // Chain directories - for (i <- 0 to dirs.length - 2) - dirs(i).children = List(dirs(i + 1)) - - // Get file - val file = DirOrFile(path(path.length - 1).toString, Nil, Some(coverage)) - - if (dirs.isEmpty) { - // File in root dir - file - } - else { - // Append file - dirs.last.children = List(file) - dirs.head + pathSanitizer.getSourceRelativePath(path) match { + case Some(relPath) => Some(convertToDirOrFile(relPath)) + case None => { + log.warn(s"skipping file coverage results for $path, was not able to retrieve the file in the configured source dir") + None + } } } diff --git a/plugin/src/main/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportParser.scala b/plugin/src/main/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportParser.scala index b5f1c5a..fe2036a 100644 --- a/plugin/src/main/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportParser.scala +++ b/plugin/src/main/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportParser.scala @@ -24,6 +24,7 @@ import com.buransky.plugins.scoverage.{ProjectStatementCoverage, ScoverageExcept import org.sonar.api.utils.log.Loggers import scala.io.Source +import com.buransky.plugins.scoverage.pathcleaner.PathSanitizer /** * Bridge between parser implementation and coverage provider. @@ -33,13 +34,13 @@ import scala.io.Source class XmlScoverageReportParser extends ScoverageReportParser { private val log = Loggers.get(classOf[XmlScoverageReportParser]) - def parse(reportFilePath: String): ProjectStatementCoverage = { + def parse(reportFilePath: String, pathSanitizer: PathSanitizer): ProjectStatementCoverage = { require(reportFilePath != null) require(!reportFilePath.trim.isEmpty) log.debug(LogUtil.f("Reading report. [" + reportFilePath + "]")) - val parser = new XmlScoverageReportConstructingParser(sourceFromFile(reportFilePath)) + val parser = new XmlScoverageReportConstructingParser(sourceFromFile(reportFilePath), pathSanitizer) parser.parse() } diff --git a/plugin/src/test/scala/com/buransky/plugins/scoverage/sensor/ScoverageSensorSpec.scala b/plugin/src/test/scala/com/buransky/plugins/scoverage/sensor/ScoverageSensorSpec.scala index 1e0c2ec..8748cda 100644 --- a/plugin/src/test/scala/com/buransky/plugins/scoverage/sensor/ScoverageSensorSpec.scala +++ b/plugin/src/test/scala/com/buransky/plugins/scoverage/sensor/ScoverageSensorSpec.scala @@ -36,6 +36,8 @@ import org.sonar.api.resources.Project.AnalysisType import org.sonar.api.scan.filesystem.PathResolver import scala.collection.JavaConversions._ +import com.buransky.plugins.scoverage.pathcleaner.PathSanitizer +import org.mockito.Matchers.any @RunWith(classOf[JUnitRunner]) @@ -94,15 +96,12 @@ class ScoverageSensorSpec extends FlatSpec with Matchers with MockitoSugar { when(settings.getString(SCOVERAGE_REPORT_PATH_PROPERTY)).thenReturn(pathToScoverageReport) when(fileSystem.baseDir).thenReturn(moduleBaseDir) when(fileSystem.predicates).thenReturn(filePredicates) - when(fileSystem.inputFiles(org.mockito.Matchers.any[FilePredicate]())).thenReturn(Nil) + when(fileSystem.inputFiles(any[FilePredicate]())).thenReturn(Nil) when(pathResolver.relativeFile(moduleBaseDir, pathToScoverageReport)).thenReturn(reportFile) - when(scoverageReportParser.parse(reportAbsolutePath)).thenReturn(projectStatementCoverage) + when(scoverageReportParser.parse(any[String](), any[PathSanitizer]())).thenReturn(projectStatementCoverage) // Execute analyse(project, context) - - verify(filePredicates).hasAbsolutePath("/home/a.scala") - verify(filePredicates).matchesPathPattern("**/x/b.scala") } class AnalyseScoverageSensorScope extends ScoverageSensorScope { @@ -110,6 +109,7 @@ class ScoverageSensorSpec extends FlatSpec with Matchers with MockitoSugar { val context = new TestSensorContext override protected lazy val scoverageReportParser = mock[ScoverageReportParser] + override protected def createPathSanitizer(sonarSources: String) = mock[PathSanitizer] } class ScoverageSensorScope extends { diff --git a/plugin/src/test/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportConstructingParserSpec.scala b/plugin/src/test/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportConstructingParserSpec.scala index aa4325e..3244847 100644 --- a/plugin/src/test/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportConstructingParserSpec.scala +++ b/plugin/src/test/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportConstructingParserSpec.scala @@ -26,33 +26,52 @@ import scala.io.Source import com.buransky.plugins.scoverage.xml.data.XmlReportFile1 import scala._ import com.buransky.plugins.scoverage.{ProjectStatementCoverage, FileStatementCoverage, DirectoryStatementCoverage} +import com.buransky.plugins.scoverage.pathcleaner.PathSanitizer @RunWith(classOf[JUnitRunner]) class XmlScoverageReportConstructingParserSpec extends FlatSpec with Matchers { behavior of "parse source" it must "parse old broken Scoverage 0.95 file correctly" in { - assertReportFile(XmlReportFile1.scoverage095Data, 24.53)(assertScoverage095Data) + val sanitizer = new PathSanitizer() { + def getSourceRelativePath(path: Seq[String]): Option[Seq[String]] = { + // do nothing + Some(path) + } + } + assertReportFile(XmlReportFile1.scoverage095Data, 24.53, sanitizer)(assertScoverage095Data) } it must "parse new fixed Scoverage 1.0.4 file correctly" in { - assertReportFile(XmlReportFile1.scoverage104Data, 50.0) { projectCoverage => + val sanitizer = new PathSanitizer() { + def getSourceRelativePath(path: Seq[String]): Option[Seq[String]] = { + // drop first 6 = /a1b2c3/workspace/sonar-test/src/main/scala + Some(path.drop(6)) + } + } + assertReportFile(XmlReportFile1.scoverage104Data, 50.0, sanitizer) { projectCoverage => assert(projectCoverage.name === "") assert(projectCoverage.children.size.toInt === 1) projectCoverage.children.head match { case rootDir: DirectoryStatementCoverage => - assert(rootDir.name == "a1b2c3") + assert(rootDir.name == "com") case other => fail(s"This is not a directory statement coverage! [$other]") } } } it must "parse file1 correctly even without XML declaration" in { - assertReportFile(XmlReportFile1.dataWithoutDeclaration, 24.53)(assertScoverage095Data) + val sanitizer = new PathSanitizer() { + def getSourceRelativePath(path: Seq[String]): Option[Seq[String]] = { + // do nothing + Some(path) + } + } + assertReportFile(XmlReportFile1.dataWithoutDeclaration, 24.53, sanitizer)(assertScoverage095Data) } - private def assertReportFile(data: String, expectedCoverage: Double)(f: (ProjectStatementCoverage) => Unit) { - val parser = new XmlScoverageReportConstructingParser(Source.fromString(data)) + private def assertReportFile(data: String, expectedCoverage: Double, pathSanitizer: PathSanitizer)(f: (ProjectStatementCoverage) => Unit) { + val parser = new XmlScoverageReportConstructingParser(Source.fromString(data), pathSanitizer) val projectCoverage = parser.parse() // Assert coverage diff --git a/plugin/src/test/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportParserSpec.scala b/plugin/src/test/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportParserSpec.scala index c049737..5d457c7 100644 --- a/plugin/src/test/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportParserSpec.scala +++ b/plugin/src/test/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportParserSpec.scala @@ -29,14 +29,14 @@ class XmlScoverageReportParserSpec extends FlatSpec with Matchers { behavior of "parse file path" it must "fail for null path" in { - the[IllegalArgumentException] thrownBy XmlScoverageReportParser().parse(null.asInstanceOf[String]) + the[IllegalArgumentException] thrownBy XmlScoverageReportParser().parse(null.asInstanceOf[String], null) } it must "fail for empty path" in { - the[IllegalArgumentException] thrownBy XmlScoverageReportParser().parse("") + the[IllegalArgumentException] thrownBy XmlScoverageReportParser().parse("", null) } it must "fail for not existing path" in { - the[ScoverageException] thrownBy XmlScoverageReportParser().parse("/x/a/b/c/1/2/3/4.xml") + the[ScoverageException] thrownBy XmlScoverageReportParser().parse("/x/a/b/c/1/2/3/4.xml", null) } } From f6654ce3aecc476b06206ab5a945f3be851c3041 Mon Sep 17 00:00:00 2001 From: Michael Zinsmaier Date: Tue, 20 Oct 2015 22:27:25 +0200 Subject: [PATCH 4/7] added some tests for the brute force path sanitizer - testing absolute report file paths - testing base dir relative report file paths - testing source dir relative report file paths --- .../BruteForceSequenceMatcherSpec.scala | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 plugin/src/test/scala/com/buransky/plugins/scoverage/pathcleaner/BruteForceSequenceMatcherSpec.scala diff --git a/plugin/src/test/scala/com/buransky/plugins/scoverage/pathcleaner/BruteForceSequenceMatcherSpec.scala b/plugin/src/test/scala/com/buransky/plugins/scoverage/pathcleaner/BruteForceSequenceMatcherSpec.scala new file mode 100644 index 0000000..5fec3f6 --- /dev/null +++ b/plugin/src/test/scala/com/buransky/plugins/scoverage/pathcleaner/BruteForceSequenceMatcherSpec.scala @@ -0,0 +1,117 @@ +/* + * Sonar Scoverage Plugin + * Copyright (C) 2013 Rado Buransky + * dev@sonar.codehaus.org + * + * 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 02 + */ +package com.buransky.plugins.scoverage.pathcleaner + +import org.junit.runner.RunWith +import org.scalatest.mock.MockitoSugar +import org.scalatest.junit.JUnitRunner +import org.scalatest.FlatSpec +import com.buransky.plugins.scoverage.pathcleaner.BruteForceSequenceMatcher.PathSeq +import org.scalatest.Matchers +import java.io.File +import org.mockito.Mockito._ + +@RunWith(classOf[JUnitRunner]) +class BruteForceSequenceMatcherSpec extends FlatSpec with Matchers with MockitoSugar { + + // file-map of all files under baseDir/sonar.sources organized by their filename + val filesMap: Map[String, Seq[PathSeq]] = Map ( + "rootTestFile.scala" -> List(List("testProject", "main", "rootTestFile.scala")), + "nestedTestFile.scala" -> List(List("testProject", "main", "some", "folders", "nestedTestFile.scala")), + "multiFile.scala" -> List( + List("testProject", "main", "some", "multiFile.scala"), + List("testProject", "main", "some", "folder", "multiFile.scala") + ) + ) + + // baseDir = testProject sonar.sources = main + val testee = new BruteForceSequenceMatcherTestee("/testProject/main", filesMap) + + + + behavior of "BruteForceSequenceMatcher with absolute report filenames" + + it should "provide just the filename for top level files" in { + testee.getSourceRelativePath(List("testProject", "main", "rootTestFile.scala")).get shouldEqual List("rootTestFile.scala") + } + + it should "provide the filename and the folders for nested files" in { + testee.getSourceRelativePath(List("testProject", "main", "some", "folders", "nestedTestFile.scala")).get shouldEqual List("some", "folders", "nestedTestFile.scala") + } + + it should "find the correct file if multiple files with same name exist" in { + testee.getSourceRelativePath(List("testProject", "main", "some", "multiFile.scala")).get shouldEqual List("some", "multiFile.scala") + testee.getSourceRelativePath(List("testProject", "main", "some", "folder", "multiFile.scala")).get shouldEqual List("some", "folder", "multiFile.scala") + } + + + + + behavior of "BruteForceSequenceMatcher with filenames relative to the base dir" + + it should "provide just the filename for top level files" in { + testee.getSourceRelativePath(List("main", "rootTestFile.scala")).get shouldEqual List("rootTestFile.scala") + } + + it should "provide the filename and the folders for nested files" in { + testee.getSourceRelativePath(List("main", "some", "folders", "nestedTestFile.scala")).get shouldEqual List("some", "folders", "nestedTestFile.scala") + } + + it should "find the correct file if multiple files with same name exist" in { + testee.getSourceRelativePath(List("main", "some", "multiFile.scala")).get shouldEqual List("some", "multiFile.scala") + testee.getSourceRelativePath(List("main", "some", "folder", "multiFile.scala")).get shouldEqual List("some", "folder", "multiFile.scala") + } + + + + + behavior of "BruteForceSequenceMatcher with filenames relative to the src dir" + + it should "provide just the filename for top level files" in { + testee.getSourceRelativePath(List("rootTestFile.scala")).get shouldEqual List("rootTestFile.scala") + } + + it should "provide the filename and the folders for nested files" in { + testee.getSourceRelativePath(List("some", "folders", "nestedTestFile.scala")).get shouldEqual List("some", "folders", "nestedTestFile.scala") + } + + it should "find the correct file if multiple files with same name exist" in { + testee.getSourceRelativePath(List("some", "multiFile.scala")).get shouldEqual List("some", "multiFile.scala") + testee.getSourceRelativePath(List("some", "folder", "multiFile.scala")).get shouldEqual List("some", "folder", "multiFile.scala") + } + + + + + class BruteForceSequenceMatcherTestee(absoluteSrcPath: String, filesMap: Map[String, Seq[PathSeq]]) + extends BruteForceSequenceMatcher(mock[File], "") { + + def srcDir = { + val dir = mock[File] + when(dir.isAbsolute).thenReturn(true) + when(dir.isDirectory).thenReturn(true) + when(dir.getAbsolutePath).thenReturn(absoluteSrcPath) + dir + } + + override private[pathcleaner] def initSourceDir(): File = srcDir + override private[pathcleaner] def initFilesMap(): Map[String, Seq[PathSeq]] = filesMap + } +} From ba7f4616513ff0d269537167a7e25f371ce1d599 Mon Sep 17 00:00:00 2001 From: Michael Zinsmaier Date: Sun, 18 Oct 2015 18:07:26 +0200 Subject: [PATCH 5/7] Added directory coverage values for better treemap support The SonarQube treemap widgets work better if metrics exist for the project, the directory and the file level. Project coverage is defined as sum of ALL children Directory coverage is defined as sum of the DIRECT children The directories have to be treated special because the SonarQube treemap displays all directories on the same level (not as a tree). Using full sums would hide bad coverage results in some cases. --- .../plugins/scoverage/StatementCoverage.scala | 34 +++++++---- .../scoverage/sensor/ScoverageSensor.scala | 60 +++++++++++++------ ...XmlScoverageReportConstructingParser.scala | 3 +- 3 files changed, 66 insertions(+), 31 deletions(-) diff --git a/plugin/src/main/scala/com/buransky/plugins/scoverage/StatementCoverage.scala b/plugin/src/main/scala/com/buransky/plugins/scoverage/StatementCoverage.scala index e8643b2..49ec40c 100644 --- a/plugin/src/main/scala/com/buransky/plugins/scoverage/StatementCoverage.scala +++ b/plugin/src/main/scala/com/buransky/plugins/scoverage/StatementCoverage.scala @@ -38,12 +38,12 @@ sealed trait StatementCoverage { /** * Total number of all statements within the source code unit, */ - val statementCount: Int + def statementCount: Int /** * Number of statements covered by unit tests. */ - val coveredStatementsCount: Int + def coveredStatementsCount: Int require(statementCount >= 0, "Statements count cannot be negative! [" + statementCount + "]") require(coveredStatementsCount >= 0, "Statements count cannot be negative! [" + @@ -57,29 +57,43 @@ sealed trait StatementCoverage { * Allows to build tree structure from state coverage values. */ trait NodeStatementCoverage extends StatementCoverage { - val children: Iterable[StatementCoverage] - val statementCount = children.map(_.statementCount).sum - val coveredStatementsCount = children.map(_.coveredStatementsCount).sum + def name: String + def children: Iterable[NodeStatementCoverage] + def statementSum: Int = children.map(_.statementSum).sum + def coveredStatementsSum: Int = children.map(_.coveredStatementsSum).sum } /** * Root node. In multi-module projects it can contain other ProjectStatementCoverage * elements as children. */ -case class ProjectStatementCoverage(name: String, children: Iterable[StatementCoverage]) - extends NodeStatementCoverage +case class ProjectStatementCoverage(name: String, children: Iterable[NodeStatementCoverage]) + extends NodeStatementCoverage { + // projects' coverage values are defined as sums of their child values + val statementCount = statementSum + val coveredStatementsCount = coveredStatementsSum +} /** * Physical directory in file system. */ -case class DirectoryStatementCoverage(name: String, children: Iterable[StatementCoverage]) - extends NodeStatementCoverage +case class DirectoryStatementCoverage(name: String, children: Iterable[NodeStatementCoverage]) + extends NodeStatementCoverage { + // directories' coverage values are defined as sums of their DIRECT child values + val statementCount = children.filter(_.isInstanceOf[FileStatementCoverage]).map(_.statementCount).sum + val coveredStatementsCount = children.filter(_.isInstanceOf[FileStatementCoverage]).map(_.coveredStatementsCount).sum +} /** * Scala source code file. */ case class FileStatementCoverage(name: String, statementCount: Int, coveredStatementsCount: Int, - statements: Iterable[CoveredStatement]) extends StatementCoverage + statements: Iterable[CoveredStatement]) extends NodeStatementCoverage { + // leaf implementation sums==values + val children = List.empty[NodeStatementCoverage] + override val statementSum = statementCount + override val coveredStatementsSum = coveredStatementsCount +} /** * Position a Scala source code file. diff --git a/plugin/src/main/scala/com/buransky/plugins/scoverage/sensor/ScoverageSensor.scala b/plugin/src/main/scala/com/buransky/plugins/scoverage/sensor/ScoverageSensor.scala index e488410..88e080f 100644 --- a/plugin/src/main/scala/com/buransky/plugins/scoverage/sensor/ScoverageSensor.scala +++ b/plugin/src/main/scala/com/buransky/plugins/scoverage/sensor/ScoverageSensor.scala @@ -25,12 +25,12 @@ import com.buransky.plugins.scoverage.language.Scala import com.buransky.plugins.scoverage.measure.ScalaMetrics import com.buransky.plugins.scoverage.util.LogUtil import com.buransky.plugins.scoverage.xml.XmlScoverageReportParser -import com.buransky.plugins.scoverage.{CoveredStatement, DirectoryStatementCoverage, FileStatementCoverage, _} -import org.sonar.api.batch.fs.{FileSystem, InputFile} -import org.sonar.api.batch.{CoverageExtension, Sensor, SensorContext} +import com.buransky.plugins.scoverage.{ CoveredStatement, DirectoryStatementCoverage, FileStatementCoverage, _ } +import org.sonar.api.batch.fs.{ FileSystem, InputFile, InputDir, InputPath } +import org.sonar.api.batch.{ CoverageExtension, Sensor, SensorContext } import org.sonar.api.config.Settings -import org.sonar.api.measures.{CoreMetrics, CoverageMeasuresBuilder, Measure} -import org.sonar.api.resources.{File, Project, Resource} +import org.sonar.api.measures.{ CoreMetrics, CoverageMeasuresBuilder, Measure } +import org.sonar.api.resources.{ File, Project, Directory, Resource } import org.sonar.api.scan.filesystem.PathResolver import org.sonar.api.utils.log.Loggers @@ -151,33 +151,55 @@ class ScoverageSensor(settings: Settings, pathResolver: PathResolver, fileSystem processChildren(projectCoverage.children, context, sonarSources) } - private def processDirectory(directoryCoverage: DirectoryStatementCoverage, context: SensorContext, - parentDirectory: String) { + private def processDirectory(directoryCoverage: DirectoryStatementCoverage, context: SensorContext, parentDirectory: String) { + // save measures if any + if (directoryCoverage.statementCount > 0) { + val path = appendFilePath(parentDirectory, directoryCoverage.name) + + getResource(path, context, false) match { + case Some(srcDir) => { + // Save directory measures + saveMeasures(context, srcDir, directoryCoverage) + } + case None => + } + } // Process children processChildren(directoryCoverage.children, context, appendFilePath(parentDirectory, directoryCoverage.name)) } private def processFile(fileCoverage: FileStatementCoverage, context: SensorContext, directory: String) { val path = appendFilePath(directory, fileCoverage.name) - val p = fileSystem.predicates() - - val files = fileSystem.inputFiles(p.and( - p.hasRelativePath(path), - p.hasLanguage(scala.getKey), - p.hasType(InputFile.Type.MAIN))).toList - - files.headOption match { - case Some(file) => - val scalaSourceFile = File.create(file.relativePath()) + getResource(path, context, true) match { + case Some(scalaSourceFile) => { // Save measures saveMeasures(context, scalaSourceFile, fileCoverage) - // Save line coverage. This is needed just for source code highlighting. saveLineCoverage(fileCoverage.statements, scalaSourceFile, context) + } + case None => + } + } + private def getResource(path: String, context: SensorContext, isFile: Boolean): Option[Resource] = { + + val inputOption: Option[InputPath] = if (isFile) { + val p = fileSystem.predicates() + Option(fileSystem.inputFile(p.and( + p.hasRelativePath(path), + p.hasLanguage(scala.getKey), + p.hasType(InputFile.Type.MAIN)))) + } else { + Option(fileSystem.inputDir(pathResolver.relativeFile(fileSystem.baseDir(), path))) + } + + inputOption match { + case Some(path: InputPath) => + Some(context.getResource(path)) case None => { - log.warn(s"File not found in file system! [$path]") + log.warn(s"File or directory not found in file system! ${path}") + None } } } diff --git a/plugin/src/main/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportConstructingParser.scala b/plugin/src/main/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportConstructingParser.scala index 77390db..d77c5e4 100644 --- a/plugin/src/main/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportConstructingParser.scala +++ b/plugin/src/main/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportConstructingParser.scala @@ -139,7 +139,7 @@ class XmlScoverageReportConstructingParser(source: Source, pathSanitizer: PathSa } } - def toStatementCoverage: StatementCoverage = { + def toStatementCoverage: NodeStatementCoverage = { val childNodes = children.map(_.toStatementCoverage) childNodes match { @@ -151,7 +151,6 @@ class XmlScoverageReportConstructingParser(source: Source, pathSanitizer: PathSa def toProjectStatementCoverage: ProjectStatementCoverage = { toStatementCoverage match { case node: NodeStatementCoverage => ProjectStatementCoverage("", node.children) - case file: FileStatementCoverage => ProjectStatementCoverage("", List(file)) case _ => throw new ScoverageException("Illegal statement coverage!") } } From 82c2fc64e64a67fd102c1a7d8950e12246d0e232 Mon Sep 17 00:00:00 2001 From: Michael Zinsmaier Date: Sun, 18 Oct 2015 18:09:10 +0200 Subject: [PATCH 6/7] added some tests to ensure that directory coverage is calculated correctly --- ...coverageReportConstructingParserSpec.scala | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/plugin/src/test/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportConstructingParserSpec.scala b/plugin/src/test/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportConstructingParserSpec.scala index 3244847..d4ab1e1 100644 --- a/plugin/src/test/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportConstructingParserSpec.scala +++ b/plugin/src/test/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportConstructingParserSpec.scala @@ -27,6 +27,8 @@ import com.buransky.plugins.scoverage.xml.data.XmlReportFile1 import scala._ import com.buransky.plugins.scoverage.{ProjectStatementCoverage, FileStatementCoverage, DirectoryStatementCoverage} import com.buransky.plugins.scoverage.pathcleaner.PathSanitizer +import com.buransky.plugins.scoverage.StatementCoverage +import com.buransky.plugins.scoverage.NodeStatementCoverage @RunWith(classOf[JUnitRunner]) class XmlScoverageReportConstructingParserSpec extends FlatSpec with Matchers { @@ -52,9 +54,16 @@ class XmlScoverageReportConstructingParserSpec extends FlatSpec with Matchers { assertReportFile(XmlReportFile1.scoverage104Data, 50.0, sanitizer) { projectCoverage => assert(projectCoverage.name === "") assert(projectCoverage.children.size.toInt === 1) + projectCoverage.children.head match { - case rootDir: DirectoryStatementCoverage => - assert(rootDir.name == "com") + case rootDir: DirectoryStatementCoverage => { + val rr = checkNode(rootDir, "com", 0, 0, 0.0).head + val test = checkNode(rr, "rr", 0, 0, 0.0).head + val sonar = checkNode(test, "test", 0, 0, 0.0).head + val mainClass = checkNode(sonar, "sonar", 2, 1, 50.0).head + + checkNode(mainClass, "MainClass.scala", 2, 1, 50.0) + } case other => fail(s"This is not a directory statement coverage! [$other]") } } @@ -111,4 +120,14 @@ class XmlScoverageReportConstructingParserSpec extends FlatSpec with Matchers { private def checkRate(expected: Double, real: Double) { BigDecimal(real).setScale(2, BigDecimal.RoundingMode.HALF_UP).should(equal(BigDecimal(expected))) } + + private def checkNode(node: NodeStatementCoverage, name: String, count: Int, covered: Int, rate: Double): Iterable[NodeStatementCoverage] = { + node.name shouldEqual name + node.statementCount shouldEqual count + node.coveredStatementsCount shouldEqual covered + + checkRate(rate, node.rate) + + node.children + } } From 1a184eee778f28c5985f51a4344028e3f771c014 Mon Sep 17 00:00:00 2001 From: Michael Zinsmaier Date: Sun, 18 Oct 2015 18:12:23 +0200 Subject: [PATCH 7/7] removed sum Formula on covered statements metric - measures should be saved for all levels (directory, project, module, file) - the sum should not be necessary any more - makes the two metrics more similar, a correct "sum" for the coverage value cannot be defined as easily. --- .../com/buransky/plugins/scoverage/measure/ScalaMetrics.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/src/main/scala/com/buransky/plugins/scoverage/measure/ScalaMetrics.scala b/plugin/src/main/scala/com/buransky/plugins/scoverage/measure/ScalaMetrics.scala index 4b7434d..23b224b 100644 --- a/plugin/src/main/scala/com/buransky/plugins/scoverage/measure/ScalaMetrics.scala +++ b/plugin/src/main/scala/com/buransky/plugins/scoverage/measure/ScalaMetrics.scala @@ -53,6 +53,5 @@ object ScalaMetrics { .setDirection(Metric.DIRECTION_BETTER) .setQualitative(false) .setDomain(CoreMetrics.DOMAIN_SIZE) - .setFormula(new org.sonar.api.measures.SumChildValuesFormula(false)) .create[java.lang.Integer]() } \ No newline at end of file