Add geo pkg
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user