184 lines
4.4 KiB
Go
184 lines
4.4 KiB
Go
package geo
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"math"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
// parseCase holds one input to ParseCoordinates and its expectation.
|
||
|
|
// expected is nil for inputs that must fail; errPart is then the
|
||
|
|
// text the error message must contain.
|
||
|
|
type parseCase struct {
|
||
|
|
in string
|
||
|
|
expected *Coordinates
|
||
|
|
errPart string
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestParseCoordinates(t *testing.T) {
|
||
|
|
var cases = []parseCase{
|
||
|
|
{in: `52.37,4.90`, expected: &Coordinates{52.37, 4.90}},
|
||
|
|
{in: `-33.87,151.21`, expected: &Coordinates{-33.87, 151.21}},
|
||
|
|
{in: `1,2`, expected: &Coordinates{1, 2}},
|
||
|
|
|
||
|
|
{in: `+1.5,-2.5`, expected: &Coordinates{1.5, -2.5}},
|
||
|
|
{in: `1e1,2.5e0`, expected: &Coordinates{10, 2.5}},
|
||
|
|
{in: `0x1p2,3`, expected: &Coordinates{4, 3}},
|
||
|
|
|
||
|
|
// ParseCoordinates does not check value ranges.
|
||
|
|
{in: `inf,1`, expected: &Coordinates{math.Inf(1), 1}},
|
||
|
|
|
||
|
|
{in: ``, errPart: `invalid string`},
|
||
|
|
{in: `52.37`, errPart: `invalid string`},
|
||
|
|
{in: `52.37,4.90,1`, errPart: `invalid string`},
|
||
|
|
{in: `a,b`, errPart: `latitude`},
|
||
|
|
{in: `1,b`, errPart: `longitude`},
|
||
|
|
{in: `1,`, errPart: `longitude`},
|
||
|
|
{in: `,2`, errPart: `latitude`},
|
||
|
|
|
||
|
|
// ParseFloat does not skip whitespace.
|
||
|
|
{in: ` 52.37 , 4.90 `, errPart: `latitude`},
|
||
|
|
|
||
|
|
// ParseFloat rejects values outside the float64 range.
|
||
|
|
{in: `1e999,2`, errPart: `latitude`},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, c := range cases {
|
||
|
|
t.Run(c.in, c.testParseCoordinates)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// testParseCoordinates checks the expected result or error for one parse case.
|
||
|
|
func (c parseCase) testParseCoordinates(t *testing.T) {
|
||
|
|
got, err := ParseCoordinates(c.in)
|
||
|
|
|
||
|
|
if c.expected == nil {
|
||
|
|
if err == nil {
|
||
|
|
t.Fatalf("got %v, want error", got)
|
||
|
|
}
|
||
|
|
if got != nil {
|
||
|
|
t.Fatalf("got %v and error %v, want nil", got, err)
|
||
|
|
}
|
||
|
|
if !strings.Contains(err.Error(), c.errPart) {
|
||
|
|
t.Fatalf("error %q, want %q", err.Error(), c.errPart)
|
||
|
|
}
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("error = %v", err)
|
||
|
|
}
|
||
|
|
if got == nil {
|
||
|
|
t.Fatalf("got nil, want %v", c.expected)
|
||
|
|
}
|
||
|
|
if got.Latitude != c.expected.Latitude {
|
||
|
|
t.Errorf(
|
||
|
|
"Latitude = %v, want %v",
|
||
|
|
got.Latitude, c.expected.Latitude,
|
||
|
|
)
|
||
|
|
}
|
||
|
|
if got.Longitude != c.expected.Longitude {
|
||
|
|
t.Errorf(
|
||
|
|
"Longitude = %v, want %v",
|
||
|
|
got.Longitude, c.expected.Longitude,
|
||
|
|
)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestParseCoordinatesNaN checks that ParseCoordinates accepts nan.
|
||
|
|
// It does not check value ranges.
|
||
|
|
func TestParseCoordinatesNaN(t *testing.T) {
|
||
|
|
got, err := ParseCoordinates("nan,2")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("error = %v", err)
|
||
|
|
}
|
||
|
|
if got == nil {
|
||
|
|
t.Fatal("got nil, want coordinates")
|
||
|
|
}
|
||
|
|
if !math.IsNaN(got.Latitude) {
|
||
|
|
t.Errorf("Latitude = %v, want NaN", got.Latitude)
|
||
|
|
}
|
||
|
|
if got.Longitude != 2 {
|
||
|
|
t.Errorf("Longitude = %v, want 2", got.Longitude)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// FuzzParseCoordinates checks that ParseCoordinates handles any input
|
||
|
|
// without panicking, that it does not return a result and an error
|
||
|
|
// together, and that a successful result matches strconv.ParseFloat
|
||
|
|
// on each comma-separated part.
|
||
|
|
func FuzzParseCoordinates(f *testing.F) {
|
||
|
|
f.Add(`52.37,4.90`)
|
||
|
|
f.Add(`-33.8688,151.2093`)
|
||
|
|
f.Add(`nan,inf`)
|
||
|
|
f.Add(``)
|
||
|
|
f.Add(`52.37`)
|
||
|
|
f.Add(`52.37,4.90,1`)
|
||
|
|
f.Add(`a,b`)
|
||
|
|
f.Add(`1,`)
|
||
|
|
f.Add(`,2`)
|
||
|
|
f.Add(`1e999,2`)
|
||
|
|
|
||
|
|
f.Fuzz(fuzzParseCoordinates)
|
||
|
|
}
|
||
|
|
|
||
|
|
func fuzzParseCoordinates(t *testing.T, s string) {
|
||
|
|
got, err := ParseCoordinates(s)
|
||
|
|
if err != nil && got != nil {
|
||
|
|
t.Fatalf(
|
||
|
|
"ParseCoordinates(%q) = %v with error %v, want nil",
|
||
|
|
s, got, err,
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
// The input must hold exactly two comma-separated parts.
|
||
|
|
parts := strings.Split(s, `,`)
|
||
|
|
if len(parts) != 2 {
|
||
|
|
if err == nil {
|
||
|
|
t.Fatalf(
|
||
|
|
"ParseCoordinates(%q) = %v, want error",
|
||
|
|
s, got,
|
||
|
|
)
|
||
|
|
}
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// Each part must hold a valid float64 value.
|
||
|
|
expectedLat, latErr := strconv.ParseFloat(parts[0], 64)
|
||
|
|
expectedLon, lonErr := strconv.ParseFloat(parts[1], 64)
|
||
|
|
if latErr != nil || lonErr != nil {
|
||
|
|
if err == nil {
|
||
|
|
t.Fatalf(
|
||
|
|
"ParseCoordinates(%q) = %v, want error",
|
||
|
|
s, got,
|
||
|
|
)
|
||
|
|
}
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("ParseCoordinates(%q) error = %v", s, err)
|
||
|
|
}
|
||
|
|
if got == nil {
|
||
|
|
t.Fatalf("ParseCoordinates(%q) = nil, want coordinates", s)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Both values come from strconv.ParseFloat. The bit comparison
|
||
|
|
// reports NaN as equal; the == operator does not.
|
||
|
|
if math.Float64bits(got.Latitude) !=
|
||
|
|
math.Float64bits(expectedLat) {
|
||
|
|
t.Fatalf(
|
||
|
|
"ParseCoordinates(%q).Latitude = %v, want %v",
|
||
|
|
s, got.Latitude, expectedLat,
|
||
|
|
)
|
||
|
|
}
|
||
|
|
if math.Float64bits(got.Longitude) !=
|
||
|
|
math.Float64bits(expectedLon) {
|
||
|
|
t.Fatalf(
|
||
|
|
"ParseCoordinates(%q).Longitude = %v, want %v",
|
||
|
|
s, got.Longitude, expectedLon,
|
||
|
|
)
|
||
|
|
}
|
||
|
|
}
|