diff --git a/.gitignore b/.gitignore index 4297a5e..9f84ca1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ project/sbt_project_definition.iml lib_managed/ target/ self-report/ +.idea \ No newline at end of file diff --git a/build.sbt b/build.sbt index 6f8b50d..c49e6bf 100755 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ organization := "reaktor" name := "scct" -version := "0.2-SNAPSHOT" +version := "0.3-SNAPSHOT" scalaVersion := "2.10.0-RC3" diff --git a/src/main/scala/reaktor/scct/Coverage.scala b/src/main/scala/reaktor/scct/Coverage.scala index ba44628..0acffcf 100755 --- a/src/main/scala/reaktor/scct/Coverage.scala +++ b/src/main/scala/reaktor/scct/Coverage.scala @@ -53,13 +53,21 @@ object Coverage { } def report = { - val projectData = new ProjectData(env, dataValues) + val filtered: List[CoveredBlock] = filteredData + + val projectData = new ProjectData(env, filtered) val writer = new HtmlReportWriter(env.reportDir) new HtmlReporter(projectData, writer).report new CoberturaReporter(projectData, writer).report BinaryReporter.report(projectData, env.reportDir) } + private def filteredData: List[CoveredBlock] = { + val excludedPaths = System.getProperty("scct.excluded.paths.regex", "").split(",").filter(_.length > 0).map(_.r) + val filter = new CoverageFilter(excludedPaths) + filter.filter(dataValues) + } + private def setupShutdownHook { Runtime.getRuntime.addShutdownHook(new Thread { override def run = { diff --git a/src/main/scala/reaktor/scct/CoverageFilter.scala b/src/main/scala/reaktor/scct/CoverageFilter.scala new file mode 100644 index 0000000..0f4e42a --- /dev/null +++ b/src/main/scala/reaktor/scct/CoverageFilter.scala @@ -0,0 +1,18 @@ +package reaktor.scct + +import scala.util.matching.Regex + +class CoverageFilter(excludeFiles : Array[Regex]) { + + private var debug = System.getProperty("scct.debug") == "true" + + private def isIncluded(block: CoveredBlock) = { + val isMatched = excludeFiles.filter( _.findFirstIn(block.name.sourceFile).isDefined).size > 0 + + if (debug && isMatched) println("scct : excluding " + block.name.sourceFile) + + !isMatched + } + + def filter(data: List[CoveredBlock]): List[CoveredBlock] = data.filter( isIncluded ) +} \ No newline at end of file diff --git a/src/main/scala/reaktor/scct/ScctInstrumentPlugin.scala b/src/main/scala/reaktor/scct/ScctInstrumentPlugin.scala index d0be6fe..ea84f01 100755 --- a/src/main/scala/reaktor/scct/ScctInstrumentPlugin.scala +++ b/src/main/scala/reaktor/scct/ScctInstrumentPlugin.scala @@ -4,8 +4,9 @@ import tools.nsc.plugins.{PluginComponent, Plugin} import java.io.File import tools.nsc.transform.{Transform, TypingTransformers} import tools.nsc.symtab.Flags -import tools.nsc.{Phase, Global} +import tools.nsc.Global import util.Random +import scala.util.matching.Regex class ScctInstrumentPlugin(val global: Global) extends Plugin { val name = "scct" @@ -19,18 +20,20 @@ class ScctInstrumentPlugin(val global: Global) extends Plugin { options.projectId = opt.substring("projectId:".length) } else if (opt.startsWith("basedir:")) { options.baseDir = new File(opt.substring("basedir:".length)) + } else if (opt.startsWith("excludePackages:")) { + options.excludePackages = opt.substring("excludePackages:".length).split(",").filter(_.length > 0).map(_.r) } else { error("Unknown option: "+opt) } } } override val optionsHelp: Option[String] = Some( - " -P:scct:projectId: identify compiled classes under project \n" + - " -P:scct:basedir: set the root dir of the project being compiled" + " -P:scct:projectId: identify compiled classes under project \n" + + " -P:scct:basedir: set the root dir of the project being compiled\n" ) } -class ScctInstrumentPluginOptions(val compilationId:String, var projectId:String, var baseDir:File) { +class ScctInstrumentPluginOptions(val compilationId:String, var projectId:String, var baseDir:File, var excludePackages: Array[Regex] = Array()) { def this() = this(System.currentTimeMillis.toString + Random.nextLong().toString, ScctInstrumentPluginOptions.defaultProjectName, ScctInstrumentPluginOptions.defaultBasedir) } @@ -45,7 +48,6 @@ object ScctInstrumentPluginOptions { class ScctTransformComponent(val global: Global, val opts:ScctInstrumentPluginOptions) extends PluginComponent with TypingTransformers with Transform { import global._ - import global.definitions._ val runsAfter = List[String]("typer") override val runsBefore = List[String]("patmat") @@ -53,6 +55,8 @@ class ScctTransformComponent(val global: Global, val opts:ScctInstrumentPluginOp val phaseName = "scctInstrumentation" def newTransformer(unit: CompilationUnit) = new Instrumenter(unit) + val filter = new CoverageFilter(opts.excludePackages) + var debug = System.getProperty("scct.debug") == "true" var saveData = true var counter = 0 @@ -70,11 +74,15 @@ class ScctTransformComponent(val global: Global, val opts:ScctInstrumentPluginOp super.run saveMetadata } + + private def saveMetadata { if (saveData) { + val filtered = filter.filter(data) + println("scct: [" + opts.projectId + "] Saving coverage data.") if (coverageFile.exists) coverageFile.delete - MetadataPickler.toFile(data, coverageFile) + MetadataPickler.toFile(filtered, coverageFile) } } } diff --git a/src/test/scala/reaktor/scct/CoverageFilterSpec.scala b/src/test/scala/reaktor/scct/CoverageFilterSpec.scala new file mode 100644 index 0000000..6fb7bfd --- /dev/null +++ b/src/test/scala/reaktor/scct/CoverageFilterSpec.scala @@ -0,0 +1,35 @@ +package reaktor.scct + +import org.specs2.mutable._ +import reaktor.scct.ClassTypes.ClassType + +class CoverageFilterSpec extends Specification { + + "empty filter does pass thru" in { + val data = List(block( "packageName" )) + + new CoverageFilter(Array()).filter(data) mustEqual data + } + + "non matching filter does pass thru" in { + val data = List(block( "packageName" )) + new CoverageFilter(Array("nonMatching".r)).filter(data) mustEqual data + } + + "filter excludes matches" in { + val blockMatching = block( "MATCHINGPART" ) + val blockNonMatching = block( "nonmatching" ) + + new CoverageFilter(Array("MATCHINGPART".r)).filter(List(blockMatching, blockNonMatching)) mustEqual List(blockNonMatching) + } + + "multiple filters" in { + val blockMatching1 = block( "MATCH1.suffix" ) + val blockMatching2 = block( "MATCH2.suffix" ) + val blockNonMatching = block( "nonmatching" ) + + new CoverageFilter(Array("^MATCH1".r, "^MATCH2".r)).filter(List(blockMatching1, blockMatching2, blockNonMatching)) mustEqual List(blockNonMatching) + } + + def block(packageName: String) = new CoveredBlock("c1", 1, Name("file1", ClassTypes.Class, packageName, "className", "projectName"), 1, false) +} \ No newline at end of file diff --git a/src/test/scala/reaktor/scct/ScctInstrumentPluginSpec.scala b/src/test/scala/reaktor/scct/ScctInstrumentPluginSpec.scala index 1bff462..714acba 100644 --- a/src/test/scala/reaktor/scct/ScctInstrumentPluginSpec.scala +++ b/src/test/scala/reaktor/scct/ScctInstrumentPluginSpec.scala @@ -3,7 +3,6 @@ package reaktor.scct import org.specs2.mutable._ import org.specs2.mock._ import tools.nsc.Global -import java.io.File class ScctInstrumentPluginSpec extends Specification with Mockito { val sut = new ScctInstrumentPlugin(smartMock[Global]) @@ -30,9 +29,10 @@ class ScctInstrumentPluginSpec extends Specification with Mockito { sut.options.baseDir.getName must not be empty } "be settable" in { - sut.processOptions(List("basedir:/base/dir", "projectId:myProject"), s => ()) + sut.processOptions(List("basedir:/base/dir", "projectId:myProject", "excludePackages:myRegex,yourRegex"), s => ()) sut.options.projectId mustEqual "myProject" sut.options.baseDir.getAbsolutePath mustEqual "/base/dir" + sut.options.excludePackages mustEqual Array("myRegex".r, "yourRegex".r) } "report error" in { var err = ""