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/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/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) - -} 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/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 + } +} 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/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 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) } }