/* * 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 java.lang.reflect.Field; import java.net.URL; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.Map; import java.util.TimeZone; import org.apache.jmeter.junit.JMeterTestCase; import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult; import org.apache.jmeter.protocol.http.util.HTTPConstants; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; public abstract class TestCacheManagerBase extends JMeterTestCase { protected static final String LOCAL_HOST = "http://localhost/"; protected static final String EXPECTED_ETAG = "0xCAFEBABEDEADBEEF"; protected static final TimeZone GMT = TimeZone.getTimeZone("GMT"); protected CacheManager cacheManager; protected String currentTimeInGMT; protected String vary = null; protected URL url; protected HTTPSampleResult sampleResultOK; protected String makeDate(Date d) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); simpleDateFormat.setTimeZone(GMT); return simpleDateFormat.format(d); } @Before public void setUp() throws Exception { this.cacheManager = new CacheManager(); this.currentTimeInGMT = makeDate(new Date()); this.url = new URL(LOCAL_HOST); this.sampleResultOK = getSampleResultWithSpecifiedResponseCode("200"); } @After public void tearDown() throws Exception { this.url = null; this.cacheManager = null; this.currentTimeInGMT = null; this.sampleResultOK = null; } protected abstract void setExpires(String expires); protected abstract void setCacheControl(String cacheControl); protected abstract void cacheResult(HTTPSampleResult result) throws Exception; protected abstract void setLastModified(String lastModified); protected abstract void checkRequestHeader(String requestHeader, String expectedValue); protected abstract void addRequestHeader(String requestHeader, String value); protected abstract void setRequestHeaders(); private void sleepTill(long deadline) { while (System.currentTimeMillis() < deadline) { try { Thread.sleep(100); } catch (InterruptedException e) { // FIXME Doing this can lead to sleep not sleeping expected time and random errors Thread.currentThread().interrupt(); return; } } } @Test public void testExpiresBug59962() throws Exception { this.cacheManager.setUseExpires(true); this.cacheManager.testIterationStart(null); assertNull("Should not find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); long start = System.currentTimeMillis(); setExpires(makeDate(new Date(start + 2000))); cacheResultWithGivenCode("304"); assertNotNull("Should find entry", getThreadCacheEntry(LOCAL_HOST)); assertTrue("Should find valid entry", this.cacheManager.inCache(url)); sleepTill(start + 2010); assertNotNull("Should find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); } @Test public void testExpires() throws Exception { this.cacheManager.setUseExpires(true); this.cacheManager.testIterationStart(null); assertNull("Should not find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); long start = System.currentTimeMillis(); setExpires(makeDate(new Date(start + 2000))); cacheResult(sampleResultOK); assertNotNull("Should find entry", getThreadCacheEntry(LOCAL_HOST)); assertTrue("Should find valid entry", this.cacheManager.inCache(url)); sleepTill(start + 2010); assertNotNull("Should find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); } @Test public void testNoExpires() throws Exception { this.cacheManager.setUseExpires(false); this.cacheManager.testIterationStart(null); assertNull("Should not find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); setExpires(makeDate(new Date(System.currentTimeMillis() + 2000))); cacheResult(sampleResultOK); assertNotNull("Should find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); } @Test public void testCacheControl() throws Exception { this.cacheManager.setUseExpires(true); this.cacheManager.testIterationStart(null); assertNull("Should not find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); long start = System.currentTimeMillis(); setExpires(makeDate(new Date(start))); setCacheControl("public, max-age=1"); cacheResult(sampleResultOK); assertNotNull("Should find entry", getThreadCacheEntry(LOCAL_HOST)); assertTrue("Should find valid entry", this.cacheManager.inCache(url)); sleepTill(start + 1010); assertNotNull("Should find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); } @Test public void testCacheVarySomething() throws Exception { testCacheVary("Something"); } @Test public void testCacheVaryAcceptEncoding() throws Exception { testCacheVary("Accept-Encoding"); } private void testCacheVary(String vary) throws Exception { this.cacheManager.setUseExpires(true); this.cacheManager.testIterationStart(null); assertNull("Should not find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); setExpires(makeDate(new Date(System.currentTimeMillis()))); setCacheControl("public, max-age=5"); this.vary = vary; cacheResult(sampleResultOK); assertNull("Should not find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); this.vary = null; } @Test public void testCacheHEAD() throws Exception { this.cacheManager.setUseExpires(true); this.cacheManager.testIterationStart(null); assertNull("Should not find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); setExpires(makeDate(new Date(System.currentTimeMillis()))); setCacheControl("public, max-age=5"); HTTPSampleResult sampleResultHEAD = getSampleResultWithSpecifiedResponseCode("200"); sampleResultHEAD.setHTTPMethod("HEAD"); cacheResult(sampleResultHEAD); assertNull("Should not find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); } @Test public void testPrivateCache() throws Exception { this.cacheManager.setUseExpires(true); this.cacheManager.testIterationStart(null); assertNull("Should not find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); long start = System.currentTimeMillis(); setExpires(makeDate(new Date(start))); setCacheControl("private, max-age=1"); cacheResult(sampleResultOK); assertNotNull("Should find entry", getThreadCacheEntry(LOCAL_HOST)); assertTrue("Should find valid entry", this.cacheManager.inCache(url)); sleepTill(start + 1010); assertNotNull("Should find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); } @Test public void testPrivateCacheNoMaxAgeNoExpire() throws Exception { this.cacheManager.setUseExpires(true); this.cacheManager.testIterationStart(null); assertNull("Should not find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); setCacheControl("private"); // Expires is not set, however RFC recommends to use // response_is_fresh = (freshness_lifetime > current_age) // We set "currentAge == X seconds", thus response will considered to // be fresh for the next 10% of X seconds == 0.1*X seconds long start = System.currentTimeMillis(); long age = 30 * 1000; // 30 seconds setLastModified(makeDate(new Date(start - age))); cacheResult(sampleResultOK); assertNotNull("Should find entry", getThreadCacheEntry(LOCAL_HOST)); assertTrue("Should find valid entry", this.cacheManager.inCache(url)); sleepTill(start + age / 10 + 10); assertNotNull("Should find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); } @Test public void testPrivateCacheExpireNoMaxAge() throws Exception { this.cacheManager.setUseExpires(true); this.cacheManager.testIterationStart(null); assertNull("Should not find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); long start = System.currentTimeMillis(); setExpires(makeDate(new Date(start + 2000))); setCacheControl("private"); cacheResult(sampleResultOK); assertNotNull("Should find entry", getThreadCacheEntry(LOCAL_HOST)); assertTrue("Should find valid entry", this.cacheManager.inCache(url)); sleepTill(start + 2010); assertNotNull("Should find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); } @Test public void testNoCache() throws Exception { this.cacheManager.setUseExpires(true); this.cacheManager.testIterationStart(null); assertNull("Should not find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); setCacheControl("no-cache"); cacheResult(sampleResultOK); assertNotNull("Should find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); } @Test public void testNoStore() throws Exception { this.cacheManager.setUseExpires(true); this.cacheManager.testIterationStart(null); assertNull("Should not find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); setCacheControl("no-store"); cacheResult(sampleResultOK); assertNull("Should not find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); } @Test public void testCacheHttpClientBug51932() throws Exception { this.cacheManager.setUseExpires(true); this.cacheManager.testIterationStart(null); assertNull("Should not find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); long start = System.currentTimeMillis(); setExpires(makeDate(new Date(start))); setCacheControl("public, max-age=1, no-transform"); cacheResult(sampleResultOK); assertNotNull("Should find entry", getThreadCacheEntry(LOCAL_HOST)); assertTrue("Should find valid entry", this.cacheManager.inCache(url)); sleepTill(start + 1010); assertNotNull("Should find entry", getThreadCacheEntry(LOCAL_HOST)); assertFalse("Should not find valid entry", this.cacheManager.inCache(url)); } @Test public void testGetClearEachIteration() throws Exception { assertFalse("Should default not to clear after each iteration.", this.cacheManager.getClearEachIteration()); this.cacheManager.setClearEachIteration(true); assertTrue("Should be settable to clear after each iteration.", this.cacheManager.getClearEachIteration()); this.cacheManager.setClearEachIteration(false); assertFalse("Should be settable not to clear after each iteration.", this.cacheManager.getClearEachIteration()); } private void cacheResultWithGivenCode(String responseCode) throws Exception { HTTPSampleResult sampleResult = getSampleResultWithSpecifiedResponseCode(responseCode); cacheResult(sampleResult); } @Test public void testSaveDetailsWithEmptySampleResultGivesNoCacheEntry() throws Exception { cacheResultWithGivenCode(""); assertTrue("Saving details with empty SampleResult should not make cache entry.", getThreadCache().isEmpty()); } @Test public void testSaveDetailsHttpMethodWithSampleResultWithResponseCode200GivesCacheEntry() throws Exception { cacheResultWithGivenCode("200"); CacheManager.CacheEntry cacheEntry = getThreadCacheEntry(LOCAL_HOST); assertNotNull("Saving SampleResult with HttpMethod & 200 response should make cache entry.", cacheEntry); assertEquals("Saving details with SampleResult & HttpMethod with 200 response should make cache entry with no etag.", EXPECTED_ETAG, cacheEntry.getEtag()); assertEquals("Saving details with SampleResult & HttpMethod with 200 response should make cache entry with no last modified date.", this.currentTimeInGMT, cacheEntry.getLastModified()); } @Test public void testSaveDetailsHttpMethodWithSampleResultWithResponseCode404GivesNoCacheEntry() throws Exception { cacheResultWithGivenCode("404"); assertNull("Saving SampleResult with HttpMethod & 404 response should not make cache entry.", getThreadCacheEntry(LOCAL_HOST)); } @Test public void testSetHeadersHttpMethodWithSampleResultWithResponseCode200GivesCacheEntry() throws Exception { addRequestHeader(HTTPConstants.IF_MODIFIED_SINCE, this.currentTimeInGMT); addRequestHeader(HTTPConstants.ETAG, EXPECTED_ETAG); cacheResultWithGivenCode("200"); setRequestHeaders(); checkRequestHeader(HTTPConstants.IF_NONE_MATCH, EXPECTED_ETAG); checkRequestHeader(HTTPConstants.IF_MODIFIED_SINCE, this.currentTimeInGMT); } @Test public void testSetHeadersHttpMethodWithSampleResultWithResponseCode404GivesNoCacheEntry() throws Exception { cacheResultWithGivenCode("404"); setRequestHeaders(); assertNull("Saving SampleResult with HttpMethod & 404 response should not make cache entry.", getThreadCacheEntry(LOCAL_HOST)); } @Test public void testClearCache() throws Exception { assertTrue("ThreadCache should be empty initially.", getThreadCache().isEmpty()); cacheResultWithGivenCode("200"); assertFalse("ThreadCache should be populated after saving details for HttpMethod with SampleResult with response code 200.", getThreadCache().isEmpty()); this.cacheManager.clear(); assertTrue("ThreadCache should be emptied by call to clear.", getThreadCache().isEmpty()); } private HTTPSampleResult getSampleResultWithSpecifiedResponseCode(String code) { HTTPSampleResult sampleResult = new HTTPSampleResult(); sampleResult.setResponseCode(code); sampleResult.setHTTPMethod("GET"); sampleResult.setURL(url); return sampleResult; } private Map<String, CacheManager.CacheEntry> getThreadCache() throws Exception { Field threadLocalfield = CacheManager.class.getDeclaredField("threadCache"); threadLocalfield.setAccessible(true); @SuppressWarnings("unchecked") ThreadLocal<Map<String, CacheManager.CacheEntry>> threadLocal = (ThreadLocal<Map<String, CacheManager.CacheEntry>>) threadLocalfield.get(this.cacheManager); return threadLocal.get(); } private CacheManager.CacheEntry getThreadCacheEntry(String url) throws Exception { return getThreadCache().get(url); } }