Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scopes BeforeAndAfter class vs trait #457

Closed
remster opened this issue Feb 23, 2016 · 8 comments
Closed

scopes BeforeAndAfter class vs trait #457

remster opened this issue Feb 23, 2016 · 8 comments

Comments

@remster
Copy link

@remster remster commented Feb 23, 2016

Hi,

I see unexpected behaviour with Scope. The following snippet:

import org.junit.runner.RunWith
import org.specs2.mock._
import org.specs2.mutable.Specification
import org.specs2.runner.JUnitRunner
import org.specs2.specification._
/**
 * A reference specs2 test case that distills/illustrates the available facilities
 */
@RunWith(classOf[JUnitRunner])
class ReferenceSpecs2Test extends Specification
  with Mockito {

  trait MyScope extends Scope with org.specs2.mutable.BeforeAfter {
    def before() {
      println("before Scoped")
    }

    def after() {
      println("after Scoped")
    }

    println("Scope Created")
  }

  "Spec2Test1" should {
    "should catch invocations" in new MyScope {
      println("test with scope")
    }
  }
}

behaves as expected:

Testing started at 15:43 ...
Scope Created
before Scoped
test with scope
after Scoped
Spec2Test1 should
should catch invocations

Process finished with exit code 0

However, when I replace 'trait' with 'class' (and it is useful when i want that scope parametrised) I see unexpected behaviour:

Testing started at 15:47 ...
before Scoped <- before called too soon
Scope Created
after Scoped <-after called before test's body
before Scoped <- before called 2nd time
test with scope
after Scoped
Spec2Test1 should
should catch invocations

Process finished with exit code 0

It's hard to reason what code gets generated with the spec2 notation: '"foo" in new Scope {}'
so it's also hard to conclude whether this behaviour is consistent with some underlying principle I am clearly unaware. Reading: http://etorreborre.github.io/specs2/guide/SPECS2-3.0/org.specs2.guide.Scopes.html - no assertions are made wrt the choice of class/trait and it thus creates an impression of being an arbitrary choice and that the body of the test simply inherits from the scope that it's given.

Is that related to multiple inheritance evasive action?

@etorreborre

This comment has been minimized.

Copy link
Owner

@etorreborre etorreborre commented Feb 23, 2016

I think that this is due to the lifecycle of classes and traits being different with DelayedInit which Scope is inheriting. It would actually be nice to avoid using scopes altogether. I you could tell me more about your use case and why you are using before/after in a scope I could try to find an alternative.

@remster

This comment has been minimized.

Copy link
Author

@remster remster commented Feb 23, 2016

Thanks Eric.

It's a suite of local Spark tests - working off a local SparkContext and
filesystem (a dedicated folder within it). Each test is fed different input
(different initial content of said dedicated folder). And of course, when
each such tests finishes, it should remove the mess it created.
Why parametrised scope is good to capture this?

  • because it can be given the parameters that can populate the folder
    (including the path to that folder - as perhaps we want each test run in
    its own - if only to land with some parallelism).
  • because it will clean up when done.

global afterEach is an obvious alternative, but it doesn't do: "each test
running from a different folder"

I have to say that I've learnt to like scopes quite a bit.
thanks again

On Tue, Feb 23, 2016 at 5:08 PM, Eric Torreborre notifications@github.com
wrote:

I think that this is due to the lifecycle of classes and traits being
different with DelayedInit which Scope is inheriting. It would actually
be nice to avoid using scopes altogether. I you could tell me more about
your use case and why you are using before/after in a scope I could try to
find an alternative.


Reply to this email directly or view it on GitHub
#457 (comment).

@etorreborre

This comment has been minimized.

Copy link
Owner

@etorreborre etorreborre commented Feb 23, 2016

Would a function like this help?

def withFolder[T : AsResult](folder: String)(runWith: Data => T): T =
  try {
    val data: Data = load(folder)
    runWith(data)
  } finally cleanup(folder)

"this should work for test 1" >> withFolder("test1") { data =>
  runSpark(data) must beOk
}

"this should work for test2" >> withFolder("test2") { data =>
  runSpark(data) must beOk
}
@remster

This comment has been minimized.

Copy link
Author

@remster remster commented Feb 25, 2016

Thanks.,

I tried playing with this, but really a more representative example is this:

def withFolder[T : AsResult](folder: String)(runWith: Data => T): T =
  try {
    val data: Data = load(folder)
    runWith(data)
  } finally cleanup(folder)

"this should work for test 1" >> withFolder("test1") { data =>
  runSpark("test1") must beOk
}

"this should work for test2" >> withFolder("test2") { data =>
  runSpark("test2") must beOk
}

showing why a parametrised scope remains more attractive (hard to
avoid naming the same folder twice)

On Tue, Feb 23, 2016 at 9:35 PM, Eric Torreborre notifications@github.com
wrote:

Would a function like this help?

def withFolder[T : AsResult](folder: String)(runWith: Data => T): T =
try {
val data: Data = load(folder)
runWith(data)
} finally cleanup(folder)
"this should work for test 1" >> withFolder("test1") { data =>
runSpark(data) must beOk
}
"this should work for test2" >> withFolder("test2") { data =>
runSpark(data) must beOk
}


Reply to this email directly or view it on GitHub
#457 (comment).

@etorreborre

This comment has been minimized.

Copy link
Owner

@etorreborre etorreborre commented Feb 25, 2016

data is whatever you want, including the folder name:

def withFolder[T : AsResult](folder: String)(runWith: String => T): T =
  try {
    val data: Data = load(folder)
    runWith(folder)
  } finally cleanup(folder)

"this should work for test 1" >> withFolder("test1") { folder =>
  runSpark(folder) must beOk
}

"this should work for test2" >> withFolder("test2") { folder =>
  runSpark(folder) must beOk
}
@etorreborre

This comment has been minimized.

Copy link
Owner

@etorreborre etorreborre commented Feb 25, 2016

I am curious about why the code doesn't highlight in your previous comment...

@remster

This comment has been minimized.

Copy link
Author

@remster remster commented Feb 25, 2016

The code didn't highlight probably because i've been using my gmail client
and "might have" highlighted something to bring to your attention.
With respect to your suggestion, i missed its crux first-time-round - it
works excellent in the offending scenario. Pity it doesn't contaminate
the test's scope (as Scope does).. but one can also think this is perhaps a
good thing.

thanks again

On Thu, Feb 25, 2016 at 8:55 AM, Eric Torreborre notifications@github.com
wrote:

I am curious about why the code doesn't highlight in your previous
comment...


Reply to this email directly or view it on GitHub
#457 (comment).

@etorreborre

This comment has been minimized.

Copy link
Owner

@etorreborre etorreborre commented Feb 25, 2016

Inheriting a trait providing attributes was what give me the idea of Scope. But the pitfalls are numerous and if we mix-in Before we bring DelayedInit under the covers which might even disappear in a subsequent version of Scala. A good old function is a lot more reliable even if a bit more verbose :-).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants
You can’t perform that action at this time.