Skip to content

Commit

Permalink
Expand unit test to cover local and remote PMTiles
Browse files Browse the repository at this point in the history
  • Loading branch information
simonpoole committed Sep 10, 2023
1 parent 14f62b7 commit 4ae4c4e
Show file tree
Hide file tree
Showing 3 changed files with 245 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package de.blau.android.services.util;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog;

import android.database.sqlite.SQLiteDatabase;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
import de.blau.android.JavaResources;
import de.blau.android.MockTileServer;
import de.blau.android.resources.TileLayerDatabase;
import de.blau.android.resources.TileLayerSource;
import de.blau.android.resources.TileLayerSource.Category;
import de.blau.android.resources.TileLayerSource.Provider;
import de.blau.android.resources.TileLayerSource.TileType;
import de.blau.android.util.FileUtil;
import de.blau.android.views.util.MapTileProviderCallback;

@RunWith(RobolectricTestRunner.class)
@Config(shadows = { ShadowSQLiteStatement.class, ShadowSQLiteProgram.class, ShadowSQLiteCloseable.class })
@LargeTest
public class MapTileFilesystemProviderLocalTest {

private static final String FIRENZE = "FIRENZE";
MapTileFilesystemProvider provider;

/**
* Pre-test setup
*/
@Before
public void setup() {
ShadowLog.setupLogging();
provider = new MapTileFilesystemProvider(ApplicationProvider.getApplicationContext(), new File("."), 1000000);
try {
JavaResources.copyFileFromResources(ApplicationProvider.getApplicationContext(), "ersatz_background.mbt", null, "/");
JavaResources.copyFileFromResources(ApplicationProvider.getApplicationContext(), "protomaps(vector)ODbL_firenze.pmtiles", null, "/");
} catch (IOException e) {
fail(e.getMessage());
}
try (TileLayerDatabase db = new TileLayerDatabase(ApplicationProvider.getApplicationContext())) {
File mbtFile = new File(FileUtil.getPublicDirectory(), "ersatz_background.mbt");
final String windowsRoot = "file://" + (System.getProperty("os.name").toLowerCase().contains("windows") ? "\\" : "");
TileLayerSource.addOrUpdateCustomLayer(ApplicationProvider.getApplicationContext(), db.getWritableDatabase(), MockTileServer.MOCK_TILE_SOURCE, null,
-1, -1, "Vespucci Test", new Provider(), Category.other, null, null, 0, 19, TileLayerSource.DEFAULT_TILE_SIZE, false,
windowsRoot + mbtFile.getAbsolutePath());
File pmTilesFile = new File(FileUtil.getPublicDirectory(), "protomaps(vector)ODbL_firenze.pmtiles");
TileLayerSource.addOrUpdateCustomLayer(ApplicationProvider.getApplicationContext(), db.getWritableDatabase(), FIRENZE, null, -1, -1, "Firenze",
new Provider(), Category.other, TileLayerSource.TYPE_PMT_3, TileType.MVT, 0, 15, TileLayerSource.DEFAULT_TILE_SIZE, false,
windowsRoot + pmTilesFile.getAbsolutePath());
} catch (IOException e) {
fail(e.getMessage());
}
// force update of tile sources
try (TileLayerDatabase tlDb = new TileLayerDatabase(ApplicationProvider.getApplicationContext()); SQLiteDatabase db = tlDb.getReadableDatabase()) {
TileLayerSource.getListsLocked(ApplicationProvider.getApplicationContext(), db, false);
}
}

/**
* Post-test teardown
*/
@After
public void teardown() {
provider.destroy();
}

@Test
public void successfulMBT() {
loadMapTileAsyncSuccessTest(MockTileServer.MOCK_TILE_SOURCE, 19, 274335, 183513);
}

@Test
public void failMBT() {
loadMapTileAsyncFailTest(MockTileServer.MOCK_TILE_SOURCE, 14, 11541, 3864);
}

@Test
public void successfulPMT() {
loadMapTileAsyncSuccessTest(FIRENZE, 14, 8704, 5972);
}

@Test
public void failPMT() {
loadMapTileAsyncFailTest(FIRENZE, 14, 11541, 3864);
}

/**
* Load a tile successfully
*/
public void loadMapTileAsyncSuccessTest(@NonNull String source, int zoom, int x, int y) {
// this should load from the server
final CountDownLatch signal1 = new CountDownLatch(1);
MapTile mockedTile = new MapTile(source, zoom, x, y);
CallbackWithResult callback = new CallbackWithResult() {

@Override
public void mapTileLoaded(String rendererID, int zoomLevel, int tileX, int tileY, byte[] aImage) throws IOException {
result = 1;
signal1.countDown();
}

@Override
public void mapTileFailed(String rendererID, int zoomLevel, int tileX, int tileY, int reason, String message) throws IOException {
result = 2;
signal1.countDown();
}
};
provider.loadMapTileAsync(mockedTile, callback);

try {
signal1.await(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
fail(e.getMessage());
}
assertEquals(1, callback.result);
}

abstract class CallbackWithResult implements MapTileProviderCallback {
int result;
}

/**
* Try to load a tile that doesn't exist
*/
public void loadMapTileAsyncFailTest(@NonNull String source, int zoom, int x, int y) {
final CountDownLatch signal1 = new CountDownLatch(1);
MapTile mockedTile = new MapTile(source, zoom, x, y);
CallbackWithResult callback = new CallbackWithResult() {

@Override
public void mapTileLoaded(String rendererID, int zoomLevel, int tileX, int tileY, byte[] aImage) throws IOException {
result = 0;
signal1.countDown();
}

@Override
public void mapTileFailed(String rendererID, int zoomLevel, int tileX, int tileY, int reason, String message) throws IOException {
result = reason;
signal1.countDown();
}
};
provider.loadMapTileAsync(mockedTile, callback);
try {
signal1.await(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
fail(e.getMessage());
}
assertEquals(MapAsyncTileProvider.DOESNOTEXIST, callback.result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@
import org.robolectric.shadows.ShadowLog;

import android.database.sqlite.SQLiteDatabase;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
import de.blau.android.MockTileServer;
import de.blau.android.PMTilesDispatcher;
import de.blau.android.net.UserAgentInterceptor;
import de.blau.android.resources.TileLayerDatabase;
import de.blau.android.resources.TileLayerSource;
import de.blau.android.resources.TileLayerSource.Category;
import de.blau.android.resources.TileLayerSource.Provider;
import de.blau.android.resources.TileLayerSource.TileType;
import de.blau.android.util.Util;
import de.blau.android.views.util.MapTileProviderCallback;
import okhttp3.mockwebserver.MockWebServer;
Expand All @@ -36,8 +41,10 @@
@LargeTest
public class MapTileFilesystemProviderTest {

MapTileFilesystemProvider provider;
MockWebServer tileServer;
private static final String FIRENZE = "FIRENZE";
MapTileFilesystemProvider provider;
MockWebServer tileServerMBT;
MockWebServer tileServerPMT;

/**
* Pre-test setup
Expand All @@ -46,7 +53,22 @@ public class MapTileFilesystemProviderTest {
public void setup() {
ShadowLog.setupLogging();
provider = new MapTileFilesystemProvider(ApplicationProvider.getApplicationContext(), new File("."), 1000000);
tileServer = MockTileServer.setupTileServer(ApplicationProvider.getApplicationContext(), "ersatz_background.mbt", true);
provider.flushCache(null);
tileServerMBT = MockTileServer.setupTileServer(ApplicationProvider.getApplicationContext(), "ersatz_background.mbt", true);
final String fileName = "protomaps(vector)ODbL_firenze.pmtiles";
tileServerPMT = new MockWebServer();
try {
PMTilesDispatcher tileDispatcher = new PMTilesDispatcher(ApplicationProvider.getApplicationContext(), fileName);
tileServerPMT.setDispatcher(tileDispatcher);
} catch (IOException e) {
fail(e.getMessage());
}
try (TileLayerDatabase db = new TileLayerDatabase(ApplicationProvider.getApplicationContext())) {
TileLayerDatabase.deleteLayerWithId(db.getWritableDatabase(), FIRENZE);
TileLayerSource.addOrUpdateCustomLayer(ApplicationProvider.getApplicationContext(), db.getWritableDatabase(), FIRENZE, null, -1, -1, "Firenze",
new Provider(), Category.other, TileLayerSource.TYPE_PMT_3, TileType.MVT, 0, 15, TileLayerSource.DEFAULT_TILE_SIZE, false,
tileServerPMT.url("/").toString() + "firenze.pmtiles");
}
// force update of tile sources
try (TileLayerDatabase tlDb = new TileLayerDatabase(ApplicationProvider.getApplicationContext()); SQLiteDatabase db = tlDb.getReadableDatabase()) {
TileLayerSource.getListsLocked(ApplicationProvider.getApplicationContext(), db, false);
Expand All @@ -60,7 +82,12 @@ public void setup() {
public void teardown() {
provider.destroy();
try {
tileServer.close();
tileServerMBT.close();
} catch (IOException e) {
// ignore
}
try {
tileServerPMT.close();
} catch (IOException e) {
// ignore
}
Expand Down Expand Up @@ -89,14 +116,43 @@ public void clearCurrentCacheTest() {
assertEquals(0, provider.getCurrentCacheByteSize());
}

@Test
public void loadTileFromTileServerSuccess() {
loadMapTileAsyncSuccessTest(tileServerMBT, MockTileServer.MOCK_TILE_SOURCE, 19, 274335, 183513);
}

@Test
public void loadTileFromTileServerFail() {
loadMapTileAsyncFailTest(tileServerMBT, MockTileServer.MOCK_TILE_SOURCE, 14, 11541, 3864);
}

@Test
public void tileServerCustomerHeaders() {
customHeaderTest(tileServerMBT, MockTileServer.MOCK_TILE_SOURCE, 19, 274335, 183514);
}

@Test
public void loadTileFromPMTilesServerSuccess() {
loadMapTileAsyncSuccessTest(tileServerPMT, FIRENZE, 14, 8703, 5971);
}

@Test
public void loadTileFromPMTilesServerFail() {
loadMapTileAsyncFailTest(tileServerPMT, FIRENZE, 14, 11541, 3864);
}

@Test
public void pmtilesServerCustomerHeaders() {
customHeaderTest(tileServerPMT, FIRENZE, 14, 8703, 5971);
}

/**
* Load a tile successfully
*/
@Test
public void loadMapTileAsyncSuccessTest() {
public void loadMapTileAsyncSuccessTest(@NonNull MockWebServer tileServer, @NonNull String source, int zoom, int x, int y) {
// this should load from the server
final CountDownLatch signal1 = new CountDownLatch(1);
MapTile mockedTile = new MapTile(MockTileServer.MOCK_TILE_SOURCE, 19, 274335, 183513);
MapTile mockedTile = new MapTile(source, zoom, x, y);
CallbackWithResult callback = new CallbackWithResult() {

@Override
Expand Down Expand Up @@ -125,7 +181,13 @@ public void mapTileFailed(String rendererID, int zoomLevel, int tileX, int tileY
} catch (InterruptedException e1) {
fail("no tileserver request found " + e1.getMessage());
}
assertEquals(1, tileServer.getRequestCount());
// flush requests
try {
while (tileServer.takeRequest(1, TimeUnit.SECONDS) != null) {
}
} catch (InterruptedException e2) {
fail(e2.getMessage());
}

// this should load from the cache
final CountDownLatch signal2 = new CountDownLatch(1);
Expand All @@ -142,7 +204,6 @@ public void mapTileFailed(String rendererID, int zoomLevel, int tileX, int tileY
} catch (InterruptedException e1) {
// this is what should happen
}
assertEquals(1, tileServer.getRequestCount());
}

abstract class CallbackWithResult implements MapTileProviderCallback {
Expand All @@ -152,10 +213,9 @@ abstract class CallbackWithResult implements MapTileProviderCallback {
/**
* Try to load a tile that doesn't exist
*/
@Test
public void loadMapTileAsyncFailTest() {
public void loadMapTileAsyncFailTest(@NonNull MockWebServer tileServer, @NonNull String source, int zoom, int x, int y) {
final CountDownLatch signal1 = new CountDownLatch(1);
MapTile mockedTile = new MapTile(MockTileServer.MOCK_TILE_SOURCE, 14, 11541, 3864);
MapTile mockedTile = new MapTile(source, zoom, x, y);
CallbackWithResult callback = new CallbackWithResult() {

@Override
Expand Down Expand Up @@ -183,19 +243,17 @@ public void mapTileFailed(String rendererID, int zoomLevel, int tileX, int tileY
fail("no tileserver request found " + e1.getMessage());
}
assertEquals(MapAsyncTileProvider.DOESNOTEXIST, callback.result);
assertEquals(1, tileServer.getRequestCount());
}

@Test
public void customHeaderTest() {
TileLayerSource layer = TileLayerSource.get(ApplicationProvider.getApplicationContext(), MockTileServer.MOCK_TILE_SOURCE, false);
public void customHeaderTest(@NonNull MockWebServer tileServer, @NonNull String source, int zoom, int x, int y) {
TileLayerSource layer = TileLayerSource.get(ApplicationProvider.getApplicationContext(), source, false);
assertNotNull(layer);
layer.setHeaders(Util.wrapInList(new TileLayerSource.Header(UserAgentInterceptor.USER_AGENT_HEADER, "Mozilla/5.0 (JOSM)")));
// this should load from the server
final CountDownLatch signal1 = new CountDownLatch(1);
MapTile mockedTile = new MapTile(MockTileServer.MOCK_TILE_SOURCE, 19, 274335, 183514); // not this needs to be a
// different tile than
// above
MapTile mockedTile = new MapTile(source, zoom, x, y); // not this needs to be a
// different tile than
// above
CallbackWithResult callback = new CallbackWithResult() {

@Override
Expand All @@ -220,11 +278,9 @@ public void mapTileFailed(String rendererID, int zoomLevel, int tileX, int tileY

try {
RecordedRequest request = tileServer.takeRequest(1, TimeUnit.SECONDS);
assertNotNull(request);
assertEquals("Mozilla/5.0 (JOSM)", request.getHeader(UserAgentInterceptor.USER_AGENT_HEADER));
} catch (InterruptedException e1) {
fail("no tileserver request found " + e1.getMessage());
}
assertEquals(1, tileServer.getRequestCount());
}
}
5 changes: 4 additions & 1 deletion src/testCommon/java/de/blau/android/PMTilesDispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class PMTilesDispatcher extends Dispatcher {
* @throws IOException if the source can't copied out of the assets and opened
*/
public PMTilesDispatcher(@NonNull Context context, @NonNull String source) throws IOException {
Log.d(DEBUG_TAG, "creating new dispatcher for " + source);
try {
File destinationDir = ContextCompat.getExternalCacheDirs(context)[0];
File file = new File(destinationDir, source);
Expand All @@ -52,7 +53,7 @@ public PMTilesDispatcher(@NonNull Context context, @NonNull String source) throw

@Override
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
Log.i(DEBUG_TAG, "request " + request);
Log.d(DEBUG_TAG, "request " + request);
try (Buffer data = new Buffer()) {
Matcher matcher = RANGE_PATTERN.matcher(request.getHeader(RANGE_HEADER));
if (matcher.find()) {
Expand All @@ -65,6 +66,7 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio
return new MockResponse().setResponseCode(HttpURLConnection.HTTP_OK).setBody(data);
}
}
Log.e(DEBUG_TAG, "no range header found");
return new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND);
} catch (IOException | NumberFormatException e) {
Log.e(DEBUG_TAG, "dispatch failed for " + request + " " + e.getMessage());
Expand All @@ -74,6 +76,7 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio

@Override
public void shutdown() {
Log.d(DEBUG_TAG, "shutting down dispatcher");
try {
channel.close();
} catch (IOException e) {
Expand Down

0 comments on commit 4ae4c4e

Please sign in to comment.