Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package com.mapbox.navigation.core.trip.service

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.mapbox.navigation.base.options.NavigationOptions
import com.mapbox.navigation.base.route.NavigationRoute
import com.mapbox.navigation.base.route.RouterOrigin
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.core.MapboxNavigationProvider
import com.mapbox.navigation.core.navigator.toFixLocation
import com.mapbox.navigation.core.replay.history.ReplayEventUpdateLocation
import com.mapbox.navigation.core.replay.history.mapToLocation
import com.mapbox.navigation.core.replay.route.ReplayRouteMapper
import com.mapbox.navigation.core.test.R
import com.mapbox.navigation.navigator.internal.MapboxNativeNavigator
import com.mapbox.navigation.navigator.internal.MapboxNativeNavigatorImpl
import com.mapbox.navigator.NavigationStatus
import com.mapbox.navigator.NavigationStatusOrigin
import com.mapbox.navigator.NavigatorObserver
import com.mapbox.navigator.RouteState
import com.mapbox.navigator.SetRoutesReason
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertTrue
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

@RunWith(AndroidJUnit4::class)
class ArtificialDriverTest {

@Test
@Ignore("test sometimes fails because of https://mapbox.atlassian.net/browse/NN-418")
fun nativeNavigatorFollowsArtificialDriverWithoutReroutes() =
runBlocking<Unit>(Dispatchers.Main) {
withNavigators { mapboxNavigation, nativeNavigator ->
mapboxNavigation.historyRecorder.startRecording()
val testRoute = getTestRoute()
val events = createArtificialLocationUpdates(testRoute)
val setRoutesResult =
nativeNavigator.setRoutes(testRoute, reason = SetRoutesReason.NEW_ROUTE)
assertTrue("result is $setRoutesResult", setRoutesResult.isValue)
val statusesTracking = async<List<NavigationStatus>> {
nativeNavigator.collectStatuses(untilRouteState = RouteState.COMPLETE)
}

for (location in events.map { it.location.mapToLocation() }) {
assertTrue(nativeNavigator.updateLocation(location.toFixLocation()))
}
Comment on lines +57 to +59
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mapbox/navnative , we have already discussed it, but can you please confirm that this is valid usage of native navigator?
I generated location updates so that they looks like the regular ones, but I pass them to NN faster than they happen in reality keeping the original time, i.e. for the native navigator time goes faster than real time.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like history recording replay, you can take the locations with proper monotonic time in them and feed back-to-back into the navigator - it will produce the correct result.


val states = statusesTracking.await()
val historyFile = suspendCoroutine<String> { continuation ->
mapboxNavigation.historyRecorder.stopRecording {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a suspend version of this method:

val historyFile = mapboxNavigation.historyRecorder.startRecording()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please help me find it? I don't see suspend version here

Copy link
Contributor Author

@VysotskiVadim VysotskiVadim Jan 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably meant this one, but artificial driver test is in a different module as it accesses internal APIs

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this one. Oh, it's a different module. OK then.

continuation.resume(it ?: "null")
}
}
val offRouteState = states.filter { it.routeState == RouteState.OFF_ROUTE }
assertTrue(
"${offRouteState.size} off-route states have been detected(" +
"more info in $historyFile): $offRouteState",
offRouteState.isEmpty()
)
}
}
}

private fun createArtificialLocationUpdates(
testRoute: NavigationRoute
): List<ReplayEventUpdateLocation> {
val replayRouteMapper = ReplayRouteMapper()
return replayRouteMapper
.mapDirectionsRouteGeometry(testRoute.directionsRoute)
.filterIsInstance<ReplayEventUpdateLocation>()
}

private suspend fun MapboxNativeNavigator.collectStatuses(
untilRouteState: RouteState
): MutableList<NavigationStatus> {
val statues = mutableListOf<NavigationStatus>()
statusUpdates()
.map { it.status }
.takeWhile { it.routeState != untilRouteState }
.toList(statues)
return statues
}

data class OnStatusUpdateParameters(
val origin: NavigationStatusOrigin,
val status: NavigationStatus
)

@OptIn(ExperimentalCoroutinesApi::class)
fun MapboxNativeNavigator.statusUpdates(): Flow<OnStatusUpdateParameters> {
return callbackFlow {
val observer = NavigatorObserver { origin, status ->
this.trySend(OnStatusUpdateParameters(origin, status))
}
addNavigatorObserver(observer)
awaitClose {
removeNavigatorObserver(observer)
}
}
}

private suspend fun withNavigators(
block: suspend (MapboxNavigation, MapboxNativeNavigator) -> Unit
) {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val mapboxNavigation = MapboxNavigationProvider.create(
NavigationOptions.Builder(context)
.accessToken(context.getString(R.string.mapbox_access_token))
.build()
)
try {
block(mapboxNavigation, MapboxNativeNavigatorImpl)
} finally {
mapboxNavigation.onDestroy()
}
}

private fun getTestRoute(): NavigationRoute {
val context = InstrumentationRegistry.getInstrumentation().targetContext
return NavigationRoute.create(
directionsResponseJson = context.resources.openRawResource(R.raw.test_long_route)
.readBytes().decodeToString(),
routeRequestUrl = "https://api.mapbox.com/directions/v5/mapbox/driving/" +
"11.566744%2C48.143769%3B8.675521%2C50.119087" +
"?alternatives=false" +
"&geometries=polyline6" +
"&language=en" +
"&overview=full" +
"&steps=true" +
"&access_token=YOUR_MAPBOX_ACCESS_TOKEN",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See NativeNavigatorCallbackOrderTest, it already uses access token that is being injected on CI.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you don't want it to be present in the URL.

routerOrigin = RouterOrigin.Custom()
).first()
}
Loading