diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBase.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBase.java index 3105d0cf1ec..4080eae7764 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBase.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBase.java @@ -730,15 +730,20 @@ public boolean hasArguments() { @Override public void addTestElement(TestElement el) { if (el instanceof CookieManager cookieManager) { - setCookieManager(cookieManager); + // Route through the private property setter to skip the "superseded by" warning: + // addTestElement is invoked on every thread-group iteration by TestCompiler, + // and the config element is the same instance as the one already attached, + // so the warning would always fire spuriously. Users still get the warning + // when they explicitly call setCookieManager with a different instance. + setCookieManagerProperty(cookieManager); } else if (el instanceof CacheManager cacheManager) { - setCacheManager(cacheManager); + setCacheManagerProperty(cacheManager); } else if (el instanceof HeaderManager headerManager) { setHeaderManager(headerManager); } else if (el instanceof AuthManager authManager) { - setAuthManager(authManager); + setAuthManagerProperty(authManager); } else if (el instanceof DNSCacheManager dnsCacheManager) { - setDNSResolver(dnsCacheManager); + setDNSResolverProperty(dnsCacheManager); } else if (el instanceof KeystoreConfig keystoreConfig) { setKeystoreConfigProperty(keystoreConfig); } else { @@ -923,13 +928,19 @@ public boolean getPostBodyRaw() { return get(getSchema().getPostBodyRaw()); } + @SuppressWarnings("ReferenceEquality") public void setAuthManager(AuthManager value) { AuthManager mgr = getAuthManager(); - if (mgr != null) { + if (mgr != null && mgr != value) { if(log.isWarnEnabled()) { log.warn("Existing AuthManager {} superseded by {}", mgr.getName(), value.getName()); } } + setAuthManagerProperty(value); + } + + // private method to allow AsyncSample and addTestElement to reset the value without performing checks + private void setAuthManagerProperty(AuthManager value) { set(getSchema().getAuthManager(), value); } @@ -961,9 +972,10 @@ private void setCookieManagerProperty(CookieManager value) { set(getSchema().getCookieManager(), value);; } + @SuppressWarnings("ReferenceEquality") public void setCookieManager(CookieManager value) { CookieManager mgr = getCookieManager(); - if (mgr != null) { + if (mgr != null && mgr != value) { if(log.isWarnEnabled()) { log.warn("Existing CookieManager {} superseded by {}", mgr.getName(), value.getName()); } @@ -983,9 +995,10 @@ private void setKeystoreConfigProperty(KeystoreConfig value) { set(getSchema().getKeystoreConfig(), value); } + @SuppressWarnings("ReferenceEquality") public void setKeystoreConfig(KeystoreConfig value) { KeystoreConfig mgr = getKeystoreConfig(); - if (mgr != null && log.isWarnEnabled()) { + if (mgr != null && mgr != value && log.isWarnEnabled()) { log.warn("Existing KeystoreConfig {} superseded by {}", mgr.getName(), value.getName()); } setKeystoreConfigProperty(value); @@ -995,9 +1008,10 @@ public KeystoreConfig getKeystoreConfig() { return getOrNull(getSchema().getKeystoreConfig()); } + @SuppressWarnings("ReferenceEquality") public void setCacheManager(CacheManager value) { CacheManager mgr = getCacheManager(); - if (mgr != null) { + if (mgr != null && mgr != value) { if(log.isWarnEnabled()) { log.warn("Existing CacheManager {} superseded by {}", mgr.getName(), value.getName()); } @@ -1013,13 +1027,19 @@ public DNSCacheManager getDNSResolver() { return getOrNull(getSchema().getDnsCacheManager()); } + @SuppressWarnings("ReferenceEquality") public void setDNSResolver(DNSCacheManager cacheManager) { DNSCacheManager mgr = getDNSResolver(); - if (mgr != null) { + if (mgr != null && mgr != cacheManager) { if(log.isWarnEnabled()) { log.warn("Existing DNSCacheManager {} superseded by {}", mgr.getName(), cacheManager.getName()); } } + setDNSResolverProperty(cacheManager); + } + + // private method to allow addTestElement to reset the value without performing checks + private void setDNSResolverProperty(DNSCacheManager cacheManager) { set(getSchema().getDnsCacheManager(), cacheManager); } diff --git a/src/protocol/http/src/test/java/org/apache/jmeter/protocol/http/sampler/HttpSamplerManagerWarningsTest.java b/src/protocol/http/src/test/java/org/apache/jmeter/protocol/http/sampler/HttpSamplerManagerWarningsTest.java new file mode 100644 index 00000000000..4268b0eec99 --- /dev/null +++ b/src/protocol/http/src/test/java/org/apache/jmeter/protocol/http/sampler/HttpSamplerManagerWarningsTest.java @@ -0,0 +1,298 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.protocol.http.sampler; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import org.apache.jmeter.config.KeystoreConfig; +import org.apache.jmeter.protocol.http.control.AuthManager; +import org.apache.jmeter.protocol.http.control.CacheManager; +import org.apache.jmeter.protocol.http.control.CookieManager; +import org.apache.jmeter.protocol.http.control.DNSCacheManager; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Regression tests for spurious "Existing XxxManager ... superseded by ..." warnings + * fired on every thread-group iteration after the first one. + * + *
Root cause: {@link HTTPSamplerBase#addTestElement(TestElement)} routed CookieManager, + * CacheManager, AuthManager and DNSCacheManager through their public {@code setXxxManager} + * methods, which log a warning whenever the existing manager is non-null. Since + * {@code clearTestElementChildren()} only clears the HeaderManager property, the manager + * reference survives across iterations and the warning fires N-1 times for N iterations.
+ * + *Fix:
+ *These tests verify both the {@code addTestElement} path (used by TestCompiler on each + * iteration) and the public setter path. Warnings are captured by redirecting the log4j2 + * "jmeter-log" file appender to a per-class temporary file via the {@code jmeter.logfile} + * system property (see {@code src/core/src/testFixtures/resources/log4j2.xml"}), so the + * tests do not depend on the working directory and can be re-run in isolation.
+ */ +public class HttpSamplerManagerWarningsTest { + + /** log4j2 system property used in src/core/src/testFixtures/resources/log4j2.xml. */ + private static final String JMeter_LOGFILE_PROPERTY = "jmeter.logfile"; + + private static Path logFile; + private static String originalLogfileProperty; + + private HTTPSamplerBase sampler; + + @BeforeAll + public static void redirectLogFile(@TempDir Path tempDir) { + logFile = tempDir.resolve("HttpSamplerManagerWarningsTest.log"); + originalLogfileProperty = System.getProperty(JMeter_LOGFILE_PROPERTY); + System.setProperty(JMeter_LOGFILE_PROPERTY, logFile.toAbsolutePath().toString()); + // Force log4j2 to re-read the configuration so the ${sys:jmeter.logfile:-jmeter.log} + // lookup in src/core/src/testFixtures/resources/log4j2.xml picks up the new path. + ((LoggerContext) LogManager.getContext(false)).reconfigure(); + } + + @AfterAll + public static void restoreLogFile() { + if (originalLogfileProperty == null) { + System.clearProperty(JMeter_LOGFILE_PROPERTY); + } else { + System.setProperty(JMeter_LOGFILE_PROPERTY, originalLogfileProperty); + } + ((LoggerContext) LogManager.getContext(false)).reconfigure(); + } + + @BeforeEach + public void setUp() { + sampler = (HTTPSamplerBase) new HttpTestSampleGui().createTestElement(); + } + + /** + * Provides the 5 replace-mode managers covered by the fix. + * Each argument is: (manager factory, display name). + */ + static StreamExisting CookieManager … superseded by … (and analogous CacheManager, AuthManager, DNSCacheManager, KeystoreConfig) warnings that fired on every thread-group iteration after the first one. addTestElement now routes replace-mode managers through their private property setters (mirroring the existing KeystoreConfig path), and the public setters no longer warn when the new value is the same instance as the existing one.