Skip to content

Commit 1ccffdf

Browse files
committed
fix: avoid calling getScript and getFilename two times in BeanShellSampler, and JSR223TestSampler
Previously, BeanShellSampler called getScript several times which might cause wrong results when the script included a function that changes on each call (e.g. couter). Now getScript is called only once per sample.
1 parent 98af2da commit 1ccffdf

5 files changed

Lines changed: 106 additions & 21 deletions

File tree

src/core/src/main/java/org/apache/jmeter/util/BeanShellTestElement.java

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.io.Serializable;
2121

22+
import org.apache.jmeter.samplers.SampleResult;
2223
import org.apache.jmeter.testelement.AbstractTestElement;
2324
import org.apache.jmeter.testelement.TestStateListener;
2425
import org.apache.jmeter.testelement.ThreadListener;
@@ -130,12 +131,31 @@ public Object clone() {
130131
* <li>Parameters</li>
131132
* <li>bsh.args</li>
132133
* </ul>
133-
* @param bsh the interpreter, not {@code null}
134+
*
135+
* @param bsh the interpreter, not {@code null}
134136
* @return the result of the script, may be {@code null}
137+
* @throws JMeterException when working with the bsh fails
138+
*/
139+
protected Object processFileOrScript(BeanShellInterpreter bsh) throws JMeterException {
140+
return processFileOrScript(bsh, null);
141+
}
142+
143+
/**
144+
* Process the file or script from the test element.
145+
* <p>
146+
* Sets the following script variables:
147+
* <ul>
148+
* <li>FileName</li>
149+
* <li>Parameters</li>
150+
* <li>bsh.args</li>
151+
* </ul>
135152
*
153+
* @param bsh the interpreter, not {@code null}
154+
* @param sampleResult sampler result to set {@code setSamplerData} or {@code null}
155+
* @return the result of the script, may be {@code null}
136156
* @throws JMeterException when working with the bsh fails
137157
*/
138-
protected Object processFileOrScript(BeanShellInterpreter bsh) throws JMeterException{
158+
protected Object processFileOrScript(BeanShellInterpreter bsh, SampleResult sampleResult) throws JMeterException {
139159
String fileName = getFilename();
140160
String params = getParameters();
141161

@@ -147,7 +167,14 @@ protected Object processFileOrScript(BeanShellInterpreter bsh) throws JMeterExce
147167
JOrphanUtils.split(params, " "));//$NON-NLS-1$
148168

149169
if (fileName.length() == 0) {
150-
return bsh.eval(getScript());
170+
String bshScript = getScript();
171+
if (sampleResult != null) {
172+
sampleResult.setSamplerData(bshScript);
173+
}
174+
return bsh.eval(bshScript);
175+
}
176+
if (sampleResult != null) {
177+
sampleResult.setSamplerData(fileName);
151178
}
152179
return bsh.source(fileName);
153180
}

src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -182,13 +182,14 @@ protected Object processFileOrScript(ScriptEngine scriptEngine, final Bindings p
182182
bindings = scriptEngine.createBindings();
183183
}
184184
populateBindings(bindings);
185-
File scriptFile = new File(getFilename());
185+
String filename = getFilename();
186+
File scriptFile = new File(filename);
186187
// Hack: bsh-2.0b5.jar BshScriptEngine implements Compilable but throws
187188
// "java.lang.Error: unimplemented"
188189
boolean supportsCompilable = scriptEngine instanceof Compilable
189190
&& !"bsh.engine.BshScriptEngine".equals(scriptEngine.getClass().getName()); // NOSONAR // $NON-NLS-1$
190191
try {
191-
if (!StringUtils.isEmpty(getFilename())) {
192+
if (!StringUtils.isEmpty(filename)) {
192193
if (!scriptFile.isFile()) {
193194
throw new ScriptException("Script file '" + scriptFile.getAbsolutePath()
194195
+ "' is not a file for element: " + getName());
@@ -213,20 +214,22 @@ protected Object processFileOrScript(ScriptEngine scriptEngine, final Bindings p
213214
}
214215
});
215216
return compiledScript.eval(bindings);
216-
} else if (!StringUtils.isEmpty(getScript())) {
217+
}
218+
String script = getScript();
219+
if (!StringUtils.isEmpty(script)) {
217220
if (supportsCompilable &&
218221
!ScriptingBeanInfoSupport.FALSE_AS_STRING.equals(cacheKey)) {
219-
computeScriptMD5();
222+
computeScriptMD5(script);
220223
CompiledScript compiledScript = getCompiledScript(scriptMd5, key -> {
221224
try {
222-
return ((Compilable) scriptEngine).compile(getScript());
225+
return ((Compilable) scriptEngine).compile(script);
223226
} catch (ScriptException e) {
224227
throw new ScriptCompilationInvocationTargetException(e);
225228
}
226229
});
227230
return compiledScript.eval(bindings);
228231
} else {
229-
return scriptEngine.eval(getScript(), bindings);
232+
return scriptEngine.eval(script, bindings);
230233
}
231234
} else {
232235
throw new ScriptException("Both script file and script text are empty for element:" + getName());
@@ -304,10 +307,10 @@ public boolean compile()
304307
/**
305308
* compute MD5 if it is null
306309
*/
307-
private void computeScriptMD5() {
310+
private void computeScriptMD5(String script) {
308311
// compute the md5 of the script if needed
309312
if(scriptMd5 == null) {
310-
scriptMd5 = ScriptCacheKey.ofString(DigestUtils.md5Hex(getScript()));
313+
scriptMd5 = ScriptCacheKey.ofString(DigestUtils.md5Hex(script));
311314
}
312315
}
313316

@@ -355,7 +358,7 @@ public void testEnded() {
355358
@Override
356359
public void testEnded(String host) {
357360
COMPILED_SCRIPT_CACHE.invalidateAll();
358-
this.scriptMd5 = null;
361+
scriptMd5 = null;
359362
}
360363

361364
public String getScriptLanguage() {

src/protocol/java/build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,10 @@ dependencies {
3030
}
3131

3232
testImplementation(project(":src:core", "testClasses"))
33+
testImplementation(projects.src.functions) {
34+
because("We need __counter function for tests")
35+
}
36+
testImplementation("org.apache-extras.beanshell:bsh") {
37+
because("BeanShellTest needs BeanShell, and previously :protocol:java was not including a dependency on BeanShell")
38+
}
3339
}

src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/sampler/BeanShellSampler.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,6 @@ public SampleResult sample(Entry e)// Entry tends to be ignored ...
102102
return res;
103103
}
104104
try {
105-
String request = getScript();
106-
String fileName = getFilename();
107-
if (fileName.length() == 0) {
108-
res.setSamplerData(request);
109-
} else {
110-
res.setSamplerData(fileName);
111-
}
112-
113105
bshInterpreter.set("SampleResult", res); //$NON-NLS-1$
114106

115107
// Set default values
@@ -120,7 +112,7 @@ public SampleResult sample(Entry e)// Entry tends to be ignored ...
120112
res.setDataType(SampleResult.TEXT); // assume text output - script can override if necessary
121113

122114
savedBsh = bshInterpreter;
123-
Object bshOut = processFileOrScript(bshInterpreter);
115+
Object bshOut = processFileOrScript(bshInterpreter, res);
124116
savedBsh = null;
125117

126118
if (bshOut != null) {// Set response data
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.jmeter.protocol.java.sampler
19+
20+
import org.apache.jmeter.engine.util.CompoundVariable
21+
import org.apache.jmeter.testelement.TestElement
22+
import org.apache.jmeter.testelement.property.FunctionProperty
23+
import org.junit.jupiter.api.Assertions.assertEquals
24+
import org.junit.jupiter.api.Test
25+
26+
class BeanShellSamplerTest {
27+
// TODO: move to TestElement itself?
28+
private fun TestElement.setFunctionProperty(name: String, expression: String) {
29+
setProperty(
30+
FunctionProperty(
31+
name,
32+
CompoundVariable(expression).function
33+
)
34+
)
35+
}
36+
37+
@Test
38+
fun `getScript executes only once`() {
39+
val sampler = BeanShellSampler().apply {
40+
name = "BeanShell Sampler"
41+
setFunctionProperty(
42+
BeanShellSampler.SCRIPT,
43+
"""ResponseMessage="COUNTER=${"$"}{__counter(FALSE)}""""
44+
)
45+
setProperty(BeanShellSampler.FILENAME, "")
46+
setProperty(BeanShellSampler.PARAMETERS, "")
47+
isRunningVersion = true
48+
}
49+
val result = sampler.sample(null)
50+
assertEquals(
51+
"COUNTER=1",
52+
result.responseMessage,
53+
"__counter(false) should return 1 on the first execution. If the value is different, it might mean " +
54+
"the script was evaluated several times"
55+
)
56+
}
57+
}

0 commit comments

Comments
 (0)