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

Last change on this file since 18106 was 18106, checked in by Don-vip, 3 years ago

fix #21150 - Add JUnit5 annotation for WireMockServer (patch by taylor.smock)

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