Skip to content

Commit

Permalink
NAVAND-1250 auto tests for offline EV routing fallback + NavigationRo…
Browse files Browse the repository at this point in the history
…ute#waypoints workaround (#7165) (#7191)
  • Loading branch information
VysotskiVadim authored May 17, 2023
1 parent 2f7cc7a commit 4342c2c
Show file tree
Hide file tree
Showing 15 changed files with 32,482 additions and 18 deletions.
1 change: 1 addition & 0 deletions changelog/unreleased/bugfixes/7165.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Fixed `NavigationRoutes#waypoints` being null for EV offline fallback.
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package com.mapbox.navigation.instrumentation_tests.core

import android.location.Location
import com.mapbox.navigation.base.route.RouterOrigin
import com.mapbox.navigation.core.directions.session.RoutesExtra
import com.mapbox.navigation.instrumentation_tests.utils.location.stayOnPosition
import com.mapbox.navigation.instrumentation_tests.utils.routes.EvRoutesProvider
import com.mapbox.navigation.instrumentation_tests.utils.routes.MockedEvRoutes
import com.mapbox.navigation.instrumentation_tests.utils.tiles.OfflineRegions
import com.mapbox.navigation.instrumentation_tests.utils.tiles.withMapboxNavigationAndOfflineTilesForRegion
import com.mapbox.navigation.instrumentation_tests.utils.withMapboxNavigation
import com.mapbox.navigation.instrumentation_tests.utils.withoutInternet
import com.mapbox.navigation.testing.ui.BaseCoreNoCleanUpTest
import com.mapbox.navigation.testing.ui.utils.coroutines.RouteRequestResult
import com.mapbox.navigation.testing.ui.utils.coroutines.getSuccessfulResultOrThrowException
import com.mapbox.navigation.testing.ui.utils.coroutines.requestRoutes
import com.mapbox.navigation.testing.ui.utils.coroutines.routesUpdates
import com.mapbox.navigation.testing.ui.utils.coroutines.sdkTest
import com.mapbox.navigation.testing.ui.utils.coroutines.setNavigationRoutesAsync
import kotlinx.coroutines.flow.first
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test

// TODO: remove in the scope of NAVAND-1351
const val INCREASED_TIMEOUT_BECAUSE_OF_REAL_ROUTING_TILES_USAGE = 80_000L

class EvOfflineTest : BaseCoreNoCleanUpTest() {

override fun setupMockLocation(): Location {
return mockLocationUpdatesRule.generateLocationUpdate {
longitude = 13.361378213031003
latitude = 52.49813341962201
}
}

@Test
fun requestRouteWithoutInternetAndTiles() = sdkTest {
val testRoute = setupBerlinEvRoute()
withMapboxNavigation { navigation ->
withoutInternet {
val routes = navigation.requestRoutes(testRoute.routeOptions)
assertTrue(routes is RouteRequestResult.Failure)
}
}
}

@Test
fun requestOnlineRouteWithoutInternetHavingTiles() = sdkTest(
timeout = INCREASED_TIMEOUT_BECAUSE_OF_REAL_ROUTING_TILES_USAGE
) {
val originalTestRoute = setupBerlinEvRoute()
withMapboxNavigationAndOfflineTilesForRegion(
OfflineRegions.Berlin
) { navigation ->
navigation.startTripSession()
withoutInternet {
val requestResult = navigation.requestRoutes(originalTestRoute.routeOptions)
.getSuccessfulResultOrThrowException()
assertEquals(RouterOrigin.Onboard, requestResult.routerOrigin)
navigation.setNavigationRoutesAsync(requestResult.routes)

assertEquals(
"onboard router doesn't add charging waypoints",
requestResult.routes.map { 2 },
requestResult.routes.map { it.waypoints?.size }
)
}
}
}

@Test
fun deviateFromOnlinePrimaryRouteWithoutInternet() = sdkTest(
timeout = INCREASED_TIMEOUT_BECAUSE_OF_REAL_ROUTING_TILES_USAGE
) {
val originalTestRoute = setupBerlinEvRoute()
val testRouteAfterReroute = setupBerlinEvRouteAfterReroute()

withMapboxNavigationAndOfflineTilesForRegion(
OfflineRegions.Berlin
) { navigation ->
navigation.startTripSession()
val requestResult = navigation.requestRoutes(originalTestRoute.routeOptions)
.getSuccessfulResultOrThrowException()
assertEquals(RouterOrigin.Offboard, requestResult.routerOrigin)
assertEquals(
"online route for this case is expected to add charging station",
listOf(3, 3),
requestResult.routes.map { it.waypoints?.size }
)
navigation.setNavigationRoutesAsync(requestResult.routes)

withoutInternet {
stayOnPosition(
// off route position
latitude = testRouteAfterReroute.origin.latitude(),
longitude = testRouteAfterReroute.origin.longitude(),
bearing = 280.0f
) {
val newRoutes = navigation.routesUpdates()
.first { it.reason == RoutesExtra.ROUTES_UPDATE_REASON_REROUTE }
assertEquals(RouterOrigin.Onboard, newRoutes.navigationRoutes.first().origin)
assertEquals(
"onboard router doesn't add waypoints",
newRoutes.navigationRoutes.map { 2 },
newRoutes.navigationRoutes.map { it.waypoints?.size }
)
}
}
}
}

private fun setupBerlinEvRouteAfterReroute(): MockedEvRoutes {
val testRouteAfterReroute = EvRoutesProvider.getBerlinEvRouteReroute(
context,
mockWebServerRule.baseUrl
)
mockWebServerRule.requestHandlers.add(testRouteAfterReroute.mockWebServerHandler)
return testRouteAfterReroute
}

private fun setupBerlinEvRoute(): MockedEvRoutes {
val originalTestRoute = EvRoutesProvider.getBerlinEvRoute(
context,
mockWebServerRule.baseUrl
)
mockWebServerRule.requestHandlers.add(originalTestRoute.mockWebServerHandler)
return originalTestRoute
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.mapbox.navigation.instrumentation_tests.utils

import androidx.test.platform.app.InstrumentationRegistry
import com.mapbox.bindgen.Value
import com.mapbox.common.TileDataDomain
import com.mapbox.common.TileStore
import com.mapbox.common.TileStoreOptions
import com.mapbox.navigation.base.options.NavigationOptions
import com.mapbox.navigation.base.options.RoutingTilesOptions
import com.mapbox.navigation.core.MapboxNavigation
Expand All @@ -9,6 +13,7 @@ import java.net.URI

inline fun BaseCoreNoCleanUpTest.withMapboxNavigation(
useRealTiles: Boolean = false,
tileStore: TileStore? = null,
block: (navigation: MapboxNavigation) -> Unit
) {
val targetContext = InstrumentationRegistry.getInstrumentation().targetContext
Expand All @@ -19,13 +24,15 @@ inline fun BaseCoreNoCleanUpTest.withMapboxNavigation(
targetContext
)
).apply {
if (!useRealTiles) {
routingTilesOptions(
RoutingTilesOptions.Builder()
.tilesBaseUri(URI(mockWebServerRule.baseUrl))
.build()
)
}
val routingTilesOptions = RoutingTilesOptions.Builder()
.apply {
if (!useRealTiles) {
tilesBaseUri(URI(mockWebServerRule.baseUrl))
}
}
.tileStore(tileStore)
.build()
routingTilesOptions(routingTilesOptions)
}
.build()
)
Expand All @@ -35,3 +42,14 @@ inline fun BaseCoreNoCleanUpTest.withMapboxNavigation(
navigation.onDestroy()
}
}

fun createTileStore(): TileStore {
val targetContext = InstrumentationRegistry.getInstrumentation().targetContext
val tileStore = TileStore.create()
tileStore.setOption(
TileStoreOptions.MAPBOX_ACCESS_TOKEN,
TileDataDomain.NAVIGATION,
Value.valueOf(getMapboxAccessTokenFromResources(targetContext))
)
return tileStore
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.mapbox.navigation.instrumentation_tests.utils

import androidx.test.platform.app.InstrumentationRegistry
import com.mapbox.navigation.testing.ui.BaseCoreNoCleanUpTest

suspend fun BaseCoreNoCleanUpTest.withoutInternet(block: suspend () -> Unit) {
withoutWifiAndMobileData {
mockWebServerRule.withoutWebServer {
block()
}
}
}

suspend fun withoutWifiAndMobileData(block: suspend () -> Unit) {
val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
uiAutomation.executeShellCommand("svc wifi disable")
uiAutomation.executeShellCommand("svc data disable")
try {
block()
} finally {
uiAutomation.executeShellCommand("svc wifi enable")
uiAutomation.executeShellCommand("svc data enable")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.mapbox.navigation.instrumentation_tests.utils.location

import com.mapbox.navigation.testing.ui.BaseCoreNoCleanUpTest
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

suspend fun BaseCoreNoCleanUpTest.stayOnPosition(
latitude: Double,
longitude: Double,
bearing: Float,
frequencyHz: Int = 10,
block: suspend () -> Unit
) {
coroutineScope {
val updateLocations = launch {
while (true) {
mockLocationUpdatesRule.pushLocationUpdate {
this.latitude = latitude
this.longitude = longitude
this.bearing = bearing
}
delay(1000L / frequencyHz)
}
}
try {
block()
} finally {
updateLocations.cancel()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.mapbox.navigation.instrumentation_tests.utils.routes

import android.content.Context
import com.mapbox.api.directions.v5.DirectionsCriteria
import com.mapbox.api.directions.v5.models.RouteOptions
import com.mapbox.geojson.Point
import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions
import com.mapbox.navigation.instrumentation_tests.R
import com.mapbox.navigation.instrumentation_tests.utils.http.MockDirectionsRequestHandler
import com.mapbox.navigation.instrumentation_tests.utils.readRawFileText
import com.mapbox.navigation.testing.ui.http.BaseMockRequestHandler

data class MockedEvRoutes(
val routeOptions: RouteOptions,
val mockWebServerHandler: BaseMockRequestHandler
) {
val origin get() = routeOptions.coordinatesList().first()
}

object EvRoutesProvider {
fun getBerlinEvRoute(context: Context, baseUrl: String? = null): MockedEvRoutes {
val routeOptions = berlinEvRouteOptions(baseUrl)
val jsonResponse = readRawFileText(context, R.raw.ev_routes_berlin)
val evRouteRequestHandler = MockDirectionsRequestHandler(
profile = DirectionsCriteria.PROFILE_DRIVING_TRAFFIC,
jsonResponse = jsonResponse,
expectedCoordinates = routeOptions.coordinatesList(),
)
return MockedEvRoutes(
routeOptions,
evRouteRequestHandler
)
}

/***
* has the same destination as [getBerlinEvRoute], but different origin,
* could be use for reroute cases
*/
fun getBerlinEvRouteReroute(context: Context, baseUrl: String? = null): MockedEvRoutes {
val originalRouteOptions = berlinEvRouteOptions(baseUrl)
val newRouteOptions = originalRouteOptions.toBuilder()
.coordinatesList(
originalRouteOptions.coordinatesList().apply {
set(0, Point.fromLngLat(13.36742058325467, 52.49745756017697))
}
)
.build()
val newRouteOnlineRouteRequestHandler = MockDirectionsRequestHandler(
profile = DirectionsCriteria.PROFILE_DRIVING_TRAFFIC,
jsonResponse = readRawFileText(context, R.raw.ev_routes_berlin_reroute),
expectedCoordinates = newRouteOptions.coordinatesList()
)
return MockedEvRoutes(newRouteOptions, newRouteOnlineRouteRequestHandler)
}

private fun berlinEvRouteOptions(baseUrl: String?): RouteOptions = RouteOptions.builder()
.applyDefaultNavigationOptions()
.coordinates("13.361378213031003,52.49813341962201;13.393450988895268,52.50913924804004")
.annotations("state_of_charge")
.alternatives(true)
.waypointsPerRoute(true)
.unrecognizedProperties(
mapOf(
"engine" to "electric",
"ev_initial_charge" to "1000",
"ev_max_charge" to "50000",
"ev_connector_types" to "ccs_combo_type1,ccs_combo_type2",
"energy_consumption_curve" to "0,300;20,160;80,140;120,180",
"ev_charging_curve" to "0,100000;40000,70000;60000,30000;80000,10000",
"ev_min_charge_at_charging_station" to "1"
)
)
.apply {
if (baseUrl != null) {
baseUrl(baseUrl)
}
}
.build()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.mapbox.navigation.instrumentation_tests.utils.tiles

import androidx.test.platform.app.InstrumentationRegistry
import com.mapbox.geojson.FeatureCollection
import com.mapbox.navigation.instrumentation_tests.R
import com.mapbox.navigation.instrumentation_tests.utils.readRawFileText

object OfflineRegions {
val Berlin = OfflineRegion(
id = "berlin-test-tiles",
geometry = BERLIN_GEOMETRY
)
}

private val BERLIN_GEOMETRY = FeatureCollection.fromJson(
readRawFileText(
InstrumentationRegistry.getInstrumentation().targetContext,
R.raw.geometry_berlin
)
).features()!!.first().geometry()!!
Loading

0 comments on commit 4342c2c

Please sign in to comment.