These are chat archives for etorreborre/specs2

4th
Apr 2017
Maravedis
@ClementMalaingre
Apr 04 2017 13:23
Hello. Is there a way to make a fragment "lazy" ? Because I have a problem with a fragment which is created and ask for a database connection before beforeAll has run to create the said database.
Eric Torreborre
@etorreborre
Apr 04 2017 13:27
Sorry I'm not on my laptop right now can you please post a short version of your spec?
Maravedis
@ClementMalaingre
Apr 04 2017 13:29
Basically, I have a trait
trait WithTestDatabase extends Specification with BeforeAfterAll with CompareHelper {
<THINGS>
  def beforeAll(): Unit = {
    withDatabase { db =>
      db.run(
        sqlu"""
          DROP DATABASE IF EXISTS #$dbname;
          CREATE DATABASE #$dbname;
        """
      )
    }
  }

  def afterAll(): Unit = {
    withDatabase { db =>
      db.run(sqlu"DROP DATABASE #$dbname")
    }
  }
I then mix in that trait when I want to do tests with my database
But I'm trying to use Doobie checking, and it does this :
private def checkAnalysis(typeName: String, pos: Option[Pos], sql: String, analysis: ConnectionIO[Analysis]): Fragments =
      s"\n$typeName defined at ${loc(pos)}\n${sql.lines.map(s => "  " + s.trim).filterNot(_.isEmpty).mkString("\n")}" >> {
        transactor.trans.apply(analysis).attempt.unsafePerformIO match {
          case -\/(e) => Fragments("SQL Compiles and Typechecks" in failure(formatError(e.getMessage)))
          case \/-(a) => Fragments("SQL Compiles and Typechecks" in ok)
            Fragments.foreach(a.paramDescriptions)  { case (s, es) => s in assertEmpty(es, pos) }
            Fragments.foreach(a.columnDescriptions) { case (s, es) => s in assertEmpty(es, pos) }
        }
      }
The thing is, transactor.trans tries to connect to the db. But apparently it's not created yet.
Hence my question, is there a way to delay this.
or to make it happen after
Maravedis
@ClementMalaingre
Apr 04 2017 14:44
I guess that another way to ask the question is : is there a way to do something before the fragments are even constructed
Eric Torreborre
@etorreborre
Apr 04 2017 15:47
Yes there is. You can override the map method in SpecificationStructure:
  /** modify the fragments */
  def map(fs: =>Fragments): Fragments = fs
Maravedis
@ClementMalaingre
Apr 04 2017 16:13
Yeah, that's what i did. It's terribly ugly, though :
override def map(fs: =>Fragments) = {
    beforeAll
    super.map(fs) ^ fragmentFactory.step(afterAll)
  }
Eric Torreborre
@etorreborre
Apr 04 2017 16:21
Given the fact that the Doobie trait is side-effecting when building its fragments I don't think there's a better way
Maravedis
@ClementMalaingre
Apr 04 2017 16:24
Aaaand it doesn't work, for now.
Is there anyway to do anything before the list of fragments is constructed?
Eric Torreborre
@etorreborre
Apr 04 2017 16:26
Before the fs fragments?
Maravedis
@ClementMalaingre
Apr 04 2017 16:26
I guess
I tried this :
Eric Torreborre
@etorreborre
Apr 04 2017 16:27
Well, that's what map does since fs is by-name
Maravedis
@ClementMalaingre
Apr 04 2017 16:28
Doesn't appending something to fs evaluate it, tho?
I don't understand. The beforeAll in my snippet above is a synchrone method, and I still have a db error
Eric Torreborre
@etorreborre
Apr 04 2017 16:32
yes the appending will do the evaluation but you will still have started the db in beforeAll
Maravedis
@ClementMalaingre
Apr 04 2017 16:33
Yeah. I have a problem somewhere which is not related to specs2, I guess
Eric Torreborre
@etorreborre
Apr 04 2017 16:33
Actually I just had a look at our code
trait QueriesTypecheckSpec extends org.specs2.mutable.Specification with DoobieAnalysisSpec with RunningPostgres {

  // this needs to be started as soon as the specification class gets instantiated
  // because Doobie creates specification fragments *after* executing the test code
  beforeAll

  def tagged(name: String)(fs: =>Fragments): Fragments =
    section(name) ^ fs ^ section(name)

  lazy val transactor: Transactor[IOLite] =
    PostgreSQLDatabase.createUnsafe.transactor.transactorIOLite

}
and in RunningPostgres we have
  def beforeAll: Unit =
    PostgresRunner.startDb()
so the trick here is to trigger beforeAll in the body of the class before anything has even a chance to happen
the drawback is pretty bad error messages if beforeAll throws an exception because specs2 will basically say that your class can not be instantiated
Maravedis
@ClementMalaingre
Apr 04 2017 16:41
Ok, it seems to work! :D Thanks. And I will open an issue in Doobie to try and correct this. Thanks a lot for your help.
Eric Torreborre
@etorreborre
Apr 04 2017 16:44
Thanks for taking care of the Doobie side of things. There is a not well known specs2 feature which might help with this situation: https://etorreborre.github.io/specs2/guide/SPECS2-3.8.9/org.specs2.guide.CreateOnlineSpecifications.html