aboutsummaryrefslogtreecommitdiff
path: root/internal/ridetest
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-08-25 13:26:22 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-08-25 13:26:22 +0200
commite73b7a9ccb4bc61e54d1fdc61b5954879c637d00 (patch)
tree34658a02faee971fa041a3f02160770ae283a6de /internal/ridetest
parentd5de3a6fefbc57bccfb1fe688f9c190ccf87d58d (diff)
Migrate cross-package ride test fixtures to the ridetest DSL
Replace raw FromColumns 4-array blocks in official_climb, mountain_pass and web test rides with ridetest.MustRide strings. Add a Heading option to ridetest for routes whose points are spaced differently per kilometer (e.g. the mountain_pass expanded-bounds route at 0.1 degrees/km). Lap, direction and cluster tests in official_climb keep FromColumns: the DSL generates monotonic northeast coords and cannot express per-point loops or reversals.
Diffstat (limited to 'internal/ridetest')
-rw-r--r--internal/ridetest/ride.go40
-rw-r--r--internal/ridetest/ride_test.go11
2 files changed, 42 insertions, 9 deletions
diff --git a/internal/ridetest/ride.go b/internal/ridetest/ride.go
index a6413a9..e74ce49 100644
--- a/internal/ridetest/ride.go
+++ b/internal/ridetest/ride.go
@@ -6,9 +6,10 @@
// ridetest.MustRide("2km at 7% | 1km at -7%")
//
// Options adjust the generated columns: Sampling (step in meters, default
-// 100), StartCoord (first point; later points progress northeast by 0.01° of
-// latitude and longitude per kilometer), StartTime (default the zero time,
-// one second between points) and StartElevation (default 0).
+// 100), StartCoord (first point; later points progress northeast at the
+// Heading rate, default 0.01° of latitude and longitude per kilometer),
+// StartTime (default the zero time, one second between points) and
+// StartElevation (default 0).
package ridetest
import (
@@ -46,6 +47,8 @@ type point struct {
type options struct {
samplingM float64
startCoord *geodist.Coord
+ headingLat float64
+ headingLon float64
startTime time.Time
startElevM float64
}
@@ -60,9 +63,28 @@ func Sampling(m float64) Option {
}
// StartCoord sets the coordinate of the first point. Each following point
-// progresses northeast by 0.01° of latitude and longitude per kilometer.
+// progresses northeast at the rate set by Heading, default 0.01° of latitude
+// and longitude per kilometer.
func StartCoord(lat, lon float64) Option {
- return func(o *options) { o.startCoord = &geodist.Coord{Lat: lat, Lon: lon} }
+ return func(o *options) {
+ o.startCoord = &geodist.Coord{Lat: lat, Lon: lon}
+ if o.headingLat == 0 {
+ o.headingLat = headingPerKm
+ }
+ if o.headingLon == 0 {
+ o.headingLon = headingPerKm
+ }
+ }
+}
+
+// Heading sets the latitude and longitude offset applied to each point per
+// kilometer of ride, in degrees (default 0.01 northeast when StartCoord is
+// set). Only meaningful together with StartCoord.
+func Heading(latPerKm, lonPerKm float64) Option {
+ return func(o *options) {
+ o.headingLat = latPerKm
+ o.headingLon = lonPerKm
+ }
}
// StartTime sets the timestamp of the first point; each following point is
@@ -80,7 +102,7 @@ func StartElevation(m float64) Option {
//
// Example: "2km at 7% | 1km at -7%".
func Ride(dsl string, opts ...Option) (ride.Ride, error) {
- o := options{samplingM: defaultSamplingM}
+ o := options{samplingM: defaultSamplingM, headingLat: headingPerKm, headingLon: headingPerKm}
for _, opt := range opts {
opt(&o)
}
@@ -250,10 +272,10 @@ func buildPoints(sections []section, o options) []point {
lat, lon = o.startCoord.Lat, o.startCoord.Lon
}
// Coordinates derive from the total distance so that exact multiples of
- // the per-kilometer heading (0.01° x km as float64) stay exact; a running
- // accumulation drifts after a few points.
+ // the per-kilometer heading stay exact; a running accumulation drifts
+ // after a few points.
coord := func(distanceM float64) geodist.Coord {
- return geodist.Coord{Lat: lat + headingPerKm*(distanceM/1000), Lon: lon + headingPerKm*(distanceM/1000)}
+ return geodist.Coord{Lat: lat + o.headingLat*(distanceM/1000), Lon: lon + o.headingLon*(distanceM/1000)}
}
points := make([]point, 0, len(sections)+1)
elevation := o.startElevM
diff --git a/internal/ridetest/ride_test.go b/internal/ridetest/ride_test.go
index 69c084d..65483f2 100644
--- a/internal/ridetest/ride_test.go
+++ b/internal/ridetest/ride_test.go
@@ -53,6 +53,17 @@ func TestRideStartCoordHeading(t *testing.T) {
assert.InDelta(t, 5.12, r.Coords()[2].Lon, 1e-9)
}
+func TestRideHeadingOption(t *testing.T) {
+ r, err := Ride("1km at 0%", StartCoord(43.0, 5.0), Heading(0.1, 0.1), Sampling(1000))
+ require.NoError(t, err)
+
+ // A single kilometer at 0.1°/km ends at (43.1, 5.1), matching rides whose
+ // points are spaced 0.1° apart over a kilometer.
+ require.Len(t, r.Coords(), 2)
+ assert.InDelta(t, 43.1, r.Coords()[1].Lat, 1e-9)
+ assert.InDelta(t, 5.1, r.Coords()[1].Lon, 1e-9)
+}
+
func TestRideStartTime(t *testing.T) {
start := time.Date(2026, 8, 1, 10, 0, 0, 0, time.UTC)
r, err := Ride("1km at 0%", StartTime(start), Sampling(500))