blob: d2909b8c286604aaf55d0ac61efad4a8d4d3e3e6 [file] [log] [blame]
/*
* 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.control;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Field;
import java.net.URISyntaxException;
import java.net.URL;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.message.AbstractHttpMessage;
import org.apache.http.message.BasicHeader;
import org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui;
import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase;
import org.apache.jmeter.protocol.http.util.HTTPConstants;
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.threads.JMeterVariables;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import com.github.benmanes.caffeine.cache.Cache;
/**
* Test {@link CacheManager} that uses HTTPHC4Impl
*/
public class TestCacheManagerThreadIteration {
private JMeterContext jmctx;
private JMeterVariables jmvars;
private static final String SAME_USER="__jmv_SAME_USER";
protected static final String LOCAL_HOST = "http://localhost/";
protected static final String EXPECTED_ETAG = "0xCAFEBABEDEADBEEF";
protected static final ZoneId GMT = ZoneId.of("GMT");
protected CacheManager cacheManager;
protected String currentTimeInGMT;
protected String vary = null;
protected URL url;
protected HTTPSampleResult sampleResultOK;
private class HttpResponseStub extends AbstractHttpMessage implements HttpResponse {
private org.apache.http.Header lastModifiedHeader;
private org.apache.http.Header etagHeader;
private String expires;
private String cacheControl;
private org.apache.http.Header dateHeader;
private List<org.apache.http.Header> headers;
public HttpResponseStub() {
this.headers = new ArrayList<>();
this.lastModifiedHeader = new BasicHeader(HTTPConstants.LAST_MODIFIED, currentTimeInGMT);
this.dateHeader = new BasicHeader(HTTPConstants.DATE, currentTimeInGMT);
this.etagHeader = new BasicHeader(HTTPConstants.ETAG, EXPECTED_ETAG);
}
/*
* (non-Javadoc)
*
* @see org.apache.http.message.AbstractHttpMessage#getAllHeaders()
*/
@Override
public org.apache.http.Header[] getAllHeaders() {
return headers.toArray(new org.apache.http.Header[headers.size()]);
}
/*
* (non-Javadoc)
*
* @see
* org.apache.http.message.AbstractHttpMessage#addHeader(org.apache.http.Header)
*/
@Override
public void addHeader(org.apache.http.Header header) {
headers.add(header);
}
/*
* (non-Javadoc)
*
* @see
* org.apache.http.message.AbstractHttpMessage#getFirstHeader(java.lang.String)
*/
@Override
public Header getFirstHeader(String headerName) {
Header[] headers = getHeaders(headerName);
if (headers.length > 0) {
return headers[0];
}
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.http.message.AbstractHttpMessage#getLastHeader(java.lang.String)
*/
@Override
public Header getLastHeader(String headerName) {
Header[] headers = getHeaders(headerName);
if (headers.length > 0) {
return headers[headers.length - 1];
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.apache.http.message.AbstractHttpMessage#getHeaders(java.lang.String)
*/
@Override
public Header[] getHeaders(String headerName) {
org.apache.http.Header header = null;
if (HTTPConstants.LAST_MODIFIED.equals(headerName)) {
header = this.lastModifiedHeader;
} else if (HTTPConstants.ETAG.equals(headerName)) {
header = this.etagHeader;
} else if (HTTPConstants.EXPIRES.equals(headerName)) {
header = expires == null ? null : new BasicHeader(HTTPConstants.EXPIRES, expires);
} else if (HTTPConstants.CACHE_CONTROL.equals(headerName)) {
header = cacheControl == null ? null : new BasicHeader(HTTPConstants.CACHE_CONTROL, cacheControl);
} else if (HTTPConstants.DATE.equals(headerName)) {
header = this.dateHeader;
} else if (HTTPConstants.VARY.equals(headerName)) {
header = vary == null ? null : new BasicHeader(HTTPConstants.VARY, vary);
}
if (header != null) {
return new org.apache.http.Header[] { header };
} else {
return super.getHeaders(headerName);
}
}
@Override
public ProtocolVersion getProtocolVersion() {
return null;
}
@Override
public StatusLine getStatusLine() {
return null;
}
@Override
public void setStatusLine(StatusLine statusline) {
}
@Override
public void setStatusLine(ProtocolVersion ver, int code) {
}
@Override
public void setStatusLine(ProtocolVersion ver, int code, String reason) {
}
@Override
public void setStatusCode(int code) throws IllegalStateException {
}
@Override
public void setReasonPhrase(String reason) throws IllegalStateException {
}
@Override
public HttpEntity getEntity() {
return null;
}
@Override
public void setEntity(HttpEntity entity) {
}
@Override
public Locale getLocale() {
return null;
}
@Override
public void setLocale(Locale loc) {
}
}
private class HttpPostStub extends HttpPost {
HttpPostStub() {
}
@Override
public java.net.URI getURI() {
try {
return url.toURI();
} catch (URISyntaxException e) {
throw new IllegalStateException();
}
}
}
protected String makeDate(Instant d) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss z")
.withLocale(Locale.US)
.withZone(GMT);
return formatter.format(d);
}
protected HTTPSampleResult getSampleResultWithSpecifiedResponseCode(String code) {
HTTPSampleResult sampleResult = new HTTPSampleResult();
sampleResult.setResponseCode(code);
sampleResult.setHTTPMethod("GET");
sampleResult.setURL(url);
return sampleResult;
}
private HttpRequestBase httpMethod;
private HttpResponse httpResponse;
@BeforeEach
public void setUp() throws Exception {
this.cacheManager = new CacheManager();
this.currentTimeInGMT = makeDate(Instant.now());
this.url = new URL(LOCAL_HOST);
this.sampleResultOK = getSampleResultWithSpecifiedResponseCode("200");
this.httpMethod = new HttpPostStub();
this.httpResponse = new HttpResponseStub();
this.httpMethod.setURI(this.url.toURI());
jmctx = JMeterContextService.getContext();
jmvars = new JMeterVariables();
}
@AfterEach
public void tearDown() throws Exception {
this.url = null;
this.httpMethod = null;
this.httpResponse = null;
this.cacheManager = new CacheManager();
this.currentTimeInGMT = null;
this.sampleResultOK = null;
}
protected void setExpires(String expires) {
((HttpResponseStub) httpResponse).expires = expires;
}
protected void setCacheControl(String cacheControl) {
((HttpResponseStub) httpResponse).cacheControl = cacheControl;
}
protected void setLastModified(String lastModified) {
((HttpResponseStub) httpResponse).lastModifiedHeader = new BasicHeader(HTTPConstants.LAST_MODIFIED,
lastModified);
}
protected void cacheResult(HTTPSampleResult result) {
this.cacheManager.saveDetails(httpResponse, result);
}
protected void addRequestHeader(String requestHeader, String value) {
this.httpMethod.addHeader(new BasicHeader(requestHeader, value));
}
protected void setRequestHeaders() {
this.cacheManager.setHeaders(this.url, this.httpMethod);
}
private Cache<String, CacheManager.CacheEntry> getThreadCache() throws Exception {
Field threadLocalfield = CacheManager.class.getDeclaredField("threadCache");
threadLocalfield.setAccessible(true);
@SuppressWarnings("unchecked")
ThreadLocal<Cache<String, CacheManager.CacheEntry>> threadLocal = (ThreadLocal<Cache<String, CacheManager.CacheEntry>>) threadLocalfield
.get(this.cacheManager);
return threadLocal.get();
}
protected CacheManager.CacheEntry getThreadCacheEntry(String url) throws Exception {
return getThreadCache().getIfPresent(url);
}
@Test
public void testCacheControlCleared() throws Exception {
this.cacheManager.setUseExpires(true);
this.cacheManager.testIterationStart(null);
assertNull(getThreadCacheEntry(LOCAL_HOST), "Should not find entry");
Header[] headers = new Header[1];
assertFalse(this.cacheManager.inCache(url, headers), "Should not find valid entry");
long start = System.currentTimeMillis();
setExpires(makeDate(Instant.ofEpochMilli(start)));
setCacheControl("public, max-age=1");
cacheResult(sampleResultOK);
assertNotNull(getThreadCacheEntry(LOCAL_HOST), "Before iternation, should find entry");
assertTrue(this.cacheManager.inCache(url, headers), "Before iternation, should find valid entry");
this.cacheManager.setClearEachIteration(true);
this.cacheManager.testIterationStart(null);
assertNull(getThreadCacheEntry(LOCAL_HOST), "After iterantion, should not find entry");
assertFalse(this.cacheManager.inCache(url, headers), "After iterantion, should not find valid entry");
}
@Test
public void testJmeterVariableCacheWhenThreadIterationIsANewUser() {
jmvars.putObject(SAME_USER, true);
jmctx.setVariables(jmvars);
HTTPSamplerBase sampler = (HTTPSamplerBase) new HttpTestSampleGui().createTestElement();
cacheManager.setControlledByThread(true);
sampler.setCacheManager(cacheManager);
sampler.setThreadContext(jmctx);
boolean res = (boolean) cacheManager.getThreadContext().getVariables().getObject(SAME_USER);
assertTrue(res, "When test different user on the different iternation, the cache should be cleared");
}
@Test
public void testJmeterVariableWhenThreadIterationIsSameUser() {
jmvars.putObject(SAME_USER, false);
jmctx.setVariables(jmvars);
HTTPSamplerBase sampler = (HTTPSamplerBase) new HttpTestSampleGui().createTestElement();
cacheManager.setControlledByThread(true);
sampler.setCacheManager(cacheManager);
sampler.setThreadContext(jmctx);
boolean res = (boolean) cacheManager.getThreadContext().getVariables().getObject(SAME_USER);
assertFalse(res, "When test different user on the different iternation, the cache shouldn't be cleared");
}
@Test
public void testCacheManagerWhenThreadIterationIsANewUser() throws Exception {
//Controlled by ThreadGroup
jmvars.putObject(SAME_USER, false);
jmctx.setVariables(jmvars);
this.cacheManager.setUseExpires(true);
this.cacheManager.testIterationStart(null);
assertNull(getThreadCacheEntry(LOCAL_HOST), "Should not find entry");
Header[] headers = new Header[1];
assertFalse(this.cacheManager.inCache(url, headers), "Should not find valid entry");
long start = System.currentTimeMillis();
setExpires(makeDate(Instant.ofEpochMilli(start)));
setCacheControl("public, max-age=1");
cacheResult(sampleResultOK);
this.cacheManager.setThreadContext(jmctx);
this.cacheManager.setControlledByThread(true);
assertNotNull(getThreadCacheEntry(LOCAL_HOST), "Before iternation, should find entry");
assertTrue(this.cacheManager.inCache(url, headers), "Before iternation, should find valid entry");
this.cacheManager.testIterationStart(null);
assertNull(getThreadCacheEntry(LOCAL_HOST), "After iterantion, should not find entry");
assertFalse(this.cacheManager.inCache(url, headers), "After iterantion, should not find valid entry");
//Controlled by cacheManager
jmvars.putObject(SAME_USER, true);
jmctx.setVariables(jmvars);
this.cacheManager.setThreadContext(jmctx);
start = System.currentTimeMillis();
setExpires(makeDate(Instant.ofEpochMilli(start)));
setCacheControl("public, max-age=1");
cacheResult(sampleResultOK);
assertNotNull(getThreadCacheEntry(LOCAL_HOST), "Before iternation, should find entry");
assertTrue(this.cacheManager.inCache(url, headers), "Before iternation, should find valid entry");
this.cacheManager.setControlledByThread(false);
this.cacheManager.setClearEachIteration(true);
this.cacheManager.testIterationStart(null);
assertNull(getThreadCacheEntry(LOCAL_HOST), "After iterantion, should not find entry");
assertFalse(this.cacheManager.inCache(url, headers), "After iterantion, should not find valid entry");
}
@Test
public void testCacheManagerWhenThreadIterationIsSameUser() throws Exception {
// Controlled by ThreadGroup
jmvars.putObject(SAME_USER, true);
jmctx.setVariables(jmvars);
this.cacheManager.setUseExpires(true);
this.cacheManager.testIterationStart(null);
assertNull(getThreadCacheEntry(LOCAL_HOST), "Should not find entry");
Header[] headers = new Header[1];
assertFalse(this.cacheManager.inCache(url, headers), "Should not find valid entry");
long start = System.currentTimeMillis();
setExpires(makeDate(Instant.ofEpochMilli(start)));
setCacheControl("public, max-age=1");
cacheResult(sampleResultOK);
this.cacheManager.setThreadContext(jmctx);
this.cacheManager.setControlledByThread(true);
assertNotNull(getThreadCacheEntry(LOCAL_HOST), "Before iteration, should find entry");
assertTrue(this.cacheManager.inCache(url, headers), "Before iteration, should find valid entry");
this.cacheManager.testIterationStart(null);
assertNotNull(getThreadCacheEntry(LOCAL_HOST), "After iteration, should find entry");
assertTrue(this.cacheManager.inCache(url, headers), "After iteration, should find valid entry");
// Controlled by cacheManager
jmvars.putObject(SAME_USER, false);
jmctx.setVariables(jmvars);
this.cacheManager.setThreadContext(jmctx);
start = System.currentTimeMillis();
setExpires(makeDate(Instant.ofEpochMilli(start)));
setCacheControl("public, max-age=1");
cacheResult(sampleResultOK);
assertNotNull(getThreadCacheEntry(LOCAL_HOST), "Before iteration, should find entry");
assertTrue(this.cacheManager.inCache(url, headers), "Before iteration, should find valid entry");
this.cacheManager.setControlledByThread(false);
this.cacheManager.setClearEachIteration(false);
this.cacheManager.testIterationStart(null);
assertNotNull(getThreadCacheEntry(LOCAL_HOST), "After iteration, should find entry");
assertTrue(this.cacheManager.inCache(url, headers), "After iteration, should find valid entry");
}
}