Skip to content

Commit

Permalink
Allow to use Java lambdas as scripts for JSR223 Pre and Post processo…
Browse files Browse the repository at this point in the history
…rs to benefit from type safety and auto completion
  • Loading branch information
rabelenda-abstracta committed Nov 23, 2020
1 parent e2a1bd4 commit fc0d859
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 12 deletions.
3 changes: 2 additions & 1 deletion checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<module name="Checker">
<module name="SuppressWarningsFilter" />
<property name="fileExtensions" value="java, properties, xml"/>

<module name="NewlineAtEndOfFile"/>
Expand All @@ -16,7 +17,7 @@
</module>

<module name="TreeWalker">

<module name="SuppressWarningsHolder" />
<module name="SuppressionCommentFilter"/>

<module name="AnnotationLocation"/>
Expand Down
26 changes: 24 additions & 2 deletions docs/use-cases.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,19 @@ public class PerformanceTest {
}
```

> Check [DslJsr223PostProcessor](../jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/postprocessors/DslJsr223PostProcessor.java) for more details and additional options.
You can also use a Java lambda instead of providing Groovy script, which benefits from Java type safety & IDEs code auto completion:

```java
jsr223PostProcessor(s -> {
if ("429".equals(s.prev.getResponseCode())) {
s.prev.setSuccessful(true);
}
})
```

> **WARNING:** using this last approach is currently only supported when using embedded JMeter engine (no support for saving to JMX and running it in JMeter GUI, or running it with BlazeMeter).
Check [DslJsr223PostProcessor](../jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/postprocessors/DslJsr223PostProcessor.java) for more details and additional options.

JSR223PostProcessor is a very powerful tool, but is not the only, nor the best, alternative for many cases where JMeter already provides a better and simpler alternative (eg: asserting response bodies contain some string). Currently, jmeter-java-dsl does not support all the features JMeter provides. So, if you need something already provided by JMeter, please create an issue in GitHub requesting such a feature or submit a pull request with the required support.

Expand Down Expand Up @@ -298,6 +310,14 @@ public class PerformanceTest {
}
```

You can also use a Java lambda instead of providing Groovy script, which benefits from Java type safety & IDEs code auto completion:

```java
jsr223PreProcessor(s -> s.vars.put("REQUEST_BODY", buildRequestBody(s.vars)))
```

> **WARNING:** using this last approach is currently only supported when using embedded JMeter engine (no support for saving to JMX and running it in JMeter GUI, or running it with BlazeMeter).
Check [DslJsr223PreProcessor](../jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/preprocessors/DslJsr223PreProcessor.java) for more details and additional options.

## Use part of a response in a following request
Expand Down Expand Up @@ -333,4 +353,6 @@ public class PerformanceTest {
}
```

Check [DslRegexExtractor](../jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/postprocessors/DslRegexExtractor.java) for more details and additional options.
Check [DslRegexExtractor](../jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/postprocessors/DslRegexExtractor.java) for more details and additional options.

For more complex scenarios you can use [previously mentioned JSR223 Post processor](#change-sample-result-statuses-with-custom-logic).
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
import us.abstracta.jmeter.javadsl.core.listeners.InfluxDbBackendListener;
import us.abstracta.jmeter.javadsl.core.listeners.JtlWriter;
import us.abstracta.jmeter.javadsl.core.postprocessors.DslJsr223PostProcessor;
import us.abstracta.jmeter.javadsl.core.postprocessors.DslJsr223PostProcessor.Jsr223PostProcessorScript;
import us.abstracta.jmeter.javadsl.core.postprocessors.DslRegexExtractor;
import us.abstracta.jmeter.javadsl.core.preprocessors.DslJsr223PreProcessor;
import us.abstracta.jmeter.javadsl.core.preprocessors.DslJsr223PreProcessor.Jsr223PreProcessorScript;
import us.abstracta.jmeter.javadsl.http.DslHttpSampler;
import us.abstracta.jmeter.javadsl.http.HttpHeaders;

Expand Down Expand Up @@ -65,6 +67,8 @@ public static DslThreadGroup threadGroup(int threads, int iterations,
* Same as {@link #threadGroup(int, int, ThreadGroupChild...)} but allowing to set a name on the
* thread group.
*
* Setting a proper name allows to properly identify the requests generated in each thread group.
*
* @see #threadGroup(int, int, ThreadGroupChild...)
*/
public static DslThreadGroup threadGroup(String name, int threads, int iterations,
Expand Down Expand Up @@ -93,6 +97,8 @@ public static DslThreadGroup threadGroup(int threads, Duration duration,
* Same as {@link #threadGroup(int, Duration, ThreadGroupChild...)} but allowing to set a name on
* the thread group.
*
* Setting a proper name allows to properly identify the requests generated in each thread group.
*
* @see #threadGroup(int, Duration, ThreadGroupChild...)
*/
public static DslThreadGroup threadGroup(String name, int threads, Duration duration,
Expand All @@ -115,6 +121,9 @@ public static DslHttpSampler httpSampler(String url) {
/**
* Same as {@link #httpSampler(String)} but allowing to set a name to the HTTP Request sampler.
*
* Setting a proper name allows to easily identify the requests generated by this sampler and
* check it's particular statistics.
*
* @see #httpSampler(String)
*/
public static DslHttpSampler httpSampler(String name, String url) {
Expand Down Expand Up @@ -167,7 +176,50 @@ public static DslRegexExtractor regexExtractor(String variableName, String regex
* @see DslJsr223PostProcessor
*/
public static DslJsr223PostProcessor jsr223PostProcessor(String script) {
return new DslJsr223PostProcessor(script);
return new DslJsr223PostProcessor(null, script);
}

/**
* Same as {@link #jsr223PostProcessor(String)} but allowing to set a name on the post processor.
*
* The name is used as logger name which allows configuring log level, appender, etc, for the post
* processor.
*
* @see #jsr223PostProcessor(String)
*/
public static DslJsr223PostProcessor jsr223PostProcessor(String name, String script) {
return new DslJsr223PostProcessor(name, script);
}

/**
* Same as {@link #jsr223PostProcessor(String)} but allowing to use Java type safety and code
* completion when specifying the script.
*
* <b>WARNING:</b> Is currently only supported to run these post processors only with embedded
* jmeter engine (no support for saving to JMX and running it in JMeter GUI, or running it with
* BlazeMeter).
*
* @see Jsr223PostProcessorScript
* @see #jsr223PostProcessor(String)
*/
public static DslJsr223PostProcessor jsr223PostProcessor(Jsr223PostProcessorScript script) {
return new DslJsr223PostProcessor(null, script);
}

/**
* Same as {@link #jsr223PostProcessor(String, String)} but allowing to use Java type safety and
* code completion when specifying the script.
*
* <b>WARNING:</b> Is currently only supported to run these post processors only with embedded
* jmeter engine (no support for saving to JMX and running it in JMeter GUI, or running it with
* BlazeMeter).
*
* @see Jsr223PostProcessorScript
* @see #jsr223PostProcessor(String, String)
*/
public static DslJsr223PostProcessor jsr223PostProcessor(String name,
Jsr223PostProcessorScript script) {
return new DslJsr223PostProcessor(name, script);
}

/**
Expand All @@ -183,7 +235,50 @@ public static DslJsr223PostProcessor jsr223PostProcessor(String script) {
* @see DslJsr223PreProcessor
*/
public static DslJsr223PreProcessor jsr223PreProcessor(String script) {
return new DslJsr223PreProcessor(script);
return new DslJsr223PreProcessor(null, script);
}

/**
* Same as {@link #jsr223PreProcessor(String)} but allowing to set a name on the pre processor.
*
* The name is used as logger name which allows configuring log level, appender, etc, for the pre
* processor.
*
* @see #jsr223PreProcessor(String)
**/
public static DslJsr223PreProcessor jsr223PreProcessor(String name, String script) {
return new DslJsr223PreProcessor(name, script);
}

/**
* Same as {@link #jsr223PreProcessor(String)} but allowing to use Java type safety and code
* completion when specifying the script.
*
* <b>WARNING:</b> Is currently only supported to run these pre processors only with embedded
* jmeter engine (no support for saving to JMX and running it in JMeter GUI, or running it with
* BlazeMeter).
*
* @see Jsr223PreProcessorScript
* @see #jsr223PreProcessor(String)
*/
public static DslJsr223PreProcessor jsr223PreProcessor(Jsr223PreProcessorScript script) {
return new DslJsr223PreProcessor(null, script);
}

/**
* Same as {@link #jsr223PreProcessor(String, String)} but allowing to use Java type safety and
* code completion when specifying the script.
*
* <b>WARNING:</b> Is currently only supported to run these pre processors only with embedded
* jmeter engine (no support for saving to JMX and running it in JMeter GUI, or running it with
* BlazeMeter).
*
* @see Jsr223PreProcessorScript
* @see #jsr223PreProcessor(String)
*/
public static DslJsr223PreProcessor jsr223PreProcessor(String name,
Jsr223PreProcessorScript script) {
return new DslJsr223PreProcessor(name, script);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,76 @@
package us.abstracta.jmeter.javadsl.core;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.testbeans.gui.TestBeanGUI;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterVariables;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.util.JSR223TestElement;
import org.slf4j.Logger;

public abstract class DslJsr223TestElement extends BaseTestElement {

private static int scriptId = 1;

private final String script;
private String language = "groovy";
/*
this property is used to hold jmeter properties that will be set when building hash tree (when
properties have already been loaded). Can't set them in constructor since JMeter properties are
not yet loaded at that time.
*/
private final Map<String, Object> jmeterProps = new HashMap<>();

public DslJsr223TestElement(String name, String script) {
super(name, TestBeanGUI.class);
public DslJsr223TestElement(String name, String defaultName, String script) {
super(name != null ? name : defaultName, TestBeanGUI.class);
this.script = script;
}

public DslJsr223TestElement(String name, String defaultName, Jsr223Script<?> script,
Class<?> varsClass) {
super(name != null ? name : defaultName, TestBeanGUI.class);
String buildScriptId = buildScriptId();
jmeterProps.put(buildScriptId, script);
this.script = buildScriptString(buildScriptId, varsClass);
System.out.println(this.script);
}

private static String buildScriptId() {
return "Jsr223Script" + scriptId++;
}

private static String buildScriptString(String scriptId, Class<?> varsClass) {
return
"// It is currently not supported to run scripts defined in Java code in JMeter GUI or"
+ " non Embedded Engine (eg: BlazeMeter).\n"
+ "def script = (" + Jsr223Script.class.getName() + ") props.get('" + scriptId + "')\n"
+ "script.run(new " + varsClass.getName()
+ "(" + buildConstructorParameters(varsClass) + "))";
}

private static String buildConstructorParameters(Class<?> varsClass) {
return Arrays.stream(varsClass.getFields())
.map(Field::getName)
.collect(Collectors.joining(","));
}

public DslJsr223TestElement language(String language) {
this.language = language;
return this;
}

@Override
protected TestElement buildTestElement() {
if (!jmeterProps.isEmpty()) {
JMeterUtils.getJMeterProperties().putAll(jmeterProps);
}
JSR223TestElement ret = buildJsr223TestElement();
ret.setProperty("script", script);
ret.setProperty("scriptLanguage", language);
Expand All @@ -29,4 +79,31 @@ protected TestElement buildTestElement() {

protected abstract JSR223TestElement buildJsr223TestElement();

protected interface Jsr223Script<T extends Jsr223ScriptVars> {

void run(T scriptVars) throws Exception;
}

protected static class Jsr223ScriptVars {

public JMeterContext ctx;
public JMeterVariables vars;
public Properties props;
public Sampler sampler;
public Logger log;
@SuppressWarnings("checkstyle:membername")
public String Label;

public Jsr223ScriptVars(JMeterContext ctx, JMeterVariables vars, Properties props,
Sampler sampler, Logger log, String label) {
this.ctx = ctx;
this.vars = vars;
this.props = props;
this.sampler = sampler;
this.log = log;
this.Label = label;
}

}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package us.abstracta.jmeter.javadsl.core.postprocessors;

import java.util.Properties;
import org.apache.jmeter.extractor.JSR223PostProcessor;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterVariables;
import org.apache.jmeter.util.JSR223TestElement;
import org.slf4j.Logger;
import us.abstracta.jmeter.javadsl.core.DslJsr223TestElement;
import us.abstracta.jmeter.javadsl.core.MultiScopedTestElement;

Expand All @@ -17,13 +23,40 @@
*/
public class DslJsr223PostProcessor extends DslJsr223TestElement implements MultiScopedTestElement {

public DslJsr223PostProcessor(String script) {
super("JSR223 PostProcessor", script);
private static final String DEFAULT_NAME = "JSR223 PostProcessor";

public DslJsr223PostProcessor(String name, String script) {
super(name, DEFAULT_NAME, script);
}

public DslJsr223PostProcessor(String name, Jsr223PostProcessorScript script) {
super(name, DEFAULT_NAME, script, Jsr223PostProcessorScriptVars.class);
}

@Override
protected JSR223TestElement buildJsr223TestElement() {
return new JSR223PostProcessor();
}

/**
* Allows to use any java code as script.
*
* @see Jsr223PostProcessorScriptVars for a list of provided variables in script execution
*/
public interface Jsr223PostProcessorScript extends Jsr223Script<Jsr223PostProcessorScriptVars> {

}

public static class Jsr223PostProcessorScriptVars extends Jsr223ScriptVars {

public SampleResult prev;

public Jsr223PostProcessorScriptVars(SampleResult prev, JMeterContext ctx, JMeterVariables vars,
Properties props, Sampler sampler, Logger log, String label) {
super(ctx, vars, props, sampler, log, label);
this.prev = prev;
}

}

}
Loading

0 comments on commit fc0d859

Please sign in to comment.