trips/router/internal/plan/integration_test.go
Greg Pomerantz 0c4a1f9f60 router: working local OSRM backend + per-backend bench goldens
- fix encoded-polyline decoder (zigzag: (n>>1) ^ -(n&1)) — caught by
  the OSRM integration tests (Boston clearance 5681km -> 38km)
- OSRM client: routes[] JSON shape, overview=full for geometry,
  polyline+geojson support
- local OSRM 26.4.1 built from source (no sudo): deps.sh (bzip2, lua
  5.2 with readline stub, oneTBB, boost 1.84 via b2 + hand-rolled
  CMake config files, osmium-tool), setup-osrm.sh (merge PBFs ->
  extract -> partition -> customize -> serve :5000); v26 flag fixes
- bench: tasks.osrm.json goldens; both backends 26/26 PASS and agree
  on all 5 optimized orders (Ladd->Cascade etc.)
- README: profile deltas (Valhalla 332min vs OSRM 410min, same route
  shape), per-backend goldens, verified self-host status
2026-09-06 01:19:37 -04:00

111 lines
3.6 KiB
Go

//go:build integration
// Integration tests hit a live router (hosted Valhalla by default, or a
// local OSRM via ROUTER_URL for the osrm backend). They verify the
// client plumbing and the stop_cost end-to-end on the real road network.
//
// Run: go test -tags integration ./...
package plan
import (
"context"
"os"
"testing"
"time"
"maps/router/internal/geo"
"maps/router/internal/route"
)
func liveRouter(t *testing.T) route.Router {
backend := os.Getenv("ROUTER_BACKEND")
switch backend {
case "osrm":
return route.NewOSRM(os.Getenv("ROUTER_URL"))
default:
return route.NewValhalla("https://valhalla1.openstreetmap.de")
}
}
var (
lincoln = geo.Point{Lat: 44.054, Lon: -71.650}
queens = geo.Point{Lat: 40.729, Lon: -73.966}
)
func TestLiveDirectRoute(t *testing.T) {
t.Parallel()
r := liveRouter(t)
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
defer cancel()
rt, err := r.Route(ctx, route.ProfileDrive, []geo.Point{lincoln, queens})
if err != nil {
t.Fatalf("route: %v", err)
}
// Lincoln NH -> Queens NY is a 5.25-5.75h drive (I-93/90/495/290).
// Anything outside this band means the backend or the dataset is wrong.
mins := rt.Duration / 60
if mins < 290 || mins > 430 {
t.Errorf("direct time = %.0f min, expected in [290, 430] (profile-dependent)", mins)
}
if rt.Distance < 480_000 || rt.Distance > 600_000 {
t.Errorf("distance = %.0f m, expected in [480km, 600km]", rt.Distance)
}
t.Logf("%s: %d min, %.0f km", r.Name(), int(mins), rt.Distance/1000)
}
// TestLiveRouteAvoidsBoston is the regression test for the user's
// "the router, not the model, knows the route" finding: the fastest
// Lincoln->Queens drive goes via I-90/I-495/I-290 (Worcester corridor),
// NOT through Boston.
func TestLiveRouteAvoidsBoston(t *testing.T) {
t.Parallel()
r := liveRouter(t)
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
defer cancel()
rt, err := r.Route(ctx, route.ProfileDrive, []geo.Point{lincoln, queens})
if err != nil {
t.Fatalf("route: %v", err)
}
boston := geo.Point{Lat: 42.36, Lon: -71.06}
var dist float64
if len(rt.Geometry) >= 2 {
dist = geo.DistToPolylineMeters(boston, rt.Geometry)
} else if !rt.BBox.Empty() {
dist = rt.BBox.DistMeters(boston)
t.Logf("using bbox distance (no geometry from backend)")
} else {
t.Skip("no geometry or bbox available")
}
if dist < 15_000 {
t.Errorf("route comes within %.1f km of Boston; expected the I-495/I-290 corridor (>= 15 km)", dist/1000)
}
t.Logf("Boston clearance: %.1f km", dist/1000)
}
// TestLiveStopCost exercises the full stop_cost pipeline on the real
// network: Cascade Falls (Worcester) sits on the I-495 corridor and
// must have a small detour; Mount Major (Alton NH) is far off it.
func TestLiveStopCost(t *testing.T) {
t.Parallel()
r := liveRouter(t)
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second)
defer cancel()
cascade := geo.Point{Lat: 42.2767, Lon: -71.806}
mtmajor := geo.Point{Lat: 43.642, Lon: -71.156}
pts := []geo.Point{cascade, mtmajor, lincoln, queens}
m, err := route.BuildMatrix(ctx, r, route.ProfileDrive, pts, 200*time.Millisecond)
if err != nil {
t.Fatalf("matrix: %v", err)
}
cascadeCost := StopCost(m, 0, Stop{ID: "cascade", DwellMin: 60})
majorCost := StopCost(m, 1, Stop{ID: "mtmajor", DwellMin: 120})
t.Logf("cascade detour = %d min; mtmajor detour = %d min", cascadeCost.DetourMin, majorCost.DetourMin)
if cascadeCost.DetourMin > 20 {
t.Errorf("Cascade Falls (on I-495 corridor) detour = %d min, want <= 20", cascadeCost.DetourMin)
}
if majorCost.DetourMin < 40 {
t.Errorf("Mount Major (off corridor) detour = %d min, want >= 40", majorCost.DetourMin)
}
}