diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..94f480de --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/docs/guide/protocols/index.md b/docs/guide/protocols/index.md index 479eccd0..d4d1edca 100644 --- a/docs/guide/protocols/index.md +++ b/docs/guide/protocols/index.md @@ -5,3 +5,4 @@ + diff --git a/docs/guide/protocols/websocket.md b/docs/guide/protocols/websocket.md new file mode 100644 index 00000000..b62c4a63 --- /dev/null +++ b/docs/guide/protocols/websocket.md @@ -0,0 +1,68 @@ +### WebSocket + +The `DslWebsocketSampler` class provides a Java DSL for creating WebSocket performance tests using JMeter. It supports the full WebSocket lifecycle including connection, data transmission, and disconnection operations. It is based on [WebSocket Samplers by Peter Doornbosch](https://bitbucket.org/pjtr/jmeter-websocket-samplers/src/master/) plugin. + +To use it, add the following dependency to your project: + +:::: code-group +::: code-group-item Maven +```xml + + us.abstracta.jmeter + jmeter-java-dsl-websocket + 2.2 + test + +``` +::: +::: code-group-item Gradle +```groovy +testImplementation 'us.abstracta.jmeter:jmeter-java-dsl-websocket:2.2' +``` +::: +:::: + + + +Below you can see a basic usage example of WebSocket protocol. + +```java +import static us.abstracta.jmeter.javadsl.JmeterDsl.*; +import static us.abstracta.jmeter.javadsl.websocket.WebsocketJMeterDsl.*; +import us.abstracta.jmeter.javadsl.core.TestPlanStats; + +public class Test { + public static void main(String[] args) throws Exception { + TestPlanStats stats = testPlan( + threadGroup(1, 1, + websocketConnect("wss://ws.postman-echo.com/raw"), + websocketWrite("Hello WebSocket!"), + websocketRead() + .children( + responseAssertion() + .equalsToStrings("Hello WebSocket!") + ), + websocketDisconnect() + ) + ).run(); + } +} +``` + +::: warning +Only `ws://` and `wss://` protocols are supported. Using any other scheme will throw an `IllegalArgumentException`. +::: + +::: tip +You can use a non-blocking read if necessary in the following way: + +```java +websocketRead().waitForResponse(false) +``` + +In this case, it is not recommended to add an assertion because the response could be empty. +::: + +::: warning +The WebSocket plugin only supports one connection per thread at a time. If you want to change the WebSocket server during execution, you should add a disconnect sampler and then establish a new connection. +::: \ No newline at end of file diff --git a/jmeter-java-dsl-cli/pom.xml b/jmeter-java-dsl-cli/pom.xml index e6a72064..300c1d40 100644 --- a/jmeter-java-dsl-cli/pom.xml +++ b/jmeter-java-dsl-cli/pom.xml @@ -65,6 +65,11 @@ jmeter-java-dsl-recorder ${project.version} + + us.abstracta.jmeter + jmeter-java-dsl-websocket + ${project.version} + org.slf4j jul-to-slf4j @@ -165,6 +170,7 @@ com.blazemeter:jmeter-plugins-random-csv-data-set com.blazemeter:jmeter-plugins-wsc com.blazemeter:jmeter-parallel + net.luminis.jmeter:jmeter-websocket-samplers info.picocli:* com.fifesoft:rsyntaxtextarea com.github.weisj:darklaf-extensions-rsyntaxarea diff --git a/jmeter-java-dsl-cli/src/main/java/us/abstracta/jmeter/javadsl/cli/Jmx2DslCommand.java b/jmeter-java-dsl-cli/src/main/java/us/abstracta/jmeter/javadsl/cli/Jmx2DslCommand.java index c92f27a2..744f14be 100644 --- a/jmeter-java-dsl-cli/src/main/java/us/abstracta/jmeter/javadsl/cli/Jmx2DslCommand.java +++ b/jmeter-java-dsl-cli/src/main/java/us/abstracta/jmeter/javadsl/cli/Jmx2DslCommand.java @@ -11,6 +11,7 @@ import us.abstracta.jmeter.javadsl.graphql.DslGraphqlSampler; import us.abstracta.jmeter.javadsl.jdbc.JdbcJmeterDsl; import us.abstracta.jmeter.javadsl.parallel.ParallelController; +import us.abstracta.jmeter.javadsl.websocket.WebsocketJMeterDsl; import us.abstracta.jmeter.javadsl.wrapper.WrapperJmeterDsl; @Command(name = "jmx2dsl", header = "Converts a JMX file to DSL code", @@ -48,6 +49,7 @@ public Integer call() throws Exception { addBuildersFrom(ElasticsearchBackendListener.class, "jmeter-java-dsl-elasticsearch-listener", codeGenerator); addBuildersFrom(DatadogBackendListener.class, "jmeter-java-dsl-datadog", codeGenerator); + addBuildersFrom(WebsocketJMeterDsl.class, "jmeter-java-dsl-websocket", codeGenerator); System.out.println(codeGenerator.generateCodeFromJmx(jmxFile)); return 0; } diff --git a/jmeter-java-dsl-octoperf/src/main/java/us/abstracta/jmeter/javadsl/octoperf/OctoPerfEngine.java b/jmeter-java-dsl-octoperf/src/main/java/us/abstracta/jmeter/javadsl/octoperf/OctoPerfEngine.java index 195158e6..d9782d20 100644 --- a/jmeter-java-dsl-octoperf/src/main/java/us/abstracta/jmeter/javadsl/octoperf/OctoPerfEngine.java +++ b/jmeter-java-dsl-octoperf/src/main/java/us/abstracta/jmeter/javadsl/octoperf/OctoPerfEngine.java @@ -31,8 +31,8 @@ import us.abstracta.jmeter.javadsl.octoperf.api.Scenario; import us.abstracta.jmeter.javadsl.octoperf.api.TableEntry; import us.abstracta.jmeter.javadsl.octoperf.api.User; -import us.abstracta.jmeter.javadsl.octoperf.api.UserLoad; -import us.abstracta.jmeter.javadsl.octoperf.api.UserLoad.UserLoadRampUp; +import us.abstracta.jmeter.javadsl.octoperf.api.UserProfile; +import us.abstracta.jmeter.javadsl.octoperf.api.UserProfile.UserLoadRampUp; import us.abstracta.jmeter.javadsl.octoperf.api.VirtualUser; import us.abstracta.jmeter.javadsl.octoperf.api.Workspace; @@ -264,8 +264,8 @@ private Scenario buildScenario(User user, Project project, List vus HashTree tree) throws IOException { Provider provider = apiClient.findProviderByWorkspace(project.getWorkspace()); String defaultRegion = provider.getRegions().keySet().iterator().next(); - List userLoads = vus.stream() - .map(vu -> new UserLoad(vu.getId(), provider.getId(), defaultRegion, + List userLoads = vus.stream() + .map(vu -> new UserProfile(vu.getId(), provider.getId(), defaultRegion, buildUserLoadConfig(tree))) .collect(Collectors.toList()); Scenario ret = apiClient.createScenario( diff --git a/jmeter-java-dsl-octoperf/src/main/java/us/abstracta/jmeter/javadsl/octoperf/api/Scenario.java b/jmeter-java-dsl-octoperf/src/main/java/us/abstracta/jmeter/javadsl/octoperf/api/Scenario.java index bb8ea60c..93b9c3ea 100644 --- a/jmeter-java-dsl-octoperf/src/main/java/us/abstracta/jmeter/javadsl/octoperf/api/Scenario.java +++ b/jmeter-java-dsl-octoperf/src/main/java/us/abstracta/jmeter/javadsl/octoperf/api/Scenario.java @@ -4,7 +4,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import java.time.Instant; -import java.util.Collections; import java.util.List; import java.util.Set; @@ -16,12 +15,11 @@ public class Scenario { private final String projectId; private final String name; private final String description = ""; - private final List userLoads; + private final List userProfiles; private final String mode = "STANDARD"; private final Instant created = Instant.now(); private final Instant lastModified = Instant.now(); private final Set tags; - private final BackendListenerSettings backendListeners = new BackendListenerSettings(); private Project project; @JsonCreator @@ -30,18 +28,18 @@ public Scenario(@JsonProperty("id") String id, @JsonProperty("tags") Set this.userId = null; this.projectId = null; this.name = null; - this.userLoads = null; + this.userProfiles = null; this.tags = tags; } - public Scenario(User user, Project project, String name, List userLoads, + public Scenario(User user, Project project, String name, List userProfiles, Set tags) { this.id = ""; this.userId = user.getId(); this.project = project; this.projectId = project.getId(); this.name = name; - this.userLoads = userLoads; + this.userProfiles = userProfiles; this.tags = tags; } @@ -67,15 +65,4 @@ public String getUrl() { return project.getBaseUrl() + "/runtime/scenario/" + id; } - public static class BackendListenerSettings { - - private final int queueSize = 5000; - private final List listeners = Collections.emptyList(); - - } - - public static class BackendListener { - - } - } diff --git a/jmeter-java-dsl-octoperf/src/main/java/us/abstracta/jmeter/javadsl/octoperf/api/UserLoad.java b/jmeter-java-dsl-octoperf/src/main/java/us/abstracta/jmeter/javadsl/octoperf/api/UserProfile.java similarity index 62% rename from jmeter-java-dsl-octoperf/src/main/java/us/abstracta/jmeter/javadsl/octoperf/api/UserLoad.java rename to jmeter-java-dsl-octoperf/src/main/java/us/abstracta/jmeter/javadsl/octoperf/api/UserProfile.java index 24de9b74..77c62d0c 100644 --- a/jmeter-java-dsl-octoperf/src/main/java/us/abstracta/jmeter/javadsl/octoperf/api/UserLoad.java +++ b/jmeter-java-dsl-octoperf/src/main/java/us/abstracta/jmeter/javadsl/octoperf/api/UserProfile.java @@ -12,37 +12,30 @@ import org.apache.jmeter.threads.ThreadGroup; import us.abstracta.jmeter.javadsl.core.threadgroups.BaseThreadGroup.SampleErrorAction; -public class UserLoad { +public class UserProfile { // we don't need getters since Jackson gets the values from fields private final String name = ""; private final String virtualUserId; private final String providerId; - private final String region; - private final UserLoadStrategy strategy; - private final BandwidthSettings bandwidth = new BandwidthSettings(); - private final BrowserSettings browser = new BrowserSettings(); - private final DnsSettings dns = new DnsSettings(); - private final ThinkTimeSettings thinktime = new ThinkTimeSettings(); + private final String location; + private final UserLoadStrategy load; private final MemorySettings memory = new MemorySettings(); - private final JtlSettings jtl = new JtlSettings(); - private final PropertiesSettings properties = new PropertiesSettings(); - private final SetUpTearDownSettings setUp = null; - private final SetUpTearDownSettings tearDown = null; + private final Engine engine = new Engine(); - public UserLoad() { + public UserProfile() { virtualUserId = null; providerId = null; - region = null; - strategy = null; + location = null; + load = null; } - public UserLoad(String virtualUserId, String providerId, String region, + public UserProfile(String virtualUserId, String providerId, String location, UserLoadStrategy strategy) { this.virtualUserId = virtualUserId; this.providerId = providerId; - this.region = region; - this.strategy = strategy; + this.location = location; + this.load = strategy; } @JsonTypeInfo(use = NAME, include = PROPERTY) @@ -53,25 +46,24 @@ public abstract static class UserLoadStrategy { } - @JsonTypeName("UserLoadRampup") + @JsonTypeName("UserProfileLoadRampUp") public static class UserLoadRampUp extends UserLoadStrategy { - private final int userload; - private final long rampup; - private final long peak; - private final long delay = 0; - private final SampleErrorAction onSampleError = SampleErrorAction.CONTINUE; + private final int plateauVus; + private final long rampUpMs; + private final long plateauMs; + private final long delayMs = 0; public UserLoadRampUp() { - userload = 0; - rampup = 0; - peak = 0; + plateauVus = 0; + rampUpMs = 0; + plateauMs = 0; } public UserLoadRampUp(int userLoad, long rampUpMillis, long peakMillis) { - this.userload = userLoad; - this.rampup = rampUpMillis; - this.peak = peakMillis; + this.plateauVus = userLoad; + this.rampUpMs = rampUpMillis; + this.plateauMs = peakMillis; } public static UserLoadRampUp fromThreadGroup(ThreadGroup threadGroup) { @@ -85,6 +77,39 @@ public static UserLoadRampUp fromThreadGroup(ThreadGroup threadGroup) { } + @JsonTypeInfo(use = NAME, include = PROPERTY) + @JsonTypeName("JmeterUserProfileEngine") + public static class Engine { + + private final EngineSettings settings = new EngineSettings(); + private final BrowserSettings browser = new BrowserSettings(); + private final BandwidthSettings bandwidth = new BandwidthSettings(); + private final DnsSettings dns = new DnsSettings(); + private final JtlSettings jtl = new JtlSettings(); + private final PropertiesSettings properties = new PropertiesSettings(); + + } + + public static class EngineSettings { + + private final ExternalLiveReportingSettings externalLiveReporting = + new ExternalLiveReportingSettings(); + private final SampleErrorAction errorHandling = SampleErrorAction.CONTINUE; + private final ThinkTimeSettings thinkTime = new ThinkTimeSettings(); + private final SetUpTearDownSettings setUp = null; + private final SetUpTearDownSettings tearDown = null; + + } + + @JsonTypeInfo(use = NAME, include = PROPERTY) + @JsonTypeName("JmeterExternalLiveReportingSettings") + public static class ExternalLiveReportingSettings { + + private final List listeners = Collections.emptyList(); + private final int queueSize = 5000; + + } + public static class SetUpTearDownSettings { } @@ -102,6 +127,8 @@ public static class BrowserSettings { private final String userAgent = ""; private final CacheManager cache = new CacheManager(); private final CookiesManager cookies = new CookiesManager(); + private final Boolean downloadResources = null; + private final Boolean keepAlive = null; } diff --git a/jmeter-java-dsl-octoperf/src/test/java/us/abstracta/jmeter/javadsl/octoperf/OctoPerfEngineTest.java b/jmeter-java-dsl-octoperf/src/test/java/us/abstracta/jmeter/javadsl/octoperf/OctoPerfEngineTest.java index 54d4ae52..502c44b8 100644 --- a/jmeter-java-dsl-octoperf/src/test/java/us/abstracta/jmeter/javadsl/octoperf/OctoPerfEngineTest.java +++ b/jmeter-java-dsl-octoperf/src/test/java/us/abstracta/jmeter/javadsl/octoperf/OctoPerfEngineTest.java @@ -6,8 +6,6 @@ import static us.abstracta.jmeter.javadsl.JmeterDsl.threadGroup; import java.time.Duration; -import java.util.HashMap; -import java.util.Map; import org.junit.jupiter.api.Test; import us.abstracta.jmeter.javadsl.core.TestPlanStats; diff --git a/jmeter-java-dsl-websocket/pom.xml b/jmeter-java-dsl-websocket/pom.xml new file mode 100644 index 00000000..7855080d --- /dev/null +++ b/jmeter-java-dsl-websocket/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + us.abstracta.jmeter + jmeter-java-dsl-parent + 2.2-SNAPSHOT + ../pom.xml + + + jmeter-java-dsl-websocket + + + + us.abstracta.jmeter + jmeter-java-dsl + ${project.version} + + + net.luminis.jmeter + jmeter-websocket-samplers + 1.3.1 + + + org.apache.jmeter + ApacheJMeter_core + + + org.apache.jmeter + jorphan + + + + + org.java-websocket + Java-WebSocket + 1.6.0 + test + + + + \ No newline at end of file diff --git a/jmeter-java-dsl-websocket/src/main/java/us/abstracta/jmeter/javadsl/websocket/WebsocketJMeterDsl.java b/jmeter-java-dsl-websocket/src/main/java/us/abstracta/jmeter/javadsl/websocket/WebsocketJMeterDsl.java new file mode 100644 index 00000000..1a82411e --- /dev/null +++ b/jmeter-java-dsl-websocket/src/main/java/us/abstracta/jmeter/javadsl/websocket/WebsocketJMeterDsl.java @@ -0,0 +1,545 @@ +package us.abstracta.jmeter.javadsl.websocket; + +import eu.luminis.jmeter.wssampler.CloseWebSocketSampler; +import eu.luminis.jmeter.wssampler.CloseWebSocketSamplerGui; +import eu.luminis.jmeter.wssampler.DataPayloadType; +import eu.luminis.jmeter.wssampler.OpenWebSocketSampler; +import eu.luminis.jmeter.wssampler.OpenWebSocketSamplerGui; +import eu.luminis.jmeter.wssampler.SingleReadWebSocketSampler; +import eu.luminis.jmeter.wssampler.SingleReadWebSocketSampler.DataType; +import eu.luminis.jmeter.wssampler.SingleReadWebSocketSamplerGui; +import eu.luminis.jmeter.wssampler.SingleWriteWebSocketSampler; +import eu.luminis.jmeter.wssampler.SingleWriteWebSocketSamplerGui; +import java.lang.reflect.Method; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.List; +import org.apache.jmeter.testelement.TestElement; +import us.abstracta.jmeter.javadsl.codegeneration.MethodCall; +import us.abstracta.jmeter.javadsl.codegeneration.MethodCallContext; +import us.abstracta.jmeter.javadsl.codegeneration.MethodParam; +import us.abstracta.jmeter.javadsl.codegeneration.SingleTestElementCallBuilder; +import us.abstracta.jmeter.javadsl.codegeneration.TestElementParamBuilder; +import us.abstracta.jmeter.javadsl.codegeneration.params.BoolParam; +import us.abstracta.jmeter.javadsl.codegeneration.params.EnumParam; +import us.abstracta.jmeter.javadsl.codegeneration.params.StringParam; +import us.abstracta.jmeter.javadsl.core.samplers.BaseSampler; + +/** + * Provides factory methods to create WebSocket samplers for performance + * testing. + *

+ * This class serves as the entry point for creating different types of + * WebSocket operations: + *

    + *
  • {@link #websocketConnect(String)} - Establish a WebSocket connection
  • + *
  • {@link #websocketWrite(String)} - Send messages to the server
  • + *
  • {@link #websocketRead()} - Read responses from the server
  • + *
  • {@link #websocketDisconnect()} - Close the WebSocket connection
  • + *
+ *

+ * Example usage: + * + *

{@code
+ *import static us.abstracta.jmeter.javadsl.JmeterDsl.*;
+ *import static us.abstracta.jmeter.javadsl.websocket.DslWebsocketSampler.webSocketSampler;
+ *import us.abstracta.jmeter.javadsl.core.TestPlanStats;
+ * 
+ *public class Test {
+ * 
+ *  public static void main(String[] args) throws Exception {
+ *    TestPlanStats stats = testPlan(
+ *      threadGroup(1, 1,
+ *        webSocketSampler().connect("wss://server.com/websocket")
+ *        webSocketSampler().write("Hello WebSocket!")
+ *        webSocketSampler().read()
+ *          .children(
+ *            responseAssertion()
+ *              .equalsToStrings("Hello WebSocket!")),
+ *        webSocketSampler().disconnect()
+ *      )
+ *    ).run();
+ *   }
+ * }
+ * }
+ * + * @since 2.2 + */ +public class WebsocketJMeterDsl { + + private WebsocketJMeterDsl() { + } + + /** + * Creates a WebSocket connect sampler to establish a connection to the server. + *

+ * After establishing the connection, use {@link #websocketWrite(String)} and + * {@link #websocketRead()} + * samplers to send and receive messages. Remember to close the connection using + * {@link #websocketDisconnect()} when finished. + *

+ * It could be also used alone to test the connection to the server. + *

+ * URL Format: {@code ws://host:port/path?query} or + * {@code wss://host:port/path?query} + *

+ * + * @param url the WebSocket server URL. Supported schemes: {@code ws://} (plain) + * and {@code wss://} (TLS) + * @return the connect sampler for further configuration or usage + * @since 2.2 + */ + public static DslConnectSampler websocketConnect(String url) { + return new DslConnectSampler(url); + } + + /** + * Creates WebSocket disconnect sampler to gracefully close the connection to + * the + *

+ * This sampler sends a close frame to the server and waits for the server's + * close frame response. It is recommended to always close connections + * explicitly to properly release resources. + * + * @return the disconnect sampler for further configuration or usage + * @since 2.2 + */ + public static DslDisconnectSampler websocketDisconnect() { + return new DslDisconnectSampler(); + } + + /** + * Creates a WebSocket write sampler to send a text message to the server. + *

+ * Requires an active WebSocket connection established via + * {@link #websocketConnect(String)}. + * + * @param requestData the message to send to the WebSocket server + * @return the write sampler for further configuration or usage + * @since 2.2 + */ + public static DslWriteSampler websocketWrite(String requestData) { + return new DslWriteSampler(requestData); + } + + /** + * Creates a WebSocket read sampler to receive a message from the server. + *

+ * By default, this sampler blocks execution until a response is received or + * the timeout is reached. This behavior can be changed using + * {@link DslReadSampler#waitForResponse(boolean)}. + *

+ * Requires an active WebSocket connection established via + * {@link #websocketConnect(String)}. + * + * @return the read sampler for further configuration or usage + * @since 2.2 + */ + public static DslReadSampler websocketRead() { + return new DslReadSampler(); + } + + public static class DslConnectSampler extends BaseSampler { + private String connectionTimeoutMillis; + private String responseTimeoutMillis; + private String server; + private String port; + private String path; + private boolean tls = false; + + private DslConnectSampler(String url) { + super("WebSocket Open Connection", OpenWebSocketSamplerGui.class); + try { + URI uri = new URI(url); + + String scheme = uri.getScheme(); + if (scheme == null || (!"ws".equals(scheme) && !"wss".equals(scheme))) { + throw new IllegalArgumentException( + "Invalid WebSocket URL. Must start with 'ws://' or 'wss://'"); + } + + this.tls = "wss".equals(scheme); + this.server = uri.getHost(); + if (this.server == null) { + throw new IllegalArgumentException("Invalid WebSocket URL. Host is required"); + } + + int port = uri.getPort(); + if (port == -1) { + this.port = this.tls ? "443" : "80"; + } else { + this.port = String.valueOf(port); + } + + String path = uri.getPath(); + if (path == null || path.isEmpty()) { + this.path = "/"; + } else { + this.path = path; + } + + String query = uri.getQuery(); + if (query != null && !query.isEmpty()) { + this.path = this.path + "?" + query; + } + + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Invalid WebSocket URL: " + url, e); + } + } + + @Override + protected TestElement buildTestElement() { + OpenWebSocketSampler ret = new OpenWebSocketSampler(); + if (connectionTimeoutMillis != null) { + ret.setConnectTimeout(connectionTimeoutMillis); + } + if (responseTimeoutMillis != null) { + ret.setReadTimeout(responseTimeoutMillis); + } + ret.setTLS(tls); + ret.setServer(server); + ret.setPort(port); + ret.setPath(path); + return ret; + } + + /** + * Sets the connection timeout for the WebSocket connection creation. + * + * @param timeoutMillis the connection timeout in milliseconds (default value is + * 20000 milliseconds) + * @return the sampler for further configuration or usage + * @since 2.2 + */ + public DslConnectSampler connectionTimeout(int timeoutMillis) { + this.connectionTimeoutMillis = String.valueOf(timeoutMillis); + return this; + } + + /** + * Same as {@link #connectionTimeout(int)} but allowing to use JMeter + * expressions + * (variables or + * functions) to solve the actual parameter values. + * + * @param timeoutMillis a JMeter expression that returns timeout in milliseconds + * (default value is 20000 milliseconds) + * @return the sampler for further configuration or usage + * @since 2.2 + */ + public DslConnectSampler connectionTimeout(String timeoutMillis) { + this.connectionTimeoutMillis = timeoutMillis; + return this; + } + + /** + * Sets the response timeout for the WebSocket negotiation. + * + * @param timeoutMillis the response timeout in milliseconds (default value is + * 6000 milliseconds) + * @return the sampler for further configuration or usage + * @since 2.2 + */ + public DslConnectSampler responseTimeout(int timeoutMillis) { + this.responseTimeoutMillis = String.valueOf(timeoutMillis); + return this; + } + + /** + * Same as {@link #responseTimeout(int)} but allowing to use JMeter expressions + * (variables or functions) to solve the actual parameter values. + * + * @param timeoutMillis a JMeter expression that returns timeout in milliseconds + * (default value is 6000 milliseconds) + * @return the sampler for further configuration or usage + * @since 2.2 + */ + public DslConnectSampler responseTimeout(String timeoutMillis) { + this.responseTimeoutMillis = timeoutMillis; + return this; + } + + public static class CodeBuilder extends SingleTestElementCallBuilder { + + public CodeBuilder(List builderMethods) { + super(OpenWebSocketSampler.class, builderMethods); + } + + @Override + protected MethodCall buildMethodCall(OpenWebSocketSampler testElement, + MethodCallContext context) { + TestElementParamBuilder paramBuilder = new TestElementParamBuilder(testElement); + MethodParam server = paramBuilder.stringParam("server", ""); + MethodParam port = paramBuilder.stringParam("port", ""); + MethodParam path = paramBuilder.stringParam("path", ""); + MethodParam tls = paramBuilder.boolParam("TLS", false); + String protocol = tls.getExpression().equals("true") ? "wss" : "ws"; + String url = protocol + "://" + server.getExpression() + ":" + port.getExpression() + + path.getExpression(); + return new MethodCall("websocketConnect", DslConnectSampler.class, + new StringParam(url)) + .chain("connectionTimeout", paramBuilder.intParam("connectTimeout", 20000)) + .chain("responseTimeout", paramBuilder.intParam("readTimeout", 6000)); + } + } + } + + public enum StatusCode implements EnumParam.EnumPropertyValue { + NORMAL_CLOSURE("1000"), + GOING_AWAY("1001"), + PROTOCOL_ERROR("1002"), + UNSUPPORTED_DATA("1003"), + NO_STATUS_CODE_PRESENT("1005"), + MESSAGE_TYPE_ERROR("1007"), + POLICY_VIOLATION("1008"), + MESSAGE_TOO_BIG_ERROR("1009"), + TLS_HANDSHAKE_ERROR("1015"); + + private final String propertyValue; + + StatusCode(String propertyValue) { + this.propertyValue = propertyValue; + } + + @Override + public String propertyValue() { + return propertyValue; + } + + public static boolean isValidStatusCode(String value) { + return Arrays.stream(StatusCode.values()) + .anyMatch(code -> code.propertyValue().equals(value)); + } + + } + + public static class DslDisconnectSampler extends BaseSampler { + private String responseTimeoutMillis; + private String statusCode; + + private DslDisconnectSampler() { + super("WebSocket Close", CloseWebSocketSamplerGui.class); + } + + @Override + protected TestElement buildTestElement() { + CloseWebSocketSampler close = new CloseWebSocketSampler(); + if (responseTimeoutMillis != null) { + close.setReadTimeout(responseTimeoutMillis); + } + if (statusCode != null) { + close.setStatusCode(statusCode); + } + return close; + } + + /** + * Sets the response timeout for the close frame from server to be received. + * + * @param timeoutMillis the response timeout in milliseconds (default value is + * 6000 milliseconds) + * @return the sampler for further configuration or usage + * @since 2.2 + */ + public DslDisconnectSampler responseTimeout(int timeoutMillis) { + this.responseTimeoutMillis = String.valueOf(timeoutMillis); + return this; + } + + /** + * Same as {@link #responseTimeout(int)} but allowing to use JMeter expressions + * (variables or functions) to solve the actual parameter values. + * + * @param timeoutMillis a JMeter expression that returns timeout in milliseconds + * (default value is 6000 milliseconds) + * @return the sampler for further configuration or usage + * @since 2.2 + */ + public DslDisconnectSampler responseTimeout(String timeoutMillis) { + this.responseTimeoutMillis = timeoutMillis; + return this; + } + + /** + * Sets the status code to indicate the reason for closing the connection. + *

+ * Common status codes: + *

    + *
  • 1000 - Normal closure (default)
  • + *
  • 1001 - Going away
  • + *
  • 1002 - Protocol error
  • + *
  • 1003 - Unsupported data
  • + *
+ *

+ * For a complete list, see + * RFC + * 6455 Section 7.4 + * + * @param statusCode the status code for the WebSocket disconnect + * @return the sampler for further configuration or usage + * @since 2.2 + */ + public DslDisconnectSampler statusCode(StatusCode statusCode) { + this.statusCode = statusCode.propertyValue(); + return this; + } + + /** + * Same as {@link #statusCode(StatusCode)} but allowing to use JMeter + * expressions + * (variables or functions) to solve the actual parameter values. + * + * @param statusCode a JMeter expression that returns the status code for the + * WebSocket disconnect + * @return the sampler for further configuration or usage + * @since 2.2 + */ + public DslDisconnectSampler statusCode(String statusCode) { + this.statusCode = statusCode; + return this; + } + + public static class CodeBuilder extends SingleTestElementCallBuilder { + + public CodeBuilder(List builderMethods) { + super(CloseWebSocketSampler.class, builderMethods); + } + + @Override + protected MethodCall buildMethodCall(CloseWebSocketSampler testElement, + MethodCallContext context) { + TestElementParamBuilder paramBuilder = new TestElementParamBuilder(testElement); + MethodParam statusCode = paramBuilder.stringParam("statusCode", "1000"); + if (StatusCode.isValidStatusCode(statusCode.getExpression())) { + statusCode = paramBuilder.enumParam("statusCode", StatusCode.NORMAL_CLOSURE); + } + return new MethodCall("websocketDisconnect", DslDisconnectSampler.class) + .chain("responseTimeout", paramBuilder.intParam("readTimeout", 6000)) + .chain("statusCode", statusCode); + } + } + } + + public static class DslWriteSampler extends BaseSampler { + private String requestData; + + private DslWriteSampler(String requestData) { + super("WebSocket Single Write", SingleWriteWebSocketSamplerGui.class); + this.requestData = requestData; + } + + @Override + protected TestElement buildTestElement() { + SingleWriteWebSocketSampler write = new SingleWriteWebSocketSampler(); + write.setType(DataPayloadType.Text); + write.setRequestData(requestData); + write.setCreateNewConnection(false); + return write; + } + + public static class CodeBuilder + extends SingleTestElementCallBuilder { + + public CodeBuilder(List builderMethods) { + super(SingleWriteWebSocketSampler.class, builderMethods); + } + + @Override + protected MethodCall buildMethodCall(SingleWriteWebSocketSampler testElement, + MethodCallContext context) { + TestElementParamBuilder paramBuilder = new TestElementParamBuilder(testElement); + MethodParam requestData = paramBuilder.stringParam("requestData", ""); + return new MethodCall("websocketWrite", DslWriteSampler.class, + new StringParam(requestData.getExpression())); + } + } + } + + public static class DslReadSampler extends BaseSampler { + private String responseTimeoutMillis; + private boolean waitForResponse = true; + + private DslReadSampler() { + super("WebSocket Single Read", SingleReadWebSocketSamplerGui.class); + } + + @Override + protected TestElement buildTestElement() { + SingleReadWebSocketSampler read = new SingleReadWebSocketSampler(); + if (responseTimeoutMillis != null) { + read.setReadTimeout(responseTimeoutMillis); + } + read.setDataType(DataType.Text); + read.setOptional(!waitForResponse); + read.setCreateNewConnection(false); + return read; + } + + /** + * Specifies whether the sampler should block execution until a response is + * received. + *

+ * When set to {@code true} (default), the sampler waits for a server response + * or until the timeout expires. When set to {@code false}, the sampler returns + * immediately if no message is available. + * + * @param waitForResponse {@code true} to block until response is received, + * {@code false} to return immediately + * @return the sampler for further configuration or usage + * @since 2.2 + */ + public DslReadSampler waitForResponse(boolean waitForResponse) { + this.waitForResponse = waitForResponse; + return this; + } + + /** + * Same as {@link #responseTimeout(int)} but allowing to use JMeter expressions + * (variables or functions) to solve the actual parameter values. + * + * @param timeoutMillis a JMeter expression that returns timeout in milliseconds + * (default value is 6000 milliseconds) + * @return the sampler for further configuration or usage + * @since 2.2 + */ + public DslReadSampler responseTimeout(String timeoutMillis) { + this.responseTimeoutMillis = timeoutMillis; + return this; + } + + /** + * Sets the response timeout for the WebSocket response to be received. + * + * @param timeoutMillis the response timeout in milliseconds (default value is + * 6000 milliseconds) + * @return the sampler for further configuration or usage + * @since 2.2 + */ + public DslReadSampler responseTimeout(int timeoutMillis) { + this.responseTimeoutMillis = String.valueOf(timeoutMillis); + return this; + } + + public static class CodeBuilder + extends SingleTestElementCallBuilder { + + public CodeBuilder(List builderMethods) { + super(SingleReadWebSocketSampler.class, builderMethods); + } + + @Override + protected MethodCall buildMethodCall(SingleReadWebSocketSampler testElement, + MethodCallContext context) { + TestElementParamBuilder paramBuilder = new TestElementParamBuilder(testElement); + boolean optionalParam = !paramBuilder.boolParam("optional", false) + .getExpression().equals("true"); + return new MethodCall("websocketRead", DslReadSampler.class) + .chain("responseTimeout", paramBuilder.intParam("readTimeout", 6000)) + .chain("waitForResponse", new BoolParam(optionalParam, true)) + .chain("createNewConnection", paramBuilder.boolParam("createNewConnection", false)); + } + } + } +} diff --git a/jmeter-java-dsl-websocket/src/test/java/DslWebsocketSamplerTest.java b/jmeter-java-dsl-websocket/src/test/java/DslWebsocketSamplerTest.java new file mode 100644 index 00000000..a1122587 --- /dev/null +++ b/jmeter-java-dsl-websocket/src/test/java/DslWebsocketSamplerTest.java @@ -0,0 +1,86 @@ +import java.util.concurrent.TimeUnit; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static us.abstracta.jmeter.javadsl.JmeterDsl.responseAssertion; +import static us.abstracta.jmeter.javadsl.JmeterDsl.testPlan; +import static us.abstracta.jmeter.javadsl.JmeterDsl.threadGroup; +import static us.abstracta.jmeter.javadsl.websocket.WebsocketJMeterDsl.*; + +import org.junit.jupiter.api.Test; +import us.abstracta.jmeter.javadsl.core.TestPlanStats; + +public class DslWebsocketSamplerTest { + + @Test + public void shouldConnectAndEchoMessageWhenWebSocketTestPlanWithEchoServer() throws Exception { + WebSocketEchoServer echoServer = new WebSocketEchoServer(0); + echoServer.start(); + echoServer.awaitStart(5, TimeUnit.SECONDS); + String wsUri = echoServer.getUri(); + TestPlanStats stats = testPlan( + threadGroup(1, 1, + websocketConnect(wsUri), + websocketWrite("Hello WebSocket Test!"), + websocketRead() + .children( + responseAssertion() + .containsSubstrings("Hello WebSocket Test!")), + websocketDisconnect())) + .run(); + assertThat(stats.overall().errorsCount()).isEqualTo(0); + } + + @Test + public void shouldThrowIllegalArgumentExceptionWhenConnectWithInvalidUrl() { + assertThrows(IllegalArgumentException.class, () -> { + websocketConnect("http://localhost:80/test"); + }); + } + + @Test + public void shouldErrorSamplerWhenConnectToUnavailableServer() throws Exception { + TestPlanStats stats = testPlan( + threadGroup(1, 1, + websocketConnect("ws://localhost:9999/nonexistent"))) + .run(); + assertThat(stats.overall().errorsCount()).isEqualTo(1); + } + + @Test + public void shouldErrorSamplerWhenConnectWithVeryShortTimeout() throws Exception { + TestPlanStats stats = testPlan( + threadGroup(1, 1, + websocketConnect("ws://localhost:8080/test") + .connectionTimeout(1) + .responseTimeout(1))) + .run(); + assertThat(stats.overall().errorsCount()).isEqualTo(1); + } + + @Test + public void shouldErrorSamplerWhenWriteOperationWhenNoPreviousConnection() throws Exception { + TestPlanStats stats = testPlan( + threadGroup(1, 1, + websocketWrite("Test message"))) + .run(); + assertThat(stats.overall().errorsCount()).isEqualTo(1); + } + + @Test + public void shouldErrorSamplerWhenReadOperationWhenNoPreviousConnection() throws Exception { + TestPlanStats stats = testPlan( + threadGroup(1, 1, + websocketRead())) + .run(); + assertThat(stats.overall().errorsCount()).isEqualTo(1); + } + + @Test + public void shouldErrorSamplerWhenDisconnectOperationWhenNoPreviousConnection() throws Exception { + TestPlanStats stats = testPlan( + threadGroup(1, 1, + websocketDisconnect())) + .run(); + assertThat(stats.overall().errorsCount()).isEqualTo(1); + } +} \ No newline at end of file diff --git a/jmeter-java-dsl-websocket/src/test/java/WebSocketEchoServer.java b/jmeter-java-dsl-websocket/src/test/java/WebSocketEchoServer.java new file mode 100644 index 00000000..6337ba4f --- /dev/null +++ b/jmeter-java-dsl-websocket/src/test/java/WebSocketEchoServer.java @@ -0,0 +1,48 @@ +import java.net.InetSocketAddress; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.java_websocket.WebSocket; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; + +public class WebSocketEchoServer extends WebSocketServer { + + private final CountDownLatch startLatch = new CountDownLatch(1); + + public WebSocketEchoServer(int port) { + super(new InetSocketAddress(port)); + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + } + + @Override + public void onMessage(WebSocket conn, String message) { + conn.send(message); + } + + @Override + public void onError(WebSocket conn, Exception ex) { + ex.printStackTrace(); + } + + @Override + public void onStart() { + startLatch.countDown(); + } + + public void awaitStart(long timeout, TimeUnit unit) throws InterruptedException { + if (!startLatch.await(timeout, unit)) { + throw new RuntimeException("WebSocket server failed to start within timeout"); + } + } + + public String getUri() { + return "ws://localhost:" + getPort(); + } +} \ No newline at end of file diff --git a/jmeter-java-dsl-websocket/src/test/java/WebsocketCodeGeneratorTest.java b/jmeter-java-dsl-websocket/src/test/java/WebsocketCodeGeneratorTest.java new file mode 100644 index 00000000..9dd3d1bd --- /dev/null +++ b/jmeter-java-dsl-websocket/src/test/java/WebsocketCodeGeneratorTest.java @@ -0,0 +1,70 @@ +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import us.abstracta.jmeter.javadsl.codegeneration.DslCodeGenerator; +import us.abstracta.jmeter.javadsl.codegeneration.TestClassTemplate; +import us.abstracta.jmeter.javadsl.core.util.StringTemplate; +import us.abstracta.jmeter.javadsl.util.TestResource; +import us.abstracta.jmeter.javadsl.websocket.WebsocketJMeterDsl; + +public class WebsocketCodeGeneratorTest { + + private static final String RESOURCES_FOLDER = "websocket-codegeneration"; + + @Test + public void shouldGenerateExpectedCodeWhenSimpleWebSocketJmxIsProvided(@TempDir Path tempDir) + throws Exception { + File solvedTemplate = solveTemplateResource("websocket-test-plan.template.jmx", tempDir); + assertThat(new DslCodeGenerator().addBuildersFrom(WebsocketJMeterDsl.class).generateCodeFromJmx(solvedTemplate)) + .isEqualToNormalizingNewlines( + solveTestClassTemplate(Collections.emptySet(), + "SimpleWebSocketTest.java")); + } + + @Test + public void shouldGenerateExpectedCodeWhenComplexWebSocketJmxIsProvided(@TempDir Path tempDir) throws Exception { + File solvedTemplate = solveTemplateResource("/complex-websocket.jmx", tempDir); + assertThat(new DslCodeGenerator().addBuildersFrom(WebsocketJMeterDsl.class).generateCodeFromJmx(solvedTemplate)) + .isEqualToNormalizingNewlines( + solveTestClassTemplate(Collections.emptySet(), + "ComplexWebSocketTest.java")); + } + + @Test + public void shouldGenerateExpectedCodeWhenWebSocketWithVariablesJmxIsProvided(@TempDir Path tempDir) + throws Exception { + assertThat(new DslCodeGenerator() + + .addBuildersFrom(WebsocketJMeterDsl.class) + .addDependency(WebsocketJMeterDsl.class, "us.abstracta.jmeter:jmeter-java-dsl-websocket") + .generateCodeFromJmx(new TestResource(RESOURCES_FOLDER + "/websocket-with-variables.jmx").file())) + .isEqualToNormalizingNewlines(solveTestClassTemplate(Collections.emptySet(), + "WebSocketWithVariablesTest.java")); + } + + private File solveTemplateResource(String resourcePath, Path tempDir) throws IOException { + String templateContents = new StringTemplate(new TestResource(RESOURCES_FOLDER + "/" + resourcePath).rawContents()) + .solve(); + Path solvedTemplate = tempDir.resolve("websocket-test-plan.jmx"); + Files.write(solvedTemplate, templateContents.getBytes(StandardCharsets.UTF_8)); + return solvedTemplate.toFile(); + } + + private String solveTestClassTemplate(Set imports, String testPlanCodeResource) + throws IOException { + return new TestClassTemplate() + .dependencies(Collections.singleton("us.abstracta.jmeter:jmeter-java-dsl")) + .imports(imports) + .testPlan(new TestResource(RESOURCES_FOLDER + "/" + testPlanCodeResource).rawContents()) + .solve(); + } + +} diff --git a/jmeter-java-dsl-websocket/src/test/resources/websocket-codegeneration/ComplexWebSocketTest.java b/jmeter-java-dsl-websocket/src/test/resources/websocket-codegeneration/ComplexWebSocketTest.java new file mode 100644 index 00000000..48647b41 --- /dev/null +++ b/jmeter-java-dsl-websocket/src/test/resources/websocket-codegeneration/ComplexWebSocketTest.java @@ -0,0 +1,14 @@ +testPlan( + threadGroup(1, 1, + websocketConnect("ws://echo.websocket.org:80/") + .connectionTimeout(15000) + .responseTimeout(10000), + websocketWrite("Hello from JMeter WebSocket Test"), + websocketRead() + .responseTimeout(10000) + .waitForResponse(false), + websocketDisconnect() + .responseTimeout(2000) + .statusCode("3000") + ) + ) \ No newline at end of file diff --git a/jmeter-java-dsl-websocket/src/test/resources/websocket-codegeneration/SimpleWebSocketTest.java b/jmeter-java-dsl-websocket/src/test/resources/websocket-codegeneration/SimpleWebSocketTest.java new file mode 100644 index 00000000..ea6f7bc2 --- /dev/null +++ b/jmeter-java-dsl-websocket/src/test/resources/websocket-codegeneration/SimpleWebSocketTest.java @@ -0,0 +1,8 @@ +testPlan( + threadGroup(1, 1, + websocketConnect("ws://ws.postman-echo.com:80/raw"), + websocketWrite("Hello WebSocket Test!"), + websocketRead(), + websocketDisconnect() + ) + ) \ No newline at end of file diff --git a/jmeter-java-dsl-websocket/src/test/resources/websocket-codegeneration/WebSocketWithVariablesTest.java b/jmeter-java-dsl-websocket/src/test/resources/websocket-codegeneration/WebSocketWithVariablesTest.java new file mode 100644 index 00000000..7f46b58c --- /dev/null +++ b/jmeter-java-dsl-websocket/src/test/resources/websocket-codegeneration/WebSocketWithVariablesTest.java @@ -0,0 +1,13 @@ +testPlan( + threadGroup(1, 1, + websocketConnect("ws://${WEBSOCKET_SERVER}:${WEBSOCKET_PORT}/raw") + .connectionTimeout("${timeout}") + .responseTimeout("${timeout}"), + websocketWrite("${MESSAGE}"), + websocketRead() + .responseTimeout("${timeout}"), + websocketDisconnect() + .responseTimeout("${timeout}") + .statusCode("${statusCode}") + ) + ) \ No newline at end of file diff --git a/jmeter-java-dsl-websocket/src/test/resources/websocket-codegeneration/complex-websocket.jmx b/jmeter-java-dsl-websocket/src/test/resources/websocket-codegeneration/complex-websocket.jmx new file mode 100644 index 00000000..17ce5036 --- /dev/null +++ b/jmeter-java-dsl-websocket/src/test/resources/websocket-codegeneration/complex-websocket.jmx @@ -0,0 +1,64 @@ + + + + + true + + + + + + + 1 + 1 + true + continue + + 1 + false + + + + + 15000 + 10000 + false + echo.websocket.org + 80 + / + + + + 20000 + Text + Hello from JMeter WebSocket Test + false + false + + 80 + + false + + + + + 20000 + 10000 + Text + true + false + false + + 80 + + + + + 2000 + 3000 + + + + + + diff --git a/jmeter-java-dsl-websocket/src/test/resources/websocket-codegeneration/websocket-test-plan.template.jmx b/jmeter-java-dsl-websocket/src/test/resources/websocket-codegeneration/websocket-test-plan.template.jmx new file mode 100644 index 00000000..7fede633 --- /dev/null +++ b/jmeter-java-dsl-websocket/src/test/resources/websocket-codegeneration/websocket-test-plan.template.jmx @@ -0,0 +1,68 @@ + + + + + + false + true + false + + + + + + + + 1 + 1 + true + continue + + 1 + false + + + + + + + false + ws.postman-echo.com + 80 + /raw + + + + 20000 + Text + Hello WebSocket Test! + false + false + + 80 + + false + + + + + 20000 + + Text + false + false + false + + 80 + + + + + + + + + + + + diff --git a/jmeter-java-dsl-websocket/src/test/resources/websocket-codegeneration/websocket-with-variables.jmx b/jmeter-java-dsl-websocket/src/test/resources/websocket-codegeneration/websocket-with-variables.jmx new file mode 100644 index 00000000..776f6ad8 --- /dev/null +++ b/jmeter-java-dsl-websocket/src/test/resources/websocket-codegeneration/websocket-with-variables.jmx @@ -0,0 +1,82 @@ + + + + + true + + + + WEBSOCKET_SERVER + ws.postman-echo.com + = + + + WEBSOCKET_PORT + 80 + = + + + MESSAGE + Hello from variable + = + + + + false + false + + + + 1 + 1 + true + continue + + 1 + false + + + + + ${timeout} + ${timeout} + false + ${WEBSOCKET_SERVER} + ${WEBSOCKET_PORT} + /raw + + + + 20000 + Text + ${MESSAGE} + false + false + + 80 + + false + + + + + 20000 + ${timeout} + Text + false + false + false + + 80 + + + + + ${timeout} + ${statusCode} + + + + + + diff --git a/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/controllers/FragmentMethodCall.java b/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/controllers/FragmentMethodCall.java index 85eba4c8..d0aceb5b 100644 --- a/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/controllers/FragmentMethodCall.java +++ b/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/controllers/FragmentMethodCall.java @@ -51,8 +51,8 @@ private static String solveMethodName(TestElement element, MethodCallContext con } private static Map getDefinedMethods(MethodCallContext context) { - // Since JMeter 5.6.3, TestElement hash calculation changed from object identity - // to property-based hashing. IdentityHashMap is used here to prevent hash collisions + // Since JMeter 5.6.3, TestElement hash calculation changed from object identity + // to property-based hashing. IdentityHashMap is used here to prevent hash collisions // that occur when multiple TestElements share identical property values. return context.getRoot().computeEntryIfAbsent(FragmentMethodCall.class, IdentityHashMap::new); } diff --git a/jmeter-java-dsl/src/test/java/us/abstracta/jmeter/javadsl/codegeneration/MethodCallBuilderTest.java b/jmeter-java-dsl/src/test/java/us/abstracta/jmeter/javadsl/codegeneration/MethodCallBuilderTest.java index 5032b2bb..0c01d14a 100644 --- a/jmeter-java-dsl/src/test/java/us/abstracta/jmeter/javadsl/codegeneration/MethodCallBuilderTest.java +++ b/jmeter-java-dsl/src/test/java/us/abstracta/jmeter/javadsl/codegeneration/MethodCallBuilderTest.java @@ -68,7 +68,7 @@ private Map extractCodeBuilderTestCodes() { .getMethods().stream() .collect( Collectors.toMap(NodeWithSimpleName::getNameAsString, - m -> m.getBody().map(Objects::toString).orElse("").replace("\r\n", "\n"))); + m -> m.getBody().map(Objects::toString).orElse(""))); } @ParameterizedTest(name = "{0}") diff --git a/jmeter-java-dsl/src/test/java/us/abstracta/jmeter/javadsl/core/StringTemplateAssert.java b/jmeter-java-dsl/src/test/java/us/abstracta/jmeter/javadsl/core/StringTemplateAssert.java index 649be002..3ed8e133 100644 --- a/jmeter-java-dsl/src/test/java/us/abstracta/jmeter/javadsl/core/StringTemplateAssert.java +++ b/jmeter-java-dsl/src/test/java/us/abstracta/jmeter/javadsl/core/StringTemplateAssert.java @@ -43,7 +43,7 @@ public static StringTemplateAssertString assertThat(String actual) { protected abstract ErrorMessageFactory getErrorMessageFactory(List> diffs); public SELF matches(TestResource template) throws IOException { - return matches(template.rawContents().replace("\r\n", "\n")); + return matches(template.rawContents()); } public SELF matches(String templateContents) throws IOException { diff --git a/pom.xml b/pom.xml index 105ad5fa..ee9121e4 100644 --- a/pom.xml +++ b/pom.xml @@ -72,6 +72,7 @@ jmeter-java-dsl-datadog jmeter-java-dsl-bridge jmeter-java-dsl-prometheus + jmeter-java-dsl-websocket