source: josm/trunk/test/unit/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJobTest.java@ 13742

Last change on this file since 13742 was 13742, checked in by wiktorn, 6 years ago

Checkstyle fixes

  • Property svn:eol-style set to native
File size: 12.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.imagery;
3
4import static org.junit.Assert.assertArrayEquals;
5import static org.junit.Assert.assertEquals;
6import static org.junit.Assert.assertTrue;
7
8import java.io.IOException;
9import java.net.MalformedURLException;
10import java.net.URL;
11import java.nio.charset.StandardCharsets;
12import java.util.concurrent.Executors;
13import java.util.concurrent.ThreadPoolExecutor;
14import java.util.concurrent.TimeUnit;
15import java.util.regex.Matcher;
16
17import org.apache.commons.jcs.access.behavior.ICacheAccess;
18import org.junit.Before;
19import org.junit.Rule;
20import org.junit.Test;
21import org.openstreetmap.gui.jmapviewer.Tile;
22import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
23import org.openstreetmap.gui.jmapviewer.tilesources.TMSTileSource;
24import org.openstreetmap.josm.TestUtils;
25import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
26import org.openstreetmap.josm.data.cache.CacheEntryAttributes;
27import org.openstreetmap.josm.data.cache.JCSCacheManager;
28import org.openstreetmap.josm.testutils.JOSMTestRules;
29import org.openstreetmap.josm.tools.Logging;
30import org.openstreetmap.josm.tools.Utils;
31
32import com.github.tomakehurst.wiremock.client.WireMock;
33import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
34import com.github.tomakehurst.wiremock.junit.WireMockRule;
35
36import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
37
38/**
39 * Unit tests for class {@link TMSCachedTileLoaderJob}.
40 */
41public class TMSCachedTileLoaderJobTest {
42
43 /**
44 * Setup tests
45 */
46 @Rule
47 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
48 public JOSMTestRules test = new JOSMTestRules().preferences();
49
50 /**
51 * mocked tile server
52 */
53 @Rule
54 public WireMockRule tileServer = new WireMockRule(WireMockConfiguration.options()
55 .dynamicPort());
56
57 @Before
58 public void clearCache() throws Exception {
59 getCache().clear();
60 }
61
62 private static ICacheAccess<String, BufferedImageCacheEntry> getCache() {
63 return JCSCacheManager.getCache("test");
64 }
65
66 private static class TestCachedTileLoaderJob extends TMSCachedTileLoaderJob {
67 private String url;
68 private String key;
69
70 TestCachedTileLoaderJob(TileLoaderListener listener, Tile tile, String key) throws IOException {
71 this(listener, tile, key, (int) TimeUnit.DAYS.toSeconds(1));
72 }
73
74 TestCachedTileLoaderJob(TileLoaderListener listener, Tile tile, String key, int minimumExpiry) throws IOException {
75 super(listener, tile, getCache(), new TileJobOptions(30000, 30000, null, minimumExpiry),
76 (ThreadPoolExecutor) Executors.newFixedThreadPool(1));
77
78 this.url = tile.getUrl();
79 this.key = key;
80 }
81
82 @Override
83 public URL getUrl() {
84 try {
85 return new URL(url);
86 } catch (MalformedURLException e) {
87 throw new RuntimeException(e);
88 }
89 }
90
91 @Override
92 protected BufferedImageCacheEntry createCacheEntry(byte[] content) {
93 return new BufferedImageCacheEntry(content);
94 }
95
96 public CacheEntryAttributes getAttributes() {
97 return attributes;
98 }
99
100 @Override
101 public boolean isObjectLoadable() {
102 // use implementation from grand parent, to avoid calling getImage on dummy data
103 if (cacheData == null) {
104 return false;
105 }
106 return cacheData.getContent().length > 0;
107 }
108 }
109
110 private static class Listener implements TileLoaderListener {
111 private CacheEntryAttributes attributes;
112 private boolean ready;
113 private byte[] data;
114
115 @Override
116 public synchronized void tileLoadingFinished(Tile tile, boolean success) {
117 ready = true;
118 this.notifyAll();
119 }
120 }
121
122 private static class MockTile extends Tile {
123 MockTile(String url) {
124 super(new MockTileSource(url), 0, 0, 0);
125 }
126 }
127
128 private static class MockTileSource extends TMSTileSource {
129 private final String url;
130
131 MockTileSource(String url) {
132 super(new ImageryInfo("mock"));
133 this.url = url;
134 }
135
136 @Override
137 public String getTileUrl(int zoom, int tilex, int tiley) throws IOException {
138 return url;
139 }
140 }
141
142 /**
143 * Tests that {@code TMSCachedTileLoaderJob#SERVICE_EXCEPTION_PATTERN} is correct.
144 */
145 @Test
146 public void testServiceExceptionPattern() {
147 test("missing parameters ['version', 'format']",
148 "<?xml version=\"1.0\"?>\n" +
149 "<!DOCTYPE ServiceExceptionReport SYSTEM \"http://schemas.opengis.net/wms/1.1.1/exception_1_1_1.dtd\">\n" +
150 "<ServiceExceptionReport version=\"1.1.1\">\n" +
151 " <ServiceException>missing parameters ['version', 'format']</ServiceException>\n" +
152 "</ServiceExceptionReport>");
153 test("Parameter 'layers' contains unacceptable layer names.",
154 "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\r\n" +
155 "<!DOCTYPE ServiceExceptionReport SYSTEM \"http://schemas.opengis.net/wms/1.1.1/exception_1_1_1.dtd\">\r\n" +
156 "<ServiceExceptionReport version=\"1.1.1\">\r\n" +
157 " <ServiceException code=\"LayerNotDefined\">\r\n" +
158 "Parameter 'layers' contains unacceptable layer names.\r\n" +
159 " </ServiceException>\r\n" +
160 "</ServiceExceptionReport>\r\n" +
161 "");
162 }
163
164 private static void test(String expected, String xml) {
165 Matcher m = TMSCachedTileLoaderJob.SERVICE_EXCEPTION_PATTERN.matcher(xml);
166 assertTrue(xml, m.matches());
167 assertEquals(expected, Utils.strip(m.group(1)));
168 }
169
170 private TestCachedTileLoaderJob submitJob(MockTile tile, String key, boolean force) throws IOException {
171 return submitJob(tile, key, 0, force);
172 }
173
174 private TestCachedTileLoaderJob submitJob(MockTile tile, String key, int minimumExpiry, boolean force) throws IOException {
175 Listener listener = new Listener();
176 TestCachedTileLoaderJob job = new TestCachedTileLoaderJob(listener, tile, key, minimumExpiry);
177 job.submit(force);
178 synchronized (listener) {
179 while (!listener.ready) {
180 try {
181 listener.wait();
182 } catch (InterruptedException e) {
183 // do nothing, wait
184 Logging.trace(e);
185 }
186 }
187 }
188 return job;
189 }
190
191 /**
192 * When tile server doesn't return any Expires/Cache-Control headers, expire should be at least MINIMUM_EXPIRES
193 * @throws IOException exception
194 */
195 @Test
196 public void testNoCacheHeaders() throws IOException {
197 long testStart = System.currentTimeMillis();
198 tileServer.stubFor(
199 WireMock.get(WireMock.urlEqualTo("/test"))
200 .willReturn(WireMock.aResponse()
201 .withBody("mock entry")
202 )
203 );
204
205 TestCachedTileLoaderJob job = submitJob(new MockTile(tileServer.url("/test")), "test", false);
206 assertExpirationAtLeast(testStart + TMSCachedTileLoaderJob.MINIMUM_EXPIRES.get(), job);
207 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), job.get().getContent());
208 job = submitJob(new MockTile(tileServer.url("/test")), "test", false); // submit another job for the same tile
209 // only one request to tile server should be made, second should come from cache
210 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test")));
211 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), job.get().getContent());
212 }
213
214 /**
215 * When tile server doesn't return any Expires/Cache-Control headers, expire should be at least minimumExpires parameter
216 * @throws IOException exception
217 */
218 @Test
219 public void testNoCacheHeadersMinimumExpires() throws IOException {
220 noCacheHeadersMinimumExpires((int) TimeUnit.MILLISECONDS.toSeconds(TMSCachedTileLoaderJob.MINIMUM_EXPIRES.get() * 2));
221 }
222
223 /**
224 * When tile server doesn't return any Expires/Cache-Control headers, expire should be at least minimumExpires parameter,
225 * which is larger than MAXIMUM_EXPIRES
226 * @throws IOException exception
227 */
228
229 @Test
230 public void testNoCacheHeadersMinimumExpiresLargerThanMaximum() throws IOException {
231 noCacheHeadersMinimumExpires((int) TimeUnit.MILLISECONDS.toSeconds(TMSCachedTileLoaderJob.MAXIMUM_EXPIRES.get() * 2));
232 }
233
234 private void noCacheHeadersMinimumExpires(int minimumExpires) throws IOException {
235 long testStart = System.currentTimeMillis();
236 tileServer.stubFor(
237 WireMock.get(WireMock.urlEqualTo("/test"))
238 .willReturn(WireMock.aResponse()
239 .withBody("mock entry")
240 )
241 );
242 TestCachedTileLoaderJob job = submitJob(new MockTile(tileServer.url("/test")), "test", minimumExpires, false);
243 assertExpirationAtLeast(testStart + minimumExpires, job);
244 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), job.get().getContent());
245 job = submitJob(new MockTile(tileServer.url("/test")), "test", false); // submit another job for the same tile
246 // only one request to tile server should be made, second should come from cache
247 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test")));
248 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), job.get().getContent());
249 }
250
251 /**
252 * When tile server returns Expires header shorter than MINIMUM_EXPIRES, we should cache if for at least MINIMUM_EXPIRES
253 * @throws IOException exception
254 */
255 @Test
256 public void testShortExpire() throws IOException {
257 long testStart = System.currentTimeMillis();
258 long expires = TMSCachedTileLoaderJob.MINIMUM_EXPIRES.get() / 2;
259 tileServer.stubFor(
260 WireMock.get(WireMock.urlEqualTo("/test"))
261 .willReturn(WireMock.aResponse()
262 .withHeader("Expires", TestUtils.getHTTPDate(testStart + expires))
263 .withBody("mock entry")
264 )
265 );
266 TestCachedTileLoaderJob job = submitJob(new MockTile(tileServer.url("/test")), "test", false);
267 assertExpirationAtLeast(testStart + TMSCachedTileLoaderJob.MINIMUM_EXPIRES.get(), job);
268 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), job.get().getContent());
269 job = submitJob(new MockTile(tileServer.url("/test")), "test", false); // submit another job for the same tile
270 // only one request to tile server should be made, second should come from cache
271 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test")));
272 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), job.get().getContent());
273 }
274
275 private void assertExpirationAtLeast(long duration, TestCachedTileLoaderJob job) {
276 assertTrue(
277 "Expiration time shorter by " +
278 -1 * (job.getAttributes().getExpirationTime() - duration) +
279 " than expected",
280 job.getAttributes().getExpirationTime() >= duration);
281 }
282
283 private void assertExpirationAtMost(long duration, TestCachedTileLoaderJob job) {
284 assertTrue(
285 "Expiration time longer by " +
286 (job.getAttributes().getExpirationTime() - duration) +
287 " than expected",
288 job.getAttributes().getExpirationTime() <= duration);
289 }
290
291 @Test
292 public void testLongExpire() throws IOException {
293 long testStart = System.currentTimeMillis();
294 long expires = TMSCachedTileLoaderJob.MAXIMUM_EXPIRES.get() * 2;
295 tileServer.stubFor(
296 WireMock.get(WireMock.urlEqualTo("/test"))
297 .willReturn(WireMock.aResponse()
298 .withHeader("Expires", TestUtils.getHTTPDate(testStart + expires))
299 .withBody("mock entry")
300 )
301 );
302 TestCachedTileLoaderJob job = submitJob(new MockTile(tileServer.url("/test")), "test", false);
303 // give 1 second margin
304 assertExpirationAtMost(testStart + TMSCachedTileLoaderJob.MAXIMUM_EXPIRES.get() + TimeUnit.SECONDS.toMillis(1), job);
305
306 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), job.get().getContent());
307 job = submitJob(new MockTile(tileServer.url("/test")), "test", false); // submit another job for the same tile
308 // only one request to tile server should be made, second should come from cache
309 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test")));
310 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), job.get().getContent());
311 }
312
313}
Note: See TracBrowser for help on using the repository browser.