diff --git a/karate-gatling/pom.xml b/karate-gatling/pom.xml index e28c28d0a..5be3d95e4 100644 --- a/karate-gatling/pom.xml +++ b/karate-gatling/pom.xml @@ -32,48 +32,83 @@ scala-library 2.13.9 + + org.junit.jupiter + junit-jupiter-api + ${junit5.version} + + + - src/main/scala - - + + src/test/resources - src/test/scala + src/test/java **/*.java - **/*.scala - + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + default-compile + + none + + + default-testCompile + + none + + + + net.alchim31.maven scala-maven-plugin 4.8.1 + + + -Jbackend:GenBCode + -Jdelambdafy:method + -release:11 + -deprecation + -feature + -unchecked + -language:implicitConversions + -language:postfixOps + + - - compile - testCompile - - - - -Jbackend:GenBCode - -Jdelambdafy:method - -release:11 - -deprecation - -feature - -unchecked - -language:implicitConversions - -language:postfixOps - - + scala-compile + compile + + add-source + compile + + + scala-test-compile + test-compile + + add-source + testCompile + + + io.gatling diff --git a/karate-gatling/src/main/java/com/intuit/karate/gatling/javaapi/KarateDsl.java b/karate-gatling/src/main/java/com/intuit/karate/gatling/javaapi/KarateDsl.java new file mode 100644 index 000000000..20f211806 --- /dev/null +++ b/karate-gatling/src/main/java/com/intuit/karate/gatling/javaapi/KarateDsl.java @@ -0,0 +1,51 @@ +package com.intuit.karate.gatling.javaapi; + +import java.util.Map; +import java.util.Set; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +import io.gatling.javaapi.core.*; +import io.gatling.javaapi.core.internal.Converters; +import io.gatling.javaapi.http.*; +import scala.collection.mutable.ArrayBuffer; +import scala.collection.mutable.Buffer; +import scala.collection.immutable.Seq; + +import com.intuit.karate.gatling.PreDef; +import com.intuit.karate.gatling.javaapi.KarateUriPattern; +import com.intuit.karate.gatling.javaapi.KarateUriPattern.KarateUriPatternBuilder; +import com.intuit.karate.gatling.MethodPause; + +import static io.gatling.javaapi.core.CoreDsl.*; +import static io.gatling.javaapi.http.HttpDsl.*; + +public class KarateDsl { + + public static KarateUriPatternBuilder uri(String uri) { + return new KarateUriPatternBuilder(uri); + } + + public static KarateProtocolBuilder karateProtocol(KarateUriPattern... patterns) { + return new KarateProtocolBuilder(Arrays.stream(patterns).collect(Collectors.toMap(KarateUriPattern::getUri, pattern -> Converters.toScalaSeq(pattern.getPauses())))); + } + + public static ActionBuilder karateFeature(String name, String... tags) { + return () -> PreDef.karateFeature(name, Converters.toScalaSeq(tags)); + } + + + public static ActionBuilder karateSet(String key, final Function supplier) { + return () -> PreDef.karateSet(key, session -> supplier.apply(new Session(session))); + } + + public static MethodPause method(String method, int durationInMillis) { + return new MethodPause(method, durationInMillis); + } + +} diff --git a/karate-gatling/src/main/java/com/intuit/karate/gatling/javaapi/KarateProtocolBuilder.java b/karate-gatling/src/main/java/com/intuit/karate/gatling/javaapi/KarateProtocolBuilder.java new file mode 100644 index 000000000..8ee4b3d03 --- /dev/null +++ b/karate-gatling/src/main/java/com/intuit/karate/gatling/javaapi/KarateProtocolBuilder.java @@ -0,0 +1,42 @@ +package com.intuit.karate.gatling.javaapi; + + +import java.util.Collections; +import java.util.function.BiFunction; + +import com.intuit.karate.Runner; +import com.intuit.karate.core.ScenarioRuntime; +import com.intuit.karate.gatling.KarateProtocol; +import com.intuit.karate.gatling.MethodPause; +import com.intuit.karate.http.HttpRequest; + +import io.gatling.core.protocol.Protocol; +import io.gatling.javaapi.core.ProtocolBuilder; +import io.gatling.javaapi.core.internal.Converters; +import scala.collection.immutable.Seq; +import scala.collection.immutable.Map; + +public class KarateProtocolBuilder implements ProtocolBuilder { + + public BiFunction nameResolver; + public Runner.Builder runner = new Runner.Builder(); + + private final Map> uriPatterns; + + // Takes a JAVA Map (easier for testing) containaing SCALA MethodPauses (easier to read, save an extra Java MethodPause class and another conversion) + public KarateProtocolBuilder(java.util.Map> uriPatterns) { + this.uriPatterns = Converters.toScalaMap(uriPatterns); + } + + @Override + public KarateProtocol protocol() { + KarateProtocol protocol = new KarateProtocol(uriPatterns); + if (nameResolver != null) { + protocol.nameResolver_$eq((req, sr) -> nameResolver.apply(req, sr)); + } + protocol.runner_$eq(runner); + return protocol; + } + + +} diff --git a/karate-gatling/src/main/java/com/intuit/karate/gatling/javaapi/KarateUriPattern.java b/karate-gatling/src/main/java/com/intuit/karate/gatling/javaapi/KarateUriPattern.java new file mode 100644 index 000000000..40ebae3db --- /dev/null +++ b/karate-gatling/src/main/java/com/intuit/karate/gatling/javaapi/KarateUriPattern.java @@ -0,0 +1,58 @@ +package com.intuit.karate.gatling.javaapi; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.intuit.karate.gatling.MethodPause; + +/** CLass to be used as a parameter of KarateDsl.karateProtocol. + * + * Instances are obtained from KarateDsl.uri() chained with nil() or pauseFor() and won't typically be created directly. + */ +public class KarateUriPattern { + final String uri; + final List pauses; + + KarateUriPattern(String uri, List pauses) { + this.uri = uri; + this.pauses = pauses; + } + + String getUri() { + return uri; + } + + List getPauses() { + return pauses; + } + + public static class KarateUriPatternBuilder { + private final String uri; + + KarateUriPatternBuilder(String uri) { + this.uri = uri; + } + + /** + * Creates a uriPattern with no pauses + * @return + */ + public KarateUriPattern nil() { + return new KarateUriPattern(uri, Collections.emptyList()); + } + + public KarateUriPattern pauseFor(String method, int durationInMillis) { + return pauseFor(KarateDsl.method(method, durationInMillis)); + } + + public KarateUriPattern pauseFor(String method1, int durationInMillis1, String method2, int durationInMillis2) { + return pauseFor(KarateDsl.method(method1, durationInMillis1), KarateDsl.method(method2, durationInMillis2)); + } + + public KarateUriPattern pauseFor(MethodPause... pauses) { + return new KarateUriPattern(uri, Arrays.asList(pauses)); + } + } +} + diff --git a/karate-gatling/src/main/scala/com/intuit/karate/gatling/Dummy.java b/karate-gatling/src/main/scala/com/intuit/karate/gatling/Dummy.java deleted file mode 100644 index c46cf1d20..000000000 --- a/karate-gatling/src/main/scala/com/intuit/karate/gatling/Dummy.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.intuit.karate.gatling; - -public class Dummy { - // just to keep the javadoc rule in maven central happy -} diff --git a/karate-gatling/src/test/java/com/intuit/karate/gatling/javaapi/KarateProtocolBuilderTest.java b/karate-gatling/src/test/java/com/intuit/karate/gatling/javaapi/KarateProtocolBuilderTest.java new file mode 100644 index 000000000..49303f828 --- /dev/null +++ b/karate-gatling/src/test/java/com/intuit/karate/gatling/javaapi/KarateProtocolBuilderTest.java @@ -0,0 +1,66 @@ +package com.intuit.karate.gatling.javaapi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.function.BiFunction; + +import org.junit.jupiter.api.Test; + +import com.intuit.karate.Runner; +import com.intuit.karate.core.ScenarioRuntime; +import com.intuit.karate.http.HttpRequest; + +import io.gatling.javaapi.core.internal.Converters; + +import com.intuit.karate.gatling.KarateProtocol; +import com.intuit.karate.gatling.MethodPause; + + +class KarateProtocolBuilderTest { + + // Validates that the supplied nameResolver, runner and uriPatterns are taken into account. + @Test + void karateProtocol() throws Exception { + KarateProtocolBuilder protocolBuilder = new KarateProtocolBuilder(Collections.singletonMap("foo", Converters.toScalaSeq(Collections.singletonList(new MethodPause("get", 110))))); + + protocolBuilder.nameResolver = (req, sr) -> "test name resolver"; + + protocolBuilder.runner.karateEnv("test"); + + KarateProtocol protocol = protocolBuilder.protocol(); + + assertEquals("test name resolver", protocol.nameResolver().apply(null, null)); + + Field envField = Runner.Builder.class.getDeclaredField("env"); + envField.setAccessible(true); + String env = (String)envField.get(protocol.runner()); + + assertEquals("test", env); + + assertEquals(110, protocol.pauseFor("foo", "get")); + } + + @Test + void uriPatterns() { + + KarateProtocol protocol = KarateDsl.karateProtocol( + KarateDsl.uri("foo").nil(), + KarateDsl.uri("bar").pauseFor("get", 110, "post", 220) + ).protocol(); + + assertEquals(110, protocol.pauseFor("bar", "get")); + assertEquals(220, protocol.pauseFor("bar", "post")); + assertEquals(0, protocol.pauseFor("bar", "put")); + assertEquals(0, protocol.pauseFor("foo", "get")); + assertEquals(0, protocol.pauseFor("foobar", "get")); + + assertTrue(protocol.pathMatches("/foo").isDefined()); + assertTrue(protocol.pathMatches("/bar").isDefined()); + assertFalse(protocol.pathMatches("/foobar").isDefined()); + + } +} diff --git a/karate-gatling/src/test/java/mock/CatsChainedSimulation.java b/karate-gatling/src/test/java/mock/CatsChainedSimulation.java new file mode 100644 index 000000000..6149b4e81 --- /dev/null +++ b/karate-gatling/src/test/java/mock/CatsChainedSimulation.java @@ -0,0 +1,45 @@ +package mock; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Random; +import java.util.stream.Stream; +import static com.intuit.karate.gatling.javaapi.KarateDsl.*; +import static io.gatling.javaapi.core.CoreDsl.details; +import static io.gatling.javaapi.core.CoreDsl.feed; +import static io.gatling.javaapi.core.CoreDsl.rampUsers; +import static io.gatling.javaapi.core.CoreDsl.scenario; + +import io.gatling.javaapi.core.*; + +public class CatsChainedSimulation extends Simulation { + + public CatsChainedSimulation() { + MockUtils.startServer(0); + + ProtocolBuilder protocol = karateProtocol(uri("/cats/{id}").nil()); + + Iterator> feeder = Stream.generate(() -> Collections.singletonMap("name", new Random().nextInt(20)+"-name")).iterator(); + + ScenarioBuilder createAndRead = scenario("createAndRead") + .group("createAndRead").on( + feed(() -> feeder) + .exec(karateSet("name", session -> session.getString("name"))) + .exec(karateFeature("classpath:mock/cats-chained.feature@name=create")) + // for demo: injecting a new variable name expected by the 'read' feature + .exec(karateSet("expectedName", session -> session.getString("name"))) + .exec(karateFeature("classpath:mock/cats-chained.feature@name=read")).exec(session -> { + System.out.println("*** id in gatling: " + session.getString("id")); + System.out.println("*** session status in gatling: " + session.asScala().status()); + return session; + })); + + + setUp( + createAndRead.injectOpen(rampUsers(10).during(5)).protocols(protocol) + ).assertions(details("createAndRead").failedRequests().percent().is(0d)); + + } + +} diff --git a/karate-gatling/src/test/java/mock/CatsSimulation.java b/karate-gatling/src/test/java/mock/CatsSimulation.java new file mode 100644 index 000000000..f84ac89a7 --- /dev/null +++ b/karate-gatling/src/test/java/mock/CatsSimulation.java @@ -0,0 +1,43 @@ +package mock; + +import com.intuit.karate.gatling.javaapi.KarateProtocolBuilder; + +import io.gatling.javaapi.core.ScenarioBuilder; +import io.gatling.javaapi.core.Simulation; + +import static io.gatling.javaapi.core.CoreDsl.*; +import static com.intuit.karate.gatling.javaapi.KarateDsl.*; + +public class CatsSimulation extends Simulation { + + public CatsSimulation() { + + MockUtils.startServer(0); + + KarateProtocolBuilder protocol = karateProtocol( + uri("/cats/{id}").nil(), + uri("/cats").pauseFor(method("get", 15), method("post", 25) + )); + + protocol.nameResolver = (req, ctx) -> req.getHeader("karate-name"); + protocol.runner.karateEnv("perf"); + + ScenarioBuilder create = scenario("create").exec(karateFeature("classpath:mock/cats-create.feature")).exec(session -> { + System.out.println("*** id in gatling: " + session.getString("id")); + System.out.println("*** session status in gatling: " + session.asScala().status()); + return session; + }); + + ScenarioBuilder delete = scenario("delete").group("delete cats").on( + exec(karateFeature("classpath:mock/cats-delete.feature@name=delete")) + ); + + ScenarioBuilder custom = scenario("custom").exec(karateFeature("classpath:mock/custom-rpc.feature")); + + setUp( + create.injectOpen(rampUsers(10).during(5)).protocols(protocol), + delete.injectOpen(rampUsers(5).during(5)).protocols(protocol), + custom.injectOpen(rampUsers(10).during(5)).protocols(protocol) + ); + } +} diff --git a/karate-gatling/src/test/scala/mock/MockUtils.java b/karate-gatling/src/test/java/mock/MockUtils.java similarity index 100% rename from karate-gatling/src/test/scala/mock/MockUtils.java rename to karate-gatling/src/test/java/mock/MockUtils.java diff --git a/karate-gatling/src/test/scala/mock/cats-chained.feature b/karate-gatling/src/test/java/mock/cats-chained.feature similarity index 100% rename from karate-gatling/src/test/scala/mock/cats-chained.feature rename to karate-gatling/src/test/java/mock/cats-chained.feature diff --git a/karate-gatling/src/test/scala/mock/cats-create.feature b/karate-gatling/src/test/java/mock/cats-create.feature similarity index 100% rename from karate-gatling/src/test/scala/mock/cats-create.feature rename to karate-gatling/src/test/java/mock/cats-create.feature diff --git a/karate-gatling/src/test/scala/mock/cats-delete-one.feature b/karate-gatling/src/test/java/mock/cats-delete-one.feature similarity index 100% rename from karate-gatling/src/test/scala/mock/cats-delete-one.feature rename to karate-gatling/src/test/java/mock/cats-delete-one.feature diff --git a/karate-gatling/src/test/scala/mock/cats-delete.feature b/karate-gatling/src/test/java/mock/cats-delete.feature similarity index 100% rename from karate-gatling/src/test/scala/mock/cats-delete.feature rename to karate-gatling/src/test/java/mock/cats-delete.feature diff --git a/karate-gatling/src/test/scala/mock/custom-rpc.feature b/karate-gatling/src/test/java/mock/custom-rpc.feature similarity index 100% rename from karate-gatling/src/test/scala/mock/custom-rpc.feature rename to karate-gatling/src/test/java/mock/custom-rpc.feature diff --git a/karate-gatling/src/test/scala/mock/mock.feature b/karate-gatling/src/test/java/mock/mock.feature similarity index 100% rename from karate-gatling/src/test/scala/mock/mock.feature rename to karate-gatling/src/test/java/mock/mock.feature diff --git a/karate-gatling/src/test/scala/mock/CatsChainedSimulation.scala b/karate-gatling/src/test/scala/mock/CatsChainedScalaSimulation.scala similarity index 96% rename from karate-gatling/src/test/scala/mock/CatsChainedSimulation.scala rename to karate-gatling/src/test/scala/mock/CatsChainedScalaSimulation.scala index 8d8cee160..e555d62e1 100644 --- a/karate-gatling/src/test/scala/mock/CatsChainedSimulation.scala +++ b/karate-gatling/src/test/scala/mock/CatsChainedScalaSimulation.scala @@ -6,7 +6,7 @@ import io.gatling.core.Predef._ import scala.concurrent.duration._ import scala.util.Random -class CatsChainedSimulation extends Simulation { +class CatsChainedScalaSimulation extends Simulation { MockUtils.startServer(0) diff --git a/karate-gatling/src/test/scala/mock/CatsSimulation.scala b/karate-gatling/src/test/scala/mock/CatsScalaSimulation.scala similarity index 95% rename from karate-gatling/src/test/scala/mock/CatsSimulation.scala rename to karate-gatling/src/test/scala/mock/CatsScalaSimulation.scala index 0c354195c..2bdc6f103 100644 --- a/karate-gatling/src/test/scala/mock/CatsSimulation.scala +++ b/karate-gatling/src/test/scala/mock/CatsScalaSimulation.scala @@ -6,7 +6,7 @@ import io.gatling.core.Predef._ import scala.concurrent.duration._ -class CatsSimulation extends Simulation { +class CatsScalaSimulation extends Simulation { MockUtils.startServer(0)