doIfOrElse does wrong branching with Map.size condition

I am crossposting from SOF http://stackoverflow.com/questions/25376542/gatling-doiforelse-does-wrong-branching-with-map-size-condition

In the following code snippet userMap is an empty map but still when it is run the else block is executed and I get error

`

package computerdatabase

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
import io.gatling.jsonpath._

class Test1 extends Simulation {

//Global Users
val userMap = scala.collection.concurrent.TrieMapString, String
//Randomiser
val rnd = new scala.util.Random
val httpConf = http
.baseURL(“http://demo1263864.mockable.io/”) // Here is the root for all relative URLs
.acceptHeader(“application/json”) // Here are the common headers
.doNotTrackHeader(“1”)
.acceptLanguageHeader(“en-US,en;q=0.5”)
.acceptEncodingHeader(“gzip, deflate”)
.userAgentHeader(“Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0”)

val headers_10 = Map(“Content-Type” → “application/json”) // Note the headers specific to a given request

val scn = scenario(“Simple Battle”)
.doIfOrElse(rnd.nextInt(3) > 2 || userMap.size == 0) {
println("++++++++++++++++" + userMap.size)
exec(
http(“If request “)
.get(“gatling1”)
.headers(headers_10)
.check(jsonPath(”$.result”).is(“SUCCESS”)) // Because we get warning when no password is sent and a new profile is created
) // executed if the session value stored in “myKey” is equal to “myValue”
.exec(session => {
println("++" + session)
userMap(“1”) = “2”
session
})
.exec(session => {
println("++" + userMap.size)
session
})
} {
val index = rnd.nextInt(userMap.size)
val userArray = userMap.toArray.map(x => Array(x._1, x._2))
exec(http(“else request”)
.get(“gatling1”)
.headers(headers_10)
.check(jsonPath("$.result").is(“SUCCESS”))) // Because we get warning when no password is sent and a new profile is created

}

setUp(scn.inject(atOnceUsers(1)).protocols(httpConf))
}

`

The output shows that the else block was executed even if userMap.size == 0

`

Simulation computerdatabase.Test1 started...
++++++++++++++++0
Exception in thread "main" java.lang.IllegalArgumentException: n must be positive
    at java.util.Random.nextInt(Random.java:300)
    at scala.util.Random.nextInt(Random.scala:65)
    at computerdatabase.Test1.<init>(Test1.scala:43)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
    at java.lang.Class.newInstance(Class.java:374)
    at io.gatling.core.runner.Runner.run(Runner.scala:37)
    at io.gatling.app.Gatling.start(Gatling.scala:235)
    at io.gatling.app.Gatling$.fromMap(Gatling.scala:54)
    at io.gatling.app.Gatling$.runGatling(Gatling.scala:79)
    at io.gatling.app.Gatling$.runGatling(Gatling.scala:58)
    at io.gatling.app.Gatling$.main(Gatling.scala:50)
    at io.gatling.app.Gatling.main(Gatling.scala)

`

Hi Neil,

The problem is that the println() if in your if block, as the two vals index and userArray get always evaluated, no matter what branch you take.
This is something important that you need to understand about Gatling’s DSL : it’s just a bunch of functions, calling each other, and chained conveniently.
Look at the signature of doIfOrElse :

`
def doIfOrElse(condition: Expression[Boolean])(thenNext: ChainBuilder)(elseNext: ChainBuilder

`

doIfOrElse takes three parameters :

  • The condition that gets evaluated to decide which branch to take
  • The chain that will be executed if the condition = true
  • The chain that will be executued if the conditin = false
    What happens here is that both blocks need to get evaluated, to have both chains ready to get executed when Gatling has to decide which chains to executed based on the condition.

This does means that both chains will be executed right away, only that the two blocks evaluated.

This explains why you see statements from both chains executed : println() from the first block, vals from the second block, and why not requests get fired either way.

You’ll need to wrap the statements in both blocks in exec blocks, so that their evaluation is deferred until the condition get evaluated :

`
.doIfOrElse(rnd.nextInt(3) > 2 || userMap.size == 0) {
exec(session => {
println("++++++++++++++++" + userMap.size)
session
})
.exec(
http(“If request “)
.get(“gatling1”)
.headers(headers_10)
.check(jsonPath(”$.result”).is(“SUCCESS”)) // Because we get warning when no password is sent and a new profile is created
) // executed if the session value stored in “myKey” is equal to “myValue”
.exec(session => {
println("++" + session)
userMap(“1”) = “2”
session
})
.exec(session => {
println("++" + userMap.size)
session
})
} {
exec(session => session.set(“index”, rnd.nextInt(userMap.size).set(“userArray”, userMap.toArray.map(x => Array(x._1, x._2))
.exec(http(“else request”)
.get(“gatling1”)
.headers(headers_10)
.check(jsonPath("$.result").is(“SUCCESS”))) // Because we get warning when no password is sent and a new profile is created

}

`

Hope this helps !

Cheers,

Pierre

Awesome !

Thank you for such detailed explanation. I think I was fundamentally treating the do if else blocks wrong.
So in my case my final code which worked is as follows

`
{//start of else block
exec(session => {

val index = rnd.nextInt(userMap.size)
val userArray = userMap.toArray.map(x => Array(x._1, x._2))

session.set(“x”, userArray(index)(0))
.set(“y”, userArray(index)(1))
})
.exec(http("else request")
`

Since index and ``userArray were only required for intermediate calculation I did not set them into session. So the conclusion is I could use these variable assignments etc in a session block but not in the if or else block. if and else block should purely contain exec().

Regards
Neil

Hi Neil,

You’re welcome :slight_smile:
This look fine indeed.Note that, if you need, you could always declare a local function in one of doIfOrElse blocks. Functions, as expected, are not evaluated until they’re used, so it would safe to define them there.

Cheers,

Pierre