Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -368,14 +368,13 @@ private void putSamplerPackageInCompilerMap(Object sampler, SamplePackage pack)
}

/**
* Parent synthetic sampler must not inherit child assertions/post-processors: JMeter would run
* them against the aggregated parent {@link SampleResult}, whose response body is empty.
* Parent synthetic sampler must not inherit child timers, assertions, or post-processors.
*/
private static SamplePackage createParentExecutionPackage(SamplePackage childPack) {
SamplePackage parentPack = new SamplePackage(
childPack.getConfigs(),
childPack.getSampleListeners(),
childPack.getTimers(),
new ArrayList<>(),
new ArrayList<>(),
new ArrayList<>(),
new ArrayList<>(),
Expand Down
63 changes: 45 additions & 18 deletions src/main/java/com/blazemeter/jmeter/http2/sampler/HTTP2Sampler.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import org.apache.jmeter.threads.JMeterVariables;
import org.apache.jmeter.threads.SamplePackage;
import org.apache.jmeter.threads.TestCompiler;
import org.apache.jmeter.timers.Timer;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.util.JOrphanUtils;
import org.apache.oro.text.MalformedCachePatternException;
Expand Down Expand Up @@ -152,6 +153,7 @@ protected Map<HTTP2ClientKey, HTTP2JettyClient> childValue(
private int requestTimeout;
private HTTPSampleResult result;
private transient List<PreProcessor> suppressedPreProcessors;
private transient List<Timer> suppressedTimers;
private transient SamplePackage suppressedSamplePackage;
private transient boolean profileInferenceWarningLogged;
private transient boolean asyncParentSampleEnabled;
Expand Down Expand Up @@ -529,8 +531,8 @@ protected HTTPSampleResult sample(URL url, String method, boolean areFollowingRe
/**
* When {@link #isAsyncParentSampleEnabled()} and this sampler returns {@code null} to JMeter,
* run post-processors and assertions here (same order as {@code JMeterThread}) so child elements
* still apply; pre-processors already ran on the initial async dispatch and stay suppressed until
* {@link #restoreSuppressedPreProcessors()} in {@code finally}.
* still apply; pre-processors and timers already ran on the initial async dispatch and stay
* suppressed until {@link #restoreSuppressedPreProcessors()} in {@code finally}.
*/
private void applyAsyncParentCompletionPipeline(HTTPSampleResult res) {
if (res == null || !isAsyncParentSampleEnabled()) {
Expand Down Expand Up @@ -1083,8 +1085,12 @@ public void iterationStart(LoopIterationEvent iterEvent) {
}
}

/**
* Clears pre-processors and timers from the {@link SamplePackage} before the async completion
* pass so JMeter does not run them twice.
*/
public void suppressPreProcessorsOnce() {
if (suppressedPreProcessors != null) {
if (suppressedSamplePackage != null) {
return;
}
try {
Expand All @@ -1099,21 +1105,32 @@ public void suppressPreProcessorsOnce() {
return;
}
SamplePackage pack = getSamplePackageFromCompiler(compiler);
if (pack == null || pack.getPreProcessors() == null) {
LOG.debug("No SamplePackage found for sampler={}, skipping pre-processor suppression",
if (pack == null) {
LOG.debug(
"No SamplePackage found for sampler={}, skipping async completion suppression",
getName());
return;
}
boolean suppressed = false;
List<PreProcessor> currentPre = pack.getPreProcessors();
if (currentPre.isEmpty()) {
return;
if (currentPre != null && !currentPre.isEmpty()) {
suppressedPreProcessors = new ArrayList<>(currentPre);
currentPre.clear();
suppressed = true;
}
List<Timer> currentTimers = pack.getTimers();
if (currentTimers != null && !currentTimers.isEmpty()) {
suppressedTimers = new ArrayList<>(currentTimers);
currentTimers.clear();
suppressed = true;
}
if (suppressed) {
suppressedSamplePackage = pack;
LOG.debug("Pre-processors/timers suppressed for async completion run (sampler={})",
getName());
}
suppressedPreProcessors = new ArrayList<>(currentPre);
suppressedSamplePackage = pack;
currentPre.clear();
LOG.debug("Pre-processors suppressed for async completion run (sampler={})", getName());
} catch (Exception e) {
LOG.debug("Failed to suppress pre-processors for async completion", e);
LOG.debug("Failed to suppress pre-processors/timers for async completion", e);
}
}

Expand All @@ -1130,18 +1147,28 @@ private SamplePackage getSamplePackageFromCompiler(TestCompiler compiler) {
}

private void restoreSuppressedPreProcessors() {
if (suppressedPreProcessors == null || suppressedSamplePackage == null) {
if (suppressedSamplePackage == null) {
return;
}
try {
List<PreProcessor> currentPre = suppressedSamplePackage.getPreProcessors();
if (currentPre != null) {
currentPre.clear();
currentPre.addAll(suppressedPreProcessors);
if (suppressedPreProcessors != null) {
List<PreProcessor> currentPre = suppressedSamplePackage.getPreProcessors();
if (currentPre != null) {
currentPre.clear();
currentPre.addAll(suppressedPreProcessors);
}
}
if (suppressedTimers != null) {
List<Timer> currentTimers = suppressedSamplePackage.getTimers();
if (currentTimers != null) {
currentTimers.clear();
currentTimers.addAll(suppressedTimers);
}
}
LOG.debug("Pre-processors restored after async completion (sampler={})", getName());
LOG.debug("Pre-processors/timers restored after async completion (sampler={})", getName());
} finally {
suppressedPreProcessors = null;
suppressedTimers = null;
suppressedSamplePackage = null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ public void parentSampleExecutionDoesNotReRunChildAssertionsOnEmptyParentBody()

SamplePackage parentPack = getCompiler(jmeterThread).configureSampler(parentSampler);
assertThat(parentPack.getAssertions()).isEmpty();
assertThat(parentPack.getTimers()).isEmpty();

SampleResult parentResult = jmeterContext.getPreviousResult();
assertThat(parentResult).isNotNull();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package com.blazemeter.jmeter.http2.sampler;

import static org.assertj.core.api.Assertions.assertThat;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.apache.jmeter.control.LoopController;
import org.apache.jmeter.processor.PreProcessor;
import org.apache.jmeter.testelement.AbstractTestElement;
import org.apache.jmeter.timers.ConstantTimer;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.threads.JMeterThread;
import org.apache.jmeter.threads.JMeterThreadMonitor;
import org.apache.jmeter.threads.ListenerNotifier;
import org.apache.jmeter.threads.SamplePackage;
import org.apache.jmeter.threads.TestCompiler;
import org.apache.jorphan.collections.HashTree;
import org.junit.Test;

public class HTTP2SamplerAsyncTimerTest {

private static class NoOpPreProcessor extends AbstractTestElement implements PreProcessor {
@Override
public void process() {
// no-op
}
}

@Test
public void shouldSuppressAndRestoreTimersForAsyncCompletion() throws Exception {
JMeterTestUtils.setupJmeterEnv();

HTTP2Sampler sampler = new HTTP2Sampler();
ConstantTimer timer = new ConstantTimer();
timer.setDelay("100");

HashTree testTree = new HashTree();
LoopController controller = new LoopController();
controller.setLoops(1);
controller.initialize();
HashTree controllerTree = testTree.add(controller);
controllerTree.add(sampler);
controllerTree.add(sampler, timer);

JMeterThreadMonitor monitor = thread -> {
// no-op
};
JMeterThread thread = new JMeterThread(testTree, monitor, new ListenerNotifier());
JMeterContextService.getContext().setThread(thread);

TestCompiler compiler = getCompiler(thread);
testTree.traverse(compiler);
SamplePackage samplePackage = compiler.configureSampler(sampler);
assertThat(samplePackage.getTimers()).hasSize(1);

sampler.suppressPreProcessorsOnce();
assertThat(samplePackage.getTimers()).isEmpty();

restoreSuppressedPreProcessors(sampler);
assertThat(samplePackage.getTimers()).hasSize(1);
}

@Test
public void shouldSuppressTimersWhenNoPreProcessors() throws Exception {
JMeterTestUtils.setupJmeterEnv();

HTTP2Sampler sampler = new HTTP2Sampler();
ConstantTimer timer = new ConstantTimer();
timer.setDelay("50");

HashTree testTree = new HashTree();
LoopController controller = new LoopController();
controller.setLoops(1);
controller.initialize();
HashTree controllerTree = testTree.add(controller);
controllerTree.add(sampler);
controllerTree.add(sampler, timer);

JMeterThread thread = new JMeterThread(testTree, thread1 -> {
}, new ListenerNotifier());
JMeterContextService.getContext().setThread(thread);

TestCompiler compiler = getCompiler(thread);
testTree.traverse(compiler);
SamplePackage samplePackage = compiler.configureSampler(sampler);
assertThat(samplePackage.getPreProcessors()).isEmpty();
assertThat(samplePackage.getTimers()).hasSize(1);

sampler.suppressPreProcessorsOnce();
assertThat(samplePackage.getTimers()).isEmpty();

restoreSuppressedPreProcessors(sampler);
assertThat(samplePackage.getTimers()).hasSize(1);
}

@Test
public void shouldSuppressBothPreProcessorsAndTimers() throws Exception {
JMeterTestUtils.setupJmeterEnv();

HTTP2Sampler sampler = new HTTP2Sampler();
ConstantTimer timer = new ConstantTimer();
timer.setDelay("100");
PreProcessor preProcessor = new NoOpPreProcessor();

HashTree testTree = new HashTree();
LoopController controller = new LoopController();
controller.setLoops(1);
controller.initialize();
HashTree controllerTree = testTree.add(controller);
controllerTree.add(sampler);
controllerTree.add(sampler, timer);
controllerTree.add(sampler, preProcessor);

JMeterThread thread = new JMeterThread(testTree, thread1 -> {
}, new ListenerNotifier());
JMeterContextService.getContext().setThread(thread);

TestCompiler compiler = getCompiler(thread);
testTree.traverse(compiler);
SamplePackage samplePackage = compiler.configureSampler(sampler);

sampler.suppressPreProcessorsOnce();
assertThat(samplePackage.getPreProcessors()).isEmpty();
assertThat(samplePackage.getTimers()).isEmpty();

restoreSuppressedPreProcessors(sampler);
assertThat(samplePackage.getPreProcessors()).hasSize(1);
assertThat(samplePackage.getTimers()).hasSize(1);
}

private TestCompiler getCompiler(JMeterThread thread) throws Exception {
Field compilerField = JMeterThread.class.getDeclaredField("compiler");
compilerField.setAccessible(true);
return (TestCompiler) compilerField.get(thread);
}

private void restoreSuppressedPreProcessors(HTTP2Sampler sampler) throws Exception {
Method restoreMethod = HTTP2Sampler.class.getDeclaredMethod("restoreSuppressedPreProcessors");
restoreMethod.setAccessible(true);
restoreMethod.invoke(sampler);
}
}
Loading