I have a exception with actors due to how it is instanciated in my tests.
I'm using Play Scala 2.5 with the provided akka library.
Here is my controller:
class MyController #Inject()(implicit context: ExecutionContext, val messagesApi: MessagesApi, system: ActorSystem) extends Controller with I18nSupport {
val (out, channel) = Concurrent.broadcast[String]
val listenerActor = system.actorOf(Listener.props, "listener")
listenerActor ! Start(channel)
def stream = Action { implicit req =>
val source = Source.fromPublisher(Streams.enumeratorToPublisher(out))
Ok.chunked(source via EventSource.flow).as("text/event-stream")
}
def myAction = Action.async {
listenerActor ! NewMessage("Action myAction call")
}
}
Here is my actor :
object Listener {
def props = Props[Listener]
case class Start(out: Concurrent.Channel[String])
case class NewMessage(message: String)
}
class Listener extends Actor {
import Listener._
var out: Option[Concurrent.Channel[String]] = None
def receive = {
case Start(out) => this.out = Some(out)
case NewMessage(msg) => this.out.map(_.push("{ \"message\": \"" + msg + "\" }"))
}
}
And my test :
class MyControllerSpec extends PlaySpec with OneAppPerSuite with ScalaFutures with MockitoSugar {
val messagesApi = app.injector.instanceOf[MessagesApi]
val ec = app.injector.instanceOf[ExecutionContext]
val actorSystem = app.injector.instanceOf[ActorSystem]
val injector = new GuiceInjectorBuilder()
.overrides(bind[MessagesApi].toInstance(messagesApi))
.overrides(bind[ExecutionContext].toInstance(ec))
.overrides(bind[ActorSystem].toInstance(actorSystem))
.injector
def myController = injector.instanceOf(classOf[MyController])
"MyController" should {...}
}
All my tests fail with the exception :
com.google.inject.ProvisionException: Unable to provision, see the following errors:
[info]
[info] 1) Error injecting constructor, akka.actor.InvalidActorNameException: actor name [listener] is not unique!
[info] at controllers.MyController.<init>(MyController.scala:29)
[info] while locating controllers.MyController
[info]
[info] 1 error
[info] at at com.google.inject.internal.InjectorImpl$2.get(InjectorImpl.java:1025)
[info] at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1051)
[info] at play.api.inject.guice.GuiceInjector.instanceOf(GuiceInjectorBuilder.scala:405)
[info] at controllers.MyControllerSpec.myController(MyControllerSpec.scala:33)
[info] at controllers.MyControllerSpec$$anonfun$1$$anonfun$apply$mcV$sp$7.apply$mcV$sp(MyControllerSpec.scala:94)
[info] at controllers.MyControllerSpec$$anonfun$1$$anonfun$apply$mcV$sp$7.apply(MyControllerSpec.scala:92)
[info] at controllers.MyControllerSpec$$anonfun$1$$anonfun$apply$mcV$sp$7.apply(MyControllerSpec.scala:92)
[info] at org.scalatest.Transformer$$anonfun$apply$1.apply$mcV$sp(Transformer.scala:22)
[info] at org.scalatest.OutcomeOf$class.outcomeOf(OutcomeOf.scala:85)
[info] at org.scalatest.OutcomeOf$.outcomeOf(OutcomeOf.scala:104)
[info] ...
[info] Cause: akka.actor.InvalidActorNameException: actor name [listener] is not unique!
[info] at akka.actor.dungeon.ChildrenContainer$NormalChildrenContainer.reserve(ChildrenContainer.scala:130)
[info] at akka.actor.dungeon.Children$class.reserveChild(Children.scala:130)
[info] at akka.actor.ActorCell.reserveChild(ActorCell.scala:374)
[info] at akka.actor.dungeon.Children$class.makeChild(Children.scala:268)
[info] at akka.actor.dungeon.Children$class.attachChild(Children.scala:46)
[info] at akka.actor.ActorCell.attachChild(ActorCell.scala:374)
[info] at akka.actor.ActorSystemImpl.actorOf(ActorSystem.scala:591)
[info] at controllers.MyController.<init>(MyController.scala:34)
[info] at controllers.MyController$$FastClassByGuice$$5133fbab.newInstance(<generated>)
[info] at com.google.inject.internal.cglib.reflect.$FastConstructor.newInstance(FastConstructor.java:40)
How to organize the code so that my actor is instanciated properly ?
=========================================================================
Update
I fixed the controller code to have it work. It is not using dependency injection anymore.
class MyController #Inject()(implicit context: ExecutionContext, val messagesApi: MessagesApi) extends Controller with I18nSupport {
val (out, channel) = Concurrent.broadcast[String]
val listenerActor = ActorSystem("listener").actorOf(Props[Listener])
listenerActor ! Start(channel)
def stream = Action { implicit req =>
val source = Source.fromPublisher(Streams.enumeratorToPublisher(out))
Ok.chunked(source via EventSource.flow).as("text/event-stream")
}
def myAction = Action.async {
listenerActor ! NewMessage("Action myAction call")
}
}
And remove the code that was injecting the ActorSystem in the test.
ActorSystem("a name").actorOf(Props[youractor])
Check if this code can help you, it needs a constract name.
Related
I'm using Play 2.4.6 with compile time dependency injection and ScalaTest. The controller's constructor has few parameters, and in an ApplicationLoader I create it.
Here is the code:
class BootstrapLoader extends ApplicationLoader {
def load(context: Context) = {
new AppComponents(context).application
}
}
class AppComponents(context: Context) extends BuiltInComponentsFromContext(context) with NingWSComponents {
lazy val router = new Routes(httpErrorHandler, authenticationController, applicationController, assets)
lazy val applicationController = new controllers.Application()
lazy val authenticationController = new controllers.Authentication()(configuration, wsApi.client)
lazy val assets = new controllers.Assets(httpErrorHandler)
}
class Authentication(implicit configuration: Configuration, val ws: WSClient) extends Controller {
def login = Action { implicit request =>
Unauthorized(s"${redirectUrl}")
}
}
class AuthenticationSpec extends PlaySpec with OneAppPerSuite {
implicit val configuration: Configuration = app.configuration
implicit val wsClient: WSClient = WS.client(app)
"when user not logged-in" should {
"return Status code Unauthorized(401) with redirect url" in {
1 mustEqual 2
}
}
}
When I'm running the test I'm getting the following error:
[info] Exception encountered when attempting to run a suite with class name: controllers.AuthenticationSpec *** ABORTED ***
[info] com.google.inject.ProvisionException: Unable to provision, see the following errors:
[info]
[info] 1) Could not find a suitable constructor in controllers.Authentication. Classes must have either one (and only one) constructor annotated with #Inject or a zero-argument constructor that is not private.
[info] at controllers.Authentication.class(Authentication.scala:19)
[info] while locating controllers.Authentication
[info] for parameter 1 at router.Routes.<init>(Routes.scala:35)
[info] while locating router.Routes
[info] while locating play.api.test.FakeRouterProvider
[info] while locating play.api.routing.Router
[info]
[info] 1 error
[info] at com.google.inject.internal.InjectorImpl$2.get(InjectorImpl.java:1025)
[info] at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1051)
[info] at play.api.inject.guice.GuiceInjector.instanceOf(GuiceInjectorBuilder.scala:321)
[info] at play.api.inject.guice.GuiceInjector.instanceOf(GuiceInjectorBuilder.scala:316)
[info] at play.api.Application$class.routes(Application.scala:112)
[info] at play.api.test.FakeApplication.routes(Fakes.scala:197)
[info] at play.api.Play$$anonfun$start$1.apply$mcV$sp(Play.scala:90)
[info] at play.api.Play$$anonfun$start$1.apply(Play.scala:87)
[info] at play.api.Play$$anonfun$start$1.apply(Play.scala:87)
[info] at play.utils.Threads$.withContextClassLoader(Threads.scala:21)
FakeApplication use GuiceApplicationBuilder, which of course does not work.
What should I do to run such tests?
Thanks
override implicit lazy val app = new BootstrapLoader().load(
ApplicationLoader.createContext(
new Environment(
new File("."), ApplicationLoader.getClass.getClassLoader, Mode.Test)))
It works in Play 2.5.1
You are getting an error because the tests are not even able to start a application. That is happening because you are using Dependency Injection in your controllers (as the error message suggests) and you need to declare them as classes, instead of as objects. As you can see at the docs:
package controllers
import play.api.mvc._
class Application extends Controller {
def index = Action {
Ok("It works!")
}
}
If your controller has some dependency to be injected, you should use the #Inject annotation in your controller constructor (again, please see the docs). Per instance:
package controllers
import play.api.mvc._
import play.api.libs.ws._
import javax.inject._
class Application #Inject() (ws: WSClient) extends Controller {
// ...
}
You can also read the Compile Time Dependency Injection docs if you are using it instead of runtime DI.
If you use specs2 you can do it. see http://loicdescotte.github.io/posts/play24-compile-time-di/
But you loose the nice api.
Scalatest / scalatest-plus has done something funky with the DI (guice) :(
I'm facing the same problem as you. I don't have a satisfying solution, the following is a mere workaround:
I ended up putting
implicit def client:WSClient = NingWSClient()
in my WithApplicationLoader class
I also found https://github.com/leanovate/play-mockws which allows you to mock ws calls. But that's not what we want here.
my guess would be that the OneAppPerSuite trait isn't using your custom application loader. you may need to override the application construction that comes from that trait and make it use your custom loader.
looks like there is an example using scalatest here: http://mariussoutier.com/blog/2015/12/06/playframework-2-4-dependency-injection-di/
some of my scala annotation macro do not seem to get expanded, is there a way to inspect/log which expression gets passed to my annotation macro at compile time, because right now the code doesn't even compile...
def virtualize(tree: Tree): Tree = atPos(tree.pos) {
tree match {
case x =>
println("LOG: "+tree) //will only be printed during runtime
c.warning(tree.pos, "LOG: "+tree) //will only generate code for a warning
super.transform(tree)
}
}
Is there a way to issue compiler warnings in annotation macros?
Thanks a lot!
if you use idea then open terminal enter sbt ~compilewill Time compilation
then you can see compilation info log like follow in the terminal :
D:\git\scala-macro-example>sbt ~compile
[info] Loading project definition from D:\git\scala-macro-example\project
[info] Set current project to scala-macro-example (in build file:/D:/git/scala-macro-example/)
[info] Updating {file:/D:/git/scala-macro-example/}root...
[info] Resolving jline#jline;2.12.1 ...
[info] Done updating.
[info] Compiling 2 Scala sources to D:\git\scala-macro-example\module\macros\target\scala-2.11\classes...
[error] D:\git\scala-macro-example\module\macros\src\main\scala\macross\teach\WithHello.scala:16: type ClassWithFunc is not a member of package macross.annotat
ion
[error] class WithHelloImpl(val c: Context) extends macross.annotation.ClassWithFunc{
[error]
after
object ShowInfo {
class Show extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro ShowImpl.apply
}
class ShowImpl(val c: Context) {
import c.universe._
def showInfo(s: String) =
c.info(c.enclosingPosition, s.split("\n").mkString("\n |---macro info---\n |", "\n |", ""), true)
def apply(annottees: c.Expr[Any]*): c.Expr[Any] = {
val a: Seq[c.universe.Tree] = annottees.map(_.tree)
showInfo(show(a.head))
c.Expr[Any](Block(a.toList, Literal(Constant(()))))
}
}
}
useing like following:
object ShowInfoUsing {
trait SuperTrait
class SuperClass
#ShowInfo.Show
class ShowInfoUsing(val i: Int = 1) extends SuperClass with SuperTrait {
def f = 1
val a = 1
}
}
you can see info logo in the terminal
[info] |---macro info---
[info] |class ShowInfoUsing extends SuperClass with SuperTrait {
[info] | <paramaccessor> val i: Int = _;
[info] | def <init>(i: Int = 1) = {
[info] | super.<init>();
[info] | ()
[info] | };
[info] | def f = 1;
[info] | val a = 1
[info] |}
[info] #ShowInfo.Show
[info]
I would like to use FunSuite to test my Spark jobs by extending FunSuite with a new function, called localTest, that runs a test with a default SparkContext:
class SparkFunSuite extends FunSuite {
def localTest(name : String)(f : SparkContext => Unit) : Unit = {
val conf = new SparkConf().setAppName(name).setMaster("local")
val sc = new SparkContext(conf)
try {
this.test(name)(f(sc))
} finally {
sc.stop
}
}
}
Then I can add tests easily to my testing suites:
class MyTestSuite extends SparkFunSuite {
localTest("My Spark test") { sc =>
assertResult(2)(sc.parallelize(Seq(1,2,3)).filter(_ <= 2).map(_ + 1).count)
}
}
The problem is that when I run the tests I get a NullPointerException:
[info] MyTestSuite:
[info] - My Spark test *** FAILED ***
[info] java.lang.NullPointerException:
[info] at org.apache.spark.SparkContext.defaultParallelism(SparkContext.scala:1215)
[info] at org.apache.spark.SparkContext.parallelize$default$2(SparkContext.scala:435)
[info] at MyTestSuite$$anonfun$1.apply(FunSuiteTest.scala:24)
[info] at MyTestSuite$$anonfun$1.apply(FunSuiteTest.scala:23)
[info] at SparkFunSuite$$anonfun$localTest$1.apply$mcV$sp(FunSuiteTest.scala:13)
[info] at SparkFunSuite$$anonfun$localTest$1.apply(FunSuiteTest.scala:13)
[info] at SparkFunSuite$$anonfun$localTest$1.apply(FunSuiteTest.scala:13)
[info] at org.scalatest.Transformer$$anonfun$apply$1.apply$mcV$sp(Transformer.scala:22)
[info] at org.scalatest.OutcomeOf$class.outcomeOf(OutcomeOf.scala:85)
[info] at org.scalatest.OutcomeOf$.outcomeOf(OutcomeOf.scala:104)
[info] ...
What is causing the NullPointerException? Is my way to use Spark not correct in this context?
I'm using Scala 2.10.4 with spark-core 1.0.2 and scalatest 2.2.2.
If you are running SparkContexts in more than one class, make sure that you put parallelExecution in Test := false in your build.sbt. I was running into the problem when I ran the command: sbt test. I would either get a NPE or a PARSING_ERROR caused by multiple SparkContexts running in the JVM.
The reason why this wasn't working is that I misused FunSuite.test. This method registers a new test when it is called, that is when FunSuite is constructed. The test will then be called when tests are run. But my localTest does some actions before and after calling FunSuite.test. In particular, after register the test with this.test(name)(f(sc)), it stops the SparkContext. When the test is called, sc is stopped and that causes the NullPointerException on the taskScheduler field of SparkContxt. The correct way to use FunSuite is:
import org.scalatest.FunSuite
import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
class SparkFunSuite extends FunSuite {
def localTest(name : String)(f : SparkContext => Unit) : Unit = {
this.test(name) {
val conf = new SparkConf()
.setAppName(name)
.setMaster("local")
.set("spark.default.parallelism", "1")
val sc = new SparkContext(conf)
try {
f(sc)
} finally {
sc.stop()
}
}
}
}
class MyTestSuite extends SparkFunSuite {
localTest("My Spark test") { sc =>
assertResult(2)(sc.parallelize(Seq(1,2,3)).filter(_ <= 2).map(_ + 1).count)
}
}
I am attempting to execute a Specification with multiple tests that all run within the same Play application and not a separate application for each test.
As such I have the following code which should print:
Play app started
[info] PlayRunningImmutableSpec
[info]
[info] + 200 status expected
[info]
[info] + 404 status expected
Play app stopped
but instead prints:
Play app started
Play app stopped
[info] PlayRunningImmutableSpec
[info]
[info]
[info] ! 200 status expected
[error] ConnectException: : Connection refused: /127.0.0.1:19001 to http://127.0.0.1:19001/
I am using Typesafe Activator 1.2.10 which includes Play 2.3.3 and Specs2 2.3.12
What is wrong with the following code, and what would work instead?
import org.specs2.Specification
import org.specs2.execute.Result
import org.specs2.specification.Step
import org.specs2.time.NoTimeConversions
import play.api.Play
import play.api.Play.current
import play.api.http.{HeaderNames, HttpProtocol, Status}
import play.api.libs.ws.WS
import play.api.test._
class PlayRunningImmutableSpec extends Specification with NoTimeConversions with PlayRunners with HeaderNames with Status with HttpProtocol with DefaultAwaitTimeout with ResultExtractors with Writeables with RouteInvokers with FutureAwaits {
override def is = s2"""
${Step(beforeAll)}
200 status expected $e1
404 status expected $e2
${Step(afterAll)}
"""
def e1: Result = {
await(WS.url(s"http://127.0.0.1:${Helpers.testServerPort}").get()).status === 200
}
def e2: Result = {
await(WS.url(s"http://127.0.0.1:${Helpers.testServerPort}/missing").get()).status === 404
}
lazy val app = FakeApplication()
private def beforeAll = {
Play.start(app)
println("Play app started")
}
private def afterAll = {
Play.stop()
println("Play app stopped")
}
}
EDIT:
I realised my error was in the use the play.api.Play.start method and now have a simple trait to handle one startup and shutdown:
trait PlayServerRunning extends SpecificationLike {
override def map(fs: => Fragments): Fragments = Step(beforeAll) ^ fs ^ Step(afterAll)
private lazy val server = TestServer(Helpers.testServerPort)
private def beforeAll = {
server.start()
}
private def afterAll = {
server.stop()
}
}
That's on propose. Tests are executed in parallel (with implementation details according execution context).
If your tests need to be sequential, you must annotate in this way. e.g.:
"X" should {
sequential
"exp1" in { ... }
"exp2" in { ... }
}
I'm using the following code to hook into SBT's logging system to send the logging messages to another process accessible via a server setting:
extraLoggers := {
val clientLogger = FullLogger {
new Logger {
def log(level: Level.Value, message: => String): Unit =
if(level >= Level.Info) server.value.send(Json.arr("print", level.toString(), message))
def success(message: => String): Unit = server.value.send(Json.arr("print", "info", message))
def trace(t: => Throwable): Unit = server.value.send(Json.arr("print", "error", t.toString))
}
}
val currentFunction = extraLoggers.value
(key: ScopedKey[_]) => clientLogger +: currentFunction(key)
}
When I look at the output being spewed out on the other server process, I don't see the messages with green [success] tags appearing. Everything else (i.e. all the [info] messages and the red [error] messages) appear just fine.
Printing out clientLogger.successEnabled gives me true.
What am I doing wrong?
DISCLAIMER: Please use with care as the answer might be incomplete or even totally wrong.
After consulting the sources of sbt my understanding is that extraLoggers is a setting that is only "A function that provides additional loggers for a given setting." and these additional loggers are additional to StandardMain.console.
If it were possible, you would therefore have to set logManager to have a reference to extraLoggers and your own custom sbt.ConsoleOut in build.sbt, e.g.
logManager := LogManager.defaults(extraLoggers.value, new ConsoleOut {
val lockObject = System.out
def print(s: String): Unit = synchronized { print(s) }
def println(s: String): Unit = synchronized { println(s) }
def println(): Unit = synchronized {
System.out.println()
}
})
It won't however work since sbt.ConsoleOut is a sealed trait and hence there is no way to use it outside the file it was defined.
Having said that, I believe, in sbt 0.13.1, it's not possible to "intercept" the [success] message that's printed out when showSuccess is true as it comes out from ConsoleOut that's outside your control.
What you can do with extraLoggers is to have your own custom logging for tasks and streams.value.log.success("Succezz") should work.
The sample build.sbt with extraLoggers and a t task to demo the custom logger.
extraLoggers := {
val clientLogger = FullLogger {
new Logger {
def log(level: Level.Value, message: => String): Unit =
if(level >= Level.Info) println(s"+++ $message at $level")
def success(message: => String): Unit = println(s"+++ success: $message")
def trace(t: => Throwable): Unit = println(s"+++ trace: throwable: $t")
}
}
val currentFunction = extraLoggers.value
(key: ScopedKey[_]) => clientLogger +: currentFunction(key)
}
val t = taskKey[Unit]("Show extraLoggers")
t := {
println(s"Using extraLoggers")
val s: TaskStreams = streams.value
val log = s.log
log.debug("Saying hi...")
log.info("Hello!")
log.error("Error")
log.success("Succezz")
}
With the file, executing the t task gives the following output:
$ sbt
[info] Loading global plugins from /Users/jacek/.sbt/0.13/plugins
[info] Loading project definition from /Users/jacek/sandbox/sbt-0.13.1-extra-loggers/project
[info] Set current project to sbt-0-13-1-extra-loggers (in build file:/Users/jacek/sandbox/sbt-0.13.1-extra-loggers/)
[sbt-0-13-1-extra-loggers]> about
[info] This is sbt 0.13.1
[info] The current project is {file:/Users/jacek/sandbox/sbt-0.13.1-extra-loggers/}sbt-0-13-1-extra-loggers 0.1-SNAPSHOT
[info] The current project is built against Scala 2.10.3
[info] Available Plugins: com.typesafe.sbt.SbtGit, com.typesafe.sbt.SbtProguard, growl.GrowlingTests, org.sbtidea.SbtIdeaPlugin, np.Plugin, com.timushev.sbt.updates.UpdatesPlugin
[info] sbt, sbt plugins, and build definitions are using Scala 2.10.3
[sbt-0-13-1-extra-loggers]> t
Using extraLoggers
[info] Hello!
+++ Hello! at info
[error] Error
+++ Error at error
[success] Succezz
+++ success: Succezz
[success] Total time: 0 s, completed Dec 16, 2013 10:30:48 PM