Skip to content

NAVAND-775: support waypoints per route#6555

Merged
tomaszrybakiewicz merged 3 commits intomainfrom
NAVAND-775-dd-sdk
Nov 29, 2022
Merged

NAVAND-775: support waypoints per route#6555
tomaszrybakiewicz merged 3 commits intomainfrom
NAVAND-775-dd-sdk

Conversation

@dzinad
Copy link
Copy Markdown
Contributor

@dzinad dzinad commented Nov 3, 2022

No description provided.

@dzinad dzinad requested a review from a team as a code owner November 3, 2022 19:03
@dzinad dzinad force-pushed the NAVAND-775-dd-sdk branch from d47869e to 9dce7a8 Compare November 3, 2022 19:04
@dzinad
Copy link
Copy Markdown
Contributor Author

dzinad commented Nov 3, 2022

Adding the DO NOT MERGE label as it requires bumping NN to 119.0.2.
Right now some instrumentation tests are expected to fail.

@dzinad dzinad added the ⚠️ DO NOT MERGE PR should not be merged! label Nov 3, 2022
@dzinad dzinad force-pushed the NAVAND-775-dd-sdk branch 2 times, most recently from 30dfbf8 to 3b903b3 Compare November 4, 2022 09:53
@codecov
Copy link
Copy Markdown

codecov bot commented Nov 4, 2022

Codecov Report

Merging #6555 (b3f5681) into main (4b43089) will increase coverage by 0.02%.
The diff coverage is 81.48%.

Impacted file tree graph

@@             Coverage Diff              @@
##               main    #6555      +/-   ##
============================================
+ Coverage     72.38%   72.40%   +0.02%     
- Complexity     5392     5399       +7     
============================================
  Files           764      764              
  Lines         29372    29377       +5     
  Branches       3477     3480       +3     
============================================
+ Hits          21260    21270      +10     
+ Misses         6719     6718       -1     
+ Partials       1393     1389       -4     
Impacted Files Coverage Δ
.../mapbox/navigation/base/internal/utils/RouterEx.kt 45.45% <0.00%> (ø)
...on/navigator/internal/MapboxNativeNavigatorImpl.kt 0.00% <0.00%> (ø)
...pbox/navigation/core/accounts/BillingController.kt 88.09% <50.00%> (ø)
...om/mapbox/navigation/base/route/NavigationRoute.kt 53.95% <71.42%> (+1.80%) ⬆️
...avigation/base/internal/route/NavigationRouteEx.kt 83.90% <100.00%> (+0.76%) ⬆️
...n/core/routerefresh/DirectionsRouteDiffProvider.kt 83.72% <100.00%> (+5.14%) ⬆️
...igation/base/internal/factory/RoadObjectFactory.kt 86.36% <0.00%> (+4.54%) ⬆️

@dzinad dzinad force-pushed the NAVAND-775-dd-sdk branch from 3b903b3 to c65370e Compare November 8, 2022 09:35
Comment on lines +98 to +146
fun ev_route_with_waypoints_in_response_root_by_default() = sdkTest {
addResponseHandler(R.raw.ev_route_response_with_waypoints_in_root, evCoordinates)
val routes = requestRoutes(evCoordinates, electric = true, waypointsPerRoute = null)

checkWaypointsInRoot(expectedEvWaypointsNamesAndLocations, routes[0])
}

@Test
fun ev_route_with_waypoints_in_response_root() = sdkTest {
addResponseHandler(R.raw.ev_route_response_with_waypoints_in_root, evCoordinates)
val routes = requestRoutes(evCoordinates, electric = true, waypointsPerRoute = false)

checkWaypointsInRoot(expectedEvWaypointsNamesAndLocations, routes[0])
}

@Test
fun ev_route_with_waypoints_per_route() = sdkTest {
addResponseHandler(R.raw.ev_route_response_with_waypoints_per_route, evCoordinates)
val routes = requestRoutes(evCoordinates, electric = true, waypointsPerRoute = true)

checkWaypointsPerRoute(expectedEvWaypointsNamesAndLocations, routes[0])
}

@Test
fun non_ev_route_with_waypoints_in_response_root_by_default() = sdkTest {
addResponseHandler(R.raw.route_response_with_waypoints_in_root, nonEvCoordinates)
val routes = requestRoutes(nonEvCoordinates, electric = false, waypointsPerRoute = null)

checkWaypointsInRoot(expectedFirstNonEvWaypointsNamesAndLocations, routes[0])
checkWaypointsInRoot(expectedFirstNonEvWaypointsNamesAndLocations, routes[1])
}

@Test
fun non_ev_route_with_waypoints_in_response_root() = sdkTest {
addResponseHandler(R.raw.route_response_with_waypoints_in_root, nonEvCoordinates)
val routes = requestRoutes(nonEvCoordinates, electric = false, waypointsPerRoute = false)

checkWaypointsInRoot(expectedFirstNonEvWaypointsNamesAndLocations, routes[0])
checkWaypointsInRoot(expectedFirstNonEvWaypointsNamesAndLocations, routes[1])
}

@Test
fun non_ev_route_with_waypoints_per_route() = sdkTest {
addResponseHandler(R.raw.route_response_with_waypoints_per_route, nonEvCoordinates)
val routes = requestRoutes(nonEvCoordinates, electric = false, waypointsPerRoute = true)

checkWaypointsPerRoute(expectedFirstNonEvWaypointsNamesAndLocations, routes[0])
checkWaypointsPerRoute(expectedSecondNonEvWaypointsNamesAndLocations, routes[1])
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

They seem like unit tests which are executed on device. I understand why it's done like this, we can't access NN in unit tests. I added NAVSDK-726 to address absence of NN in unit tests later.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, not because of that. It's not a unit test. It's an integration test: we request routes and parse the response.
We test the significant part of the SDK, not some specific class.
We already have such tests: see NavigationRouteTest.
And we don't have access to NN classes here either: everything that's checked here is represented by the SDK classes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It seems that NavigationRoute#internalWaypoints is calculated using NN, isn't it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes and they are checked here as well. What are you saying?

@dzinad dzinad force-pushed the NAVAND-775-dd-sdk branch 2 times, most recently from 9131ebd to feb2153 Compare November 14, 2022 08:18
@dzinad
Copy link
Copy Markdown
Contributor Author

dzinad commented Nov 14, 2022

Supported in NN and checked. Removing DO NOT MERGE.

@dzinad dzinad removed the ⚠️ DO NOT MERGE PR should not be merged! label Nov 14, 2022
Comment on lines +12 to +28
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as ApproximateCoordinates

if (abs(latitude - other.latitude) > tolerance) return false
if (abs(longitude - other.longitude) > tolerance) return false

return true
}

override fun hashCode(): Int {
var result = latitude.hashCode()
result = 31 * result + longitude.hashCode()
return result
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

as I understand two approximate coordinates could be equals, but their hash codes will be different. In this case data structures like hash tables won't be able to find equal objects. Not sure if it's important problem, but this could be unexpected

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmmm, I think you're right. It's not a problem now because it's used from assertEquals.
But I don't see the point of using ApproximateCoordinates as a key in hash table. Because for 2 different coordinates you'll get the same key... However, I think it would make sense to just return 0 here to avoid this unexpectedness.

}

private fun DirectionsResponse.Builder.updateWaypoints(
private fun buildNewWaypoints(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I should have asked this before, but I noticed this functionality only now.
What kind of update in waypoints do we expect? We don't expect to receive waypoints with different coordinates, do we?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We don't. Only the metadata will be updated for now. Do you think we should update only unrecognizedProperties? Maybe it makes sense: we have a white-list of what we update.

Copy link
Copy Markdown
Contributor

@vadzim-vys vadzim-vys Nov 15, 2022

Choose a reason for hiding this comment

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

yep, reading the code, it's not obvious what's expected to change and what's not. It would be nice to explicitly update fields which we expect to be changed or explicitly save the ones we don't expect to be updated.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment on lines +156 to +180
route1 = NavigationRoute(
directionsResponse1,
0,
routeOptions1,
mockk(relaxUnitFun = true) {
every { routeId } returns id1
every { responseUuid } returns "uuid#0"
every { routerOrigin } returns RouterOrigin.ONBOARD
every { routeInfo } returns RouteInfo(listOf(mockk(relaxed = true)))
}
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe?

Suggested change
route1 = NavigationRoute(
directionsResponse1,
0,
routeOptions1,
mockk(relaxUnitFun = true) {
every { routeId } returns id1
every { responseUuid } returns "uuid#0"
every { routerOrigin } returns RouterOrigin.ONBOARD
every { routeInfo } returns RouteInfo(listOf(mockk(relaxed = true)))
}
)
route1 = createNavigationRoutes(
response = directionsResponse1,
options = routeOptions1
).first()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I need to mock id. I can use spy but IMO switching to createNavigationRoutes gives no profit here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

createNavigationRoutes generates in the same way as NN does, so it's very similar, if not identical, to how the real system works. is that enough for unit tests?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

How can I mock the id if I use createNavigationRoutes?
I only see they way with creating a spy.
My question is: why is it better than the current approach?

// #0
arrayOf(null, null, null, null),
arrayOf(false, null, null, null),
arrayOf(true, null, null, null),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How can it happen that a route doesn't have waypoints? Is that done for backward compatibility?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That's the point of unit tests. Technically they can be null and the method that is being tested accounts for this. And to invoke this method we don't need to know the whole business logic of the SDK (that it can't IRL have null waypoints).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I see your point.

that it can't IRL have null waypoints

should the API on NavigationRoute be nullable then?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Technically they can be null. Because both directionsRoute.waypoints() and directionsResponse.waypoints() are nullable. Our backend claims one of them will be returned (depending on the waypoints_per_route parameter).
But what if this behaviour changes? And do we really trust backend that much? You mentioned yourself that it's better to introduce custom request timeout than rely on NN.
I can return an empty list instead of course. WDYT?

arrayOf(null, filledWaypoints2, null, filledWaypoints2),
arrayOf(false, filledWaypoints2, null, filledWaypoints2),
arrayOf(true, filledWaypoints2, null, null),
// #21
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

just curious, why do you need this numbers?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In the test description the index is used as a description. This way it's easy to understand which case fails. Another way is adding a specific description and I like it, but I think that here it's not really necessary because it's quite clear from the input parameters and it would only kinda duplicate them. For more complex tests description is good though.

Comment on lines +128 to +154
val route = NavigationRoute(
DirectionsResponse.builder()
.routes(
listOf(
DirectionsRoute.builder()
.distance(1.0)
.duration(2.9)
.waypoints(routeWaypoints)
.build()
)
)
.waypoints(responseWaypoints)
.code("Ok")
.build(),
0,
RouteOptions.builder()
.profile(DirectionsCriteria.PROFILE_DRIVING_TRAFFIC)
.coordinatesList(
listOf(
Point.fromLngLat(1.1, 2.2),
Point.fromLngLat(3.3, 4.4)
)
)
.waypointsPerRoute(waypointsPerRoute)
.build(),
mockk(relaxed = true)
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

would com.mapbox.navigation.testing.factories.NavigationRouteFactoryKt#createNavigationRoutes simplify this setup?

Comment on lines +375 to +377
mapboxNavigation.setNavigationRoutesAndWaitForUpdate(requestedRoutes)
mapboxNavigation.startTripSession()
stayOnInitialPosition()
val updatedRoutes = waitUntilRefresh().navigationRoutes
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we set route created with charge level 18000 to navigation which already has charge level 17000. Is that expected?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure. The routes were requested when the charge was 18000. Then the charge dropped to 17000 and we told MapboxNavigation about it. Then we set the requested routes. I don't see anything invalid in this case. The latest data (17000) should be used in the refresh request.

@dzinad dzinad force-pushed the NAVAND-775-dd-sdk branch 2 times, most recently from afec69a to 7b23f59 Compare November 16, 2022 10:26
Comment thread CHANGELOG.md Outdated
@dzinad dzinad force-pushed the NAVAND-775-dd-sdk branch 2 times, most recently from e3d6434 to b231e9f Compare November 18, 2022 14:50
@dzinad dzinad requested a review from vadzim-vys November 22, 2022 07:32
@LukasPaczos
Copy link
Copy Markdown

LukasPaczos commented Nov 28, 2022

I added missing docs and updated the BillingController to also use NavigationRoute#waypoints. LGTM but I'd appreciate another round from @mapbox/navigation-android.

@tomaszrybakiewicz
Copy link
Copy Markdown
Contributor

I've rebased on top of the latest main branch and resolved conflicts in CHANGELOG.

@tomaszrybakiewicz tomaszrybakiewicz enabled auto-merge (squash) November 29, 2022 22:22
@tomaszrybakiewicz tomaszrybakiewicz merged commit 592818b into main Nov 29, 2022
@tomaszrybakiewicz tomaszrybakiewicz deleted the NAVAND-775-dd-sdk branch November 29, 2022 23:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants