Skip to content

Commit 3ed8ee8

Browse files
committed
Merge pull request #22 from MichaelZinsmaier/PR_PathSanitization
Improved path handling, reported filenames are converted to src dir relative paths
2 parents c8b465b + f6654ce commit 3ed8ee8

File tree

13 files changed

+363
-128
lines changed

13 files changed

+363
-128
lines changed

plugin/src/main/scala/com/buransky/plugins/scoverage/ScoverageReportParser.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919
*/
2020
package com.buransky.plugins.scoverage
2121

22+
import com.buransky.plugins.scoverage.pathcleaner.PathSanitizer
23+
2224
/**
2325
* Interface for Scoverage report parser.
2426
*
2527
* @author Rado Buransky
2628
*/
2729
trait ScoverageReportParser {
28-
def parse(reportFilePath: String): ProjectStatementCoverage
30+
def parse(reportFilePath: String, pathSanitizer: PathSanitizer): ProjectStatementCoverage
2931
}
3032

3133
/**
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Sonar Scoverage Plugin
3+
* Copyright (C) 2013 Rado Buransky
4+
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public
17+
* License along with this program; if not, write to the Free Software
18+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
19+
*/
20+
package com.buransky.plugins.scoverage.pathcleaner
21+
22+
import java.io.File
23+
import org.apache.commons.io.FileUtils
24+
import org.apache.commons.io.FileUtils
25+
import BruteForceSequenceMatcher._
26+
import com.buransky.plugins.scoverage.util.PathUtil
27+
import scala.collection.JavaConversions._
28+
import org.sonar.api.utils.log.Loggers
29+
30+
object BruteForceSequenceMatcher {
31+
32+
val extensions = Array[String]("java", "scala")
33+
34+
type PathSeq = Seq[String]
35+
}
36+
37+
/**
38+
* Helper that allows to convert a report path into a source folder relative path by testing it against
39+
* the tree of source files.
40+
*
41+
* Assumes that all report paths of a given report have a common root. Dependent of the scoverage
42+
* report this root is either something outside the actual project (absolute path), the base dir of the project
43+
* (report path relative to base dir) or some sub folder of the project.
44+
*
45+
* By reverse mapping a report path against the tree of all file children of the source folder the correct filesystem file
46+
* can be found and the report path can be converted into a source dir relative path. *
47+
*
48+
* @author Michael Zinsmaier
49+
*/
50+
class BruteForceSequenceMatcher(baseDir: File, sourcePath: String) extends PathSanitizer {
51+
52+
private val sourceDir = initSourceDir()
53+
require(sourceDir.isAbsolute)
54+
require(sourceDir.isDirectory)
55+
56+
private val log = Loggers.get(classOf[BruteForceSequenceMatcher])
57+
private val sourcePathLength = PathUtil.splitPath(sourceDir.getAbsolutePath).size
58+
private val filesMap = initFilesMap()
59+
60+
61+
def getSourceRelativePath(reportPath: PathSeq): Option[PathSeq] = {
62+
// match with file system map of files
63+
val relPathOption = for {
64+
absPathCandidates <- filesMap.get(reportPath.last)
65+
path <- absPathCandidates.find(absPath => absPath.endsWith(reportPath))
66+
} yield path.drop(sourcePathLength)
67+
68+
relPathOption
69+
}
70+
71+
// mock able helpers that allow us to remove the dependency to the real file system during tests
72+
73+
private[pathcleaner] def initSourceDir(): File = {
74+
val sourceDir = new File(baseDir, sourcePath)
75+
sourceDir
76+
}
77+
78+
private[pathcleaner] def initFilesMap(): Map[String, Seq[PathSeq]] = {
79+
val srcFiles = FileUtils.iterateFiles(sourceDir, extensions, true)
80+
val paths = srcFiles.map(file => PathUtil.splitPath(file.getAbsolutePath)).toSeq
81+
82+
// group them by filename, in case multiple files have the same name
83+
paths.groupBy(path => path.last)
84+
}
85+
86+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Sonar Scoverage Plugin
3+
* Copyright (C) 2013 Rado Buransky
4+
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public
17+
* License along with this program; if not, write to the Free Software
18+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
19+
*/
20+
package com.buransky.plugins.scoverage.pathcleaner
21+
22+
/**
23+
* @author Michael Zinsmaier
24+
*/
25+
trait PathSanitizer {
26+
27+
/** tries to convert the given path such that it is relative to the
28+
* projects/modules source directory.
29+
*
30+
* @return Some(source folder relative path) or None if the path cannot be converted
31+
*/
32+
def getSourceRelativePath(path: Seq[String]): Option[Seq[String]]
33+
34+
}

plugin/src/main/scala/com/buransky/plugins/scoverage/sensor/ScoverageSensor.scala

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import org.sonar.api.scan.filesystem.PathResolver
3535
import org.sonar.api.utils.log.Loggers
3636

3737
import scala.collection.JavaConversions._
38+
import com.buransky.plugins.scoverage.pathcleaner.BruteForceSequenceMatcher
39+
import com.buransky.plugins.scoverage.pathcleaner.PathSanitizer
3840

3941
/**
4042
* Main sensor for importing Scoverage report to Sonar.
@@ -53,7 +55,16 @@ class ScoverageSensor(settings: Settings, pathResolver: PathResolver, fileSystem
5355
scoverageReportPath match {
5456
case Some(reportPath) =>
5557
// Single-module project
56-
processProject(scoverageReportParser.parse(reportPath), project, context)
58+
val srcOption = Option(settings.getString(project.getName() + ".sonar.sources"))
59+
val sonarSources = srcOption match {
60+
case Some(src) => src
61+
case None => {
62+
log.warn(s"could not find settings key ${project.getName()}.sonar.sources assuming src/main/scala.")
63+
"src/main/scala"
64+
}
65+
}
66+
val pathSanitizer = createPathSanitizer(sonarSources)
67+
processProject(scoverageReportParser.parse(reportPath, pathSanitizer), project, context, sonarSources)
5768

5869
case None =>
5970
// Multi-module project has report path set for each module individually
@@ -63,6 +74,9 @@ class ScoverageSensor(settings: Settings, pathResolver: PathResolver, fileSystem
6374

6475
override val toString = getClass.getSimpleName
6576

77+
protected def createPathSanitizer(sonarSources: String): PathSanitizer
78+
= new BruteForceSequenceMatcher(fileSystem.baseDir(), sonarSources)
79+
6680
private lazy val scoverageReportPath: Option[String] = {
6781
settings.getString(SCOVERAGE_REPORT_PATH_PROPERTY) match {
6882
case null => None
@@ -127,14 +141,14 @@ class ScoverageSensor(settings: Settings, pathResolver: PathResolver, fileSystem
127141
}
128142
}
129143

130-
private def processProject(projectCoverage: ProjectStatementCoverage, project: Project, context: SensorContext) {
144+
private def processProject(projectCoverage: ProjectStatementCoverage, project: Project, context: SensorContext, sonarSources: String) {
131145
// Save measures
132146
saveMeasures(context, project, projectCoverage)
133147

134148
log.info(LogUtil.f("Statement coverage for " + project.getKey + " is " + ("%1.2f" format projectCoverage.rate)))
135149

136150
// Process children
137-
processChildren(projectCoverage.children, context, "")
151+
processChildren(projectCoverage.children, context, sonarSources)
138152
}
139153

140154
private def processDirectory(directoryCoverage: DirectoryStatementCoverage, context: SensorContext,
@@ -147,9 +161,8 @@ class ScoverageSensor(settings: Settings, pathResolver: PathResolver, fileSystem
147161
val path = appendFilePath(directory, fileCoverage.name)
148162
val p = fileSystem.predicates()
149163

150-
val pathPredicate = if (new io.File(path).isAbsolute) p.hasAbsolutePath(path) else p.matchesPathPattern("**/" + path)
151164
val files = fileSystem.inputFiles(p.and(
152-
pathPredicate,
165+
p.hasRelativePath(path),
153166
p.hasLanguage(scala.getKey),
154167
p.hasType(InputFile.Type.MAIN))).toList
155168

@@ -164,10 +177,7 @@ class ScoverageSensor(settings: Settings, pathResolver: PathResolver, fileSystem
164177
saveLineCoverage(fileCoverage.statements, scalaSourceFile, context)
165178

166179
case None => {
167-
fileSystem.inputFiles(p.all()).foreach { inputFile =>
168-
log.debug(inputFile.absolutePath())
169-
}
170-
log.warn(s"File not found in file system! [$pathPredicate]")
180+
log.warn(s"File not found in file system! [$path]")
171181
}
172182
}
173183
}

plugin/src/main/scala/com/buransky/plugins/scoverage/util/PathUtil.scala

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,24 @@
2020
package com.buransky.plugins.scoverage.util
2121

2222
import java.io.File
23+
import scala.Iterator
2324
/**
2425
* File path helper.
2526
*
2627
* @author Rado Buransky
2728
*/
2829
object PathUtil {
29-
def splitPath(filePath: String, separator: String = File.separator): List[String] =
30-
filePath.split(separator.replaceAllLiterally("\\", "\\\\")).toList match {
31-
case "" :: tail if tail.nonEmpty => separator :: tail
32-
case other => other
33-
}
34-
}
30+
31+
def splitPath(filePath: String): List[String] = {
32+
new FileParentIterator(new File(filePath)).toList.reverse
33+
}
34+
35+
class FileParentIterator(private var f: File) extends Iterator[String] {
36+
def hasNext: Boolean = f != null && !f.getName().isEmpty()
37+
def next(): String = {
38+
val name = f.getName()
39+
f = f.getParentFile
40+
name
41+
}
42+
}
43+
}

plugin/src/main/scala/com/buransky/plugins/scoverage/xml/StubScoverageReportParser.scala

Lines changed: 0 additions & 62 deletions
This file was deleted.

0 commit comments

Comments
 (0)