Skip to content

Commit 7782fe5

Browse files
committed
Fixed all tests. Updated handling of for loops
1 parent 938cdd2 commit 7782fe5

File tree

3 files changed

+78
-28
lines changed

3 files changed

+78
-28
lines changed

project/Scoverage.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ object Scoverage extends Build {
77
val Version = "1.0.3-SNAPSHOT"
88
val Scala = "2.11.4"
99
val MockitoVersion = "1.9.5"
10-
val ScalatestVersion = "2.2.2"
10+
val ScalatestVersion = "2.2.3"
1111

1212
lazy val LocalTest = config("local") extend Test
1313

@@ -77,8 +77,8 @@ object Scoverage extends Build {
7777
.settings(name := "scalac-scoverage-plugin")
7878
.settings(appSettings: _*)
7979
.settings(libraryDependencies ++= Seq(
80-
"org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided",
81-
"org.scala-lang" % "scala-compiler" % scalaVersion.value % "provided",
80+
"org.scala-lang" % "scala-reflect" % Scala % "provided",
81+
"org.scala-lang" % "scala-compiler" % Scala % "provided",
8282
"org.joda" % "joda-convert" % "1.6" % "test",
8383
"joda-time" % "joda-time" % "2.3" % "test",
8484
"com.typesafe.scala-logging" %% "scala-logging-slf4j" % "2.1.2" % "test"

scalac-scoverage-plugin/src/main/scala/scoverage/plugin.scala

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import java.util.concurrent.atomic.AtomicInteger
55

66
import scala.reflect.internal.ModifierFlags
77
import scala.reflect.internal.util.SourceFile
8-
import scala.tools.nsc._
9-
import scala.tools.nsc.plugins.{Plugin, PluginComponent}
8+
import scala.tools.nsc.Global
9+
import scala.tools.nsc.plugins.{PluginComponent, Plugin}
1010
import scala.tools.nsc.transform.{Transform, TypingTransformers}
1111

1212
/** @author Stephen Samuel */
@@ -142,6 +142,16 @@ class ScoverageInstrumentationComponent(val global: Global)
142142

143143
def transformStatements(trees: List[Tree]): List[Tree] = trees.map(process)
144144

145+
def transformForCases(cases: List[CaseDef]): List[CaseDef] = {
146+
// we don't instrument the synthetic case _ => false clause
147+
cases.dropRight(1).map(c => {
148+
treeCopy.CaseDef(
149+
// in a for-loop we don't care about instrumenting the guards, as they are synthetically generated
150+
c, c.pat, process(c.guard), process(c.body)
151+
)
152+
}) ++ cases.takeRight(1)
153+
}
154+
145155
def transformCases(cases: List[CaseDef]): List[CaseDef] = {
146156
cases.map(c => {
147157
treeCopy.CaseDef(
@@ -167,7 +177,7 @@ class ScoverageInstrumentationComponent(val global: Global)
167177
safeStart(tree),
168178
safeEnd(tree),
169179
safeLine(tree),
170-
original.toString(),
180+
original.toString,
171181
Option(original.symbol).fold("<nosymbol>")(_.fullNameString),
172182
tree.getClass.getSimpleName,
173183
branch
@@ -225,6 +235,8 @@ class ScoverageInstrumentationComponent(val global: Global)
225235
def traverseApplication(t: Tree): Tree = {
226236
t match {
227237
case a: ApplyToImplicitArgs => treeCopy.Apply(a, traverseApplication(a.fun), transformStatements(a.args))
238+
case Apply(Select(_, TermName("withFilter")), List(fun@Function(params, body)))
239+
if fun.symbol.isSynthetic && fun.toString.contains("check$ifrefutable$1") => t
228240
case a: Apply => treeCopy.Apply(a, traverseApplication(a.fun), transformStatements(a.args))
229241
case a: TypeApply => treeCopy.TypeApply(a, traverseApplication(a.fun), transformStatements(a.args))
230242
case s: Select => treeCopy.Select(s, traverseApplication(s.qualifier), s.name)
@@ -323,11 +335,6 @@ class ScoverageInstrumentationComponent(val global: Global)
323335
// ignore macro definitions in 2.11
324336
case DefDef(mods, _, _, _, _, _) if mods.isMacro => tree
325337

326-
case d: DefDef =>
327-
println(d)
328-
val e = d
329-
d
330-
331338
// this will catch methods defined as macros, eg def test = macro testImpl
332339
// it will not catch macro implemenations
333340
case d: DefDef if d.symbol != null
@@ -428,16 +435,17 @@ class ScoverageInstrumentationComponent(val global: Global)
428435
// pattern match clauses will be instrumented per case
429436
case m@Match(selector: Tree, cases: List[CaseDef]) =>
430437
// we can be fairly sure this was generated as part of a for loop
431-
if (selector.toString().contains("check$")
438+
if (selector.toString.contains("check$")
432439
&& selector.tpe.annotations.mkString == "unchecked"
433440
&& m.cases.last.toString == "case _ => false") {
434-
// todo check these assumptions for 2.11
435-
treeCopy
436-
.Match(tree, instrument(selector, selector), transformCases(cases.dropRight(1)) ++ cases.takeRight(1))
441+
treeCopy.Match(tree, process(selector), transformForCases(cases))
437442
} else {
438-
if (selector.symbol.isSynthetic)
443+
// if the selector was added by compiler, we don't want to instrument it....
444+
// that usually means some construct is being transformed into a match
445+
if (Option(selector.symbol).exists(_.isSynthetic))
439446
treeCopy.Match(tree, selector, transformCases(cases))
440447
else
448+
// .. but we will if it was a user match
441449
treeCopy.Match(tree, process(selector), transformCases(cases))
442450
}
443451

scalac-scoverage-plugin/src/test/scala/scoverage/PluginCoverageTest.scala

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ class PluginCoverageTest
1919
| }
2020
|} """.stripMargin)
2121
assert(!compiler.reporter.hasErrors)
22-
// we should have 2 statements - initialising the val and executing string sub in the def
22+
// we expect:
23+
// instrumenting the default-param which becomes a method call invocation
24+
// the method makeGreeting is entered.
2325
compiler.assertNMeasuredStatements(2)
2426
}
2527

@@ -89,30 +91,69 @@ class PluginCoverageTest
8991
compiler.assertNMeasuredStatements(6)
9092
}
9193

92-
test("scoverage should instrument selectors in match") {
94+
test("scoverage should instrument non basic selector") {
95+
val compiler = ScoverageCompiler.default
96+
compiler.compileCodeSnippet( """ trait A {
97+
| def someValue = "sammy"
98+
| def foo(a:String) = someValue match {
99+
| case any => "yes"
100+
| }
101+
|} """.stripMargin)
102+
assert(!compiler.reporter.hasErrors)
103+
// should instrument:
104+
// the someValue method entry
105+
// the selector call
106+
// case block "yes" literal
107+
compiler.assertNMeasuredStatements(3)
108+
}
109+
110+
test("scoverage should instrument conditional selectors in a match") {
93111
val compiler = ScoverageCompiler.default
94112
compiler.compileCodeSnippet( """ trait A {
95113
| def foo(a:String) = (if (a == "hello") 1 else 2) match {
96114
| case any => "yes"
97115
| }
98116
|} """.stripMargin)
99117
assert(!compiler.reporter.hasErrors)
100-
// should instrument the method call, the if clause, thenp, thenp literal, elsep, elsep literal, case block,
101-
// case block literal
102-
compiler.assertNMeasuredStatements(8)
118+
// should instrument:
119+
// the if clause,
120+
// thenp block,
121+
// thenp literal "1",
122+
// elsep block,
123+
// elsep literal "2",
124+
// case block "yes" literal
125+
compiler.assertNMeasuredStatements(6)
103126
}
104127

105128
// https://github.com/scoverage/sbt-scoverage/issues/16
106-
test("scoverage should instrument for-loops but not the generated default case") {
129+
test("scoverage should instrument for-loops but not the generated scaffolding") {
107130
val compiler = ScoverageCompiler.default
108131
compiler.compileCodeSnippet( """ trait A {
109132
| def print1(list: List[String]) = for (string: String <- list) println(string)
110133
|} """.stripMargin)
111134
assert(!compiler.reporter.hasErrors)
112135
assert(!compiler.reporter.hasWarnings)
113-
// should have one statement for the withFilter invoke, one of the match selector,
114-
// one of the case block, one for the case string RHS value, one for the foreach block.
115-
compiler.assertNMeasuredStatements(5)
136+
// should instrument:
137+
// the def method entry
138+
// foreach body
139+
// specifically we want to avoid the withFilter partial function added by the compiler
140+
compiler.assertNMeasuredStatements(2)
141+
}
142+
143+
test("scoverage should instrument for-loop guards") {
144+
val compiler = ScoverageCompiler.default
145+
146+
compiler.compileCodeSnippet( """object A {
147+
| def foo(list: List[String]) = for (string: String <- list if string.length > 5)
148+
| println(string)
149+
|} """.stripMargin)
150+
assert(!compiler.reporter.hasErrors)
151+
assert(!compiler.reporter.hasWarnings)
152+
// should instrument:
153+
// foreach body
154+
// the guard
155+
// but we want to avoid the withFilter partial function added by the compiler
156+
compiler.assertNMeasuredStatements(3)
116157
}
117158

118159
test("scoverage should correctly handle new with args (apply with list of args)") {
@@ -188,14 +229,15 @@ class PluginCoverageTest
188229
compiler.compileCodeSnippet( """ trait A {
189230
| def foo(name: Any) = name match {
190231
| case i : Int => 1
191-
| case b : Boolean => 2
232+
| case b : Boolean => println("boo")
192233
| case _ => 3
193234
| }
194235
|} """.stripMargin)
195236
assert(!compiler.reporter.hasErrors)
196237
assert(!compiler.reporter.hasWarnings)
197-
// should have one statement for each literal, one for each case block, and one for the selector.
198-
compiler.assertNMeasuredStatements(7)
238+
// should have one statement for each case body
239+
// selector is a constant so would be ignored.
240+
compiler.assertNMeasuredStatements(3)
199241
}
200242

201243
test("scoverage should support yields") {

0 commit comments

Comments
 (0)