Skip to content

Commit

Permalink
[BREAKING] Drop conflicting FiniteDuration implicit conversions, close
Browse files Browse the repository at this point in the history
…#4195

Motivation:

In 3.7, we introduced `Int => Expression[FiniteDuration]`  implicit conversions.
In some cases, for example `queryParam("foo", 1)`, we want the existing `T => Expression[T]` implicit conversion to trigger, but the new implicit seems to have higher priority, probably because it's more precise.

Modification:

* drop the new implicit Expression[FiniteDuration] conversions
* add a new function to Expression implicit conversion
* make sure that value to Expression implicit conversion can't be applied on Strings (we want to EL conversion to trigger)
* the Scala compiler doesn't properly figure out implicit conversions when using overloading. I couldn't figure out the rules here?! Use more overloads to remove some of the needs of implicit conversions and convert manually instead.

Result:

`queryParam("foo", 1)`, properly generate an `Expression[Int]` and not an `Expression[FiniteDuration]`.
  • Loading branch information
slandelle committed Jan 24, 2022
1 parent 26474c4 commit 4a92419
Show file tree
Hide file tree
Showing 10 changed files with 346 additions and 356 deletions.
5 changes: 0 additions & 5 deletions gatling-core/src/main/scala/io/gatling/core/Predef.scala
Expand Up @@ -19,7 +19,6 @@ package io.gatling.core
import scala.concurrent.duration._

import io.gatling.core.config.GatlingConfiguration
import io.gatling.core.session._

object Predef extends CoreDsl {

Expand Down Expand Up @@ -106,12 +105,8 @@ object Predef extends CoreDsl {
}

implicit def integerToFiniteDuration(i: Integer): FiniteDuration = intToFiniteDuration(i.toInt)
implicit def integerToFiniteDurationExpression(i: Integer): Expression[FiniteDuration] = intToFiniteDuration(i.toInt).expressionSuccess

implicit def intToFiniteDuration(i: Int): FiniteDuration = i.seconds
implicit def intToFiniteDurationExpression(i: Int): Expression[FiniteDuration] = i.seconds.expressionSuccess

implicit def jlongToFiniteDuration(i: java.lang.Long): FiniteDuration = i.toLong.seconds
implicit def jlongToFiniteDurationExpression(i: java.lang.Long): Expression[FiniteDuration] = i.toLong.seconds.expressionSuccess

}
Expand Up @@ -38,7 +38,24 @@ object NonValidable {
implicit val d1, d2 = exclude[ActionBuilder]

// Partially apply the type to be compatible with context bounds:
type Types[Scope] = {
type NonValidableTypes[Scope] = {
type DoesNotContain[X] = Exclude[Scope, X]
}
}

sealed trait NonValidableNonString

@SuppressWarnings(Array("org.wartremover.warts.PublicInference"))
object NonValidableNonString {
private val exclude = Exclude.list[NonValidableNonString]
implicit val a_1, a_2 = exclude[SessionAttribute]
implicit val b_1, b_2 = exclude[ChainBuilder]
implicit val c_1, c_2 = exclude[ScenarioBuilder]
implicit val d_1, d_2 = exclude[ActionBuilder]
implicit val e_1, e_2 = exclude[String]

// Partially apply the type to be compatible with context bounds:
type NonValidableNonStringTypes[Scope] = {
type DoesNotContain[X] = Exclude[Scope, X]
}
}
Expand All @@ -59,9 +76,11 @@ class NoUnexpectedValidationLifting[T](value: T) {
trait ValidationImplicits {

import NonValidable._
import NonValidableNonString._

implicit def stringToExpression[T: TypeCaster: Types[NonValidable]#DoesNotContain: ClassTag](string: String): Expression[T] = string.el
implicit def value2Success[T: Types[NonValidable]#DoesNotContain](value: T): Validation[T] = value.success
implicit def value2Expression[T: Types[NonValidable]#DoesNotContain](value: T): Expression[T] = value.expressionSuccess
implicit def stringToExpression[T: TypeCaster: ClassTag](string: String): Expression[T] = string.el
implicit def value2Success[T: NonValidableTypes[NonValidable]#DoesNotContain](value: T): Validation[T] = value.success
implicit def value2Expression[T: NonValidableNonStringTypes[NonValidableNonString]#DoesNotContain](value: T): Expression[T] = value.expressionSuccess
implicit def function2Expression[T](f: Session => T): Expression[T] = session => safely()(f(session))
implicit def value2NoUnexpectedValidationLifting[T](value: T): NoUnexpectedValidationLifting[T] = new NoUnexpectedValidationLifting(value)
}
Expand Up @@ -35,11 +35,6 @@ private[structure] trait Feeds[B] extends Execs[B] {
def feed(feederBuilder: FeederBuilder): B =
feed(feederBuilder, Feeds.OneExpression)

@deprecated("Feeding multiple records at once and translating names will be dropped in the next release", "3.7.0")
// we need this overload because Int => Expression[Int] and Int => Expression[FiniteDuration] suddenly conflict?!
def feed(feederBuilder: FeederBuilder, number: Int): B =
feed(feederBuilder, number.expressionSuccess)

/**
* Chain an action that will inject multiple data records into the virtual users' Session
*
Expand Down
99 changes: 83 additions & 16 deletions gatling-core/src/main/scala/io/gatling/core/structure/Loops.scala
Expand Up @@ -18,12 +18,13 @@ package io.gatling.core.structure

import java.util.UUID

import scala.concurrent.duration.FiniteDuration
import scala.concurrent.duration._

import io.gatling.commons.util.Clock
import io.gatling.commons.validation.Validation
import io.gatling.commons.validation._
import io.gatling.core.action.builder._
import io.gatling.core.session._
import io.gatling.core.session.el.El

private[structure] trait Loops[B] extends Execs[B] {

Expand Down Expand Up @@ -52,6 +53,17 @@ private[structure] trait Loops[B] extends Execs[B] {
)
}

// we need these overrides because we can't add an Int => Expression[FiniteDuration]
// that would clash with Int => Expression[Any] when need for queryParam
def during(duration: Int)(chain: ChainBuilder): B =
during(duration.seconds.expressionSuccess)(chain)
def during(duration: Int, counterName: String)(chain: ChainBuilder): B =
during(duration.seconds.expressionSuccess, counterName)(chain)
def during(duration: Int, exitASAP: Boolean)(chain: ChainBuilder): B =
during(duration.seconds.expressionSuccess, exitASAP = exitASAP)(chain)
def during(duration: Int, counterName: String, exitASAP: Boolean)(chain: ChainBuilder): B =
during(duration.seconds.expressionSuccess, counterName, exitASAP)(chain)

@SuppressWarnings(Array("org.wartremover.warts.DefaultArguments"))
def during(duration: Expression[FiniteDuration], counterName: String = UUID.randomUUID.toString, exitASAP: Boolean = true)(chain: ChainBuilder): B =
clockBasedLoop(
Expand Down Expand Up @@ -87,22 +99,77 @@ private[structure] trait Loops[B] extends Execs[B] {
conditionValue <- condition(session)
} yield conditionValue && clock.nowMillis - session.loopTimestampValue(counterName) <= durationValue.toMillis

@SuppressWarnings(Array("org.wartremover.warts.DefaultArguments"))
def asLongAsDuring(
condition: Expression[Boolean],
duration: Expression[FiniteDuration],
counterName: String = UUID.randomUUID.toString,
exitASAP: Boolean = true
)(chain: ChainBuilder): B =
// we need all those overloads because of Scala bug with implicit conversions inference
def asLongAsDuring[D](condition: String, duration: Int)(chain: ChainBuilder): B =
asLongAsDuring(condition, duration, UUID.randomUUID.toString)(chain)
def asLongAsDuring[D](condition: String, duration: Int, counterName: String)(chain: ChainBuilder): B =
asLongAsDuring(condition, duration, counterName, exitASAP = true)(chain)
def asLongAsDuring[D](condition: String, duration: Int, exitASAP: Boolean)(chain: ChainBuilder): B =
asLongAsDuring(condition, duration, UUID.randomUUID.toString, exitASAP)(chain)
def asLongAsDuring[D](condition: String, duration: Int, counterName: String, exitASAP: Boolean)(chain: ChainBuilder): B =
asLongAsDuring(condition, duration.seconds.expressionSuccess, counterName, exitASAP)(chain)

def asLongAsDuring[D](condition: String, duration: Expression[FiniteDuration])(chain: ChainBuilder): B =
asLongAsDuring(condition, duration, UUID.randomUUID.toString)(chain)
def asLongAsDuring[D](condition: String, duration: Expression[FiniteDuration], counterName: String)(chain: ChainBuilder): B =
asLongAsDuring(condition, duration, counterName, exitASAP = true)(chain)
def asLongAsDuring[D](condition: String, duration: Expression[FiniteDuration], exitASAP: Boolean)(chain: ChainBuilder): B =
asLongAsDuring(condition, duration, UUID.randomUUID.toString, exitASAP)(chain)
def asLongAsDuring[D](condition: String, duration: Expression[FiniteDuration], counterName: String, exitASAP: Boolean)(chain: ChainBuilder): B =
asLongAsDuring(condition.el[Boolean], duration, counterName, exitASAP)(chain)

def asLongAsDuring[D](condition: Expression[Boolean], duration: Int)(chain: ChainBuilder): B =
asLongAsDuring(condition, duration, UUID.randomUUID.toString)(chain)
def asLongAsDuring[D](condition: Expression[Boolean], duration: Int, counterName: String)(chain: ChainBuilder): B =
asLongAsDuring(condition, duration, counterName, exitASAP = true)(chain)
def asLongAsDuring[D](condition: Expression[Boolean], duration: Int, exitASAP: Boolean)(chain: ChainBuilder): B =
asLongAsDuring(condition, duration, UUID.randomUUID.toString, exitASAP)(chain)
def asLongAsDuring[D](condition: Expression[Boolean], duration: Int, counterName: String, exitASAP: Boolean)(chain: ChainBuilder): B =
asLongAsDuring(condition, duration.seconds.expressionSuccess, counterName, exitASAP)(chain)

def asLongAsDuring[D](condition: Expression[Boolean], duration: Expression[FiniteDuration])(chain: ChainBuilder): B =
asLongAsDuring(condition, duration, UUID.randomUUID.toString)(chain)
def asLongAsDuring[D](condition: Expression[Boolean], duration: Expression[FiniteDuration], counterName: String)(chain: ChainBuilder): B =
asLongAsDuring(condition, duration, counterName, exitASAP = true)(chain)
def asLongAsDuring[D](condition: Expression[Boolean], duration: Expression[FiniteDuration], exitASAP: Boolean)(chain: ChainBuilder): B =
asLongAsDuring(condition, duration, UUID.randomUUID.toString, exitASAP)(chain)
def asLongAsDuring[D](condition: Expression[Boolean], duration: Expression[FiniteDuration], counterName: String, exitASAP: Boolean)(chain: ChainBuilder): B =
clockBasedLoop(continueCondition(condition, duration, counterName), chain, counterName, exitASAP, AsLongAsDuringLoopType)

@SuppressWarnings(Array("org.wartremover.warts.DefaultArguments"))
def doWhileDuring(
condition: Expression[Boolean],
duration: Expression[FiniteDuration],
counterName: String = UUID.randomUUID.toString,
exitASAP: Boolean = true
)(chain: ChainBuilder): B =
def doWhileDuring[D](condition: String, duration: Int)(chain: ChainBuilder): B =
doWhileDuring(condition, duration, UUID.randomUUID.toString)(chain)
def doWhileDuring[D](condition: String, duration: Int, counterName: String)(chain: ChainBuilder): B =
doWhileDuring(condition, duration, counterName, exitASAP = true)(chain)
def doWhileDuring[D](condition: String, duration: Int, exitASAP: Boolean)(chain: ChainBuilder): B =
doWhileDuring(condition, duration, UUID.randomUUID.toString, exitASAP)(chain)
def doWhileDuring[D](condition: String, duration: Int, counterName: String, exitASAP: Boolean)(chain: ChainBuilder): B =
doWhileDuring(condition, duration.seconds.expressionSuccess, counterName, exitASAP)(chain)

def doWhileDuring[D](condition: String, duration: Expression[FiniteDuration])(chain: ChainBuilder): B =
doWhileDuring(condition, duration, UUID.randomUUID.toString)(chain)
def doWhileDuring[D](condition: String, duration: Expression[FiniteDuration], counterName: String)(chain: ChainBuilder): B =
doWhileDuring(condition, duration, counterName, exitASAP = true)(chain)
def doWhileDuring[D](condition: String, duration: Expression[FiniteDuration], exitASAP: Boolean)(chain: ChainBuilder): B =
doWhileDuring(condition, duration, UUID.randomUUID.toString, exitASAP)(chain)
def doWhileDuring[D](condition: String, duration: Expression[FiniteDuration], counterName: String, exitASAP: Boolean)(chain: ChainBuilder): B =
doWhileDuring(condition.el[Boolean], duration, counterName, exitASAP)(chain)

def doWhileDuring[D](condition: Expression[Boolean], duration: Int)(chain: ChainBuilder): B =
doWhileDuring(condition, duration, UUID.randomUUID.toString)(chain)
def doWhileDuring[D](condition: Expression[Boolean], duration: Int, counterName: String)(chain: ChainBuilder): B =
doWhileDuring(condition, duration, counterName, exitASAP = true)(chain)
def doWhileDuring[D](condition: Expression[Boolean], duration: Int, exitASAP: Boolean)(chain: ChainBuilder): B =
doWhileDuring(condition, duration, UUID.randomUUID.toString, exitASAP)(chain)
def doWhileDuring[D](condition: Expression[Boolean], duration: Int, counterName: String, exitASAP: Boolean)(chain: ChainBuilder): B =
doWhileDuring(condition, duration.seconds.expressionSuccess, counterName, exitASAP)(chain)

def doWhileDuring[D](condition: Expression[Boolean], duration: Expression[FiniteDuration])(chain: ChainBuilder): B =
doWhileDuring(condition, duration, UUID.randomUUID.toString)(chain)
def doWhileDuring[D](condition: Expression[Boolean], duration: Expression[FiniteDuration], counterName: String)(chain: ChainBuilder): B =
doWhileDuring(condition, duration, counterName, exitASAP = true)(chain)
def doWhileDuring[D](condition: Expression[Boolean], duration: Expression[FiniteDuration], exitASAP: Boolean)(chain: ChainBuilder): B =
doWhileDuring(condition, duration, UUID.randomUUID.toString, exitASAP)(chain)
def doWhileDuring[D](condition: Expression[Boolean], duration: Expression[FiniteDuration], counterName: String, exitASAP: Boolean)(chain: ChainBuilder): B =
clockBasedLoop(continueCondition(condition, duration, counterName), chain, counterName, exitASAP, DoWhileDuringType)

private def simpleLoop(
Expand Down

0 comments on commit 4a92419

Please sign in to comment.