initial version
This commit is contained in:
592
main_test.go
Normal file
592
main_test.go
Normal file
@@ -0,0 +1,592 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/tkrajina/gpxgo/gpx"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseMultiStops(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected []MultiStop
|
||||
}{
|
||||
{
|
||||
name: "Empty input",
|
||||
input: "",
|
||||
expected: []MultiStop{},
|
||||
},
|
||||
{
|
||||
name: "Single stop",
|
||||
input: "59.3293,18.0686:1",
|
||||
expected: []MultiStop{
|
||||
{Lat: 59.3293, Lon: 18.0686, Nights: 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Multiple stops",
|
||||
input: "59.3293,18.0686:1;60.1282,18.6435:2",
|
||||
expected: []MultiStop{
|
||||
{Lat: 59.3293, Lon: 18.0686, Nights: 1},
|
||||
{Lat: 60.1282, Lon: 18.6435, Nights: 2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid format",
|
||||
input: "invalid",
|
||||
expected: []MultiStop{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := parseMultiStops(tt.input)
|
||||
if len(result) != len(tt.expected) {
|
||||
t.Errorf("Expected %d stops, got %d", len(tt.expected), len(result))
|
||||
return
|
||||
}
|
||||
for i, stop := range result {
|
||||
if stop.Lat != tt.expected[i].Lat || stop.Lon != tt.expected[i].Lon || stop.Nights != tt.expected[i].Nights {
|
||||
t.Errorf("Stop %d mismatch: expected %+v, got %+v", i, tt.expected[i], stop)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHaversine(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
lat1 float64
|
||||
lon1 float64
|
||||
lat2 float64
|
||||
lon2 float64
|
||||
expected float64
|
||||
}{
|
||||
{
|
||||
name: "Same point",
|
||||
lat1: 59.3293,
|
||||
lon1: 18.0686,
|
||||
lat2: 59.3293,
|
||||
lon2: 18.0686,
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "Stockholm to Uppsala",
|
||||
lat1: 59.3293,
|
||||
lon1: 18.0686,
|
||||
lat2: 59.8586,
|
||||
lon2: 17.6389,
|
||||
expected: 63.63, // Approximate distance in km
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := haversine(tt.lat1, tt.lon1, tt.lat2, tt.lon2)
|
||||
// Allow for small floating point differences
|
||||
if math.Abs(result-tt.expected) > 0.5 {
|
||||
t.Errorf("Expected distance of %.2f km, got %.2f km", tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeCutIndexes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cutIndexes []int
|
||||
days int
|
||||
totalPoints int
|
||||
expected []int
|
||||
}{
|
||||
{
|
||||
name: "No cuts",
|
||||
cutIndexes: []int{},
|
||||
days: 5,
|
||||
totalPoints: 100,
|
||||
expected: []int{},
|
||||
},
|
||||
{
|
||||
name: "Fewer cuts than days",
|
||||
cutIndexes: []int{25, 50, 75},
|
||||
days: 5,
|
||||
totalPoints: 100,
|
||||
expected: []int{25, 50, 75},
|
||||
},
|
||||
{
|
||||
name: "More cuts than days",
|
||||
cutIndexes: []int{20, 40, 60, 80, 99},
|
||||
days: 3,
|
||||
totalPoints: 100,
|
||||
expected: []int{20, 40, 99},
|
||||
},
|
||||
{
|
||||
name: "Duplicate cuts (rest days)",
|
||||
cutIndexes: []int{25, 25, 50, 75},
|
||||
days: 5,
|
||||
totalPoints: 100,
|
||||
expected: []int{25, 25, 50, 75},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := normalizeCutIndexes(tt.cutIndexes, tt.days, tt.totalPoints)
|
||||
if len(result) != len(tt.expected) {
|
||||
t.Errorf("Expected %d cuts, got %d", len(tt.expected), len(result))
|
||||
return
|
||||
}
|
||||
for i, cut := range result {
|
||||
if cut != tt.expected[i] {
|
||||
t.Errorf("Cut %d mismatch: expected %d, got %d", i, tt.expected[i], cut)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateSegment(t *testing.T) {
|
||||
// Create test points
|
||||
points := []TrackPoint{
|
||||
{Index: 0, Distance: 0, Elevation: 0},
|
||||
{Index: 1, Distance: 10, Elevation: 100},
|
||||
{Index: 2, Distance: 20, Elevation: 200},
|
||||
{Index: 3, Distance: 30, Elevation: 300},
|
||||
{Index: 4, Distance: 40, Elevation: 400},
|
||||
{Index: 5, Distance: 50, Elevation: 500},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
start int
|
||||
end int
|
||||
cutIndexes []int
|
||||
segmentIndex int
|
||||
expectedLen int
|
||||
}{
|
||||
{
|
||||
name: "Normal segment",
|
||||
start: 0,
|
||||
end: 3,
|
||||
cutIndexes: []int{3, 5},
|
||||
segmentIndex: 0,
|
||||
expectedLen: 3, // Points 0, 1, 2
|
||||
},
|
||||
{
|
||||
name: "Forced stop (end <= start)",
|
||||
start: 3,
|
||||
end: 3,
|
||||
cutIndexes: []int{3, 5},
|
||||
segmentIndex: 0,
|
||||
expectedLen: 2, // Points 3, 4 (halfway to the next cut)
|
||||
},
|
||||
{
|
||||
name: "Last segment",
|
||||
start: 3,
|
||||
end: 5,
|
||||
cutIndexes: []int{3, 5},
|
||||
segmentIndex: 1,
|
||||
expectedLen: 2, // Points 3, 4
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := createSegment(points, tt.start, tt.end, tt.cutIndexes, tt.segmentIndex)
|
||||
if len(result) != tt.expectedLen {
|
||||
t.Errorf("Expected segment length %d, got %d", tt.expectedLen, len(result))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDaySegment(t *testing.T) {
|
||||
// Create test points
|
||||
points := []TrackPoint{
|
||||
{Index: 0, Distance: 0, Elevation: 0},
|
||||
{Index: 1, Distance: 10, Elevation: 100},
|
||||
{Index: 2, Distance: 20, Elevation: 200},
|
||||
}
|
||||
|
||||
dayNumber := 1
|
||||
elevFactor := 4.0
|
||||
forestRadius := 3
|
||||
resupplyRadius := 500
|
||||
|
||||
// This test will not query for forest or resupply points
|
||||
// since that would require network access. We're just testing the
|
||||
// calculation of distance, elevation, and effort.
|
||||
segment := createDaySegment(points, dayNumber, elevFactor, forestRadius, resupplyRadius, false)
|
||||
|
||||
if segment.DayNumber != dayNumber {
|
||||
t.Errorf("Expected day number %d, got %d", dayNumber, segment.DayNumber)
|
||||
}
|
||||
|
||||
expectedDist := 20.0 // Last point distance - first point distance
|
||||
if math.Abs(segment.Distance-expectedDist) > 0.1 {
|
||||
t.Errorf("Expected distance %.1f, got %.1f", expectedDist, segment.Distance)
|
||||
}
|
||||
|
||||
expectedElev := 200.0 // Last point elevation - first point elevation
|
||||
if math.Abs(segment.Elevation-expectedElev) > 0.1 {
|
||||
t.Errorf("Expected elevation %.1f, got %.1f", expectedElev, segment.Elevation)
|
||||
}
|
||||
|
||||
expectedEffort := expectedDist + (expectedElev / 100.0 * elevFactor)
|
||||
if math.Abs(segment.Effort-expectedEffort) > 0.1 {
|
||||
t.Errorf("Expected effort %.1f, got %.1f", expectedEffort, segment.Effort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateCutIndexes(t *testing.T) {
|
||||
// Create test route data
|
||||
points := []TrackPoint{
|
||||
{Index: 0, Distance: 0, Elevation: 0},
|
||||
{Index: 1, Distance: 25, Elevation: 250}, // Effort: 25 + (250/100*4) = 35
|
||||
{Index: 2, Distance: 50, Elevation: 500}, // Effort: 50 + (500/100*4) = 70
|
||||
{Index: 3, Distance: 75, Elevation: 750}, // Effort: 75 + (750/100*4) = 105
|
||||
{Index: 4, Distance: 100, Elevation: 1000}, // Effort: 100 + (1000/100*4) = 140
|
||||
}
|
||||
|
||||
routeData := RouteData{
|
||||
Points: points,
|
||||
TotalDist: 100,
|
||||
TotalElev: 1000,
|
||||
TotalEffort: 140,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
days int
|
||||
elevFactor float64
|
||||
multiStops []MultiStop
|
||||
expected []int
|
||||
}{
|
||||
{
|
||||
name: "2 days, no stops",
|
||||
days: 2,
|
||||
elevFactor: 4.0,
|
||||
multiStops: []MultiStop{},
|
||||
expected: []int{2, 4}, // Cut at index 2 (effort 70) and 4 (end)
|
||||
},
|
||||
{
|
||||
name: "3 days, no stops",
|
||||
days: 3,
|
||||
elevFactor: 4.0,
|
||||
multiStops: []MultiStop{},
|
||||
expected: []int{1, 3, 4}, // Cut at index 1, 3, and 4 (end)
|
||||
},
|
||||
{
|
||||
name: "3 days, with one multi-stop",
|
||||
days: 3,
|
||||
elevFactor: 4.0,
|
||||
multiStops: []MultiStop{
|
||||
{Lat: 0, Lon: 0, Nights: 1}, // This will match point 0
|
||||
},
|
||||
expected: []int{0, 1, 4}, // Cut at index 0 (forced stop), 1, and 4 (end)
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := calculateCutIndexes(routeData, tt.days, tt.elevFactor, tt.multiStops)
|
||||
if len(result) != len(tt.expected) {
|
||||
t.Errorf("Expected %d cuts, got %d", len(tt.expected), len(result))
|
||||
return
|
||||
}
|
||||
for i, cut := range result {
|
||||
if cut != tt.expected[i] {
|
||||
t.Errorf("Cut %d mismatch: expected %d, got %d", i, tt.expected[i], cut)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// New tests for refactored functions
|
||||
|
||||
func TestFindForcedStopIndexes(t *testing.T) {
|
||||
// Create test points with latitude and longitude
|
||||
points := []TrackPoint{
|
||||
{Index: 0, Point: gpx.GPXPoint{Point: gpx.Point{Latitude: 59.3293, Longitude: 18.0686}}},
|
||||
{Index: 1, Point: gpx.GPXPoint{Point: gpx.Point{Latitude: 59.8586, Longitude: 17.6389}}},
|
||||
{Index: 2, Point: gpx.GPXPoint{Point: gpx.Point{Latitude: 60.1282, Longitude: 18.6435}}},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
points []TrackPoint
|
||||
multiStops []MultiStop
|
||||
expected []int
|
||||
}{
|
||||
{
|
||||
name: "No stops",
|
||||
points: points,
|
||||
multiStops: []MultiStop{},
|
||||
expected: []int{},
|
||||
},
|
||||
{
|
||||
name: "One stop matching first point",
|
||||
points: points,
|
||||
multiStops: []MultiStop{
|
||||
{Lat: 59.3293, Lon: 18.0686, Nights: 1}, // Matches point 0
|
||||
},
|
||||
expected: []int{0},
|
||||
},
|
||||
{
|
||||
name: "One stop with multiple nights",
|
||||
points: points,
|
||||
multiStops: []MultiStop{
|
||||
{Lat: 59.8586, Lon: 17.6389, Nights: 2}, // Matches point 1
|
||||
},
|
||||
expected: []int{1, 1}, // Duplicated for 2 nights
|
||||
},
|
||||
{
|
||||
name: "Multiple stops",
|
||||
points: points,
|
||||
multiStops: []MultiStop{
|
||||
{Lat: 59.3293, Lon: 18.0686, Nights: 1}, // Matches point 0
|
||||
{Lat: 60.1282, Lon: 18.6435, Nights: 1}, // Matches point 2
|
||||
},
|
||||
expected: []int{0, 2},
|
||||
},
|
||||
{
|
||||
name: "Stop too far from any point",
|
||||
points: points,
|
||||
multiStops: []MultiStop{
|
||||
{Lat: 55.0, Lon: 15.0, Nights: 1}, // Far from all points
|
||||
},
|
||||
expected: []int{}, // No match within 2km
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := findForcedStopIndexes(tt.points, tt.multiStops)
|
||||
if len(result) != len(tt.expected) {
|
||||
t.Errorf("Expected %d forced stops, got %d", len(tt.expected), len(result))
|
||||
return
|
||||
}
|
||||
for i, idx := range result {
|
||||
if idx != tt.expected[i] {
|
||||
t.Errorf("Forced stop %d mismatch: expected %d, got %d", i, tt.expected[i], idx)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleTooManyForcedStops(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
forcedStopIndexes []int
|
||||
days int
|
||||
lastPointIndex int
|
||||
totalPoints int
|
||||
expected []int
|
||||
}{
|
||||
{
|
||||
name: "Fewer forced stops than days",
|
||||
forcedStopIndexes: []int{25, 50},
|
||||
days: 4,
|
||||
lastPointIndex: 99,
|
||||
totalPoints: 100,
|
||||
expected: []int{25, 50, 99},
|
||||
},
|
||||
{
|
||||
name: "More forced stops than days",
|
||||
forcedStopIndexes: []int{20, 40, 60, 80},
|
||||
days: 3,
|
||||
lastPointIndex: 99,
|
||||
totalPoints: 100,
|
||||
expected: []int{20, 40, 99}, // Only first (days-1) forced stops are kept
|
||||
},
|
||||
{
|
||||
name: "Equal forced stops and days",
|
||||
forcedStopIndexes: []int{33, 66},
|
||||
days: 3,
|
||||
lastPointIndex: 99,
|
||||
totalPoints: 100,
|
||||
expected: []int{33, 66, 99},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := handleTooManyForcedStops(tt.forcedStopIndexes, tt.days, tt.lastPointIndex, tt.totalPoints)
|
||||
if len(result) != len(tt.expected) {
|
||||
t.Errorf("Expected %d cuts, got %d", len(tt.expected), len(result))
|
||||
return
|
||||
}
|
||||
for i, cut := range result {
|
||||
if cut != tt.expected[i] {
|
||||
t.Errorf("Cut %d mismatch: expected %d, got %d", i, tt.expected[i], cut)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateSegmentsBetweenStops(t *testing.T) {
|
||||
// Create test points
|
||||
points := []TrackPoint{
|
||||
{Index: 0, Distance: 0, Elevation: 0},
|
||||
{Index: 1, Distance: 10, Elevation: 100},
|
||||
{Index: 2, Distance: 20, Elevation: 200},
|
||||
{Index: 3, Distance: 30, Elevation: 300},
|
||||
{Index: 4, Distance: 40, Elevation: 400},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
points []TrackPoint
|
||||
allStops []int
|
||||
elevFactor float64
|
||||
expected []struct {
|
||||
start, end int
|
||||
effort float64
|
||||
}
|
||||
}{
|
||||
{
|
||||
name: "No stops",
|
||||
points: points,
|
||||
allStops: []int{},
|
||||
elevFactor: 4.0,
|
||||
expected: []struct {
|
||||
start, end int
|
||||
effort float64
|
||||
}{},
|
||||
},
|
||||
{
|
||||
name: "One segment",
|
||||
points: points,
|
||||
allStops: []int{0, 4},
|
||||
elevFactor: 4.0,
|
||||
expected: []struct {
|
||||
start, end int
|
||||
effort float64
|
||||
}{
|
||||
{0, 4, 56.0}, // Effort: 40 + (400/100*4) = 56
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Multiple segments",
|
||||
points: points,
|
||||
allStops: []int{0, 2, 4},
|
||||
elevFactor: 4.0,
|
||||
expected: []struct {
|
||||
start, end int
|
||||
effort float64
|
||||
}{
|
||||
{0, 2, 28.0}, // Effort: 20 + (200/100*4) = 28
|
||||
{2, 4, 42.0}, // Effort: (30-20) + ((300-200)/100*4) + (40-30) + ((400-300)/100*4) = 42
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Duplicate stops (rest days)",
|
||||
points: points,
|
||||
allStops: []int{0, 2, 2, 4},
|
||||
elevFactor: 4.0,
|
||||
expected: []struct {
|
||||
start, end int
|
||||
effort float64
|
||||
}{
|
||||
{0, 2, 28.0}, // Effort: 20 + (200/100*4) = 28
|
||||
{2, 4, 42.0}, // Effort: (30-20) + ((300-200)/100*4) + (40-30) + ((400-300)/100*4) = 42
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := createSegmentsBetweenStops(tt.points, tt.allStops, tt.elevFactor)
|
||||
if len(result) != len(tt.expected) {
|
||||
t.Errorf("Expected %d segments, got %d", len(tt.expected), len(result))
|
||||
return
|
||||
}
|
||||
for i, seg := range result {
|
||||
if seg.start != tt.expected[i].start || seg.end != tt.expected[i].end {
|
||||
t.Errorf("Segment %d bounds mismatch: expected (%d,%d), got (%d,%d)",
|
||||
i, tt.expected[i].start, tt.expected[i].end, seg.start, seg.end)
|
||||
}
|
||||
if math.Abs(seg.effort-tt.expected[i].effort) > 0.1 {
|
||||
t.Errorf("Segment %d effort mismatch: expected %.1f, got %.1f",
|
||||
i, tt.expected[i].effort, seg.effort)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleTooManyCuts(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cutIndexes []int
|
||||
forcedStopIndexes []int
|
||||
days int
|
||||
lastPointIndex int
|
||||
expectedLen int
|
||||
}{
|
||||
{
|
||||
name: "Fewer cuts than days",
|
||||
cutIndexes: []int{25, 50},
|
||||
forcedStopIndexes: []int{},
|
||||
days: 4,
|
||||
lastPointIndex: 99,
|
||||
expectedLen: 4, // Will add cuts to reach days
|
||||
},
|
||||
{
|
||||
name: "More cuts than days",
|
||||
cutIndexes: []int{20, 40, 60, 80},
|
||||
forcedStopIndexes: []int{},
|
||||
days: 3,
|
||||
lastPointIndex: 99,
|
||||
expectedLen: 3, // Will reduce to days
|
||||
},
|
||||
{
|
||||
name: "With forced stops",
|
||||
cutIndexes: []int{20, 40, 60, 80},
|
||||
forcedStopIndexes: []int{20, 60},
|
||||
days: 3,
|
||||
lastPointIndex: 99,
|
||||
expectedLen: 3, // Will keep forced stops and last point
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := handleTooManyCuts(tt.cutIndexes, tt.forcedStopIndexes, tt.days, tt.lastPointIndex)
|
||||
if len(result) != tt.expectedLen {
|
||||
t.Errorf("Expected %d cuts, got %d", tt.expectedLen, len(result))
|
||||
}
|
||||
|
||||
// Check that forced stops are included
|
||||
for _, forcedIdx := range tt.forcedStopIndexes {
|
||||
found := false
|
||||
for _, resultIdx := range result {
|
||||
if resultIdx == forcedIdx {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Forced stop %d not found in result", forcedIdx)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that last point is included
|
||||
lastPointFound := false
|
||||
for _, resultIdx := range result {
|
||||
if resultIdx == tt.lastPointIndex {
|
||||
lastPointFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !lastPointFound {
|
||||
t.Errorf("Last point %d not found in result", tt.lastPointIndex)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user