How to build a wrapper around scalatest's "it" keyword? - scala

I am attempting to build a wrapper around scalatest's it keyword. However, this solution does not seem to work as intended. Moreover, it does not even compile:
trait MyFunSpec extends FunSpec {
private val _it: FunSpec.this.ItWord = it
protected def it(specText: String, testTags: org.scalatest.Tag*)
// ...do something extra here...
(testFun: => Any /* Assertion */)(implicit pos: Position): Unit = {
_it(specText, testTags: _*)(testFun)(pos)
// ...do something extra here...
}
}
The error message I am getting after compiling this code is as follows:
[error] MyFunSpec.scala: ambiguous reference to overloaded definition,
[error] both method it in trait MyFunSpec of type (specText: String, testTags:
org.scalatest.Tag*)(testFun: => Any)(implicit pos:
org.scalactic.source.Position)Unit
[error] and value it in trait FunSpecLike of type => FunSpecSpec.this.ItWord
[error] match argument types (String)
Please note the main idea is that method's name remains it, so renaming it to something like alternativeIt is not a satisfactory solution here.
Any suggestions, what am I doing wrong here? Any solution would be highly appreciated! Thanks!

Try this:
trait MyFunSpec extends FunSpec {
override protected val it = new ItWord {
override def apply(specText: String,testTags: org.scalatest.Tag*)(testFun: => Any)(implicit pos: org.scalactic.source.Position): Unit = {
println("Before")
super.apply(specText, testTags:_*)(testFun)(pos)
println("After")
}
}
}

Related

Recursively wrapping method invocations with compiler plugins/macros

OUTLINE
I have an API that looks something like this:
package com.example
object ExternalApi {
def create[T <: SpecialElement](elem: T): TypeConstructor[T] =
TypeConstructor(elem)
def create1[T <: SpecialElement](elem: T): TypeConstructor[T] =
TypeConstructor(elem)
def create2[T <: SpecialElement](elem: T): TypeConstructor[T] =
TypeConstructor(elem)
//...
}
object MyApi {
def process[T <: TypeConstructor[_ <: SpecialElement]](
l: T,
metadata: List[String]): T = {
println("I've been called!")
//do some interesting stuff with the List's type parameter here
l
}
}
case class TypeConstructor[E](elem: E)
trait SpecialElement
The ExternalApi (which is actually external to my lib, so no modifying that) has a series of calls that I'd like to automatically wrap with MyApi.process calls, with the metadata argument derived from the final type of T.
To illustrate, the calls to be wrapped could have any form, including nested calls, and calls within other AST subtree types (such as Blocks), e.g. :
package com.example.test
import com.example.{ExternalApi, SpecialElement}
object ApiPluginTest extends App {
//one possible form
val targetList = ExternalApi.create(Blah("name"))
//and another
ExternalApi.create2(ExternalApi.create1(Blah("sth")).elem)
//and yet another
val t = {
val sub1 = ExternalApi.create(Blah("anything"))
val sub2 = ExternalApi.create1(sub1.elem)
sub2
}
}
case class Blah(name: String) extends SpecialElement
Since compiler plugins handle matching structures within ASTs recursively "for free", I've decided to go with them.
However, due to the fact that I need to match a specific type signature, the plugin follows the typer phase.
Here's the code of the PluginComponent:
package com.example.plugin
import com.example.{SpecialElement, TypeConstructor}
import scala.tools.nsc.Global
import scala.tools.nsc.plugins.PluginComponent
import scala.tools.nsc.transform.Transform
class WrapInApiCallComponent(val global: Global)
extends PluginComponent
with Transform {
protected def newTransformer(unit: global.CompilationUnit) =
WrapInApiCallTransformer
val runsAfter: List[String] = List("typer") //since we need the type
val phaseName: String = WrapInApiCallComponent.Name
import global._
object WrapInApiCallTransformer extends Transformer {
override def transform(tree: global.Tree) = {
val transformed = super.transform(tree)
transformed match {
case call # Apply(_, _) =>
if (call.tpe != null && call.tpe.finalResultType <:< typeOf[
TypeConstructor[_ <: SpecialElement]]) {
println(s"Found relevant call $call")
val typeArguments = call.tpe.typeArgs.map(_.toString).toList
val listSymbOf = symbolOf[List.type]
val wrappedFuncSecondArgument =
q"$listSymbOf.apply(..$typeArguments)"
val apiObjSymbol = symbolOf[com.example.MyApi.type]
val wrappedCall =
q"$apiObjSymbol.process[${call.tpe.finalResultType}]($call, $wrappedFuncSecondArgument)"
//explicit typing, otherwise later phases throw NPEs etc.
val ret = typer.typed(wrappedCall)
println(showRaw(ret))
println("----")
ret
} else {
call
}
case _ => transformed
}
}
}
}
object WrapInApiCallComponent {
val Name = "api_embed_component"
}
This seems to resolve identifiers, as well as types, correctly, outputting e.g.:
Apply(TypeApply(Select(TypeTree().setOriginal(Ident(com.example.MyApi)), TermName("process")), List(TypeTree())), List(Apply(TypeApply(Select(Select(Select(Ident(com), com.example), com.example.MyApi), TermName("create")), List(TypeTree())), List(Apply(Select(Ident(com.example.test.Blah), TermName("apply")), List(Literal(Constant("name")))))), Apply(TypeApply(Select(TypeTree().setOriginal(Ident(scala.collection.immutable.List)), TermName("apply")), List(TypeTree())), List(Literal(Constant("com.example.test.Blah"))))))
Unfortunately, I get an error during compilation starting with:
scala.reflect.internal.FatalError:
[error]
[error] Unexpected tree in genLoad: com.example.MyApi.type/class scala.reflect.internal.Trees$TypeTree at: RangePosition([projectpath]/testPluginAutoWrap/compiler_plugin_test/src/main/scala/com/example/test/ApiPluginTest.scala, 108, 112, 112)
[error] while compiling: [projectpath]/testPluginAutoWrap/compiler_plugin_test/src/main/scala/com/example/test/ApiPluginTest.scala
[error] during phase: jvm
[error] library version: version 2.12.4
[error] compiler version: version 2.12.4
[error] reconstructed args: -Xlog-implicits -classpath [classpath here]
[error]
[error] last tree to typer: TypeTree(class String)
[error] tree position: line 23 of [projectpath]/testPluginAutoWrap/compiler_plugin_test/src/main/scala/com/example/test/ApiPluginTest.scala
QUESTION
It looks I'm screwing something up with type definitions, but what is it?
Specifically:
How do I correctly wrap every ExternalApi.createX call with an MyApi.process call, constrained by the requirements provided above?
NOTES
Given the amount of boilerplate required, I've set up a complete example project. It's available here.
The answer does not have to define a compiler plugin. If you're able to cover all the relevant calls with a macro, that is fine as well.
Originally the wrapped call was to something like: def process[T <: TypeConstructor[_ <: SpecialElement] : TypeTag](l: T): T, the setup here is actually a workaround. So if you are able to generate a wrapped call of this type, i.e. one that includes a runtime TypeTag[T], that's fine as well.

Correct method signature for RichGroupReduceFunction?

The below extended class of RichGroupReduceFunction, does not compile. The signature seemingly does not match the interface. I can't tell the difference.
class SPointReduce extends RichGroupReduceFunction[Int, Int] {
override def reduce (
values: Iterable[Int],
out: Collector[Int]): Unit = {
values.foreach {
value: Int =>
out.collect(value)
}
}
}
The compiler reports:
Error:(62, 16) method reduce overrides nothing.
Note: the super classes of class SPointReduce contain the
following, non final members named reduce: def reduce(x$1:
Iterable[Nothing],x$2: org.apache.flink.util.Collector[Nothing]): Unit
override def reduce (
You have to make sure that you import the java.lang.Iterable when you override the reduce method of RichGroupReduceFunction. Otherwise, you will get the above mentioned error.

Scala implicit conversion from parent trait

The following code does not compile:
import scala.language.implicitConversions
trait Base {
class Wrp[+T](val v: T) // wrapper / internal representation
}
trait BooleanOps extends Base {
// implicit conversion
implicit def lift2BooleanOpsCls(x: Boolean): BooleanOpsCls =
new BooleanOpsCls(new Wrp[Boolean](x))
class BooleanOpsCls(wx: Wrp[Boolean]) {
def ||(wy: =>Wrp[Boolean]): Wrp[Boolean] = new Wrp[Boolean](wx.v || wy.v)
}
}
trait MyExample extends BooleanOps {
// test method
def foo(): Wrp[Boolean] = {
val ret: Wrp[Boolean] = false || new Wrp[Boolean](true)
ret
}
}
Output:
MyExample.scala:18: error: type mismatch;
found : MyExample.this.Wrp[Boolean]
required: Boolean
val ret: Wrp[Boolean] = false || new Wrp[Boolean](true)
^
But if I:
1) put the class Wrp outside of Base
or
2) move the body of BooleanOps to MyExample
everything compiles.
Why does not the original example work? If you have some insight in this behavior, help would be appreciated. Thank you.
One issue is the call-by-name nature of the argument in the def ||(wy: =>Wrp[Boolean])
if you rewite it to def ||(wy: Wrp[Boolean]) it works
but I agree that it is weird that it works if you move around Wrp or BooleanOpsCls! Intended or bug of implicit resolution??
The original example will work if you rename the || method. The compiler finds the false.||() method and doesn't bother to look for an implicit that might also work there.
The problem is that there is no single class called Wrp (ignore the T for a moment) -- Base does not define Wrp but rather defines a named subclass of each and every concrete class that extends Base. The implicits are a red herring, too. The error that's the giveaway is the mention of MyExample.this.Wrp -- remember that there is no such class even as MyExample -- val x = new MyExample would have type Object with MyExample.

Scala: "No manifest available for type T"

I am working on a Lift project with mixed Scala and Java code.
On the Java side, I have the following relevant items:
interface IEntity
interface IDAO<T extends IEntity> {
void persist(T t);
}
On the Scala side, I have the following:
abstract class Binding[T <: IEntity] extends NgModel {
def unbind: T
}
class BasicService[E <: IEntity](serviceName: String, dataAccessObject: IDAO[E]) {
def render = renderIfNotAlreadyDefined(
angular.module("myapp.services")
.factory(serviceName,
jsObjFactory()
.jsonCall("persist", (binding: Binding[E]) => { //<---COMPILATION ERROR
try {
dataAccessObject.persist(binding.unbind)
Empty
} catch {
case e: Exception => Failure(e.getMessage)
}
})
)
)
}
This code will not compile. I get the following error at the point indicated above:
No Manifest available for Binding[E].
It is not clear at all to me why this occurs, but I am guessing it has something to do with this being a nested method invocation. The code compiles fine if I declare a member function with Binding[E] as a parameter, for example:
def someFunction(binding: Binding[E] = { // same code as above }
Why does this happen, and how can I work around it?
Turns out this is relatively easily solved by implicitly passing on the manifest for the type in question, either in the constructor or the method itself:
class BasicService[E <: IEntity](serviceName: String, dataAccessObject: IDAO[E])(implicit m: Manifest[Binding[E]]) {
or
def render(implicit m: Manifest[Binding[E]])

fixture.FeatureSpec / withFixture(OneArgTest) issue

I want to share a Database / Knowledge base, for a suite of test. I want it to be available for any test suite. I am using featureSpec
Following the Documentation, I use fixture.FeatureSpec.
As of now i have defined the following:
trait InteractionKbFixtureTrait { this: fixture.Suite =>
type FixtureParam = InteractionKB
def withFixture(test: OneArgTest): Unit = {
val kb = KbFactory.createKb("", "") // create the fixture
try {
this.withFixture(test.toNoArgTest(kb)) // "loan" the fixture to the test
} finally {
//kb.stop() // clean up the fixture
}
}
}
followed by
class ExampleSpec extends fixture.FeatureSpec with InteractionKbFixtureTrait {
}
I get the following error :
Type overriding method withFixture in trait Suite of type (test: ExampleSpec.this.OneArgTest) org.scalatest.Outcome;
method withFixture in trait InteractionKbFixtureTrait of type (test: ExampleSpec.this.OneArgTest) Unit has incompatible type InteractionKbFixtureTrait.scala
Any help ?
Method def withFixture should return type org.scalatest.Outcome, whereas your def withFixture is returning Unit. Error message is saying that quite clearly.
So for fixing it you need to change line:
def withFixture(test: OneArgTest): Unit = {
like so:
def withFixture(test: OneArgTest): org.scalatest.Outcome = {