Adding some custom DSL for session management

344 views
Skip to first unread message

John Arrowwood

unread,
Oct 8, 2014, 12:17:25 PM10/8/14
to gat...@googlegroups.com
I'm working on adding some syntactic sugar to the Gatling DSL to handle the kinds of things that I find myself doing often.  Some examples:

scenario( name )
.exec(
  http
( desc )
   
.get( path )
   
.check( jsonPath( "$.result[*]" ).saveAs( OBJECT_LIST ) )
)
// pick one of the objects at random
.set( OBJECT_JSON ).from( OBJECT_LIST )  // I may change the syntax to make it more obvious
.extract( "$.id" ).from( $(OBJECT_JSON) ).into( OBJECT_ID )



I'm following the "pimp my library pattern" that worked before.  But now it is not working correctly, and I'm stumped as to why.

import scala.util.Random

import io.gatling.core.session.{ Expression, Session }
import io.gatling.core.Predef._
import io.gatling.core.validation._
import io.gatling.core.structure.ChainBuilder
import io.gatling.core.json.Boon
import io.gatling.core.check.extractor.jsonpath._

object SessionManagement {
 
 
implicit class SessionManagementExtensions( val c : ChainBuilder ) {
 
    trait
SetSessionVariableAPI {
     
def from( src: String ) : ChainBuilder
     
def to[T]( value: Expression[T] ) : ChainBuilder
   
}

   
def set( dest: String ) = new SetSessionVariableAPI {

     
def from( src: String ) =
        c
.exec( session => {
          val list
= session( src ).as[Vector[String]]
          val i
= if ( list.size == 0 ) -1 else Random.nextInt( list.size )
          val value
= if ( i > 0 ) list(i) else "INVALID_" + dest
          session
.set( dest, value )
       
})

     
def to[T]( value: Expression[T] ) =
        c
.exec( session => session.set( dest, value(session) ) )

    }

    trait
ExtractFromInto { def into( name: String ) : ChainBuilder }
    trait
ExtractFrom { def from( json: Expression[String] ) : ExtractFromInto }

   
def extract( path: String ) = new ExtractFrom {
     
def from( json: Expression[String] ) = new ExtractFromInto {
        def into( name: String ) =
          c
.exec( session => {
            val parsed
= Boon.parse( json(session).toString )
            session
.set( name,  JsonPathExtractor.extractAll[String]( parsed, path ).get )
         
})
      }
   
}

 
}

}

This seems to compile (which doesn't mean I did it right, but at least it compiles), but when I try to use it, I get an error:

11:56:52.169 [ERROR] i.g.a.ZincCompiler$ - /src/rtde-testing/performance/rtde/simulations/com/cigna/rtde/scenarios/sandbox.scala:17: value set is not a member of io.gatling.core.structure.ChainBuilder
possible cause
: maybe a semicolon is missing before `value set'?
11:56:52.172 [ERROR] i.g.a.ZincCompiler$ -     .set( "FOO" ).to("bar")
11:56:52.173 [ERROR] i.g.a.ZincCompiler$ -      ^


Can you see what I'm doing wrong that might cause me to get this kind of an error?

Stéphane Landelle

unread,
Oct 8, 2014, 1:05:26 PM10/8/14
to gat...@googlegroups.com
You bootstrapped from a scenario, so you get a chain of ScenarioBuilders, when you can call "inject" at some point.

ScenarioBuilder doesn't extend ChainBuilder. Both extend StructureBuilder.

--
You received this message because you are subscribed to the Google Groups "Gatling User Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to gatling+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

John Arrowwood

unread,
Oct 8, 2014, 2:50:42 PM10/8/14
to gat...@googlegroups.com
That was one of my issues.  I'd love to make it work for either/or, and if that is easy to do, I'd love to know how to do it.

The other problem was, in order to use the implicit, I have to import SessionManagement._.  I'm sufficiently new to this that this requirement did not occur to me right away.

I have .set(VAR).to(VAL) working, including with session expansion of VAL.
I have .set(VAR).from(LIST) working, also, 
And I just finished making .extract(PATH).from(JSON).into(VAL) work

I you have the time, I'd love some feedback on my code.  Style, approach, scala-esqueness, whatever.  :)

object SessionManagement {

  implicit class SessionManagementExtensions( val c : ChainBuilder ) {

    def evaluate( session: Session, string: Expression[String] ) : Any = string(session) match {
      
case Success(x) => x
      
case Failure(msg) => throw new Error(msg)
    
}

    trait SetSessionVariableAPI {
      def from( src: String ) : ChainBuilder

     
def to( value: Any ) : ChainBuilder
   
}  

   
def set( dest: String ) = new SetSessionVariableAPI {

     
def from( src: String ) =  
        c
.exec( session => {
          session
.set( dest, session.attributes(src) match {
           
case s:String => s
           
case list:Vector[String] => {
              val i
= if ( list.size == 0 ) -1 else Random.nextInt( list.size )
             
if ( i >= 0 ) list(i)
             
else "INVALID_" + dest
           
}  
         
}  
       
)  
     
} )  
       
     
def to( value: Any ) =
        c
.exec( session => session.set( dest, value match {
         
case s:String => evaluate( session, s )
         
case _ => value
       
} ) )
   
}    
     
    trait
ExtractFromInto { def into( name: String ) : ChainBuilder }
    trait
ExtractFrom { def from( json: String ) : ExtractFromInto }
     
   
def extract( path: String ) = new ExtractFrom {
     
def from( json: String ) = new ExtractFromInto {
       
def into( name: String ) =
          c
.exec( session => {  
            val parsed
= Boon.parse( evaluate( session, json ).toString )
            session
.set( name,
             
JsonPathExtractor.extractAll[String]( parsed, path ) match {
               
case Success(x) => {
                 
if ( x.isEmpty ) ""
                 
else {
                    val list
= x.toList
                   
if ( list.size > 1 ) list.toVector
                   
else list.head  
                 
}  
               
}  
               
case Failure(msg) => throw new Error(msg)
             
}  
           
)  
         
})
     
}    
   
}  
 
}  
}


Stéphane Landelle

unread,
Oct 8, 2014, 3:21:18 PM10/8/14
to gat...@googlegroups.com
Not found of set/to and set/from. It looks like the same thing to me. And I would name it set/as.

And I would use Gatling Expression/EL instead of introducing some different behavior, like your random thing. There's a ".random()" in Gatling EL that does this job.

import io.gatling.core.structure.StructureBuilder
implicit class SessionManagementExtensions[T <: StructureBuilder[T]](val c: T) { // see T here? proper way to have it work with both ChainBuilder and ScenarioBuilder

def set(dest: String) = new {

def as(value: Expression[Any]): T =
c.exec { session =>
value(session).map(v => session.set(dest, v))
}
}
}

This way, you can write:
.set("foo").as("bar")
.set("foo").as(2)
.set("foo").as("${bar}")
.set("foo").as("${bar.random()}")

John Arrowwood

unread,
Oct 8, 2014, 4:48:22 PM10/8/14
to gat...@googlegroups.com
I much prefer your implementation of "as" over my implementation of "to" - much cleaner and shorter, like I figured it should be.

And I agree about set/from and set/to being unclear.  I hadn't settled on the API yet, I was just trying to prove I could do it at all.

When I first decided to implement .from() I had not yet discovered that EL has a .random() syntax.  I would like to do it that way, but because I use constants in place of session variable names, that complicates things a touch.

Instead of "${bar}" I write $(BAR) which converts to "${bar}", but my session variable name is checked at compile time.  To get the benefits of the EL random, I would need a syntax for how to express it using constants.  But I am torn on what that should look like:

.set( SOME_VAR ).as( random( LIST ) )
.set( SOME_VAR ).as( randomFrom( LIST ) )
.set( SOME_VAR ).as( randomOneOf( LIST ) )
.set( SOME_VAR ).as( oneOf( LIST ) )
.set( SOME_VAR ).as( anyOneOf( LIST ) )
.set( SOME_VAR ).as( pickFrom( LIST ) ) 
.set( SOME_VAR ).as( pickOne( LIST ) ) 
.set( SOME_VAR ).as( chooseFrom( LIST ) ) 
.set( SOME_VAR ).as( chooseOne( LIST ) ) 

Do you have an opinion on what it should be?  :)

John Arrowwood

unread,
Oct 8, 2014, 4:52:20 PM10/8/14
to gat...@googlegroups.com
Oh, by the way, what does .random() do if the element in question is a single value and not a vector/list?  


On Wednesday, October 8, 2014 12:21:18 PM UTC-7, Stéphane Landelle wrote:

Stéphane Landelle

unread,
Oct 8, 2014, 4:54:23 PM10/8/14
to gat...@googlegroups.com
a Failure, as expected. You're not supposed to call random on something that's not a Seq.
Reply all
Reply to author
Forward
0 new messages