Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved path handling, reported filenames are converted to src dir relative paths #22

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}

}
Original file line number Diff line number Diff line change
@@ -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]]

}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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

Expand All @@ -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]")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}

This file was deleted.

Loading