From a9b76408d0001e2b38e82e2a0a6e65daf76e8a24 Mon Sep 17 00:00:00 2001 From: Peter Stace Date: Mon, 16 Feb 2026 18:01:56 +1100 Subject: [PATCH 1/3] Consolidate test helpers --- CHANGELOG.md | 3 + CLAUDE.md | 2 + geom/accessor_test.go | 185 +++++----- geom/alg_convex_hull_test.go | 5 +- geom/alg_densify_test.go | 11 +- geom/alg_distance_test.go | 5 +- geom/alg_dump_test.go | 55 ++- geom/alg_exact_equals_test.go | 18 +- geom/alg_intersects_test.go | 5 +- geom/alg_linear_interpolation_test.go | 18 +- geom/alg_overlay_test.go | 46 +-- geom/alg_point_on_surface_test.go | 6 +- geom/alg_relate_test.go | 31 +- geom/alg_rotating_calipers_test.go | 7 +- geom/alg_simplify_test.go | 13 +- geom/attr_test.go | 253 ++++++------- geom/ctor_from_coords.go | 14 + geom/ctor_from_coords_test.go | 32 +- geom/dump_coordinates_test.go | 44 ++- geom/geojson_feature_collection_test.go | 54 +-- geom/geojson_marshal_test.go | 6 +- geom/geojson_unmarshal_test.go | 51 +-- geom/marshal_unmarshal_test.go | 19 +- geom/perf_test.go | 3 +- geom/snap_to_grid_test.go | 9 +- geom/sql_test.go | 23 +- geom/twkb_test.go | 57 +-- geom/type_coordinates_test.go | 3 +- geom/type_envelope_test.go | 345 +++++++++--------- geom/type_geometry_deep_equal_test.go | 6 +- geom/type_geometry_test.go | 77 ++-- geom/type_null_geometry_test.go | 17 +- geom/type_sequence_test.go | 37 +- geom/util_test.go | 324 ---------------- geom/validation_helpers_test.go | 21 ++ geom/validation_test.go | 106 +++--- geom/wkb_test.go | 61 ++-- geom/wkt_test.go | 29 +- geom/xy_test.go | 21 +- geom/zero_value_test.go | 59 +-- geos/entrypoints_test.go | 176 ++++----- internal/cartodemo/cartodemo_test.go | 46 +-- internal/cartodemo/rasterize/draw_test.go | 7 +- .../cartodemo/rasterize/rasterizer_test.go | 7 +- internal/cartodemo/rasterize/util_test.go | 23 -- internal/cmprefimpl/cmpgeos/extract_source.go | 19 +- internal/cmprefimpl/cmppg/checks.go | 18 +- internal/cmprefimpl/cmppg/fuzz_test.go | 3 +- internal/test/test.go | 70 +++- test-helper-consolidation.md | 90 +++++ 50 files changed, 1208 insertions(+), 1332 deletions(-) delete mode 100644 geom/util_test.go create mode 100644 geom/validation_helpers_test.go delete mode 100644 internal/cartodemo/rasterize/util_test.go create mode 100644 test-helper-consolidation.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 09bd7ef7..fe08a025 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- Add `NewEnvelopeXY` constructor for building an `Envelope` from variadic x/y + coordinate pairs, following the existing `New*XY` constructor pattern. + ## v0.58.0 2026-02-15 diff --git a/CLAUDE.md b/CLAUDE.md index af5b2e8f..f4d4d17d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1 +1,3 @@ # CLAUDE.md + +Update CHANGELOG.md whenever making a change visible to users of this module. diff --git a/geom/accessor_test.go b/geom/accessor_test.go index 21c25591..bad30b40 100644 --- a/geom/accessor_test.go +++ b/geom/accessor_test.go @@ -4,21 +4,26 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func TestPointAccessorNonEmpty(t *testing.T) { - xy, ok := geomFromWKT(t, "POINT(1 2)").MustAsPoint().XY() - expectBoolEq(t, ok, true) - expectXYEq(t, xy, geom.XY{1, 2}) + xy, ok := test.FromWKT(t, "POINT(1 2)").MustAsPoint().XY() + test.Eq(t, ok, true) + test.Eq(t, xy, geom.XY{1, 2}) } func TestPointAccessorEmpty(t *testing.T) { - _, ok := geomFromWKT(t, "POINT EMPTY").MustAsPoint().XY() - expectBoolEq(t, ok, false) + _, ok := test.FromWKT(t, "POINT EMPTY").MustAsPoint().XY() + test.Eq(t, ok, false) +} + +func xyCoords(x, y float64) geom.Coordinates { + return geom.Coordinates{XY: geom.XY{x, y}, Type: geom.DimXY} } func TestLineStringAccessor(t *testing.T) { - ls := geomFromWKT(t, "LINESTRING(1 2,3 4,5 6)").MustAsLineString() + ls := test.FromWKT(t, "LINESTRING(1 2,3 4,5 6)").MustAsLineString() seq := ls.Coordinates() pt12 := xyCoords(1, 2) pt34 := xyCoords(3, 4) @@ -26,148 +31,148 @@ func TestLineStringAccessor(t *testing.T) { t.Run("start", func(t *testing.T) { want := geom.NewPoint(pt12) - expectGeomEq(t, ls.StartPoint().AsGeometry(), want.AsGeometry()) + test.ExactEquals(t, ls.StartPoint().AsGeometry(), want.AsGeometry()) }) t.Run("end", func(t *testing.T) { want := geom.NewPoint(pt56) - expectGeomEq(t, ls.EndPoint().AsGeometry(), want.AsGeometry()) + test.ExactEquals(t, ls.EndPoint().AsGeometry(), want.AsGeometry()) }) t.Run("num points", func(t *testing.T) { - expectIntEq(t, seq.Length(), 3) + test.Eq(t, seq.Length(), 3) }) t.Run("point n", func(t *testing.T) { - expectPanics(t, func() { seq.Get(-1) }) - expectCoordsEq(t, seq.Get(0), pt12) - expectCoordsEq(t, seq.Get(1), pt34) - expectCoordsEq(t, seq.Get(2), pt56) - expectPanics(t, func() { seq.Get(3) }) + test.Panics(t, func() { seq.Get(-1) }) + test.Eq(t, seq.Get(0), pt12) + test.Eq(t, seq.Get(1), pt34) + test.Eq(t, seq.Get(2), pt56) + test.Panics(t, func() { seq.Get(3) }) }) } func TestLineStringEmptyAccessor(t *testing.T) { - ls := geomFromWKT(t, "LINESTRING EMPTY").MustAsLineString() + ls := test.FromWKT(t, "LINESTRING EMPTY").MustAsLineString() seq := ls.Coordinates() - emptyPoint := geomFromWKT(t, "POINT EMPTY") + emptyPoint := test.FromWKT(t, "POINT EMPTY") t.Run("start", func(t *testing.T) { - expectGeomEq(t, ls.StartPoint().AsGeometry(), emptyPoint) + test.ExactEquals(t, ls.StartPoint().AsGeometry(), emptyPoint) }) t.Run("end", func(t *testing.T) { - expectGeomEq(t, ls.EndPoint().AsGeometry(), emptyPoint) + test.ExactEquals(t, ls.EndPoint().AsGeometry(), emptyPoint) }) t.Run("num points", func(t *testing.T) { - expectIntEq(t, seq.Length(), 0) + test.Eq(t, seq.Length(), 0) }) t.Run("point n", func(t *testing.T) { - expectPanics(t, func() { seq.Get(-1) }) - expectPanics(t, func() { seq.Get(0) }) - expectPanics(t, func() { seq.Get(1) }) + test.Panics(t, func() { seq.Get(-1) }) + test.Panics(t, func() { seq.Get(0) }) + test.Panics(t, func() { seq.Get(1) }) }) } func TestLineStringAccessorWithDuplicates(t *testing.T) { - ls := geomFromWKT(t, "LINESTRING(1 2,3 4,3 4,5 6)").MustAsLineString() + ls := test.FromWKT(t, "LINESTRING(1 2,3 4,3 4,5 6)").MustAsLineString() seq := ls.Coordinates() pt12 := xyCoords(1, 2) pt34 := xyCoords(3, 4) pt56 := xyCoords(5, 6) t.Run("num points", func(t *testing.T) { - expectIntEq(t, seq.Length(), 4) + test.Eq(t, seq.Length(), 4) }) t.Run("point n", func(t *testing.T) { - expectPanics(t, func() { seq.Get(-1) }) - expectCoordsEq(t, seq.Get(0), pt12) - expectCoordsEq(t, seq.Get(1), pt34) - expectCoordsEq(t, seq.Get(2), pt34) - expectCoordsEq(t, seq.Get(3), pt56) - expectPanics(t, func() { seq.Get(4) }) + test.Panics(t, func() { seq.Get(-1) }) + test.Eq(t, seq.Get(0), pt12) + test.Eq(t, seq.Get(1), pt34) + test.Eq(t, seq.Get(2), pt34) + test.Eq(t, seq.Get(3), pt56) + test.Panics(t, func() { seq.Get(4) }) }) } func TestLineStringAccessorWithMoreDuplicates(t *testing.T) { - ls := geomFromWKT(t, "LINESTRING(1 2,1 2,3 4,3 4,3 4,5 6,5 6)").MustAsLineString() + ls := test.FromWKT(t, "LINESTRING(1 2,1 2,3 4,3 4,3 4,5 6,5 6)").MustAsLineString() seq := ls.Coordinates() pt12 := xyCoords(1, 2) pt34 := xyCoords(3, 4) pt56 := xyCoords(5, 6) t.Run("num points", func(t *testing.T) { - expectIntEq(t, seq.Length(), 7) + test.Eq(t, seq.Length(), 7) }) t.Run("point n", func(t *testing.T) { - expectPanics(t, func() { seq.Get(-1) }) - expectCoordsEq(t, seq.Get(0), pt12) - expectCoordsEq(t, seq.Get(1), pt12) - expectCoordsEq(t, seq.Get(2), pt34) - expectCoordsEq(t, seq.Get(3), pt34) - expectCoordsEq(t, seq.Get(4), pt34) - expectCoordsEq(t, seq.Get(5), pt56) - expectCoordsEq(t, seq.Get(6), pt56) - expectPanics(t, func() { seq.Get(7) }) + test.Panics(t, func() { seq.Get(-1) }) + test.Eq(t, seq.Get(0), pt12) + test.Eq(t, seq.Get(1), pt12) + test.Eq(t, seq.Get(2), pt34) + test.Eq(t, seq.Get(3), pt34) + test.Eq(t, seq.Get(4), pt34) + test.Eq(t, seq.Get(5), pt56) + test.Eq(t, seq.Get(6), pt56) + test.Panics(t, func() { seq.Get(7) }) }) } func TestPolygonAccessor(t *testing.T) { - poly := geomFromWKT(t, "POLYGON((0 0,5 0,5 3,0 3,0 0),(1 1,2 1,2 2,1 2,1 1),(3 1,4 1,4 2,3 2,3 1))").MustAsPolygon() - outer := geomFromWKT(t, "LINESTRING(0 0,5 0,5 3,0 3,0 0)") - inner0 := geomFromWKT(t, "LINESTRING(1 1,2 1,2 2,1 2,1 1)") - inner1 := geomFromWKT(t, "LINESTRING(3 1,4 1,4 2,3 2,3 1)") - - expectGeomEq(t, poly.ExteriorRing().AsGeometry(), outer) - expectIntEq(t, poly.NumInteriorRings(), 2) - expectPanics(t, func() { poly.InteriorRingN(-1) }) - expectGeomEq(t, poly.InteriorRingN(0).AsGeometry(), inner0) - expectGeomEq(t, poly.InteriorRingN(1).AsGeometry(), inner1) - expectPanics(t, func() { poly.InteriorRingN(2) }) + poly := test.FromWKT(t, "POLYGON((0 0,5 0,5 3,0 3,0 0),(1 1,2 1,2 2,1 2,1 1),(3 1,4 1,4 2,3 2,3 1))").MustAsPolygon() + outer := test.FromWKT(t, "LINESTRING(0 0,5 0,5 3,0 3,0 0)") + inner0 := test.FromWKT(t, "LINESTRING(1 1,2 1,2 2,1 2,1 1)") + inner1 := test.FromWKT(t, "LINESTRING(3 1,4 1,4 2,3 2,3 1)") + + test.ExactEquals(t, poly.ExteriorRing().AsGeometry(), outer) + test.Eq(t, poly.NumInteriorRings(), 2) + test.Panics(t, func() { poly.InteriorRingN(-1) }) + test.ExactEquals(t, poly.InteriorRingN(0).AsGeometry(), inner0) + test.ExactEquals(t, poly.InteriorRingN(1).AsGeometry(), inner1) + test.Panics(t, func() { poly.InteriorRingN(2) }) } func TestMultiPointAccessor(t *testing.T) { - mp := geomFromWKT(t, "MULTIPOINT((4 5),(2 3),(8 7))").MustAsMultiPoint() - pt0 := geomFromWKT(t, "POINT(4 5)") - pt1 := geomFromWKT(t, "POINT(2 3)") - pt2 := geomFromWKT(t, "POINT(8 7)") - - expectIntEq(t, mp.NumPoints(), 3) - expectPanics(t, func() { mp.PointN(-1) }) - expectGeomEq(t, mp.PointN(0).AsGeometry(), pt0) - expectGeomEq(t, mp.PointN(1).AsGeometry(), pt1) - expectGeomEq(t, mp.PointN(2).AsGeometry(), pt2) - expectPanics(t, func() { mp.PointN(3) }) + mp := test.FromWKT(t, "MULTIPOINT((4 5),(2 3),(8 7))").MustAsMultiPoint() + pt0 := test.FromWKT(t, "POINT(4 5)") + pt1 := test.FromWKT(t, "POINT(2 3)") + pt2 := test.FromWKT(t, "POINT(8 7)") + + test.Eq(t, mp.NumPoints(), 3) + test.Panics(t, func() { mp.PointN(-1) }) + test.ExactEquals(t, mp.PointN(0).AsGeometry(), pt0) + test.ExactEquals(t, mp.PointN(1).AsGeometry(), pt1) + test.ExactEquals(t, mp.PointN(2).AsGeometry(), pt2) + test.Panics(t, func() { mp.PointN(3) }) } func TestMultiLineStringAccessors(t *testing.T) { - mls := geomFromWKT(t, "MULTILINESTRING((1 2,3 4,5 6),(7 8,9 10,11 12))").MustAsMultiLineString() - ls0 := geomFromWKT(t, "LINESTRING(1 2,3 4,5 6)") - ls1 := geomFromWKT(t, "LINESTRING(7 8,9 10,11 12)") - - expectIntEq(t, mls.NumLineStrings(), 2) - expectPanics(t, func() { mls.LineStringN(-1) }) - expectGeomEq(t, mls.LineStringN(0).AsGeometry(), ls0) - expectGeomEq(t, mls.LineStringN(1).AsGeometry(), ls1) - expectPanics(t, func() { mls.LineStringN(2) }) + mls := test.FromWKT(t, "MULTILINESTRING((1 2,3 4,5 6),(7 8,9 10,11 12))").MustAsMultiLineString() + ls0 := test.FromWKT(t, "LINESTRING(1 2,3 4,5 6)") + ls1 := test.FromWKT(t, "LINESTRING(7 8,9 10,11 12)") + + test.Eq(t, mls.NumLineStrings(), 2) + test.Panics(t, func() { mls.LineStringN(-1) }) + test.ExactEquals(t, mls.LineStringN(0).AsGeometry(), ls0) + test.ExactEquals(t, mls.LineStringN(1).AsGeometry(), ls1) + test.Panics(t, func() { mls.LineStringN(2) }) } func TestMultiPolygonAccessors(t *testing.T) { - polys := geomFromWKT(t, "MULTIPOLYGON(((0 0,0 1,1 0,0 0)),((2 0,2 1,3 0,2 0)))").MustAsMultiPolygon() - poly0 := geomFromWKT(t, "POLYGON((0 0,0 1,1 0,0 0))") - poly1 := geomFromWKT(t, "POLYGON((2 0,2 1,3 0,2 0))") - - expectIntEq(t, polys.NumPolygons(), 2) - expectPanics(t, func() { polys.PolygonN(-1) }) - expectGeomEq(t, polys.PolygonN(0).AsGeometry(), poly0) - expectGeomEq(t, polys.PolygonN(1).AsGeometry(), poly1) - expectPanics(t, func() { polys.PolygonN(2) }) + polys := test.FromWKT(t, "MULTIPOLYGON(((0 0,0 1,1 0,0 0)),((2 0,2 1,3 0,2 0)))").MustAsMultiPolygon() + poly0 := test.FromWKT(t, "POLYGON((0 0,0 1,1 0,0 0))") + poly1 := test.FromWKT(t, "POLYGON((2 0,2 1,3 0,2 0))") + + test.Eq(t, polys.NumPolygons(), 2) + test.Panics(t, func() { polys.PolygonN(-1) }) + test.ExactEquals(t, polys.PolygonN(0).AsGeometry(), poly0) + test.ExactEquals(t, polys.PolygonN(1).AsGeometry(), poly1) + test.Panics(t, func() { polys.PolygonN(2) }) } func TestGeometryCollectionAccessors(t *testing.T) { - geoms := geomFromWKT(t, "GEOMETRYCOLLECTION(POLYGON((0 0,0 1,1 0,0 0)),POLYGON((2 0,2 1,3 0,2 0)))").MustAsGeometryCollection() - geom0 := geomFromWKT(t, "POLYGON((0 0,0 1,1 0,0 0))") - geom1 := geomFromWKT(t, "POLYGON((2 0,2 1,3 0,2 0))") - - expectIntEq(t, geoms.NumGeometries(), 2) - expectPanics(t, func() { geoms.GeometryN(-1) }) - expectGeomEq(t, geoms.GeometryN(0), geom0) - expectGeomEq(t, geoms.GeometryN(1), geom1) - expectPanics(t, func() { geoms.GeometryN(2) }) + geoms := test.FromWKT(t, "GEOMETRYCOLLECTION(POLYGON((0 0,0 1,1 0,0 0)),POLYGON((2 0,2 1,3 0,2 0)))").MustAsGeometryCollection() + geom0 := test.FromWKT(t, "POLYGON((0 0,0 1,1 0,0 0))") + geom1 := test.FromWKT(t, "POLYGON((2 0,2 1,3 0,2 0))") + + test.Eq(t, geoms.NumGeometries(), 2) + test.Panics(t, func() { geoms.GeometryN(-1) }) + test.ExactEquals(t, geoms.GeometryN(0), geom0) + test.ExactEquals(t, geoms.GeometryN(1), geom1) + test.Panics(t, func() { geoms.GeometryN(2) }) } diff --git a/geom/alg_convex_hull_test.go b/geom/alg_convex_hull_test.go index 24c4b655..9e737f75 100644 --- a/geom/alg_convex_hull_test.go +++ b/geom/alg_convex_hull_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func TestConvexHull(t *testing.T) { @@ -199,8 +200,8 @@ func TestConvexHull(t *testing.T) { } { t.Run(strconv.Itoa(i), func(t *testing.T) { t.Logf("input: %s", tt.input) - got := geomFromWKT(t, tt.input).ConvexHull() - expectGeomEq(t, got, geomFromWKT(t, tt.output), geom.IgnoreOrder) + got := test.FromWKT(t, tt.input).ConvexHull() + test.ExactEquals(t, got, test.FromWKT(t, tt.output), geom.IgnoreOrder) }) } } diff --git a/geom/alg_densify_test.go b/geom/alg_densify_test.go index 5b0b8896..11899a88 100644 --- a/geom/alg_densify_test.go +++ b/geom/alg_densify_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func TestDensifyEmpty(t *testing.T) { @@ -27,7 +28,7 @@ func TestDensifyEmpty(t *testing.T) { t.Run(ct.String(), func(t *testing.T) { input := empty.ForceCoordinatesType(ct) got := input.Densify(1.0) - expectGeomEq(t, got, input) + test.ExactEquals(t, got, input) }) } }) @@ -67,9 +68,9 @@ func TestDensify(t *testing.T) { {"GEOMETRYCOLLECTION(POINT(0 0),LINESTRING(0 0,1 1))", 1.0, "GEOMETRYCOLLECTION(POINT(0 0),LINESTRING(0 0,0.5 0.5,1 1))"}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - input := geomFromWKT(t, tc.input) + input := test.FromWKT(t, tc.input) got := input.Densify(tc.maxDist) - expectGeomEqWKT(t, got, tc.want) + test.ExactEqualsWKT(t, got, tc.want) }) } } @@ -87,8 +88,8 @@ func TestDensifyInvalidMaxDist(t *testing.T) { {"MULTIPOINT((0 0))", 0}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - input := geomFromWKT(t, tc.input) - expectPanics(t, func() { input.Densify(tc.maxDist) }) + input := test.FromWKT(t, tc.input) + test.Panics(t, func() { input.Densify(tc.maxDist) }) }) } } diff --git a/geom/alg_distance_test.go b/geom/alg_distance_test.go index 37c9ce36..0acd5121 100644 --- a/geom/alg_distance_test.go +++ b/geom/alg_distance_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func TestDistance(t *testing.T) { @@ -113,8 +114,8 @@ func TestDistance(t *testing.T) { desc = "rev" } t.Run(desc, func(t *testing.T) { - g1 := geomFromWKT(t, tt.wkt1) - g2 := geomFromWKT(t, tt.wkt2) + g1 := test.FromWKT(t, tt.wkt1) + g2 := test.FromWKT(t, tt.wkt2) if flip { g1, g2 = g2, g1 } diff --git a/geom/alg_dump_test.go b/geom/alg_dump_test.go index af422e0d..c846ca8a 100644 --- a/geom/alg_dump_test.go +++ b/geom/alg_dump_test.go @@ -3,8 +3,43 @@ package geom_test import ( "strconv" "testing" + + "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) +func expectDumpEqWKT(tb testing.TB, got []geom.Geometry, wantWKTs []string) { + tb.Helper() + test.Eq(tb, len(got), len(wantWKTs)) + for i, wkt := range wantWKTs { + test.ExactEquals(tb, got[i], test.FromWKT(tb, wkt)) + } +} + +func upcastPoints(ps []geom.Point) []geom.Geometry { + gs := make([]geom.Geometry, len(ps)) + for i, p := range ps { + gs[i] = p.AsGeometry() + } + return gs +} + +func upcastLineStrings(lss []geom.LineString) []geom.Geometry { + gs := make([]geom.Geometry, len(lss)) + for i, ls := range lss { + gs[i] = ls.AsGeometry() + } + return gs +} + +func upcastPolygons(ps []geom.Polygon) []geom.Geometry { + gs := make([]geom.Geometry, len(ps)) + for i, p := range ps { + gs[i] = p.AsGeometry() + } + return gs +} + func TestDumpGeometry(t *testing.T) { for i, tc := range []struct { inputWKT string @@ -84,9 +119,7 @@ func TestDumpGeometry(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - want := geomsFromWKTs(t, tc.wantOutputWKT) - got := geomFromWKT(t, tc.inputWKT).Dump() - expectGeomsEq(t, got, want) + expectDumpEqWKT(t, test.FromWKT(t, tc.inputWKT).Dump(), tc.wantOutputWKT) }) } } @@ -118,9 +151,7 @@ func TestDumpMultiPoint(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - want := geomsFromWKTs(t, tc.wantOutputWKT) - got := upcastPoints(geomFromWKT(t, tc.inputWKT).MustAsMultiPoint().Dump()) - expectGeomsEq(t, got, want) + expectDumpEqWKT(t, upcastPoints(test.FromWKT(t, tc.inputWKT).MustAsMultiPoint().Dump()), tc.wantOutputWKT) }) } } @@ -152,9 +183,7 @@ func TestDumpMultiLineString(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - want := geomsFromWKTs(t, tc.wantOutputWKT) - got := upcastLineStrings(geomFromWKT(t, tc.inputWKT).MustAsMultiLineString().Dump()) - expectGeomsEq(t, got, want) + expectDumpEqWKT(t, upcastLineStrings(test.FromWKT(t, tc.inputWKT).MustAsMultiLineString().Dump()), tc.wantOutputWKT) }) } } @@ -178,9 +207,7 @@ func TestDumpMultiPolygon(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - want := geomsFromWKTs(t, tc.wantOutputWKT) - got := upcastPolygons(geomFromWKT(t, tc.inputWKT).MustAsMultiPolygon().Dump()) - expectGeomsEq(t, got, want) + expectDumpEqWKT(t, upcastPolygons(test.FromWKT(t, tc.inputWKT).MustAsMultiPolygon().Dump()), tc.wantOutputWKT) }) } } @@ -212,9 +239,7 @@ func TestDumpGeometryCollection(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - want := geomsFromWKTs(t, tc.wantOutputWKT) - got := geomFromWKT(t, tc.inputWKT).MustAsGeometryCollection().Dump() - expectGeomsEq(t, got, want) + expectDumpEqWKT(t, test.FromWKT(t, tc.inputWKT).MustAsGeometryCollection().Dump(), tc.wantOutputWKT) }) } } diff --git a/geom/alg_exact_equals_test.go b/geom/alg_exact_equals_test.go index d04e4b35..b96c2ada 100644 --- a/geom/alg_exact_equals_test.go +++ b/geom/alg_exact_equals_test.go @@ -26,8 +26,8 @@ func TestExactEqualsZTolerance(t *testing.T) { {"(1 2 5)", "(1 3 5.05)", false}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - g1 := geomFromWKT(t, "POINT "+zmc.zOrM+tc.body1) - g2 := geomFromWKT(t, "POINT "+zmc.zOrM+tc.body2) + g1 := test.FromWKT(t, "POINT "+zmc.zOrM+tc.body1) + g2 := test.FromWKT(t, "POINT "+zmc.zOrM+tc.body2) gotEq := geom.ExactEquals(g1, g2, zmc.opt) if gotEq != tc.wantEq { t.Errorf("got=%v want=%v", gotEq, tc.wantEq) @@ -248,7 +248,7 @@ func TestExactEquals(t *testing.T) { t.Run("reflexive", func(t *testing.T) { for key, wkt := range wkts { t.Run(key, func(t *testing.T) { - g := geomFromWKT(t, wkt) + g := test.FromWKT(t, wkt) t.Run("no options", func(t *testing.T) { if !geom.ExactEquals(g, g) { t.Logf("WKT: %v", wkt) @@ -272,8 +272,8 @@ func TestExactEquals(t *testing.T) { break } } - gA := geomFromWKT(t, wkts[keyA]) - gB := geomFromWKT(t, wkts[keyB]) + gA := test.FromWKT(t, wkts[keyA]) + gB := test.FromWKT(t, wkts[keyB]) got := geom.ExactEquals(gA, gB, geom.ToleranceXY(0.125)) if got != want { t.Logf("WKT A: %v", wkts[keyA]) @@ -298,8 +298,8 @@ func TestExactEquals(t *testing.T) { break } } - gA := geomFromWKT(t, wkts[keyA]) - gB := geomFromWKT(t, wkts[keyB]) + gA := test.FromWKT(t, wkts[keyA]) + gB := test.FromWKT(t, wkts[keyB]) got := geom.ExactEquals(gA, gB, geom.IgnoreOrder) if got != want { t.Logf("WKT A: %v", wkts[keyA]) @@ -319,7 +319,7 @@ func TestExactEqualsNonSimpleRing(t *testing.T) { wkt1 = "POLYGON((0 0,3 3,3 0,0 3,0 0),(1 1,2 2,2 1,1 2,1 1))" wkt2 = "POLYGON((0 0,3 3,3 0,0 3,0 0),(2 2,2 1,1 2,1 1,2 2))" ) - g1 := geomFromWKT(t, wkt1, geom.NoValidate{}) - g2 := geomFromWKT(t, wkt2, geom.NoValidate{}) + g1 := test.FromWKT(t, wkt1, geom.NoValidate{}) + g2 := test.FromWKT(t, wkt2, geom.NoValidate{}) test.True(t, geom.ExactEquals(g1, g2, geom.IgnoreOrder)) } diff --git a/geom/alg_intersects_test.go b/geom/alg_intersects_test.go index 423a4eac..3ab471ea 100644 --- a/geom/alg_intersects_test.go +++ b/geom/alg_intersects_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func TestIntersects(t *testing.T) { @@ -384,8 +385,8 @@ func TestIntersects(t *testing.T) { } } } - g1 := geomFromWKT(t, tt.in1) - g2 := geomFromWKT(t, tt.in2) + g1 := test.FromWKT(t, tt.in1) + g2 := test.FromWKT(t, tt.in2) t.Run("fwd", runTest(g1, g2)) t.Run("rev", runTest(g2, g1)) }) diff --git a/geom/alg_linear_interpolation_test.go b/geom/alg_linear_interpolation_test.go index 24f55c15..d60cea73 100644 --- a/geom/alg_linear_interpolation_test.go +++ b/geom/alg_linear_interpolation_test.go @@ -4,6 +4,8 @@ import ( "fmt" "strconv" "testing" + + "github.com/peterstace/simplefeatures/internal/test" ) func TestInterpolatePointEmpty(t *testing.T) { @@ -11,10 +13,10 @@ func TestInterpolatePointEmpty(t *testing.T) { for _, ratio := range []float64{-0.5, 0, 0.5, 1, 1.5} { t.Run(fmt.Sprintf("%v_%v", variant, ratio), func(t *testing.T) { inputWKT := "LINESTRING " + variant + " EMPTY" - input := geomFromWKT(t, inputWKT).MustAsLineString() + input := test.FromWKT(t, inputWKT).MustAsLineString() wantWKT := "POINT " + variant + " EMPTY" got := input.InterpolatePoint(ratio).AsGeometry() - expectGeomEqWKT(t, got, wantWKT) + test.ExactEqualsWKT(t, got, wantWKT) }) } } @@ -77,11 +79,11 @@ func TestInterpolatePoint(t *testing.T) { {"LINESTRING(0 0,3 0,3 1)", 0.875, "POINT(3.0 0.5)"}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - ls := geomFromWKT(t, tc.lsWKT).MustAsLineString() + ls := test.FromWKT(t, tc.lsWKT).MustAsLineString() t.Logf("ls: %v", ls.AsText()) t.Logf("frac: %v", tc.frac) got := ls.InterpolatePoint(tc.frac).AsGeometry() - expectGeomEqWKT(t, got, tc.wantWKT) + test.ExactEqualsWKT(t, got, tc.wantWKT) }) } } @@ -96,9 +98,9 @@ func TestInterpolateEvenlySpacedPointsEmpty(t *testing.T) { } { t.Run(fmt.Sprintf("%v_%v", variant, n), func(t *testing.T) { inputWKT := "LINESTRING " + variant + " EMPTY" - input := geomFromWKT(t, inputWKT).MustAsLineString() + input := test.FromWKT(t, inputWKT).MustAsLineString() got := input.InterpolateEvenlySpacedPoints(n).AsGeometry() - expectGeomEqWKT(t, got, want) + test.ExactEqualsWKT(t, got, want) }) } } @@ -121,9 +123,9 @@ func TestInterpolateEvenlySpacedPoints(t *testing.T) { {"LINESTRING(0 0, 3 0,3 1)", 1, "MULTIPOINT(2 0)"}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - ls := geomFromWKT(t, tc.lsWKT).MustAsLineString() + ls := test.FromWKT(t, tc.lsWKT).MustAsLineString() got := ls.InterpolateEvenlySpacedPoints(tc.n).AsGeometry() - expectGeomEqWKT(t, got, tc.wantWKT) + test.ExactEqualsWKT(t, got, tc.wantWKT) }) } } diff --git a/geom/alg_overlay_test.go b/geom/alg_overlay_test.go index 67dd9807..11296303 100644 --- a/geom/alg_overlay_test.go +++ b/geom/alg_overlay_test.go @@ -1343,8 +1343,8 @@ func TestBinaryOp(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - g1 := geomFromWKT(t, geomCase.input1) - g2 := geomFromWKT(t, geomCase.input2) + g1 := test.FromWKT(t, geomCase.input1) + g2 := test.FromWKT(t, geomCase.input2) t.Logf("input1: %s", geomCase.input1) t.Logf("input2: %s", geomCase.input2) for _, opCase := range []struct { @@ -1362,12 +1362,12 @@ func TestBinaryOp(t *testing.T) { if opCase.want == "" { t.Skip("Skipping test because it's not specified or is commented out") } - want := geomFromWKT(t, opCase.want) + want := test.FromWKT(t, opCase.want) got, err := opCase.op(g1, g2) if err != nil { t.Fatalf("could not perform op: %v", err) } - expectGeomEq(t, got, want, geom.IgnoreOrder, geom.ToleranceXY(1e-7)) + test.ExactEquals(t, got, want, geom.IgnoreOrder, geom.ToleranceXY(1e-7)) }) } t.Run("relate", func(t *testing.T) { @@ -1429,9 +1429,9 @@ func TestBinaryOpNoCrash(t *testing.T) { } { t.Run(strconv.Itoa(i), func(t *testing.T) { gA, err := geom.UnmarshalWKT(tc.inputA) - expectNoErr(t, err) + test.NoErr(t, err) gB, err := geom.UnmarshalWKT(tc.inputB) - expectNoErr(t, err) + test.NoErr(t, err) for _, op := range []struct { name string @@ -1476,8 +1476,8 @@ func TestOverlayAndRelateBothInputsEmpty(t *testing.T) { for i, inputA := range inputs { for j, inputB := range inputs { - gA := geomFromWKT(t, inputA.wkt) - gB := geomFromWKT(t, inputB.wkt) + gA := test.FromWKT(t, inputA.wkt) + gB := test.FromWKT(t, inputB.wkt) t.Run(fmt.Sprintf("%d_%d", i, j), func(t *testing.T) { t.Run("union", func(t *testing.T) { got, err := geom.Union(gA, gB) @@ -1548,9 +1548,9 @@ func TestOverlayAndRelateOnlyOneInputEmpty(t *testing.T) { } for i, emptyInput := range emptyInputs { - emptyGeom := geomFromWKT(t, emptyInput.wkt) + emptyGeom := test.FromWKT(t, emptyInput.wkt) for j, nonEmptyInput := range nonEmptyInputs { - nonEmptyGeom := geomFromWKT(t, nonEmptyInput.wkt) + nonEmptyGeom := test.FromWKT(t, nonEmptyInput.wkt) t.Run(fmt.Sprintf("%d_%d", i, j), func(t *testing.T) { t.Run("union", func(t *testing.T) { t.Run("fwd", func(t *testing.T) { @@ -1632,9 +1632,9 @@ func TestIntersectionEnvelopesDisjoint(t *testing.T) { } for i, inputA := range inputs { - gA := geomFromWKT(t, inputA.wkt) + gA := test.FromWKT(t, inputA.wkt) for j, inputB := range inputs { - gB := geomFromWKT(t, inputB.wkt).TransformXY(func(xy geom.XY) geom.XY { + gB := test.FromWKT(t, inputB.wkt).TransformXY(func(xy geom.XY) geom.XY { return xy.Add(geom.XY{X: 100, Y: 100}) }) t.Run(fmt.Sprintf("%d_%d", i, j), func(t *testing.T) { @@ -1804,17 +1804,17 @@ func TestUnaryUnionAndUnionMany(t *testing.T) { t.Run(strconv.Itoa(i), func(t *testing.T) { var inputs []geom.Geometry for _, wkt := range tc.inputWKTs { - inputs = append(inputs, geomFromWKT(t, wkt)) + inputs = append(inputs, test.FromWKT(t, wkt)) } t.Run("UnionMany", func(t *testing.T) { got, err := geom.UnionMany(inputs) - expectNoErr(t, err) - expectGeomEqWKT(t, got, tc.wantWKT, geom.IgnoreOrder) + test.NoErr(t, err) + test.ExactEqualsWKT(t, got, tc.wantWKT, geom.IgnoreOrder) }) t.Run("UnaryUnion", func(t *testing.T) { got, err := geom.UnaryUnion(geom.NewGeometryCollection(inputs).AsGeometry()) - expectNoErr(t, err) - expectGeomEqWKT(t, got, tc.wantWKT, geom.IgnoreOrder) + test.NoErr(t, err) + test.ExactEqualsWKT(t, got, tc.wantWKT, geom.IgnoreOrder) }) }) } @@ -1830,13 +1830,13 @@ func TestBinaryOpOutputOrdering(t *testing.T) { {"MULTIPOLYGON(((0 0,0 1,1 1,1 0,0 0)),((1 1,1 2,2 2,2 1,1 1)))"}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - in := geomFromWKT(t, tc.wkt) + in := test.FromWKT(t, tc.wkt) got1, err := geom.Union(in, in) - expectNoErr(t, err) + test.NoErr(t, err) got2, err := geom.Union(in, in) - expectNoErr(t, err) + test.NoErr(t, err) // Ensure ordering is stable over multiple executions: - expectGeomEq(t, got1, got2) + test.ExactEquals(t, got1, got2) }) } } @@ -1864,8 +1864,8 @@ func TestNoPanic(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - g1 := geomFromWKT(t, tc.input1) - g2 := geomFromWKT(t, tc.input2) + g1 := test.FromWKT(t, tc.input1) + g2 := test.FromWKT(t, tc.input2) // Used to panic before a bug fix was put in place. _, _ = tc.op(g1, g2) }) diff --git a/geom/alg_point_on_surface_test.go b/geom/alg_point_on_surface_test.go index 85ad3e30..f1007377 100644 --- a/geom/alg_point_on_surface_test.go +++ b/geom/alg_point_on_surface_test.go @@ -3,6 +3,8 @@ package geom_test import ( "strconv" "testing" + + "github.com/peterstace/simplefeatures/internal/test" ) func TestPointOnSurface(t *testing.T) { @@ -76,10 +78,10 @@ func TestPointOnSurface(t *testing.T) { {"GEOMETRYCOLLECTION(LINESTRING(0 0,1 1),POINT(0.5 0.5))", "POINT(0 0)"}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - input := geomFromWKT(t, tt.inputWKT) + input := test.FromWKT(t, tt.inputWKT) got := input.PointOnSurface() t.Logf("input: %v", tt.inputWKT) - expectGeomEq(t, got.AsGeometry(), geomFromWKT(t, tt.outputWKT)) + test.ExactEquals(t, got.AsGeometry(), test.FromWKT(t, tt.outputWKT)) }) } } diff --git a/geom/alg_relate_test.go b/geom/alg_relate_test.go index aa3da8df..3af50b86 100644 --- a/geom/alg_relate_test.go +++ b/geom/alg_relate_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) // These tests aren't exhaustive, because we are leveraging the Relate @@ -276,11 +277,11 @@ func TestRelate(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - g1 := geomFromWKT(t, tt.wkt1) - g2 := geomFromWKT(t, tt.wkt2) + g1 := test.FromWKT(t, tt.wkt1) + g2 := test.FromWKT(t, tt.wkt2) t.Run("Equals", func(t *testing.T) { got, err := geom.Equals(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got != tt.equals { t.Logf("WKT1: %v", tt.wkt1) t.Logf("WKT2: %v", tt.wkt2) @@ -289,7 +290,7 @@ func TestRelate(t *testing.T) { }) t.Run("Disjoint", func(t *testing.T) { got, err := geom.Disjoint(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got != tt.disjoint { t.Logf("WKT1: %v", tt.wkt1) t.Logf("WKT2: %v", tt.wkt2) @@ -298,7 +299,7 @@ func TestRelate(t *testing.T) { }) t.Run("Touches", func(t *testing.T) { got, err := geom.Touches(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got != tt.touches { t.Logf("WKT1: %v", tt.wkt1) t.Logf("WKT2: %v", tt.wkt2) @@ -307,7 +308,7 @@ func TestRelate(t *testing.T) { }) t.Run("Contains", func(t *testing.T) { got, err := geom.Contains(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got != tt.contains { t.Logf("WKT1: %v", tt.wkt1) t.Logf("WKT2: %v", tt.wkt2) @@ -316,7 +317,7 @@ func TestRelate(t *testing.T) { }) t.Run("Covers", func(t *testing.T) { got, err := geom.Covers(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got != tt.covers { t.Logf("WKT1: %v", tt.wkt1) t.Logf("WKT2: %v", tt.wkt2) @@ -325,7 +326,7 @@ func TestRelate(t *testing.T) { }) t.Run("Within", func(t *testing.T) { got, err := geom.Within(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got != tt.within { t.Logf("WKT1: %v", tt.wkt1) t.Logf("WKT2: %v", tt.wkt2) @@ -334,7 +335,7 @@ func TestRelate(t *testing.T) { }) t.Run("CoveredBy", func(t *testing.T) { got, err := geom.CoveredBy(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got != tt.coveredBy { t.Logf("WKT1: %v", tt.wkt1) t.Logf("WKT2: %v", tt.wkt2) @@ -423,13 +424,13 @@ func TestCrosses(t *testing.T) { run := func(rev bool) func(*testing.T) { return func(t *testing.T) { t.Helper() - g1 := geomFromWKT(t, tt.wkt1) - g2 := geomFromWKT(t, tt.wkt2) + g1 := test.FromWKT(t, tt.wkt1) + g2 := test.FromWKT(t, tt.wkt2) if rev { g1, g2 = g2, g1 } got, err := geom.Crosses(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got != tt.want { t.Logf("WKT1: %v", tt.wkt1) t.Logf("WKT2: %v", tt.wkt2) @@ -480,13 +481,13 @@ func TestOverlaps(t *testing.T) { run := func(rev bool) func(t *testing.T) { return func(t *testing.T) { t.Helper() - g1 := geomFromWKT(t, tt.wkt1) - g2 := geomFromWKT(t, tt.wkt2) + g1 := test.FromWKT(t, tt.wkt1) + g2 := test.FromWKT(t, tt.wkt2) if rev { g1, g2 = g2, g1 } got, err := geom.Overlaps(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got != tt.want { t.Logf("WKT1: %v", tt.wkt1) t.Logf("WKT2: %v", tt.wkt2) diff --git a/geom/alg_rotating_calipers_test.go b/geom/alg_rotating_calipers_test.go index 738f420b..a4c800f5 100644 --- a/geom/alg_rotating_calipers_test.go +++ b/geom/alg_rotating_calipers_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func TestRotatingCalipers(t *testing.T) { @@ -135,7 +136,7 @@ func TestRotatingCalipers(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - in := geomFromWKT(t, tc.input) + in := test.FromWKT(t, tc.input) t.Logf("input: %s", in.AsText()) opts := []geom.ExactEqualsOption{ @@ -145,12 +146,12 @@ func TestRotatingCalipers(t *testing.T) { t.Run("RotatedMinimumAreaBoundingRectangle", func(t *testing.T) { got := geom.RotatedMinimumAreaBoundingRectangle(in) - expectGeomEqWKT(t, got, tc.wantMinAreaRect, opts...) + test.ExactEqualsWKT(t, got, tc.wantMinAreaRect, opts...) }) t.Run("MinimumWidth", func(t *testing.T) { if tc.wantMinWidthRect != "" { got := geom.RotatedMinimumWidthBoundingRectangle(in) - expectGeomEqWKT(t, got, tc.wantMinWidthRect, opts...) + test.ExactEqualsWKT(t, got, tc.wantMinWidthRect, opts...) } }) }) diff --git a/geom/alg_simplify_test.go b/geom/alg_simplify_test.go index 8c246a98..7ef28844 100644 --- a/geom/alg_simplify_test.go +++ b/geom/alg_simplify_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func TestSimplify(t *testing.T) { @@ -114,15 +115,15 @@ func TestSimplify(t *testing.T) { {"GEOMETRYCOLLECTION EMPTY", 0.1, "GEOMETRYCOLLECTION EMPTY"}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - in := geomFromWKT(t, tc.input) - want := geomFromWKT(t, tc.output) + in := test.FromWKT(t, tc.input) + want := test.FromWKT(t, tc.output) t.Logf("input: %v", in.AsText()) t.Logf("threshold: %v", tc.threshold) t.Logf("want: %v", want.AsText()) got, err := in.Simplify(tc.threshold) - expectNoErr(t, err) + test.NoErr(t, err) t.Logf("got: %v", got.AsText()) - expectGeomEq(t, got, want, geom.IgnoreOrder) + test.ExactEquals(t, got, want, geom.IgnoreOrder) }) } } @@ -164,10 +165,10 @@ func TestSimplifyErrorCases(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - in := geomFromWKT(t, tc.wkt) + in := test.FromWKT(t, tc.wkt) _, err := in.Simplify(tc.threshold) var want geom.ValidationError - expectErrAs(t, err, &want) + test.ErrAs(t, err, &want) }) } } diff --git a/geom/attr_test.go b/geom/attr_test.go index 49a19325..14c8e38a 100644 --- a/geom/attr_test.go +++ b/geom/attr_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func TestIsEmptyDimension(t *testing.T) { @@ -93,16 +94,16 @@ func TestEnvelope(t *testing.T) { } { t.Run(strconv.Itoa(i), func(t *testing.T) { t.Log("wkt:", tt.wkt) - g := geomFromWKT(t, tt.wkt) + g := test.FromWKT(t, tt.wkt) env := g.Envelope() gotMin, ok := env.Min().XY() - expectTrue(t, ok) - expectXYEq(t, gotMin, tt.min) + test.True(t, ok) + test.Eq(t, gotMin, tt.min) gotMax, ok := env.Max().XY() - expectTrue(t, ok) - expectXYEq(t, gotMax, tt.max) + test.True(t, ok) + test.Eq(t, gotMax, tt.max) }) } } @@ -125,9 +126,9 @@ func TestNoEnvelope(t *testing.T) { "GEOMETRYCOLLECTION(POINT EMPTY)", } { t.Run(wkt, func(t *testing.T) { - g := geomFromWKT(t, wkt) + g := test.FromWKT(t, wkt) got := g.Envelope() - expectTrue(t, got.IsEmpty()) + test.True(t, got.IsEmpty()) }) } } @@ -196,7 +197,7 @@ func TestIsSimple(t *testing.T) { } { t.Run(strconv.Itoa(i), func(t *testing.T) { t.Logf("wkt: %s", tt.wkt) - got, defined := geomFromWKT(t, tt.wkt).IsSimple() + got, defined := test.FromWKT(t, tt.wkt).IsSimple() if !defined { t.Fatal("not defined") } @@ -208,8 +209,8 @@ func TestIsSimple(t *testing.T) { } func TestIsSimpleGeometryCollection(t *testing.T) { - _, defined := geomFromWKT(t, "GEOMETRYCOLLECTION(POINT(1 2))").IsSimple() - expectBoolEq(t, defined, false) + _, defined := test.FromWKT(t, "GEOMETRYCOLLECTION(POINT(1 2))").IsSimple() + test.Eq(t, defined, false) } func TestBoundary(t *testing.T) { @@ -307,9 +308,9 @@ func TestBoundary(t *testing.T) { } { t.Run(strconv.Itoa(i), func(t *testing.T) { t.Logf("WKT: %v", tt.wkt) - want := geomFromWKT(t, tt.boundary) - got := geomFromWKT(t, tt.wkt).Boundary() - expectGeomEq(t, got, want) + want := test.FromWKT(t, tt.boundary) + got := test.FromWKT(t, tt.wkt).Boundary() + test.ExactEquals(t, got, want) }) } } @@ -317,67 +318,67 @@ func TestBoundary(t *testing.T) { func TestCoordinatesSequence(t *testing.T) { t.Run("point", func(t *testing.T) { t.Run("populated", func(t *testing.T) { - c, ok := geomFromWKT(t, "POINT(1 2)").MustAsPoint().Coordinates() - expectBoolEq(t, ok, true) - expectXYEq(t, c.XY, geom.XY{1, 2}) + c, ok := test.FromWKT(t, "POINT(1 2)").MustAsPoint().Coordinates() + test.Eq(t, ok, true) + test.Eq(t, c.XY, geom.XY{1, 2}) }) t.Run("empty", func(t *testing.T) { - _, ok := geomFromWKT(t, "POINT EMPTY").MustAsPoint().Coordinates() - expectBoolEq(t, ok, false) + _, ok := test.FromWKT(t, "POINT EMPTY").MustAsPoint().Coordinates() + test.Eq(t, ok, false) }) }) t.Run("linestring", func(t *testing.T) { - seq := geomFromWKT(t, "LINESTRING(0 1,2 3,4 5)").MustAsLineString().Coordinates() - expectIntEq(t, seq.Length(), 3) - expectXYEq(t, seq.GetXY(0), geom.XY{0, 1}) - expectXYEq(t, seq.GetXY(1), geom.XY{2, 3}) - expectXYEq(t, seq.GetXY(2), geom.XY{4, 5}) + seq := test.FromWKT(t, "LINESTRING(0 1,2 3,4 5)").MustAsLineString().Coordinates() + test.Eq(t, seq.Length(), 3) + test.Eq(t, seq.GetXY(0), geom.XY{0, 1}) + test.Eq(t, seq.GetXY(1), geom.XY{2, 3}) + test.Eq(t, seq.GetXY(2), geom.XY{4, 5}) }) t.Run("linestring with dupe", func(t *testing.T) { - seq := geomFromWKT(t, "LINESTRING(1 5,5 2,5 2,4 9)").MustAsLineString().Coordinates() - expectIntEq(t, seq.Length(), 4) - expectXYEq(t, seq.GetXY(0), geom.XY{1, 5}) - expectXYEq(t, seq.GetXY(1), geom.XY{5, 2}) - expectXYEq(t, seq.GetXY(2), geom.XY{5, 2}) - expectXYEq(t, seq.GetXY(3), geom.XY{4, 9}) + seq := test.FromWKT(t, "LINESTRING(1 5,5 2,5 2,4 9)").MustAsLineString().Coordinates() + test.Eq(t, seq.Length(), 4) + test.Eq(t, seq.GetXY(0), geom.XY{1, 5}) + test.Eq(t, seq.GetXY(1), geom.XY{5, 2}) + test.Eq(t, seq.GetXY(2), geom.XY{5, 2}) + test.Eq(t, seq.GetXY(3), geom.XY{4, 9}) }) t.Run("polygon", func(t *testing.T) { - seq := geomFromWKT(t, "POLYGON((0 0,0 10,10 0,0 0),(2 2,2 7,7 2,2 2))").MustAsPolygon().Coordinates() - expectIntEq(t, len(seq), 2) - expectIntEq(t, seq[0].Length(), 4) - expectXYEq(t, seq[0].GetXY(0), geom.XY{0, 0}) - expectXYEq(t, seq[0].GetXY(1), geom.XY{0, 10}) - expectXYEq(t, seq[0].GetXY(2), geom.XY{10, 0}) - expectXYEq(t, seq[0].GetXY(3), geom.XY{0, 0}) - expectIntEq(t, seq[1].Length(), 4) - expectXYEq(t, seq[1].GetXY(0), geom.XY{2, 2}) - expectXYEq(t, seq[1].GetXY(1), geom.XY{2, 7}) - expectXYEq(t, seq[1].GetXY(2), geom.XY{7, 2}) - expectXYEq(t, seq[1].GetXY(3), geom.XY{2, 2}) + seq := test.FromWKT(t, "POLYGON((0 0,0 10,10 0,0 0),(2 2,2 7,7 2,2 2))").MustAsPolygon().Coordinates() + test.Eq(t, len(seq), 2) + test.Eq(t, seq[0].Length(), 4) + test.Eq(t, seq[0].GetXY(0), geom.XY{0, 0}) + test.Eq(t, seq[0].GetXY(1), geom.XY{0, 10}) + test.Eq(t, seq[0].GetXY(2), geom.XY{10, 0}) + test.Eq(t, seq[0].GetXY(3), geom.XY{0, 0}) + test.Eq(t, seq[1].Length(), 4) + test.Eq(t, seq[1].GetXY(0), geom.XY{2, 2}) + test.Eq(t, seq[1].GetXY(1), geom.XY{2, 7}) + test.Eq(t, seq[1].GetXY(2), geom.XY{7, 2}) + test.Eq(t, seq[1].GetXY(3), geom.XY{2, 2}) }) t.Run("multipoint", func(t *testing.T) { - seq := geomFromWKT(t, "MULTIPOINT(0 1,2 3,EMPTY,4 5)").MustAsMultiPoint().Coordinates() - expectIntEq(t, seq.Length(), 3) - expectXYEq(t, seq.GetXY(0), geom.XY{0, 1}) - expectXYEq(t, seq.GetXY(1), geom.XY{2, 3}) - expectXYEq(t, seq.GetXY(2), geom.XY{4, 5}) + seq := test.FromWKT(t, "MULTIPOINT(0 1,2 3,EMPTY,4 5)").MustAsMultiPoint().Coordinates() + test.Eq(t, seq.Length(), 3) + test.Eq(t, seq.GetXY(0), geom.XY{0, 1}) + test.Eq(t, seq.GetXY(1), geom.XY{2, 3}) + test.Eq(t, seq.GetXY(2), geom.XY{4, 5}) }) t.Run("multilinestring", func(t *testing.T) { - seq := geomFromWKT(t, "MULTILINESTRING((0 0,0 10,10 0,0 0),(2 2,2 8,8 2,2 2))").MustAsMultiLineString().Coordinates() - expectIntEq(t, len(seq), 2) - expectIntEq(t, seq[0].Length(), 4) - expectXYEq(t, seq[0].GetXY(0), geom.XY{0, 0}) - expectXYEq(t, seq[0].GetXY(1), geom.XY{0, 10}) - expectXYEq(t, seq[0].GetXY(2), geom.XY{10, 0}) - expectXYEq(t, seq[0].GetXY(3), geom.XY{0, 0}) - expectIntEq(t, seq[1].Length(), 4) - expectXYEq(t, seq[1].GetXY(0), geom.XY{2, 2}) - expectXYEq(t, seq[1].GetXY(1), geom.XY{2, 8}) - expectXYEq(t, seq[1].GetXY(2), geom.XY{8, 2}) - expectXYEq(t, seq[1].GetXY(3), geom.XY{2, 2}) + seq := test.FromWKT(t, "MULTILINESTRING((0 0,0 10,10 0,0 0),(2 2,2 8,8 2,2 2))").MustAsMultiLineString().Coordinates() + test.Eq(t, len(seq), 2) + test.Eq(t, seq[0].Length(), 4) + test.Eq(t, seq[0].GetXY(0), geom.XY{0, 0}) + test.Eq(t, seq[0].GetXY(1), geom.XY{0, 10}) + test.Eq(t, seq[0].GetXY(2), geom.XY{10, 0}) + test.Eq(t, seq[0].GetXY(3), geom.XY{0, 0}) + test.Eq(t, seq[1].Length(), 4) + test.Eq(t, seq[1].GetXY(0), geom.XY{2, 2}) + test.Eq(t, seq[1].GetXY(1), geom.XY{2, 8}) + test.Eq(t, seq[1].GetXY(2), geom.XY{8, 2}) + test.Eq(t, seq[1].GetXY(3), geom.XY{2, 2}) }) t.Run("multipolygon", func(t *testing.T) { - seq := geomFromWKT(t, ` + seq := test.FromWKT(t, ` MULTIPOLYGON( ( (0 0,0 10,10 0,0 0), @@ -389,31 +390,31 @@ func TestCoordinatesSequence(t *testing.T) { ) )`, ).MustAsMultiPolygon().Coordinates() - expectIntEq(t, len(seq), 2) - - expectIntEq(t, len(seq[0]), 2) - expectIntEq(t, seq[0][0].Length(), 4) - expectXYEq(t, seq[0][0].GetXY(0), geom.XY{0, 0}) - expectXYEq(t, seq[0][0].GetXY(1), geom.XY{0, 10}) - expectXYEq(t, seq[0][0].GetXY(2), geom.XY{10, 0}) - expectXYEq(t, seq[0][0].GetXY(3), geom.XY{0, 0}) - expectIntEq(t, seq[0][1].Length(), 4) - expectXYEq(t, seq[0][1].GetXY(0), geom.XY{2, 2}) - expectXYEq(t, seq[0][1].GetXY(1), geom.XY{2, 7}) - expectXYEq(t, seq[0][1].GetXY(2), geom.XY{7, 2}) - expectXYEq(t, seq[0][1].GetXY(3), geom.XY{2, 2}) - - expectIntEq(t, len(seq[1]), 2) - expectIntEq(t, seq[1][0].Length(), 4) - expectXYEq(t, seq[1][0].GetXY(0), geom.XY{100, 100}) - expectXYEq(t, seq[1][0].GetXY(1), geom.XY{100, 110}) - expectXYEq(t, seq[1][0].GetXY(2), geom.XY{110, 100}) - expectXYEq(t, seq[1][0].GetXY(3), geom.XY{100, 100}) - expectIntEq(t, seq[1][1].Length(), 4) - expectXYEq(t, seq[1][1].GetXY(0), geom.XY{102, 102}) - expectXYEq(t, seq[1][1].GetXY(1), geom.XY{102, 107}) - expectXYEq(t, seq[1][1].GetXY(2), geom.XY{107, 102}) - expectXYEq(t, seq[1][1].GetXY(3), geom.XY{102, 102}) + test.Eq(t, len(seq), 2) + + test.Eq(t, len(seq[0]), 2) + test.Eq(t, seq[0][0].Length(), 4) + test.Eq(t, seq[0][0].GetXY(0), geom.XY{0, 0}) + test.Eq(t, seq[0][0].GetXY(1), geom.XY{0, 10}) + test.Eq(t, seq[0][0].GetXY(2), geom.XY{10, 0}) + test.Eq(t, seq[0][0].GetXY(3), geom.XY{0, 0}) + test.Eq(t, seq[0][1].Length(), 4) + test.Eq(t, seq[0][1].GetXY(0), geom.XY{2, 2}) + test.Eq(t, seq[0][1].GetXY(1), geom.XY{2, 7}) + test.Eq(t, seq[0][1].GetXY(2), geom.XY{7, 2}) + test.Eq(t, seq[0][1].GetXY(3), geom.XY{2, 2}) + + test.Eq(t, len(seq[1]), 2) + test.Eq(t, seq[1][0].Length(), 4) + test.Eq(t, seq[1][0].GetXY(0), geom.XY{100, 100}) + test.Eq(t, seq[1][0].GetXY(1), geom.XY{100, 110}) + test.Eq(t, seq[1][0].GetXY(2), geom.XY{110, 100}) + test.Eq(t, seq[1][0].GetXY(3), geom.XY{100, 100}) + test.Eq(t, seq[1][1].Length(), 4) + test.Eq(t, seq[1][1].GetXY(0), geom.XY{102, 102}) + test.Eq(t, seq[1][1].GetXY(1), geom.XY{102, 107}) + test.Eq(t, seq[1][1].GetXY(2), geom.XY{107, 102}) + test.Eq(t, seq[1][1].GetXY(3), geom.XY{102, 102}) }) } @@ -453,10 +454,10 @@ func TestTransformXY(t *testing.T) { {"GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(1 2)))", "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(1.5 2)))"}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - g := geomFromWKT(t, tt.wktIn) + g := test.FromWKT(t, tt.wktIn) got := g.TransformXY(transform) - want := geomFromWKT(t, tt.wktOut) - expectGeomEq(t, got, want) + want := test.FromWKT(t, tt.wktOut) + test.ExactEquals(t, got, want) }) } } @@ -539,10 +540,10 @@ func TestTransform(t *testing.T) { {"GEOMETRYCOLLECTION ZM(POINT ZM(1 2 3 4))", "GEOMETRYCOLLECTION ZM(POINT ZM(1 4 9 16))"}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - g := geomFromWKT(t, tt.wktIn) + g := test.FromWKT(t, tt.wktIn) got, err := g.Transform(transform) - expectNoErr(t, err) - expectGeomEqWKT(t, got, tt.wktOut) + test.NoErr(t, err) + test.ExactEqualsWKT(t, got, tt.wktOut) }) } } @@ -558,7 +559,7 @@ func TestIsRing(t *testing.T) { {"LINESTRING(0 0,1 0,1 1,0 1)", false}, // not closed } { t.Run(strconv.Itoa(i), func(t *testing.T) { - got := geomFromWKT(t, tt.wkt).MustAsLineString().IsRing() + got := test.FromWKT(t, tt.wkt).MustAsLineString().IsRing() if got != tt.want { t.Logf("WKT: %v", tt.wkt) t.Errorf("got=%v want=%v", got, tt.want) @@ -577,7 +578,7 @@ func TestIsClosed(t *testing.T) { {"LINESTRING(0 0,1 0,1 1,0 1)", false}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - got := geomFromWKT(t, tt.wkt).MustAsLineString().IsClosed() + got := test.FromWKT(t, tt.wkt).MustAsLineString().IsClosed() if got != tt.want { t.Logf("WKT: %v", tt.wkt) t.Errorf("got=%v want=%v", got, tt.want) @@ -613,7 +614,7 @@ func TestLength(t *testing.T) { )`, 1 + math.Sqrt(5) + math.Sqrt(2) + math.Sqrt(5)}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - got := geomFromWKT(t, tt.wkt).Length() + got := test.FromWKT(t, tt.wkt).Length() if math.Abs(tt.want-got) > 1e-6 { t.Errorf("got=%v want=%v", got, tt.want) } @@ -654,7 +655,7 @@ func TestArea(t *testing.T) { ))`, 8.0}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - got := geomFromWKT(t, tt.wkt).Area() + got := test.FromWKT(t, tt.wkt).Area() if got != tt.want { t.Errorf("got=%v want=%v", got, tt.want) } @@ -702,7 +703,7 @@ func TestSignedArea(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { t.Logf("input: %s", tc.input) - g := geomFromWKT(t, tc.input) + g := test.FromWKT(t, tc.input) got := g.Area(geom.SignedArea) if got != tc.expected { t.Errorf("expected: %f, got: %f", tc.expected, got) @@ -722,7 +723,7 @@ func TestTransformedArea(t *testing.T) { {"MULTIPOLYGON(((0 0,1 0,0 1,0 0)),((2 2,3 2,2 3,2 2)))", 0.125}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - got := geomFromWKT(t, tt.wkt).Area(geom.WithTransform(func(xy geom.XY) geom.XY { + got := test.FromWKT(t, tt.wkt).Area(geom.WithTransform(func(xy geom.XY) geom.XY { xy.X *= 0.5 xy.Y *= 0.25 return xy @@ -735,7 +736,7 @@ func TestTransformedArea(t *testing.T) { } func TestTransformedAreaInvocationCount(t *testing.T) { - g := geomFromWKT(t, "POLYGON((0 0,0 1,1 0,0 0))") + g := test.FromWKT(t, "POLYGON((0 0,0 1,1 0,0 0))") var count int g.Area(geom.WithTransform(func(xy geom.XY) geom.XY { count++ @@ -743,7 +744,7 @@ func TestTransformedAreaInvocationCount(t *testing.T) { })) // Each of the 4 points making up the polygon get transformed once each. - expectIntEq(t, count, 4) + test.Eq(t, count, 4) } func TestCentroid(t *testing.T) { @@ -803,8 +804,8 @@ func TestCentroid(t *testing.T) { {"GEOMETRYCOLLECTION(POLYGON((151 -33,151.00001 -33,151.00001 -33.00001,151 -33.00001,151 -33)))", "POINT(151.000005 -33.000005)"}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - got := geomFromWKT(t, tt.input).Centroid() - want := geomFromWKT(t, tt.output) + got := test.FromWKT(t, tt.input).Centroid() + want := test.FromWKT(t, tt.output) if !geom.ExactEquals(want, got.AsGeometry(), geom.ToleranceXY(0.00000001)) { t.Log(tt.input) t.Errorf("got=%v want=%v", got.AsText(), tt.output) @@ -814,16 +815,16 @@ func TestCentroid(t *testing.T) { } func TestLineStringToMultiLineString(t *testing.T) { - ls := geomFromWKT(t, "LINESTRING(1 2,3 4,5 6)").MustAsLineString() + ls := test.FromWKT(t, "LINESTRING(1 2,3 4,5 6)").MustAsLineString() got := ls.AsMultiLineString() - want := geomFromWKT(t, "MULTILINESTRING((1 2,3 4,5 6))") + want := test.FromWKT(t, "MULTILINESTRING((1 2,3 4,5 6))") if !geom.ExactEquals(got.AsGeometry(), want) { t.Errorf("want=%v got=%v", want, got) } } func TestPolygonToMultiPolygon(t *testing.T) { - p := geomFromWKT(t, "POLYGON((0 0,0 1,1 0,0 0))").MustAsPolygon() + p := test.FromWKT(t, "POLYGON((0 0,0 1,1 0,0 0))").MustAsPolygon() mp := p.AsMultiPolygon() if mp.AsText() != "MULTIPOLYGON(((0 0,0 1,1 0,0 0)))" { t.Errorf("got %v", mp.AsText()) @@ -961,9 +962,9 @@ func TestReverse(t *testing.T) { } { t.Run(strconv.Itoa(i), func(t *testing.T) { t.Log("Input", tt.wkt) - want := geomFromWKT(t, tt.boundary) - got := geomFromWKT(t, tt.wkt).Reverse() - expectGeomEq(t, got, want) + want := test.FromWKT(t, tt.boundary) + got := test.FromWKT(t, tt.wkt).Reverse() + test.ExactEquals(t, got, want) }) } } @@ -1130,9 +1131,9 @@ func TestForceCoordinatesType(t *testing.T) { t.Run(strconv.Itoa(i), func(t *testing.T) { t.Log("input", tt.input) t.Log("ct", tt.ct) - got := geomFromWKT(t, tt.input).ForceCoordinatesType(tt.ct) - want := geomFromWKT(t, tt.output) - expectGeomEq(t, got, want) + got := test.FromWKT(t, tt.input).ForceCoordinatesType(tt.ct) + want := test.FromWKT(t, tt.output) + test.ExactEquals(t, got, want) }) } } @@ -1255,14 +1256,14 @@ func TestForceWindingDirection(t *testing.T) { } { t.Run(tt.desc, func(t *testing.T) { t.Run("ForceCW", func(t *testing.T) { - got := geomFromWKT(t, tt.input).ForceCW() - want := geomFromWKT(t, tt.forceCW) - expectGeomEq(t, got, want) + got := test.FromWKT(t, tt.input).ForceCW() + want := test.FromWKT(t, tt.forceCW) + test.ExactEquals(t, got, want) }) t.Run("ForceCCW", func(t *testing.T) { - got := geomFromWKT(t, tt.input).ForceCCW() - want := geomFromWKT(t, tt.forceCCW) - expectGeomEq(t, got, want) + got := test.FromWKT(t, tt.input).ForceCCW() + want := test.FromWKT(t, tt.forceCCW) + test.ExactEquals(t, got, want) }) }) } @@ -1672,9 +1673,9 @@ func TestSummary(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - g := geomFromWKT(t, tc.wkt) - expectStringEq(t, g.Summary(), tc.wantSummary) - expectStringEq(t, g.String(), tc.wantSummary) + g := test.FromWKT(t, tc.wkt) + test.Eq(t, g.Summary(), tc.wantSummary) + test.Eq(t, g.String(), tc.wantSummary) }) } } @@ -1701,17 +1702,17 @@ func TestPolygonDumpRings(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - input := geomFromWKT(t, tc.inputWKT).MustAsPolygon() + input := test.FromWKT(t, tc.inputWKT).MustAsPolygon() wantRings := make([]geom.LineString, len(tc.wantRingWKTs)) for j, wantRingWKT := range tc.wantRingWKTs { - wantRings[j] = geomFromWKT(t, wantRingWKT).MustAsLineString() + wantRings[j] = test.FromWKT(t, wantRingWKT).MustAsLineString() } gotRings := input.DumpRings() - expectIntEq(t, len(gotRings), len(wantRings)) + test.Eq(t, len(gotRings), len(wantRings)) for j := range wantRings { - expectGeomEq(t, + test.ExactEquals(t, gotRings[j].AsGeometry(), wantRings[j].AsGeometry(), ) @@ -1721,7 +1722,7 @@ func TestPolygonDumpRings(t *testing.T) { // rather than just a copy of its header: otherRings := input.DumpRings() if len(otherRings) > 0 { - expectTrue(t, &gotRings[0] != &otherRings[0]) + test.True(t, &gotRings[0] != &otherRings[0]) } }) } @@ -1740,10 +1741,10 @@ func TestFlipCoordinates(t *testing.T) { {"GEOMETRYCOLLECTION(POINT(1 2),LINESTRING(3 4,5 6))", "GEOMETRYCOLLECTION(POINT(2 1),LINESTRING(4 3,6 5))"}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - g := geomFromWKT(t, tt.wktIn) + g := test.FromWKT(t, tt.wktIn) got := g.FlipCoordinates() - want := geomFromWKT(t, tt.wktOut) - expectGeomEq(t, got, want) + want := test.FromWKT(t, tt.wktOut) + test.ExactEquals(t, got, want) }) } } diff --git a/geom/ctor_from_coords.go b/geom/ctor_from_coords.go index 57cd3944..916cc76d 100644 --- a/geom/ctor_from_coords.go +++ b/geom/ctor_from_coords.go @@ -227,6 +227,20 @@ func NewPolygonXYZM(xyzms ...[]float64) Polygon { return polygonFromCoords(xyzms, DimXYZM) } +// NewEnvelopeXY builds a new [Envelope] from x and y coordinates, x1, y1, x2, +// y2, ..., xn, yn. If the number of coordinates is not a multiple of 2 the +// function will panic. +func NewEnvelopeXY(xys ...float64) Envelope { + if len(xys)%2 != 0 { + panic("geom: coordinate arguments to NewEnvelopeXY must have a length that is a multiple of 2") + } + var env Envelope + for i := 0; i < len(xys); i += 2 { + env = env.ExpandToIncludeXY(XY{xys[i], xys[i+1]}) + } + return env +} + // NewSingleRingPolygonXY builds a new XY [Polygon] from the x and y coordinates // of its exterior ring, in the form x1, y1, x2, y2, ..., xn, yn, x1, y1 (the // first and last coordinates of the ring should be the same). If the number of diff --git a/geom/ctor_from_coords_test.go b/geom/ctor_from_coords_test.go index 46f28dd8..d19abde3 100644 --- a/geom/ctor_from_coords_test.go +++ b/geom/ctor_from_coords_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) // TODO: Test panics. @@ -95,7 +96,36 @@ func TestCoordinateConstructors(t *testing.T) { {geom.NewMultiPolygonXYM([][]float64{{0, 0, 10, 0, 1, 11, 1, 1, 12, 0, 0, 13}}, [][]float64{{2, 2, 20, 2, 1, 21, 1, 2, 22, 2, 2, 23}}), "MULTIPOLYGON M(((0 0 10,0 1 11,1 1 12,0 0 13)),((2 2 20,2 1 21,1 2 22,2 2 23)))"}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - expectGeomEqWKT(t, tc.got.AsGeometry(), tc.wantWKT) + test.ExactEqualsWKT(t, tc.got.AsGeometry(), tc.wantWKT) }) } } + +func TestNewEnvelopeXY(t *testing.T) { + t.Run("no args gives empty envelope", func(t *testing.T) { + test.Eq(t, geom.NewEnvelopeXY(), geom.Envelope{}) + }) + t.Run("single point", func(t *testing.T) { + got := geom.NewEnvelopeXY(1, 2) + want := geom.Envelope{}.ExpandToIncludeXY(geom.XY{1, 2}) + test.Eq(t, got, want) + }) + t.Run("two points", func(t *testing.T) { + got := geom.NewEnvelopeXY(1, 2, 3, 4) + want := geom.Envelope{}.ExpandToIncludeXY(geom.XY{1, 2}).ExpandToIncludeXY(geom.XY{3, 4}) + test.Eq(t, got, want) + }) + t.Run("two points reverse order", func(t *testing.T) { + got := geom.NewEnvelopeXY(3, 4, 1, 2) + want := geom.NewEnvelopeXY(1, 2, 3, 4) + test.Eq(t, got, want) + }) + t.Run("three points", func(t *testing.T) { + got := geom.NewEnvelopeXY(1, 2, 3, 4, 5, 6) + want := geom.Envelope{}.ExpandToIncludeXY(geom.XY{1, 2}).ExpandToIncludeXY(geom.XY{3, 4}).ExpandToIncludeXY(geom.XY{5, 6}) + test.Eq(t, got, want) + }) + t.Run("odd number of args panics", func(t *testing.T) { + test.Panics(t, func() { geom.NewEnvelopeXY(1) }) + }) +} diff --git a/geom/dump_coordinates_test.go b/geom/dump_coordinates_test.go index 19ffb887..a40c8d89 100644 --- a/geom/dump_coordinates_test.go +++ b/geom/dump_coordinates_test.go @@ -4,8 +4,40 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) +func expectSequenceEq(tb testing.TB, got, want geom.Sequence) { + tb.Helper() + show := func() { + tb.Logf("len(got): %d, ct(got): %s", got.Length(), got.CoordinatesType()) + for i := 0; i < got.Length(); i++ { + tb.Logf("got[%d]: %v", i, got.Get(i)) + } + tb.Logf("len(want): %d, ct(want): %s", want.Length(), want.CoordinatesType()) + for i := 0; i < want.Length(); i++ { + tb.Logf("want[%d]: %v", i, want.Get(i)) + } + } + if got.CoordinatesType() != want.CoordinatesType() { + tb.Errorf("mismatched coordinate type") + show() + return + } + if got.Length() != want.Length() { + tb.Errorf("length mismatch") + show() + return + } + for i := 0; i < got.Length(); i++ { + w := want.Get(i) + g := got.Get(i) + if g != w { + tb.Errorf("mismatch at %d: got:%v want:%v", i, g, w) + } + } +} + func TestDumpCoordinatesPoint(t *testing.T) { for _, tc := range []struct { description string @@ -54,7 +86,7 @@ func TestDumpCoordinatesPoint(t *testing.T) { }, } { t.Run(tc.description, func(t *testing.T) { - got := geomFromWKT(t, tc.inputWKT).MustAsPoint().DumpCoordinates() + got := test.FromWKT(t, tc.inputWKT).MustAsPoint().DumpCoordinates() expectSequenceEq(t, got, tc.want) }) } @@ -108,7 +140,7 @@ func TestDumpCoordinatesMultiLineString(t *testing.T) { }, } { t.Run(tc.description, func(t *testing.T) { - got := geomFromWKT(t, tc.inputWKT).MustAsMultiLineString().DumpCoordinates() + got := test.FromWKT(t, tc.inputWKT).MustAsMultiLineString().DumpCoordinates() expectSequenceEq(t, got, tc.want) }) } @@ -162,7 +194,7 @@ func TestDumpCoordinatesPolygon(t *testing.T) { }, } { t.Run(tc.description, func(t *testing.T) { - got := geomFromWKT(t, tc.inputWKT).MustAsPolygon().DumpCoordinates() + got := test.FromWKT(t, tc.inputWKT).MustAsPolygon().DumpCoordinates() expectSequenceEq(t, got, tc.want) }) } @@ -216,7 +248,7 @@ func TestDumpCoordinatesMultiPolygon(t *testing.T) { }, } { t.Run(tc.description, func(t *testing.T) { - got := geomFromWKT(t, tc.inputWKT).MustAsMultiPolygon().DumpCoordinates() + got := test.FromWKT(t, tc.inputWKT).MustAsMultiPolygon().DumpCoordinates() expectSequenceEq(t, got, tc.want) }) } @@ -255,7 +287,7 @@ func TestDumpCoordinatesGeometryCollection(t *testing.T) { }, } { t.Run(tc.description, func(t *testing.T) { - got := geomFromWKT(t, tc.inputWKT).MustAsGeometryCollection().DumpCoordinates() + got := test.FromWKT(t, tc.inputWKT).MustAsGeometryCollection().DumpCoordinates() expectSequenceEq(t, got, tc.want) }) } @@ -304,7 +336,7 @@ func TestDumpCoordinatesGeometry(t *testing.T) { }, } { t.Run(tc.description, func(t *testing.T) { - got := geomFromWKT(t, tc.inputWKT).DumpCoordinates() + got := test.FromWKT(t, tc.inputWKT).DumpCoordinates() expectSequenceEq(t, got, tc.want) }) } diff --git a/geom/geojson_feature_collection_test.go b/geom/geojson_feature_collection_test.go index 788d297e..be4f509c 100644 --- a/geom/geojson_feature_collection_test.go +++ b/geom/geojson_feature_collection_test.go @@ -58,19 +58,19 @@ func TestGeoJSONFeatureCollectionValidUnmarshal(t *testing.T) { var fc geom.GeoJSONFeatureCollection err := json.NewDecoder(strings.NewReader(input)).Decode(&fc) - expectNoErr(t, err) + test.NoErr(t, err) - expectIntEq(t, len(fc.Features), 2) + test.Eq(t, len(fc.Features), 2) f0 := fc.Features[0] f1 := fc.Features[1] - expectStringEq(t, f0.ID.(string), "id0") - expectBoolEq(t, reflect.DeepEqual(f0.Properties, map[string]any{"prop0": "value0", "prop1": "value1"}), true) - expectGeomEq(t, f0.Geometry, geomFromWKT(t, "LINESTRING(102 0,103 1,104 0,105 1)")) + test.Eq(t, f0.ID.(string), "id0") + test.Eq(t, reflect.DeepEqual(f0.Properties, map[string]any{"prop0": "value0", "prop1": "value1"}), true) + test.ExactEquals(t, f0.Geometry, test.FromWKT(t, "LINESTRING(102 0,103 1,104 0,105 1)")) - expectStringEq(t, f1.ID.(string), "id1") - expectBoolEq(t, reflect.DeepEqual(f1.Properties, map[string]any{"prop0": "value2", "prop1": "value3"}), true) - expectGeomEq(t, f1.Geometry, geomFromWKT(t, "POLYGON((100 0,101 0,101 1,100 1,100 0))")) + test.Eq(t, f1.ID.(string), "id1") + test.Eq(t, reflect.DeepEqual(f1.Properties, map[string]any{"prop0": "value2", "prop1": "value3"}), true) + test.ExactEquals(t, f1.Geometry, test.FromWKT(t, "POLYGON((100 0,101 0,101 1,100 1,100 0))")) } func TestGeoJSONFeatureCollectionInvalidUnmarshal(t *testing.T) { @@ -115,7 +115,7 @@ func TestGeoJSONFeatureCollectionInvalidUnmarshal(t *testing.T) { // what the other test cases are based on. var fc geom.GeoJSONFeatureCollection r := strings.NewReader(tt.input) - expectNoErr(t, json.NewDecoder(r).Decode(&fc)) + test.NoErr(t, json.NewDecoder(r).Decode(&fc)) continue } t.Run(strconv.Itoa(i), func(t *testing.T) { @@ -134,8 +134,8 @@ func TestGeoJSONFeatureCollectionInvalidUnmarshal(t *testing.T) { func TestGeoJSONFeatureCollectionEmpty(t *testing.T) { out, err := json.Marshal(geom.GeoJSONFeatureCollection{}) - expectNoErr(t, err) - expectStringEq(t, string(out), `{"type":"FeatureCollection","features":[]}`) + test.NoErr(t, err) + test.Eq(t, string(out), `{"type":"FeatureCollection","features":[]}`) } func TestGeoJSONFeatureCollectionNil(t *testing.T) { @@ -143,28 +143,28 @@ func TestGeoJSONFeatureCollectionNil(t *testing.T) { Features: nil, ForeignMembers: nil, }) - expectNoErr(t, err) - expectStringEq(t, string(out), `{"type":"FeatureCollection","features":[]}`) + test.NoErr(t, err) + test.Eq(t, string(out), `{"type":"FeatureCollection","features":[]}`) } func TestGeoJSONFeatureCollectionAndPropertiesNil(t *testing.T) { - out, err := json.Marshal(geom.GeoJSONFeatureCollection{Features: []geom.GeoJSONFeature{{Geometry: geomFromWKT(t, "POINT(1 2)")}}}) - expectNoErr(t, err) - expectStringEq(t, string(out), `{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]},"properties":{}}]}`) + out, err := json.Marshal(geom.GeoJSONFeatureCollection{Features: []geom.GeoJSONFeature{{Geometry: test.FromWKT(t, "POINT(1 2)")}}}) + test.NoErr(t, err) + test.Eq(t, string(out), `{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]},"properties":{}}]}`) } func TestGeoJSONFeatureCollectionAndPropertiesSet(t *testing.T) { out, err := json.Marshal(geom.GeoJSONFeatureCollection{ Features: []geom.GeoJSONFeature{{ - Geometry: geomFromWKT(t, "POINT(1 2)"), + Geometry: test.FromWKT(t, "POINT(1 2)"), ID: "myid", Properties: map[string]any{ "foo": "bar", }, }}, }) - expectNoErr(t, err) - expectStringEq(t, string(out), `{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]},"id":"myid","properties":{"foo":"bar"}}]}`) + test.NoErr(t, err) + test.Eq(t, string(out), `{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]},"id":"myid","properties":{"foo":"bar"}}]}`) } func TestGeoJSONFeatureForeignMembers(t *testing.T) { @@ -206,15 +206,15 @@ func TestGeoJSONFeatureForeignMembers(t *testing.T) { ForeignMembers: tc.members, } got, err := json.Marshal(feat) - expectNoErr(t, err) - expectStringEq(t, string(got), tc.json) + test.NoErr(t, err) + test.Eq(t, string(got), tc.json) }) t.Run("unmarshal", func(t *testing.T) { var feat geom.GeoJSONFeature err := json.Unmarshal([]byte(tc.json), &feat) - expectNoErr(t, err) + test.NoErr(t, err) if len(feat.ForeignMembers) != 0 || len(tc.members) != 0 { - expectDeepEq(t, feat.ForeignMembers, tc.members) + test.DeepEqual(t, feat.ForeignMembers, tc.members) } }) }) @@ -291,15 +291,15 @@ func TestGeoJSONFeatureCollectionForeignMembers(t *testing.T) { ForeignMembers: tc.members, } got, err := json.Marshal(fc) - expectNoErr(t, err) - expectStringEq(t, string(got), tc.json) + test.NoErr(t, err) + test.Eq(t, string(got), tc.json) }) t.Run("unmarshal", func(t *testing.T) { var fc geom.GeoJSONFeatureCollection err := json.Unmarshal([]byte(tc.json), &fc) - expectNoErr(t, err) + test.NoErr(t, err) if len(fc.ForeignMembers) != 0 || len(tc.members) != 0 { - expectDeepEq(t, fc.ForeignMembers, tc.members) + test.DeepEqual(t, fc.ForeignMembers, tc.members) } }) }) diff --git a/geom/geojson_marshal_test.go b/geom/geojson_marshal_test.go index 42c9cea9..f3f6ecc7 100644 --- a/geom/geojson_marshal_test.go +++ b/geom/geojson_marshal_test.go @@ -3,6 +3,8 @@ package geom_test import ( "encoding/json" "testing" + + "github.com/peterstace/simplefeatures/internal/test" ) func TestGeoJSONMarshal(t *testing.T) { @@ -439,9 +441,9 @@ func TestGeoJSONMarshal(t *testing.T) { }, } { t.Run(tt.wkt, func(t *testing.T) { - geom := geomFromWKT(t, tt.wkt) + geom := test.FromWKT(t, tt.wkt) gotJSON, err := json.Marshal(geom) - expectNoErr(t, err) + test.NoErr(t, err) if string(gotJSON) != tt.want { t.Error("json doesn't match") t.Logf("got: %v", string(gotJSON)) diff --git a/geom/geojson_unmarshal_test.go b/geom/geojson_unmarshal_test.go index 9b74eb7d..52865ba7 100644 --- a/geom/geojson_unmarshal_test.go +++ b/geom/geojson_unmarshal_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func TestGeoJSONUnmarshalValid(t *testing.T) { @@ -172,9 +173,9 @@ func TestGeoJSONUnmarshalValid(t *testing.T) { } { t.Run(strconv.Itoa(i), func(t *testing.T) { got, err := geom.UnmarshalGeoJSON([]byte(tt.geojson)) - expectNoErr(t, err) - want := geomFromWKT(t, tt.wkt) - expectGeomEq(t, got, want) + test.NoErr(t, err) + want := test.FromWKT(t, tt.wkt) + test.ExactEquals(t, got, want) }) } } @@ -224,9 +225,9 @@ func TestGeoJSONUnmarshalValidXYZM(t *testing.T) { } { t.Run(strconv.Itoa(i), func(t *testing.T) { got, err := geom.UnmarshalGeoJSON([]byte(tt.geojson)) - expectNoErr(t, err) - want := geomFromWKT(t, tt.wkt) - expectGeomEq(t, got, want) + test.NoErr(t, err) + want := test.FromWKT(t, tt.wkt) + test.ExactEquals(t, got, want) }) } } @@ -271,9 +272,9 @@ func TestGeoJSONUnmarshalValidAllowAdditionalCoordDimensions(t *testing.T) { } { t.Run(strconv.Itoa(i), func(t *testing.T) { got, err := geom.UnmarshalGeoJSON([]byte(tt.geojson)) - expectNoErr(t, err) - want := geomFromWKT(t, tt.wkt) - expectGeomEq(t, got, want) + test.NoErr(t, err) + want := test.FromWKT(t, tt.wkt) + test.ExactEquals(t, got, want) }) } } @@ -477,8 +478,8 @@ func TestGeoJSONUnmarshalIntoConcreteGeometryValid(t *testing.T) { } { t.Run(tc.target.Type().String(), func(t *testing.T) { err := json.Unmarshal([]byte(tc.geojson), tc.target) - expectNoErr(t, err) - expectGeomEq(t, tc.target.AsGeometry(), geomFromWKT(t, tc.wantWKT)) + test.NoErr(t, err) + test.ExactEquals(t, tc.target.AsGeometry(), test.FromWKT(t, tc.wantWKT)) }) } } @@ -508,7 +509,7 @@ func TestGeoJSONUnmarshalIntoConcreteGeometryWrongType(t *testing.T) { `{"type":"MultiPolygon","coordinates":[[[[0,0],[0,1],[1,0],[0,0]]]]}`, `{"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[1,2]}]}`, } { - srcTyp := geomFromGeoJSON(t, geojson).Type() + srcTyp := test.FromGeoJSON(t, geojson).Type() t.Run("source_"+srcTyp.String(), func(t *testing.T) { destType := tc.dest.Type() if srcTyp == destType { @@ -521,7 +522,7 @@ func TestGeoJSONUnmarshalIntoConcreteGeometryWrongType(t *testing.T) { SourceType: srcTyp, DestinationType: destType, } - expectErrIs(t, err, want) + test.ErrIs(t, err, want) }) } }) @@ -531,34 +532,34 @@ func TestGeoJSONUnmarshalIntoConcreteGeometryWrongType(t *testing.T) { func TestGeoJSONUnmarshalIntoConcreteGeometryDoesNotAlterParent(t *testing.T) { t.Run("MultiPoint", func(t *testing.T) { const parentWKT = "MULTIPOINT((1 2))" - parent := geomFromWKT(t, parentWKT).MustAsMultiPoint() + parent := test.FromWKT(t, parentWKT).MustAsMultiPoint() child := parent.PointN(0) err := json.Unmarshal([]byte(`{"type":"Point","coordinates":[9,9]}`), &child) - expectNoErr(t, err) - expectGeomEq(t, parent.AsGeometry(), geomFromWKT(t, parentWKT)) + test.NoErr(t, err) + test.ExactEquals(t, parent.AsGeometry(), test.FromWKT(t, parentWKT)) }) t.Run("MultiLineString", func(t *testing.T) { const parentWKT = "MULTILINESTRING((1 2,3 4))" - parent := geomFromWKT(t, parentWKT).MustAsMultiLineString() + parent := test.FromWKT(t, parentWKT).MustAsMultiLineString() child := parent.LineStringN(0) err := json.Unmarshal([]byte(`{"type":"LineString","coordinates":[[9,9],[8,8]]}`), &child) - expectNoErr(t, err) - expectGeomEq(t, parent.AsGeometry(), geomFromWKT(t, parentWKT)) + test.NoErr(t, err) + test.ExactEquals(t, parent.AsGeometry(), test.FromWKT(t, parentWKT)) }) t.Run("MultiPolygon", func(t *testing.T) { const parentWKT = "MULTIPOLYGON(((0 0,0 1,1 0,0 0)))" - parent := geomFromWKT(t, parentWKT).MustAsMultiPolygon() + parent := test.FromWKT(t, parentWKT).MustAsMultiPolygon() child := parent.PolygonN(0) err := json.Unmarshal([]byte(`{"type":"Polygon","coordinates":[[[4,4],[4,5],[5,4],[4,4]]]}`), &child) - expectNoErr(t, err) - expectGeomEq(t, parent.AsGeometry(), geomFromWKT(t, parentWKT)) + test.NoErr(t, err) + test.ExactEquals(t, parent.AsGeometry(), test.FromWKT(t, parentWKT)) }) t.Run("GeometryCollection", func(t *testing.T) { const parentWKT = "GEOMETRYCOLLECTION(POINT(1 2))" - parent := geomFromWKT(t, parentWKT).MustAsGeometryCollection() + parent := test.FromWKT(t, parentWKT).MustAsGeometryCollection() child := parent.GeometryN(0) err := json.Unmarshal([]byte(`{"type":"Point","coordinates":[9,9]}`), &child) - expectNoErr(t, err) - expectGeomEq(t, parent.AsGeometry(), geomFromWKT(t, parentWKT)) + test.NoErr(t, err) + test.ExactEquals(t, parent.AsGeometry(), test.FromWKT(t, parentWKT)) }) } diff --git a/geom/marshal_unmarshal_test.go b/geom/marshal_unmarshal_test.go index 72f1d26b..8bb39519 100644 --- a/geom/marshal_unmarshal_test.go +++ b/geom/marshal_unmarshal_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func TestMarshalUnmarshal(t *testing.T) { @@ -182,8 +183,8 @@ func TestMarshalUnmarshal(t *testing.T) { t.Run("wkt", func(t *testing.T) { for i, wkt := range wkts { t.Run(strconv.Itoa(i), func(t *testing.T) { - got := geomFromWKT(t, wkt).AsText() - expectStringEq(t, got, wkt) + got := test.FromWKT(t, wkt).AsText() + test.Eq(t, got, wkt) }) } }) @@ -191,19 +192,19 @@ func TestMarshalUnmarshal(t *testing.T) { t.Run("wkb", func(t *testing.T) { for i, wkt := range wkts { t.Run(strconv.Itoa(i), func(t *testing.T) { - original := geomFromWKT(t, wkt) + original := test.FromWKT(t, wkt) wkb := original.AsBinary() reconstructed, err := geom.UnmarshalWKB(wkb) - expectNoErr(t, err) + test.NoErr(t, err) reconstructedWKT := reconstructed.AsText() - expectStringEq(t, wkt, reconstructedWKT) + test.Eq(t, wkt, reconstructedWKT) }) } }) t.Run("geojson", func(t *testing.T) { for i, wkt := range wkts { - original := geomFromWKT(t, wkt) + original := test.FromWKT(t, wkt) if original.CoordinatesType().IsMeasured() { // GeoJSON will drop Measures continue @@ -234,11 +235,11 @@ func TestMarshalUnmarshal(t *testing.T) { t.Run(strconv.Itoa(i), func(t *testing.T) { geojson, err := json.Marshal(original) - expectNoErr(t, err) + test.NoErr(t, err) var reconstructed geom.Geometry - expectNoErr(t, json.Unmarshal(geojson, &reconstructed)) + test.NoErr(t, json.Unmarshal(geojson, &reconstructed)) reconstructedWKT := reconstructed.AsText() - expectStringEq(t, wkt, reconstructedWKT) + test.Eq(t, wkt, reconstructedWKT) }) } }) diff --git a/geom/perf_test.go b/geom/perf_test.go index 0edb2fd8..91429fc6 100644 --- a/geom/perf_test.go +++ b/geom/perf_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) // regularPolygon computes a regular polygon circumscribed by a circle with the @@ -390,7 +391,7 @@ func BenchmarkForceCWandForceCCW(b *testing.B) { {"MULTIPOLYGON(((40 40, 20 45, 45 30, 40 40)),((20 35, 10 30, 10 10, 30 5, 45 20, 20 35),(30 20, 20 15, 20 25, 30 20)))", geom.TypeMultiPolygon, false, true, "all CCW"}, {"GEOMETRYCOLLECTION(POLYGON((0 0,0 5,5 5,5 0,0 0)), MULTIPOLYGON(((40 40, 45 30, 20 45, 40 40)),((20 35, 45 20, 30 5, 10 10, 10 30, 20 35),(30 20, 20 25, 20 15, 30 20))))", geom.TypeGeometryCollection, true, false, "all CW"}, } { - g := geomFromWKT(b, tc.wkt) + g := test.FromWKT(b, tc.wkt) for _, correct := range map[string]bool{ "correct": true, "incorrect": false, diff --git a/geom/snap_to_grid_test.go b/geom/snap_to_grid_test.go index 4374db8a..9150b08c 100644 --- a/geom/snap_to_grid_test.go +++ b/geom/snap_to_grid_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func TestSnapPiFloat64ToGrid(t *testing.T) { @@ -43,8 +44,8 @@ func TestSnapPiFloat64ToGrid(t *testing.T) { pt := geom.XY{0, math.Pi}.AsPoint() pt = pt.SnapToGrid(tc.dp) xy, ok := pt.XY() - expectTrue(t, ok) - expectFloat64Eq(t, xy.Y, tc.want) + test.True(t, ok) + test.Eq(t, xy.Y, tc.want) }) } } @@ -92,9 +93,9 @@ func TestSnapToGrid(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - in := geomFromWKT(t, tc.input) + in := test.FromWKT(t, tc.input) got := in.SnapToGrid(1) - expectGeomEqWKT(t, got, tc.output) + test.ExactEqualsWKT(t, got, tc.output) }) } } diff --git a/geom/sql_test.go b/geom/sql_test.go index bb93c775..ebd75e9f 100644 --- a/geom/sql_test.go +++ b/geom/sql_test.go @@ -5,10 +5,11 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func TestSQLValueGeometry(t *testing.T) { - g := geomFromWKT(t, "POINT(1 2)") + g := test.FromWKT(t, "POINT(1 2)") val, err := g.Value() if err != nil { t.Fatal(err) @@ -17,19 +18,19 @@ func TestSQLValueGeometry(t *testing.T) { if err != nil { t.Fatal(err) } - expectGeomEq(t, g, geom) + test.ExactEquals(t, g, geom) } func TestSQLScanGeometry(t *testing.T) { const wkt = "POINT(2 3)" - wkb := geomFromWKT(t, wkt).AsBinary() + wkb := test.FromWKT(t, wkt).AsBinary() var g geom.Geometry check := func(t *testing.T, err error) { t.Helper() if err != nil { t.Fatal(err) } - expectGeomEq(t, g, geomFromWKT(t, wkt)) + test.ExactEquals(t, g, test.FromWKT(t, wkt)) } t.Run("string", func(t *testing.T) { g = geom.Geometry{} @@ -56,12 +57,12 @@ func TestSQLValueConcrete(t *testing.T) { } { t.Run(strconv.Itoa(i), func(t *testing.T) { t.Log(wkt) - in := geomFromWKT(t, wkt) + in := test.FromWKT(t, wkt) val, err := in.Value() - expectNoErr(t, err) + test.NoErr(t, err) out, err := geom.UnmarshalWKB(val.([]byte)) - expectNoErr(t, err) - expectGeomEq(t, out, in) + test.NoErr(t, err) + test.ExactEquals(t, out, in) }) } } @@ -83,10 +84,10 @@ func TestSQLScanConcrete(t *testing.T) { {"GEOMETRYCOLLECTION(MULTIPOLYGON(((0 0,1 0,0 1,0 0))))", new(geom.GeometryCollection)}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - wkb := geomFromWKT(t, tc.wkt).AsBinary() + wkb := test.FromWKT(t, tc.wkt).AsBinary() err := tc.concrete.Scan(wkb) - expectNoErr(t, err) - expectStringEq(t, tc.concrete.AsText(), tc.wkt) + test.NoErr(t, err) + test.Eq(t, tc.concrete.AsText(), tc.wkt) }) } } diff --git a/geom/twkb_test.go b/geom/twkb_test.go index 1252407a..230651ec 100644 --- a/geom/twkb_test.go +++ b/geom/twkb_test.go @@ -2,6 +2,7 @@ package geom_test import ( "bytes" + "encoding/hex" "fmt" "math" "os" @@ -9,6 +10,7 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) // TWKBTestCases is outside a single test function to allow multiple @@ -345,7 +347,8 @@ var TWKBTestCases = []struct { func TestTWKBUnmarshalMarshalValid(t *testing.T) { for _, tc := range TWKBTestCases { t.Run(tc.description, func(t *testing.T) { - twkb := hexStringToBytes(t, tc.twkbHex) + twkb, err := hex.DecodeString(tc.twkbHex) + test.NoErr(t, err) t.Logf("TWKB (hex): %v", tc.twkbHex) t.Run("decode", func(t *testing.T) { @@ -355,19 +358,19 @@ func TestTWKBUnmarshalMarshalValid(t *testing.T) { t.Run("geometry", func(t *testing.T) { g, err := geom.UnmarshalTWKB(twkb) - expectNoErr(t, err) - expectGeomEqWKT(t, g, tc.wkt) + test.NoErr(t, err) + test.ExactEqualsWKT(t, g, tc.wkt) }) t.Run("envelope", func(t *testing.T) { gotExtEnv, ok, err := geom.UnmarshalTWKBEnvelope(twkb) - expectNoErr(t, err) - expectBoolEq(t, ok, tc.hasBBox) + test.NoErr(t, err) + test.Eq(t, ok, tc.hasBBox) if ok { - wantC1, ok := geomFromWKT(t, tc.listedBBox[0]).MustAsPoint().Coordinates() - expectTrue(t, ok) - wantC2, ok := geomFromWKT(t, tc.listedBBox[1]).MustAsPoint().Coordinates() - expectTrue(t, ok) + wantC1, ok := test.FromWKT(t, tc.listedBBox[0]).MustAsPoint().Coordinates() + test.True(t, ok) + wantC2, ok := test.FromWKT(t, tc.listedBBox[1]).MustAsPoint().Coordinates() + test.True(t, ok) wantC1.X, wantC2.X = minMax(wantC1.X, wantC2.X) wantC1.Y, wantC2.Y = minMax(wantC1.Y, wantC2.Y) @@ -375,34 +378,34 @@ func TestTWKBUnmarshalMarshalValid(t *testing.T) { wantC1.M, wantC2.M = minMax(wantC1.M, wantC2.M) gotXY1, gotXY2, ok := gotExtEnv.XYEnvelope.MinMaxXYs() - expectTrue(t, ok) + test.True(t, ok) ct := wantC1.Type - expectCoordinatesTypeEq(t, ct, wantC2.Type) + test.Eq(t, ct, wantC2.Type) minZ, maxZ, hasZ := gotExtEnv.ZRange.MinMax() minM, maxM, hasM := gotExtEnv.MRange.MinMax() - expectBoolEq(t, hasZ, ct.Is3D()) - expectBoolEq(t, hasM, ct.IsMeasured()) + test.Eq(t, hasZ, ct.Is3D()) + test.Eq(t, hasM, ct.IsMeasured()) - expectXYEq(t, gotXY1, wantC1.XY) - expectXYEq(t, gotXY2, wantC2.XY) + test.Eq(t, gotXY1, wantC1.XY) + test.Eq(t, gotXY2, wantC2.XY) if ct.Is3D() { - expectFloat64Eq(t, minZ, wantC1.Z) - expectFloat64Eq(t, maxZ, wantC2.Z) + test.Eq(t, minZ, wantC1.Z) + test.Eq(t, maxZ, wantC2.Z) } if ct.IsMeasured() { - expectFloat64Eq(t, minM, wantC1.M) - expectFloat64Eq(t, maxM, wantC2.M) + test.Eq(t, minM, wantC1.M) + test.Eq(t, maxM, wantC2.M) } } }) t.Run("id list", func(t *testing.T) { got, has, err := geom.UnmarshalTWKBIDList(twkb) - expectNoErr(t, err) - expectBoolEq(t, has, tc.hasIDList) - expectInt64SliceEq(t, got, tc.listedIDs) + test.NoErr(t, err) + test.Eq(t, has, tc.hasIDList) + test.DeepEqual(t, got, tc.listedIDs) }) t.Run("size", func(t *testing.T) { @@ -411,10 +414,10 @@ func TestTWKBUnmarshalMarshalValid(t *testing.T) { buf := make([]byte, len(twkb)+extra) copy(buf, twkb) got, ok, err := geom.UnmarshalTWKBSize(buf) - expectNoErr(t, err) - expectBoolEq(t, ok, tc.hasSize) + test.NoErr(t, err) + test.Eq(t, ok, tc.hasSize) if tc.hasSize { - expectIntEq(t, got, len(twkb)) + test.Eq(t, got, len(twkb)) } }) } @@ -427,7 +430,7 @@ func TestTWKBUnmarshalMarshalValid(t *testing.T) { } // Encode the WKT's geometry as TWKB and check its bytes match the expected TWKB bytes. - g := geomFromWKT(t, tc.wkt) + g := test.FromWKT(t, tc.wkt) opts := []geom.TWKBWriterOption{} if tc.hasZ { opts = append(opts, geom.TWKBPrecisionZ(tc.precZ)) @@ -498,7 +501,7 @@ func TestWriteTWKBSQLFile(t *testing.T) { } err := os.WriteFile("../twkb_sql.txt", []byte(sql), 0o600) - expectNoErr(t, err) + test.NoErr(t, err) } func TestZigZagInt(t *testing.T) { diff --git a/geom/type_coordinates_test.go b/geom/type_coordinates_test.go index 6bddb1b5..5ef30500 100644 --- a/geom/type_coordinates_test.go +++ b/geom/type_coordinates_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func TestCoordinatesString(t *testing.T) { @@ -35,7 +36,7 @@ func TestCoordinatesString(t *testing.T) { } { t.Run(strconv.Itoa(i), func(t *testing.T) { got := tc.coords.String() - expectStringEq(t, got, tc.want) + test.Eq(t, got, tc.want) }) } } diff --git a/geom/type_envelope_test.go b/geom/type_envelope_test.go index 45e5a01c..0b64bbec 100644 --- a/geom/type_envelope_test.go +++ b/geom/type_envelope_test.go @@ -7,23 +7,10 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" "github.com/peterstace/simplefeatures/rtree" ) -func onePtEnv(x, y float64) geom.Envelope { - return geom.Envelope{}.ExpandToIncludeXY(geom.XY{X: x, Y: y}) -} - -func twoPtEnv(minX, minY, maxX, maxY float64) geom.Envelope { - if minX > maxX { - panic(fmt.Sprintf("X values out of order: %v %v", minX, maxX)) - } - if minY > maxY { - panic(fmt.Sprintf("Y values out of order: %v %v", minY, maxY)) - } - return onePtEnv(minX, minY).ExpandToIncludeXY(geom.XY{X: maxX, Y: maxY}) -} - func TestEnvelopeNew(t *testing.T) { for _, tc := range []struct { desc string @@ -43,22 +30,22 @@ func TestEnvelopeNew(t *testing.T) { { desc: "single element", xys: []geom.XY{{1, 2}}, - want: onePtEnv(1, 2), + want: geom.NewEnvelopeXY(1, 2), }, { desc: "two same elements", xys: []geom.XY{{1, 2}, {1, 2}}, - want: onePtEnv(1, 2), + want: geom.NewEnvelopeXY(1, 2), }, { desc: "two different elements", xys: []geom.XY{{1, 2}, {-1, 3}}, - want: twoPtEnv(-1, 2, 1, 3), + want: geom.NewEnvelopeXY(-1, 2, 1, 3), }, } { t.Run(tc.desc, func(t *testing.T) { got := geom.NewEnvelope(tc.xys...) - expectEnvEq(t, got, tc.want) + test.Eq(t, got, tc.want) }) } } @@ -88,7 +75,7 @@ func TestEnvelopeAttributes(t *testing.T) { }, { description: "single point", - env: onePtEnv(1, 2), + env: geom.NewEnvelopeXY(1, 2), isEmpty: false, isPoint: true, isLine: false, @@ -103,7 +90,7 @@ func TestEnvelopeAttributes(t *testing.T) { }, { description: "two horizontal points", - env: twoPtEnv(1, 4, 3, 4), + env: geom.NewEnvelopeXY(1, 4, 3, 4), isEmpty: false, isPoint: false, isLine: true, @@ -118,7 +105,7 @@ func TestEnvelopeAttributes(t *testing.T) { }, { description: "two vertical points", - env: twoPtEnv(4, 1, 4, 3), + env: geom.NewEnvelopeXY(4, 1, 4, 3), isEmpty: false, isPoint: false, isLine: true, @@ -133,7 +120,7 @@ func TestEnvelopeAttributes(t *testing.T) { }, { description: "two diagonal points", - env: twoPtEnv(1, 4, 3, 7), + env: geom.NewEnvelopeXY(1, 4, 3, 7), isEmpty: false, isPoint: false, isLine: false, @@ -149,50 +136,50 @@ func TestEnvelopeAttributes(t *testing.T) { } { t.Run(tc.description, func(t *testing.T) { t.Run("IsEmpty", func(t *testing.T) { - expectBoolEq(t, tc.env.IsEmpty(), tc.isEmpty) + test.Eq(t, tc.env.IsEmpty(), tc.isEmpty) }) t.Run("IsPoint", func(t *testing.T) { - expectBoolEq(t, tc.env.IsPoint(), tc.isPoint) + test.Eq(t, tc.env.IsPoint(), tc.isPoint) }) t.Run("IsLine", func(t *testing.T) { - expectBoolEq(t, tc.env.IsLine(), tc.isLine) + test.Eq(t, tc.env.IsLine(), tc.isLine) }) t.Run("IsRectangle", func(t *testing.T) { - expectBoolEq(t, tc.env.IsRectangle(), tc.isRect) + test.Eq(t, tc.env.IsRectangle(), tc.isRect) }) t.Run("Area", func(t *testing.T) { - expectFloat64Eq(t, tc.env.Area(), tc.area) + test.Eq(t, tc.env.Area(), tc.area) }) t.Run("Width", func(t *testing.T) { - expectFloat64Eq(t, tc.env.Width(), tc.width) + test.Eq(t, tc.env.Width(), tc.width) }) t.Run("Height", func(t *testing.T) { - expectFloat64Eq(t, tc.env.Height(), tc.height) + test.Eq(t, tc.env.Height(), tc.height) }) t.Run("Center", func(t *testing.T) { - expectGeomEqWKT(t, tc.env.Center().AsGeometry(), tc.center) + test.ExactEqualsWKT(t, tc.env.Center().AsGeometry(), tc.center) }) t.Run("Min", func(t *testing.T) { - expectGeomEqWKT(t, tc.env.Min().AsGeometry(), tc.min) + test.ExactEqualsWKT(t, tc.env.Min().AsGeometry(), tc.min) }) t.Run("Max", func(t *testing.T) { - expectGeomEqWKT(t, tc.env.Max().AsGeometry(), tc.max) + test.ExactEqualsWKT(t, tc.env.Max().AsGeometry(), tc.max) }) t.Run("MinMaxXYs", func(t *testing.T) { gotMin, gotMax, gotOK := tc.env.MinMaxXYs() - expectBoolEq(t, gotOK, !tc.isEmpty) + test.Eq(t, gotOK, !tc.isEmpty) if gotOK { - wantMin, minOK := geomFromWKT(t, tc.min).MustAsPoint().XY() - expectTrue(t, minOK) - expectXYEq(t, gotMin, wantMin) + wantMin, minOK := test.FromWKT(t, tc.min).MustAsPoint().XY() + test.True(t, minOK) + test.Eq(t, gotMin, wantMin) - wantMax, maxOK := geomFromWKT(t, tc.max).MustAsPoint().XY() - expectTrue(t, maxOK) - expectXYEq(t, gotMax, wantMax) + wantMax, maxOK := test.FromWKT(t, tc.max).MustAsPoint().XY() + test.True(t, maxOK) + test.Eq(t, gotMax, wantMax) } }) t.Run("AsGeometry", func(t *testing.T) { - expectGeomEqWKT(t, tc.env.AsGeometry(), tc.geom, geom.IgnoreOrder) + test.ExactEqualsWKT(t, tc.env.AsGeometry(), tc.geom, geom.IgnoreOrder) }) }) } @@ -201,28 +188,28 @@ func TestEnvelopeAttributes(t *testing.T) { func TestEnvelopeExpandToIncludeXY(t *testing.T) { t.Run("empty", func(t *testing.T) { env := geom.Envelope{}.ExpandToIncludeXY(geom.XY{1, 2}) - expectGeomEqWKT(t, env.Min().AsGeometry(), "POINT(1 2)") - expectGeomEqWKT(t, env.Max().AsGeometry(), "POINT(1 2)") + test.ExactEqualsWKT(t, env.Min().AsGeometry(), "POINT(1 2)") + test.ExactEqualsWKT(t, env.Max().AsGeometry(), "POINT(1 2)") }) t.Run("single point extend to same", func(t *testing.T) { - env := onePtEnv(1, 2).ExpandToIncludeXY(geom.XY{1, 2}) - expectGeomEqWKT(t, env.Min().AsGeometry(), "POINT(1 2)") - expectGeomEqWKT(t, env.Max().AsGeometry(), "POINT(1 2)") + env := geom.NewEnvelopeXY(1, 2).ExpandToIncludeXY(geom.XY{1, 2}) + test.ExactEqualsWKT(t, env.Min().AsGeometry(), "POINT(1 2)") + test.ExactEqualsWKT(t, env.Max().AsGeometry(), "POINT(1 2)") }) t.Run("single point extend to different", func(t *testing.T) { - env := onePtEnv(1, 2).ExpandToIncludeXY(geom.XY{-1, 3}) - expectGeomEqWKT(t, env.Min().AsGeometry(), "POINT(-1 2)") - expectGeomEqWKT(t, env.Max().AsGeometry(), "POINT(1 3)") + env := geom.NewEnvelopeXY(1, 2).ExpandToIncludeXY(geom.XY{-1, 3}) + test.ExactEqualsWKT(t, env.Min().AsGeometry(), "POINT(-1 2)") + test.ExactEqualsWKT(t, env.Max().AsGeometry(), "POINT(1 3)") }) t.Run("area extend within", func(t *testing.T) { - env := twoPtEnv(1, 2, 3, 4).ExpandToIncludeXY(geom.XY{2, 3}) - expectGeomEqWKT(t, env.Min().AsGeometry(), "POINT(1 2)") - expectGeomEqWKT(t, env.Max().AsGeometry(), "POINT(3 4)") + env := geom.NewEnvelopeXY(1, 2, 3, 4).ExpandToIncludeXY(geom.XY{2, 3}) + test.ExactEqualsWKT(t, env.Min().AsGeometry(), "POINT(1 2)") + test.ExactEqualsWKT(t, env.Max().AsGeometry(), "POINT(3 4)") }) t.Run("area extend outside", func(t *testing.T) { - env := twoPtEnv(1, 2, 3, 4).ExpandToIncludeXY(geom.XY{100, 200}) - expectGeomEqWKT(t, env.Min().AsGeometry(), "POINT(1 2)") - expectGeomEqWKT(t, env.Max().AsGeometry(), "POINT(100 200)") + env := geom.NewEnvelopeXY(1, 2, 3, 4).ExpandToIncludeXY(geom.XY{100, 200}) + test.ExactEqualsWKT(t, env.Min().AsGeometry(), "POINT(1 2)") + test.ExactEqualsWKT(t, env.Max().AsGeometry(), "POINT(100 200)") }) } @@ -239,7 +226,7 @@ func TestEnvelopeContains(t *testing.T) { }, }, { - env: onePtEnv(1, 2), + env: geom.NewEnvelopeXY(1, 2), subtests: map[geom.XY]bool{ {}: false, {1, 2}: true, @@ -247,7 +234,7 @@ func TestEnvelopeContains(t *testing.T) { }, }, { - env: twoPtEnv(1, 2, 4, 5), + env: geom.NewEnvelopeXY(1, 2, 4, 5), subtests: func() map[geom.XY]bool { m := map[geom.XY]bool{} for x := 0; x <= 5; x++ { @@ -263,7 +250,7 @@ func TestEnvelopeContains(t *testing.T) { for xy, want := range tc.subtests { t.Run(fmt.Sprintf("xy %v want %v", xy, want), func(t *testing.T) { got := tc.env.Contains(xy) - expectBoolEq(t, got, want) + test.Eq(t, got, want) }) } }) @@ -284,60 +271,60 @@ func TestEnvelopeExpandToIncludeEnvelope(t *testing.T) { }, { desc: "point and empty", - e1: onePtEnv(1, 2), + e1: geom.NewEnvelopeXY(1, 2), e2: geom.Envelope{}, - want: onePtEnv(1, 2), + want: geom.NewEnvelopeXY(1, 2), }, { desc: "rect and empty", - e1: twoPtEnv(1, 1, 2, 2), + e1: geom.NewEnvelopeXY(1, 1, 2, 2), e2: geom.Envelope{}, - want: twoPtEnv(1, 1, 2, 2), + want: geom.NewEnvelopeXY(1, 1, 2, 2), }, { desc: "same point", - e1: onePtEnv(1, 2), - e2: onePtEnv(1, 2), - want: onePtEnv(1, 2), + e1: geom.NewEnvelopeXY(1, 2), + e2: geom.NewEnvelopeXY(1, 2), + want: geom.NewEnvelopeXY(1, 2), }, { desc: "same rect", - e1: twoPtEnv(1, 1, 2, 2), - e2: twoPtEnv(1, 1, 2, 2), - want: twoPtEnv(1, 1, 2, 2), + e1: geom.NewEnvelopeXY(1, 1, 2, 2), + e2: geom.NewEnvelopeXY(1, 1, 2, 2), + want: geom.NewEnvelopeXY(1, 1, 2, 2), }, { desc: "point and point", - e1: onePtEnv(1, 2), - e2: onePtEnv(-1, 3), - want: twoPtEnv(-1, 2, 1, 3), + e1: geom.NewEnvelopeXY(1, 2), + e2: geom.NewEnvelopeXY(-1, 3), + want: geom.NewEnvelopeXY(-1, 2, 1, 3), }, { desc: "point and rect", - e1: twoPtEnv(1, 1, 2, 2), - e2: onePtEnv(3, 1), - want: twoPtEnv(1, 1, 3, 2), + e1: geom.NewEnvelopeXY(1, 1, 2, 2), + e2: geom.NewEnvelopeXY(3, 1), + want: geom.NewEnvelopeXY(1, 1, 3, 2), }, { desc: "rect inside other", - e1: twoPtEnv(1, 11, 4, 14), - e2: twoPtEnv(2, 12, 3, 13), - want: twoPtEnv(1, 11, 4, 14), + e1: geom.NewEnvelopeXY(1, 11, 4, 14), + e2: geom.NewEnvelopeXY(2, 12, 3, 13), + want: geom.NewEnvelopeXY(1, 11, 4, 14), }, { desc: "rect overlapping corner", - e1: twoPtEnv(1, 11, 3, 13), - e2: twoPtEnv(2, 12, 4, 14), - want: twoPtEnv(1, 11, 4, 14), + e1: geom.NewEnvelopeXY(1, 11, 3, 13), + e2: geom.NewEnvelopeXY(2, 12, 4, 14), + want: geom.NewEnvelopeXY(1, 11, 4, 14), }, } { t.Run(tc.desc+" fwd", func(t *testing.T) { got := tc.e1.ExpandToIncludeEnvelope(tc.e2) - expectEnvEq(t, got, tc.want) + test.Eq(t, got, tc.want) }) t.Run(tc.desc+" rev", func(t *testing.T) { got := tc.e2.ExpandToIncludeEnvelope(tc.e1) - expectEnvEq(t, got, tc.want) + test.Eq(t, got, tc.want) }) } } @@ -363,37 +350,37 @@ func TestEnvelopeInvalidXYInteractions(t *testing.T) { } { t.Run(fmt.Sprintf("new_envelope_with_first_arg_invalid_%d", i), func(t *testing.T) { env := geom.NewEnvelope(tc.xy) - expectValidity(t, env, tc.violation) + expectRuleViolation(t, env, tc.violation) }) t.Run(fmt.Sprintf("new_envelope_with_second_arg_invalid_%d", i), func(t *testing.T) { env := geom.NewEnvelope(geom.XY{}, tc.xy) - expectValidity(t, env, tc.violation) + expectRuleViolation(t, env, tc.violation) }) t.Run(fmt.Sprintf("expand_to_include_invalid_xy_%d", i), func(t *testing.T) { env := geom.NewEnvelope(geom.XY{-1, -1}, geom.XY{1, 1}) env = env.ExpandToIncludeXY(tc.xy) - expectValidity(t, env, tc.violation) + expectRuleViolation(t, env, tc.violation) }) t.Run(fmt.Sprintf("expand_from_invalid_to_include_env_%d", i), func(t *testing.T) { env := geom.NewEnvelope(tc.xy) env = env.ExpandToIncludeXY(geom.XY{1, 1}) - expectValidity(t, env, tc.violation) + expectRuleViolation(t, env, tc.violation) }) t.Run(fmt.Sprintf("expand_to_include_invalid_env_%d", i), func(t *testing.T) { base := geom.NewEnvelope(geom.XY{-1, -1}, geom.XY{1, 1}) other := geom.NewEnvelope(tc.xy) env := base.ExpandToIncludeEnvelope(other) - expectValidity(t, env, tc.violation) + expectRuleViolation(t, env, tc.violation) }) t.Run(fmt.Sprintf("expand_from_invalid_to_include_env_%d", i), func(t *testing.T) { base := geom.NewEnvelope(tc.xy) other := geom.NewEnvelope(geom.XY{-1, -1}, geom.XY{1, 1}) env := base.ExpandToIncludeEnvelope(other) - expectValidity(t, env, tc.violation) + expectRuleViolation(t, env, tc.violation) }) t.Run(fmt.Sprintf("contains_invalid_xy_%d", i), func(t *testing.T) { env := geom.NewEnvelope(geom.XY{-1, -1}, geom.XY{1, 1}) - expectFalse(t, env.Contains(tc.xy)) + test.False(t, env.Contains(tc.xy)) }) } } @@ -407,42 +394,42 @@ func TestEnvelopeIntersects(t *testing.T) { {geom.Envelope{}, geom.Envelope{}, false}, // Empty vs non-empty. - {geom.Envelope{}, onePtEnv(0, 0), false}, - {geom.Envelope{}, twoPtEnv(0, 0, 1, 1), false}, + {geom.Envelope{}, geom.NewEnvelopeXY(0, 0), false}, + {geom.Envelope{}, geom.NewEnvelopeXY(0, 0, 1, 1), false}, // Single pt vs single pt. - {onePtEnv(0, 0), onePtEnv(0, 0), true}, - {onePtEnv(1, 2), onePtEnv(1, 2), true}, - {onePtEnv(1, 2), onePtEnv(1, 3), false}, - {onePtEnv(1, 2), onePtEnv(2, 2), false}, + {geom.NewEnvelopeXY(0, 0), geom.NewEnvelopeXY(0, 0), true}, + {geom.NewEnvelopeXY(1, 2), geom.NewEnvelopeXY(1, 2), true}, + {geom.NewEnvelopeXY(1, 2), geom.NewEnvelopeXY(1, 3), false}, + {geom.NewEnvelopeXY(1, 2), geom.NewEnvelopeXY(2, 2), false}, // Single pt vs rect. - {onePtEnv(0, 0), twoPtEnv(0, 0, 1, 1), true}, - {onePtEnv(1, 1), twoPtEnv(0, 0, 1, 1), true}, - {onePtEnv(0, 1), twoPtEnv(0, 0, 1, 1), true}, - {onePtEnv(1, 0), twoPtEnv(0, 0, 1, 1), true}, - {onePtEnv(0.5, 0.5), twoPtEnv(0, 0, 1, 1), true}, - {onePtEnv(0.5, 1.5), twoPtEnv(0, 0, 1, 1), false}, + {geom.NewEnvelopeXY(0, 0), geom.NewEnvelopeXY(0, 0, 1, 1), true}, + {geom.NewEnvelopeXY(1, 1), geom.NewEnvelopeXY(0, 0, 1, 1), true}, + {geom.NewEnvelopeXY(0, 1), geom.NewEnvelopeXY(0, 0, 1, 1), true}, + {geom.NewEnvelopeXY(1, 0), geom.NewEnvelopeXY(0, 0, 1, 1), true}, + {geom.NewEnvelopeXY(0.5, 0.5), geom.NewEnvelopeXY(0, 0, 1, 1), true}, + {geom.NewEnvelopeXY(0.5, 1.5), geom.NewEnvelopeXY(0, 0, 1, 1), false}, // Rect vs Rect. - {twoPtEnv(0, 0, 1, 1), twoPtEnv(2, 2, 3, 3), false}, - {twoPtEnv(0, 2, 1, 3), twoPtEnv(2, 0, 3, 1), false}, - {twoPtEnv(0, 0, 1, 1), twoPtEnv(1, 1, 2, 2), true}, - {twoPtEnv(0, 1, 1, 2), twoPtEnv(1, 0, 2, 1), true}, - {twoPtEnv(0, 0, 2, 2), twoPtEnv(1, 1, 3, 3), true}, - {twoPtEnv(0, 1, 2, 3), twoPtEnv(1, 0, 3, 2), true}, - {twoPtEnv(0, 0, 2, 1), twoPtEnv(1, 0, 3, 1), true}, - {twoPtEnv(0, 0, 1, 2), twoPtEnv(0, 1, 1, 3), true}, - {twoPtEnv(0, 0, 2, 2), twoPtEnv(1, -1, 3, 3), true}, - {twoPtEnv(0, 0, 2, 2), twoPtEnv(1, -1, 3, 3), true}, - {twoPtEnv(-1, 0, 2, 1), twoPtEnv(0, -1, 1, 2), true}, - {twoPtEnv(0, 0, 1, 1), twoPtEnv(-1, -1, 2, 2), true}, - {twoPtEnv(0, 0, 1, 1), twoPtEnv(1, 0, 2, 1), true}, - {twoPtEnv(0, 0, 1, 1), twoPtEnv(0, 1, 1, 2), true}, - {twoPtEnv(0, 0, 1, 1), twoPtEnv(2, 0, 3, 1), false}, - {twoPtEnv(0, 0, 1, 1), twoPtEnv(0, 2, 1, 3), false}, - {twoPtEnv(0, 0, 1, 1), twoPtEnv(2, -1, 3, 2), false}, - {twoPtEnv(0, 0, 1, 1), twoPtEnv(-1, -2, 2, -1), false}, + {geom.NewEnvelopeXY(0, 0, 1, 1), geom.NewEnvelopeXY(2, 2, 3, 3), false}, + {geom.NewEnvelopeXY(0, 2, 1, 3), geom.NewEnvelopeXY(2, 0, 3, 1), false}, + {geom.NewEnvelopeXY(0, 0, 1, 1), geom.NewEnvelopeXY(1, 1, 2, 2), true}, + {geom.NewEnvelopeXY(0, 1, 1, 2), geom.NewEnvelopeXY(1, 0, 2, 1), true}, + {geom.NewEnvelopeXY(0, 0, 2, 2), geom.NewEnvelopeXY(1, 1, 3, 3), true}, + {geom.NewEnvelopeXY(0, 1, 2, 3), geom.NewEnvelopeXY(1, 0, 3, 2), true}, + {geom.NewEnvelopeXY(0, 0, 2, 1), geom.NewEnvelopeXY(1, 0, 3, 1), true}, + {geom.NewEnvelopeXY(0, 0, 1, 2), geom.NewEnvelopeXY(0, 1, 1, 3), true}, + {geom.NewEnvelopeXY(0, 0, 2, 2), geom.NewEnvelopeXY(1, -1, 3, 3), true}, + {geom.NewEnvelopeXY(0, 0, 2, 2), geom.NewEnvelopeXY(1, -1, 3, 3), true}, + {geom.NewEnvelopeXY(-1, 0, 2, 1), geom.NewEnvelopeXY(0, -1, 1, 2), true}, + {geom.NewEnvelopeXY(0, 0, 1, 1), geom.NewEnvelopeXY(-1, -1, 2, 2), true}, + {geom.NewEnvelopeXY(0, 0, 1, 1), geom.NewEnvelopeXY(1, 0, 2, 1), true}, + {geom.NewEnvelopeXY(0, 0, 1, 1), geom.NewEnvelopeXY(0, 1, 1, 2), true}, + {geom.NewEnvelopeXY(0, 0, 1, 1), geom.NewEnvelopeXY(2, 0, 3, 1), false}, + {geom.NewEnvelopeXY(0, 0, 1, 1), geom.NewEnvelopeXY(0, 2, 1, 3), false}, + {geom.NewEnvelopeXY(0, 0, 1, 1), geom.NewEnvelopeXY(2, -1, 3, 2), false}, + {geom.NewEnvelopeXY(0, 0, 1, 1), geom.NewEnvelopeXY(-1, -2, 2, -1), false}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { got1 := tt.e1.Intersects(tt.e2) @@ -466,38 +453,38 @@ func TestEnvelopeCovers(t *testing.T) { {geom.Envelope{}, geom.Envelope{}, false}, // Empty vs single pt. - {geom.Envelope{}, onePtEnv(1, 2), false}, - {onePtEnv(1, 2), geom.Envelope{}, false}, - {geom.Envelope{}, onePtEnv(0, 0), false}, - {onePtEnv(0, 0), geom.Envelope{}, false}, + {geom.Envelope{}, geom.NewEnvelopeXY(1, 2), false}, + {geom.NewEnvelopeXY(1, 2), geom.Envelope{}, false}, + {geom.Envelope{}, geom.NewEnvelopeXY(0, 0), false}, + {geom.NewEnvelopeXY(0, 0), geom.Envelope{}, false}, // Empty vs rect. - {geom.Envelope{}, twoPtEnv(1, 2, 3, 4), false}, - {twoPtEnv(1, 2, 3, 4), geom.Envelope{}, false}, + {geom.Envelope{}, geom.NewEnvelopeXY(1, 2, 3, 4), false}, + {geom.NewEnvelopeXY(1, 2, 3, 4), geom.Envelope{}, false}, // Single pt vs single pt. - {onePtEnv(1, 2), onePtEnv(1, 2), true}, - {onePtEnv(1, 2), onePtEnv(3, 2), false}, - {onePtEnv(1, 2), onePtEnv(1, 3), false}, - {onePtEnv(1, 2), onePtEnv(3, 3), false}, + {geom.NewEnvelopeXY(1, 2), geom.NewEnvelopeXY(1, 2), true}, + {geom.NewEnvelopeXY(1, 2), geom.NewEnvelopeXY(3, 2), false}, + {geom.NewEnvelopeXY(1, 2), geom.NewEnvelopeXY(1, 3), false}, + {geom.NewEnvelopeXY(1, 2), geom.NewEnvelopeXY(3, 3), false}, // Single pt vs single rect. - {onePtEnv(1, 2), twoPtEnv(1, 2, 3, 4), false}, - {onePtEnv(1, 2), twoPtEnv(0, 0, 3, 3), false}, - {twoPtEnv(0, 0, 3, 3), onePtEnv(1, 2), true}, - {twoPtEnv(0, 0, 3, 3), onePtEnv(0, 0), true}, - {twoPtEnv(0, 0, 3, 3), onePtEnv(3, 3), true}, - {twoPtEnv(0, 0, 3, 3), onePtEnv(0, 3), true}, - {twoPtEnv(0, 0, 3, 3), onePtEnv(3, 4), false}, - {twoPtEnv(0, 0, 3, 3), onePtEnv(4, 3), false}, + {geom.NewEnvelopeXY(1, 2), geom.NewEnvelopeXY(1, 2, 3, 4), false}, + {geom.NewEnvelopeXY(1, 2), geom.NewEnvelopeXY(0, 0, 3, 3), false}, + {geom.NewEnvelopeXY(0, 0, 3, 3), geom.NewEnvelopeXY(1, 2), true}, + {geom.NewEnvelopeXY(0, 0, 3, 3), geom.NewEnvelopeXY(0, 0), true}, + {geom.NewEnvelopeXY(0, 0, 3, 3), geom.NewEnvelopeXY(3, 3), true}, + {geom.NewEnvelopeXY(0, 0, 3, 3), geom.NewEnvelopeXY(0, 3), true}, + {geom.NewEnvelopeXY(0, 0, 3, 3), geom.NewEnvelopeXY(3, 4), false}, + {geom.NewEnvelopeXY(0, 0, 3, 3), geom.NewEnvelopeXY(4, 3), false}, // Rect vs Rect - {twoPtEnv(0, 0, 1, 1), twoPtEnv(2, 0, 3, 1), false}, - {twoPtEnv(0, 0, 2, 2), twoPtEnv(1, 1, 3, 3), false}, - {twoPtEnv(0, 0, 3, 3), twoPtEnv(1, 1, 2, 2), true}, - {twoPtEnv(0, 0, 2, 2), twoPtEnv(1, 1, 2, 2), true}, - {twoPtEnv(1, 1, 2, 2), twoPtEnv(0, 0, 3, 3), false}, - {twoPtEnv(1, 1, 2, 2), twoPtEnv(0, 0, 2, 2), false}, + {geom.NewEnvelopeXY(0, 0, 1, 1), geom.NewEnvelopeXY(2, 0, 3, 1), false}, + {geom.NewEnvelopeXY(0, 0, 2, 2), geom.NewEnvelopeXY(1, 1, 3, 3), false}, + {geom.NewEnvelopeXY(0, 0, 3, 3), geom.NewEnvelopeXY(1, 1, 2, 2), true}, + {geom.NewEnvelopeXY(0, 0, 2, 2), geom.NewEnvelopeXY(1, 1, 2, 2), true}, + {geom.NewEnvelopeXY(1, 1, 2, 2), geom.NewEnvelopeXY(0, 0, 3, 3), false}, + {geom.NewEnvelopeXY(1, 1, 2, 2), geom.NewEnvelopeXY(0, 0, 2, 2), false}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { got := tt.env1.Covers(tt.env2) @@ -512,13 +499,13 @@ func TestEnvelopeDistance(t *testing.T) { t.Run("empty", func(t *testing.T) { t.Run("both", func(t *testing.T) { _, ok := geom.Envelope{}.Distance(geom.Envelope{}) - expectFalse(t, ok) + test.False(t, ok) }) t.Run("only one", func(t *testing.T) { - _, ok := geom.Envelope{}.Distance(onePtEnv(1, 2)) - expectFalse(t, ok) - _, ok = onePtEnv(1, 2).Distance(geom.Envelope{}) - expectFalse(t, ok) + _, ok := geom.Envelope{}.Distance(geom.NewEnvelopeXY(1, 2)) + test.False(t, ok) + _, ok = geom.NewEnvelopeXY(1, 2).Distance(geom.Envelope{}) + test.False(t, ok) }) }) t.Run("non-empty", func(t *testing.T) { @@ -527,33 +514,33 @@ func TestEnvelopeDistance(t *testing.T) { want float64 }{ // Pt vs pt. - {onePtEnv(3, 0), onePtEnv(4, 0), 1}, - {onePtEnv(3, 0), onePtEnv(3, 1), 1}, - {onePtEnv(3, 0), onePtEnv(4, 1), math.Sqrt(2)}, + {geom.NewEnvelopeXY(3, 0), geom.NewEnvelopeXY(4, 0), 1}, + {geom.NewEnvelopeXY(3, 0), geom.NewEnvelopeXY(3, 1), 1}, + {geom.NewEnvelopeXY(3, 0), geom.NewEnvelopeXY(4, 1), math.Sqrt(2)}, // Pt vs rect. - {onePtEnv(2, 1), twoPtEnv(1, 2, 3, 4), 1}, - {onePtEnv(2, 1), twoPtEnv(2, 2, 3, 3), 1}, - {onePtEnv(2, 1), twoPtEnv(3, 2, 4, 3), math.Sqrt(2)}, + {geom.NewEnvelopeXY(2, 1), geom.NewEnvelopeXY(1, 2, 3, 4), 1}, + {geom.NewEnvelopeXY(2, 1), geom.NewEnvelopeXY(2, 2, 3, 3), 1}, + {geom.NewEnvelopeXY(2, 1), geom.NewEnvelopeXY(3, 2, 4, 3), math.Sqrt(2)}, // Rect vs rect. - {twoPtEnv(0, 0, 2, 2), twoPtEnv(1, 1, 3, 3), 0}, - {twoPtEnv(0, 0, 1, 1), twoPtEnv(2, 0, 2, 1), 1}, - {twoPtEnv(0, 0, 1, 1), twoPtEnv(0, 3, 1, 4), 2}, - {twoPtEnv(0, 0, 1, 1), twoPtEnv(2, 2, 3, 3), math.Sqrt(2)}, - {twoPtEnv(0, 2, 1, 3), twoPtEnv(2, 0, 3, 1), math.Sqrt(2)}, - {twoPtEnv(0, 0, 1, 1), twoPtEnv(1, 1, 2, 2), 0}, - {twoPtEnv(0, 1, 1, 2), twoPtEnv(1, 0, 2, 1), 0}, - {twoPtEnv(0, 0, 1, 1), twoPtEnv(1, 0, 2, 1), 0}, - {twoPtEnv(0, 0, 1, 1), twoPtEnv(0, 1, 1, 2), 0}, + {geom.NewEnvelopeXY(0, 0, 2, 2), geom.NewEnvelopeXY(1, 1, 3, 3), 0}, + {geom.NewEnvelopeXY(0, 0, 1, 1), geom.NewEnvelopeXY(2, 0, 2, 1), 1}, + {geom.NewEnvelopeXY(0, 0, 1, 1), geom.NewEnvelopeXY(0, 3, 1, 4), 2}, + {geom.NewEnvelopeXY(0, 0, 1, 1), geom.NewEnvelopeXY(2, 2, 3, 3), math.Sqrt(2)}, + {geom.NewEnvelopeXY(0, 2, 1, 3), geom.NewEnvelopeXY(2, 0, 3, 1), math.Sqrt(2)}, + {geom.NewEnvelopeXY(0, 0, 1, 1), geom.NewEnvelopeXY(1, 1, 2, 2), 0}, + {geom.NewEnvelopeXY(0, 1, 1, 2), geom.NewEnvelopeXY(1, 0, 2, 1), 0}, + {geom.NewEnvelopeXY(0, 0, 1, 1), geom.NewEnvelopeXY(1, 0, 2, 1), 0}, + {geom.NewEnvelopeXY(0, 0, 1, 1), geom.NewEnvelopeXY(0, 1, 1, 2), 0}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { got1, ok1 := tt.env1.Distance(tt.env2) got2, ok2 := tt.env2.Distance(tt.env1) - expectTrue(t, ok1) - expectTrue(t, ok2) - expectFloat64Eq(t, got1, got2) - expectFloat64Eq(t, got1, tt.want) + test.True(t, ok1) + test.True(t, ok2) + test.Eq(t, got1, got2) + test.Eq(t, got1, tt.want) }) } }) @@ -568,12 +555,12 @@ func TestEnvelopeTransformXY(t *testing.T) { want geom.Envelope }{ {geom.Envelope{}, geom.Envelope{}}, - {onePtEnv(1, 2), onePtEnv(1.5, 5)}, - {twoPtEnv(1, 2, 3, 4), twoPtEnv(1.5, 5, 4.5, 10)}, + {geom.NewEnvelopeXY(1, 2), geom.NewEnvelopeXY(1.5, 5)}, + {geom.NewEnvelopeXY(1, 2, 3, 4), geom.NewEnvelopeXY(1.5, 5, 4.5, 10)}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { got := tc.input.TransformXY(transform) - expectEnvEq(t, got, tc.want) + test.Eq(t, got, tc.want) }) } } @@ -581,15 +568,15 @@ func TestEnvelopeTransformXY(t *testing.T) { func TestEnvelopeTransformBugFix(t *testing.T) { // Reproduces a bug where a transform that alters which coordinates are min // and max causes a malformed envelope. - env := twoPtEnv(1, 2, 3, 4) + env := geom.NewEnvelopeXY(1, 2, 3, 4) got := env.TransformXY(func(in geom.XY) geom.XY { return geom.XY{-in.X, -in.Y} }) - expectEnvEq(t, got, twoPtEnv(-3, -4, -1, -2)) + test.Eq(t, got, geom.NewEnvelopeXY(-3, -4, -1, -2)) } func BenchmarkEnvelopeTransformXY(b *testing.B) { - input := twoPtEnv(1, 2, 3, 4) + input := geom.NewEnvelopeXY(1, 2, 3, 4) b.ResetTimer() for i := 0; i < b.N; i++ { input.TransformXY(func(in geom.XY) geom.XY { @@ -619,21 +606,21 @@ func TestBoundingDiagonal(t *testing.T) { t.Run(strconv.Itoa(i), func(t *testing.T) { env := geom.NewEnvelope(tc.env...) got := env.BoundingDiagonal() - expectGeomEqWKT(t, got, tc.want) + test.ExactEqualsWKT(t, got, tc.want) }) } } func TestEnvelopeEmptyAsBox(t *testing.T) { _, ok := geom.Envelope{}.AsBox() - expectFalse(t, ok) + test.False(t, ok) } func TestEnvelopeNonEmptyAsBox(t *testing.T) { - got, ok := twoPtEnv(1, 2, 3, 4).AsBox() - expectTrue(t, ok) + got, ok := geom.NewEnvelopeXY(1, 2, 3, 4).AsBox() + test.True(t, ok) want := rtree.Box{MinX: 1, MinY: 2, MaxX: 3, MaxY: 4} - expectTrue(t, got == want) + test.True(t, got == want) } func TestEnvelopeString(t *testing.T) { @@ -642,12 +629,12 @@ func TestEnvelopeString(t *testing.T) { want string }{ {geom.Envelope{}, "ENVELOPE EMPTY"}, - {twoPtEnv(1.5, 2.5, 3.5, 4.5), "ENVELOPE(1.5 2.5,3.5 4.5)"}, + {geom.NewEnvelopeXY(1.5, 2.5, 3.5, 4.5), "ENVELOPE(1.5 2.5,3.5 4.5)"}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { got := fmt.Sprintf("%v", tc.env) t.Log(got) - expectTrue(t, got == tc.want) + test.True(t, got == tc.want) }) } } diff --git a/geom/type_geometry_deep_equal_test.go b/geom/type_geometry_deep_equal_test.go index 091f0f6d..6b7af029 100644 --- a/geom/type_geometry_deep_equal_test.go +++ b/geom/type_geometry_deep_equal_test.go @@ -32,8 +32,8 @@ func TestReflectDeepEqualWorks(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - g1 := geomFromWKT(t, tt.wkt) - g2 := geomFromWKT(t, tt.wkt) + g1 := test.FromWKT(t, tt.wkt) + g2 := test.FromWKT(t, tt.wkt) test.ExactEquals(t, g1, g2) // The geometries are semantically equal. test.DeepEqual(t, g1, g2) // And reflect.DeepEqual works as expected. }) @@ -49,7 +49,7 @@ func TestReflectDeepEqualWorksForZeroValue(t *testing.T) { var g1 geom.Geometry // Constructed from WKT. - g2 := geomFromWKT(t, "GEOMETRYCOLLECTION EMPTY") + g2 := test.FromWKT(t, "GEOMETRYCOLLECTION EMPTY") // Manually constructed with nil slice. g3 := geom.NewGeometryCollection(nil).AsGeometry() diff --git a/geom/type_geometry_test.go b/geom/type_geometry_test.go index d21ce7d6..faf68ad4 100644 --- a/geom/type_geometry_test.go +++ b/geom/type_geometry_test.go @@ -8,38 +8,39 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func TestZeroGeometry(t *testing.T) { var z geom.Geometry - expectBoolEq(t, z.IsGeometryCollection(), true) + test.Eq(t, z.IsGeometryCollection(), true) z.MustAsGeometryCollection() // Doesn't crash. - expectStringEq(t, z.AsText(), "GEOMETRYCOLLECTION EMPTY") + test.Eq(t, z.AsText(), "GEOMETRYCOLLECTION EMPTY") gc, ok := z.AsGeometryCollection() - expectTrue(t, ok) - expectIntEq(t, gc.NumGeometries(), 0) + test.True(t, ok) + test.Eq(t, gc.NumGeometries(), 0) var buf bytes.Buffer err := json.NewEncoder(&buf).Encode(z) - expectNoErr(t, err) - expectStringEq(t, strings.TrimSpace(buf.String()), `{"type":"GeometryCollection","geometries":[]}`) + test.NoErr(t, err) + test.Eq(t, strings.TrimSpace(buf.String()), `{"type":"GeometryCollection","geometries":[]}`) pt := geom.XY{1, 2}.AsPoint() z = pt.AsGeometry() // Set away from zero value - expectBoolEq(t, z.IsPoint(), true) + test.Eq(t, z.IsPoint(), true) err = json.NewDecoder(&buf).Decode(&z) - expectNoErr(t, err) - expectBoolEq(t, z.IsPoint(), false) - expectBoolEq(t, z.IsGeometryCollection(), true) - expectBoolEq(t, z.IsEmpty(), true) + test.NoErr(t, err) + test.Eq(t, z.IsPoint(), false) + test.Eq(t, z.IsGeometryCollection(), true) + test.Eq(t, z.IsEmpty(), true) z = geom.Geometry{} _ = z.AsBinary() // Doesn't crash _, err = z.Value() - expectNoErr(t, err) + test.NoErr(t, err) - expectIntEq(t, z.Dimension(), -1) + test.Eq(t, z.Dimension(), -1) } func TestGeometryType(t *testing.T) { @@ -66,7 +67,7 @@ func TestGeometryType(t *testing.T) { } { t.Run(strconv.Itoa(i), func(t *testing.T) { t.Log("wkt:", tt.wkt) - g := geomFromWKT(t, tt.wkt) + g := test.FromWKT(t, tt.wkt) if tt.geoType != g.Type() { t.Errorf("expect: %s, got %s", tt.geoType, g.Type()) } @@ -90,7 +91,7 @@ func TestGeometryTypeString(t *testing.T) { } { t.Run(tc.want, func(t *testing.T) { got := tc.typ.String() - expectStringEq(t, got, tc.want) + test.Eq(t, got, tc.want) }) } } @@ -106,69 +107,69 @@ func TestAsConcreteType(t *testing.T) { "MULTIPOLYGON(((0 0,0 1,1 0,0 0)))", } { t.Run(wkt, func(t *testing.T) { - g := geomFromWKT(t, wkt) + g := test.FromWKT(t, wkt) if g.IsGeometryCollection() { concrete, ok := g.AsGeometryCollection() - expectTrue(t, ok) - expectFalse(t, concrete.IsEmpty()) + test.True(t, ok) + test.False(t, concrete.IsEmpty()) } else { _, ok := g.AsGeometryCollection() - expectFalse(t, ok) + test.False(t, ok) } if g.IsPoint() { concrete, ok := g.AsPoint() - expectTrue(t, ok) - expectFalse(t, concrete.IsEmpty()) + test.True(t, ok) + test.False(t, concrete.IsEmpty()) } else { _, ok := g.AsPoint() - expectFalse(t, ok) + test.False(t, ok) } if g.IsLineString() { concrete, ok := g.AsLineString() - expectTrue(t, ok) - expectFalse(t, concrete.IsEmpty()) + test.True(t, ok) + test.False(t, concrete.IsEmpty()) } else { _, ok := g.AsLineString() - expectFalse(t, ok) + test.False(t, ok) } if g.IsPolygon() { concrete, ok := g.AsPolygon() - expectTrue(t, ok) - expectFalse(t, concrete.IsEmpty()) + test.True(t, ok) + test.False(t, concrete.IsEmpty()) } else { _, ok := g.AsPolygon() - expectFalse(t, ok) + test.False(t, ok) } if g.IsMultiPoint() { concrete, ok := g.AsMultiPoint() - expectTrue(t, ok) - expectFalse(t, concrete.IsEmpty()) + test.True(t, ok) + test.False(t, concrete.IsEmpty()) } else { _, ok := g.AsMultiPoint() - expectFalse(t, ok) + test.False(t, ok) } if g.IsMultiLineString() { concrete, ok := g.AsMultiLineString() - expectTrue(t, ok) - expectFalse(t, concrete.IsEmpty()) + test.True(t, ok) + test.False(t, concrete.IsEmpty()) } else { _, ok := g.AsMultiLineString() - expectFalse(t, ok) + test.False(t, ok) } if g.IsMultiPolygon() { concrete, ok := g.AsMultiPolygon() - expectTrue(t, ok) - expectFalse(t, concrete.IsEmpty()) + test.True(t, ok) + test.False(t, concrete.IsEmpty()) } else { _, ok := g.AsMultiPolygon() - expectFalse(t, ok) + test.False(t, ok) } }) } @@ -211,7 +212,7 @@ func TestGeometryIsCWandIsCCW(t *testing.T) { } { t.Run(strconv.Itoa(i), func(t *testing.T) { t.Log("wkt:", tt.wkt) - g := geomFromWKT(t, tt.wkt) + g := test.FromWKT(t, tt.wkt) if tt.geoType != g.Type() { t.Errorf("expect: type %s, got %s", tt.geoType, g.Type()) } diff --git a/geom/type_null_geometry_test.go b/geom/type_null_geometry_test.go index a6d444f7..ca9753c6 100644 --- a/geom/type_null_geometry_test.go +++ b/geom/type_null_geometry_test.go @@ -6,10 +6,11 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func TestNullGeometryScan(t *testing.T) { - wkb := geomFromWKT(t, "POINT(1 2)").AsBinary() + wkb := test.FromWKT(t, "POINT(1 2)").AsBinary() for _, tc := range []struct { description string @@ -39,10 +40,10 @@ func TestNullGeometryScan(t *testing.T) { var ng geom.NullGeometry scn := sql.Scanner(&ng) err := scn.Scan(tc.value) - expectNoErr(t, err) - expectBoolEq(t, tc.wantValid, ng.Valid) + test.NoErr(t, err) + test.Eq(t, tc.wantValid, ng.Valid) if tc.wantValid { - expectGeomEq(t, ng.Geometry, geomFromWKT(t, tc.wantWKT)) + test.ExactEquals(t, ng.Geometry, test.FromWKT(t, tc.wantWKT)) } }) } @@ -61,14 +62,14 @@ func TestNullGeometryValue(t *testing.T) { }, { description: "point geometry", - input: geom.NullGeometry{Valid: true, Geometry: geomFromWKT(t, "POINT(1 2)")}, - want: geomFromWKT(t, "POINT(1 2)").AsBinary(), + input: geom.NullGeometry{Valid: true, Geometry: test.FromWKT(t, "POINT(1 2)")}, + want: test.FromWKT(t, "POINT(1 2)").AsBinary(), }, } { t.Run(tc.description, func(t *testing.T) { valuer := driver.Valuer(tc.input) got, err := valuer.Value() - expectNoErr(t, err) + test.NoErr(t, err) if got == nil { if tc.want != nil { t.Fatalf("got nil but didn't want nil") @@ -79,7 +80,7 @@ func TestNullGeometryValue(t *testing.T) { if !ok { t.Fatalf("didn't get bytes") } - expectBytesEq(t, gotBytes, tc.want) + test.DeepEqual(t, gotBytes, tc.want) }) } } diff --git a/geom/type_sequence_test.go b/geom/type_sequence_test.go index c5ba24b8..75f2afff 100644 --- a/geom/type_sequence_test.go +++ b/geom/type_sequence_test.go @@ -6,12 +6,13 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func TestSequenceZeroValue(t *testing.T) { var seq geom.Sequence - expectIntEq(t, seq.Length(), 0) - expectCoordinatesTypeEq(t, seq.CoordinatesType(), geom.DimXY) + test.Eq(t, seq.Length(), 0) + test.Eq(t, seq.CoordinatesType(), geom.DimXY) } func TestSequenceCoordinatesType(t *testing.T) { @@ -23,7 +24,7 @@ func TestSequenceCoordinatesType(t *testing.T) { } { t.Run(ct.String(), func(t *testing.T) { seq := geom.NewSequence(nil, ct) - expectCoordinatesTypeEq(t, seq.CoordinatesType(), ct) + test.Eq(t, seq.CoordinatesType(), ct) }) } } @@ -31,15 +32,15 @@ func TestSequenceCoordinatesType(t *testing.T) { func checkSequence(t *testing.T, seq geom.Sequence, coords []geom.Coordinates) { t.Helper() gotLen := seq.Length() - expectIntEq(t, gotLen, len(coords)) - expectPanics(t, func() { seq.Get(-1) }) - expectPanics(t, func() { seq.GetXY(-1) }) + test.Eq(t, gotLen, len(coords)) + test.Panics(t, func() { seq.Get(-1) }) + test.Panics(t, func() { seq.GetXY(-1) }) for j, c := range coords { - expectCoordsEq(t, c, seq.Get(j)) - expectXYEq(t, c.XY, seq.GetXY(j)) + test.Eq(t, c, seq.Get(j)) + test.Eq(t, c.XY, seq.GetXY(j)) } - expectPanics(t, func() { seq.Get(len(coords)) }) - expectPanics(t, func() { seq.GetXY(len(coords)) }) + test.Panics(t, func() { seq.Get(len(coords)) }) + test.Panics(t, func() { seq.GetXY(len(coords)) }) } func TestSequenceLengthAndGet(t *testing.T) { @@ -175,7 +176,7 @@ func TestSequenceLengthAndGet(t *testing.T) { } forced := tt.seq.ForceCoordinatesType(ct) checkSequence(t, forced, wantCoords) - expectCoordinatesTypeEq(t, forced.CoordinatesType(), ct) + test.Eq(t, forced.CoordinatesType(), ct) }) } }) @@ -189,17 +190,17 @@ func TestSequencEnvelope(t *testing.T) { want geom.Envelope }{ {nil, geom.DimXY, geom.Envelope{}}, - {[]float64{1, 2}, geom.DimXY, onePtEnv(1, 2)}, - {[]float64{3, 2, 1, 4}, geom.DimXY, twoPtEnv(1, 2, 3, 4)}, - {[]float64{3, 6, 1, 4, 5, 2}, geom.DimXY, twoPtEnv(1, 2, 5, 6)}, + {[]float64{1, 2}, geom.DimXY, geom.NewEnvelopeXY(1, 2)}, + {[]float64{3, 2, 1, 4}, geom.DimXY, geom.NewEnvelopeXY(1, 2, 3, 4)}, + {[]float64{3, 6, 1, 4, 5, 2}, geom.DimXY, geom.NewEnvelopeXY(1, 2, 5, 6)}, {nil, geom.DimXYZ, geom.Envelope{}}, - {[]float64{1, 2, 3}, geom.DimXYZ, onePtEnv(1, 2)}, - {[]float64{3, 2, 0, 1, 4, 0}, geom.DimXYZ, twoPtEnv(1, 2, 3, 4)}, - {[]float64{3, 6, -1, 1, 4, -1, 5, 2, -1}, geom.DimXYZ, twoPtEnv(1, 2, 5, 6)}, + {[]float64{1, 2, 3}, geom.DimXYZ, geom.NewEnvelopeXY(1, 2)}, + {[]float64{3, 2, 0, 1, 4, 0}, geom.DimXYZ, geom.NewEnvelopeXY(1, 2, 3, 4)}, + {[]float64{3, 6, -1, 1, 4, -1, 5, 2, -1}, geom.DimXYZ, geom.NewEnvelopeXY(1, 2, 5, 6)}, } { t.Run(strconv.Itoa(i), func(t *testing.T) { seq := geom.NewSequence(tc.floats, tc.ct) - expectEnvEq(t, seq.Envelope(), tc.want) + test.Eq(t, seq.Envelope(), tc.want) }) } } diff --git a/geom/util_test.go b/geom/util_test.go deleted file mode 100644 index 8c9152fc..00000000 --- a/geom/util_test.go +++ /dev/null @@ -1,324 +0,0 @@ -package geom_test - -import ( - "bytes" - "errors" - "math" - "reflect" - "strconv" - "strings" - "testing" - - "github.com/peterstace/simplefeatures/geom" -) - -func geomFromWKT(tb testing.TB, wkt string, nv ...geom.NoValidate) geom.Geometry { - tb.Helper() - geom, err := geom.UnmarshalWKT(wkt, nv...) - if err != nil { - tb.Fatalf("could not unmarshal WKT:\n wkt: %s\n err: %v", wkt, err) - } - return geom -} - -//nolint:unparam -func geomsFromWKTs(tb testing.TB, wkts []string, nv ...geom.NoValidate) []geom.Geometry { - tb.Helper() - var gs []geom.Geometry - for _, wkt := range wkts { - g, err := geom.UnmarshalWKT(wkt, nv...) - if err != nil { - tb.Fatalf("could not unmarshal WKT:\n wkt: %s\n err: %v", wkt, err) - } - gs = append(gs, g) - } - return gs -} - -func geomFromGeoJSON(tb testing.TB, geojson string, nv ...geom.NoValidate) geom.Geometry { - tb.Helper() - g, err := geom.UnmarshalGeoJSON([]byte(geojson), nv...) - if err != nil { - tb.Fatalf("could not unmarshal GeoJSON:\n geojson: %s\n err: %v", geojson, err) - } - return g -} - -func xyCoords(x, y float64) geom.Coordinates { - return geom.Coordinates{XY: geom.XY{x, y}, Type: geom.DimXY} -} - -func upcastPoints(ps []geom.Point) []geom.Geometry { - gs := make([]geom.Geometry, len(ps)) - for i, p := range ps { - gs[i] = p.AsGeometry() - } - return gs -} - -func upcastLineStrings(lss []geom.LineString) []geom.Geometry { - gs := make([]geom.Geometry, len(lss)) - for i, ls := range lss { - gs[i] = ls.AsGeometry() - } - return gs -} - -func upcastPolygons(ps []geom.Polygon) []geom.Geometry { - gs := make([]geom.Geometry, len(ps)) - for i, p := range ps { - gs[i] = p.AsGeometry() - } - return gs -} - -func expectPanics(tb testing.TB, fn func()) { - tb.Helper() - defer func() { - if r := recover(); r != nil { - return - } - tb.Errorf("didn't panic") - }() - fn() -} - -func expectNoErr(tb testing.TB, err error) { - tb.Helper() - if err != nil { - tb.Fatalf("unexpected error: %v", err) - } -} - -//nolint:unused -func expectErr(tb testing.TB, err error) { - tb.Helper() - if err == nil { - tb.Fatal("expected error but got nil") - } -} - -func expectErrIs(tb testing.TB, err, want error) { - tb.Helper() - if !errors.Is(err, want) { - tb.Errorf("\ngot: %v\nwant: %v\n", err, want) - } -} - -func expectErrAs(tb testing.TB, err error, target any) { - tb.Helper() - if !errors.As(err, target) { - targetType := reflect.ValueOf(target).Elem().Interface() - tb.Errorf("%#v not assignable after unwrapping to %T", err, targetType) - } -} - -func expectValidationErrWithReason(tb testing.TB, err error, reason geom.RuleViolation) { - tb.Helper() - var ve geom.ValidationError - expectErrAs(tb, err, &ve) - expectStringEq(tb, string(ve.RuleViolation), string(reason)) -} - -// expectValidity either expects the geometry/envelope to be valid (when rv is -// ""), or the geometry to be invalid with the given rule violation. -func expectValidity(tb testing.TB, g interface{ Validate() error }, rv geom.RuleViolation) { - tb.Helper() - err := g.Validate() - if rv == "" { - expectNoErr(tb, err) - return - } - expectValidationErrWithReason(tb, err, rv) -} - -func expectDeepEq(tb testing.TB, got, want any) { - tb.Helper() - if !reflect.DeepEqual(got, want) { - tb.Errorf("\ngot: %v\nwant: %v\n", got, want) - } -} - -func expectGeomEq(tb testing.TB, got, want geom.Geometry, opts ...geom.ExactEqualsOption) { - tb.Helper() - if !geom.ExactEquals(got, want, opts...) { - tb.Errorf("\ngot: %v\nwant: %v\n", got.AsText(), want.AsText()) - } -} - -//nolint:unused -func expectGeomApproxEq(tb testing.TB, got, want geom.Geometry) { - tb.Helper() - eq, err := geom.Equals(got, want) - if err != nil { - tb.Errorf("\ngot: %v\nwant: %v\nerr: %v\n", got.AsText(), want.AsText(), err) - } - if !eq { - tb.Errorf("\ngot: %v\nwant: %v\n", got.AsText(), want.AsText()) - } -} - -func expectGeomEqWKT(tb testing.TB, got geom.Geometry, wantWKT string, opts ...geom.ExactEqualsOption) { - tb.Helper() - want := geomFromWKT(tb, wantWKT) - expectGeomEq(tb, got, want, opts...) -} - -//nolint:unparam -func expectGeomsEq(tb testing.TB, got, want []geom.Geometry, opts ...geom.ExactEqualsOption) { - tb.Helper() - if len(got) != len(want) { - tb.Errorf("\ngot: len %d\nwant: len %d\n", len(got), len(want)) - } - for i := range got { - if !geom.ExactEquals(got[i], want[i], opts...) { - tb.Errorf("\ngot: %v\nwant: %v\n", got[i].AsText(), want[i].AsText()) - } - } -} - -func expectCoordsEq(tb testing.TB, got, want geom.Coordinates) { - tb.Helper() - if got != want { - tb.Errorf("\ngot: %v\nwant: %v\n", got, want) - } -} - -func expectStringEq(tb testing.TB, got, want string) { - tb.Helper() - if got != want { - tb.Errorf("\ngot: %s\nwant: %s\n", quotedString(got), quotedString(want)) - } -} - -//nolint:unused -func expectSubstring(tb testing.TB, got, wantSubstring string) { - tb.Helper() - if !strings.Contains(got, wantSubstring) { - tb.Errorf("\ngot: %s\nwant substring: %s\n", quotedString(got), quotedString(wantSubstring)) - } -} - -func quotedString(s string) string { - if strconv.CanBackquote(s) { - return "`" + s + "`" - } - return strconv.Quote(s) -} - -func expectIntEq(tb testing.TB, got, want int) { - tb.Helper() - if got != want { - tb.Errorf("\ngot: %d\nwant: %d\n", got, want) - } -} - -func expectBoolEq(tb testing.TB, got, want bool) { - tb.Helper() - if got != want { - tb.Errorf("\ngot: %t\nwant: %t\n", got, want) - } -} - -func expectTrue(tb testing.TB, got bool) { - tb.Helper() - expectBoolEq(tb, got, true) -} - -func expectFalse(tb testing.TB, got bool) { - tb.Helper() - expectBoolEq(tb, got, false) -} - -func expectXYEq(tb testing.TB, got, want geom.XY) { - tb.Helper() - if got != want { - tb.Errorf("\ngot: %v\nwant: %v\n", got, want) - } -} - -func expectXYWithinTolerance(tb testing.TB, got, want geom.XY, tolerance float64) { - tb.Helper() - if delta := math.Abs(got.Sub(want).Length()); delta > tolerance { - tb.Errorf("\ngot: %v\nwant: %v\n", got, want) - } -} - -func expectCoordinatesTypeEq(tb testing.TB, got, want geom.CoordinatesType) { - tb.Helper() - if got != want { - tb.Errorf("\ngot: %v\nwant: %v\n", got, want) - } -} - -func expectBytesEq(tb testing.TB, got, want []byte) { - tb.Helper() - if !bytes.Equal(got, want) { - tb.Errorf("\ngot: %v\nwant: %v\n", got, want) - } -} - -func expectFloat64Eq(tb testing.TB, got, want float64) { - tb.Helper() - if got != want { - tb.Errorf("\ngot: %v\nwant: %v\n", got, want) - } -} - -func expectEnvEq(tb testing.TB, got, want geom.Envelope) { - tb.Helper() - if geom.ExactEquals(got.Min().AsGeometry(), want.Min().AsGeometry()) && - geom.ExactEquals(got.Max().AsGeometry(), want.Max().AsGeometry()) { - return - } - tb.Errorf("\ngot: %v\nwant: %v", got, want) -} - -func expectSequenceEq(tb testing.TB, got, want geom.Sequence) { - tb.Helper() - show := func() { - tb.Logf("len(got): %d, ct(got): %s", got.Length(), got.CoordinatesType()) - for i := 0; i < got.Length(); i++ { - tb.Logf("got[%d]: %v", i, got.Get(i)) - } - tb.Logf("len(want): %d, ct(want): %s", want.Length(), want.CoordinatesType()) - for i := 0; i < want.Length(); i++ { - tb.Logf("want[%d]: %v", i, want.Get(i)) - } - } - if got.CoordinatesType() != want.CoordinatesType() { - tb.Errorf("mismatched coordinate type") - show() - return - } - if got.Length() != want.Length() { - tb.Errorf("length mismatch") - show() - return - } - for i := 0; i < got.Length(); i++ { - w := want.Get(i) - g := got.Get(i) - if g != w { - tb.Errorf("mismatch at %d: got:%v want:%v", i, g, w) - } - } -} - -func expectInt64SliceEq(tb testing.TB, got, want []int64) { - tb.Helper() - mismatch := false - if len(got) != len(want) { - mismatch = true - } else { - for i := range got { - if got[i] != want[i] { - mismatch = true - break - } - } - } - if mismatch { - tb.Errorf("\ngot: %v\nwant: %v\n", got, want) - } -} diff --git a/geom/validation_helpers_test.go b/geom/validation_helpers_test.go new file mode 100644 index 00000000..29daf664 --- /dev/null +++ b/geom/validation_helpers_test.go @@ -0,0 +1,21 @@ +package geom_test + +// This helper lives here rather than in internal/test because it references +// geom.ValidationError and geom.RuleViolation, which are only exported via +// geom/export_test.go (the standard Go pattern for test-only exports). A +// regular (non-test) package like internal/test cannot see those symbols. + +import ( + "testing" + + "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" +) + +func expectRuleViolation(tb testing.TB, g interface{ Validate() error }, rv geom.RuleViolation) { + tb.Helper() + err := g.Validate() + var ve geom.ValidationError + test.ErrAs(tb, err, &ve) + test.Eq(tb, string(ve.RuleViolation), string(rv)) +} diff --git a/geom/validation_test.go b/geom/validation_test.go index a3efd680..d19361a9 100644 --- a/geom/validation_test.go +++ b/geom/validation_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func xy(x, y float64) geom.Coordinates { @@ -14,13 +15,17 @@ func xy(x, y float64) geom.Coordinates { } func TestPointValidation(t *testing.T) { + t.Run("valid", func(t *testing.T) { + pt := geom.NewPoint(xy(0, 0)) + test.NoErr(t, pt.Validate()) + }) + nan := math.NaN() inf := math.Inf(+1) for i, tc := range []struct { reason geom.RuleViolation input geom.Coordinates }{ - {"", xy(0, 0)}, {geom.ViolateNaN, xy(nan, 0)}, {geom.ViolateNaN, xy(0, nan)}, {geom.ViolateNaN, xy(nan, nan)}, @@ -31,21 +36,26 @@ func TestPointValidation(t *testing.T) { {geom.ViolateInf, xy(0, -inf)}, {geom.ViolateInf, xy(-inf, -inf)}, } { - t.Run(fmt.Sprintf("point_%d", i), func(t *testing.T) { + t.Run(fmt.Sprintf("invalid_%d", i), func(t *testing.T) { pt := geom.NewPoint(tc.input) - expectValidity(t, pt, tc.reason) + expectRuleViolation(t, pt, tc.reason) }) } } func TestLineStringValidation(t *testing.T) { + t.Run("valid", func(t *testing.T) { + seq := geom.NewSequence([]float64{0, 0, 1, 1}, geom.DimXY) + ls := geom.NewLineString(seq) + test.NoErr(t, ls.Validate()) + }) + nan := math.NaN() inf := math.Inf(+1) for i, tc := range []struct { reason geom.RuleViolation inputs []float64 }{ - {"", []float64{0, 0, 1, 1}}, {geom.ViolateTwoPoints, []float64{0, 0}}, {geom.ViolateTwoPoints, []float64{1, 1}}, {geom.ViolateTwoPoints, []float64{0, 0, 0, 0}}, @@ -57,10 +67,10 @@ func TestLineStringValidation(t *testing.T) { {geom.ViolateInf, []float64{0, 0, 1, 1, 2, -inf}}, {geom.ViolateInf, []float64{0, 0, 1, 1, -inf, 2}}, } { - t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Run(fmt.Sprintf("invalid_%d", i), func(t *testing.T) { seq := geom.NewSequence(tc.inputs, geom.DimXY) ls := geom.NewLineString(seq) - expectValidity(t, ls, tc.reason) + expectRuleViolation(t, ls, tc.reason) }) } } @@ -89,7 +99,7 @@ func TestPolygonValidation(t *testing.T) { if err != nil { t.Error(err) } - expectNoErr(t, poly.Validate()) + test.NoErr(t, poly.Validate()) }) } @@ -174,59 +184,75 @@ func TestPolygonValidation(t *testing.T) { t.Run("invalid_"+strconv.Itoa(i), func(t *testing.T) { t.Run("Constructor", func(t *testing.T) { _, err := geom.UnmarshalWKT(tc.wkt) - expectValidationErrWithReason(t, err, tc.reason) + var ve geom.ValidationError + test.ErrAs(t, err, &ve) + test.Eq(t, string(ve.RuleViolation), string(tc.reason)) }) t.Run("Validate", func(t *testing.T) { poly, err := geom.UnmarshalWKT(tc.wkt, geom.NoValidate{}) - expectNoErr(t, err) - expectValidity(t, poly, tc.reason) + test.NoErr(t, err) + expectRuleViolation(t, poly, tc.reason) }) }) } } func TestMultiPointValidation(t *testing.T) { + t.Run("valid", func(t *testing.T) { + mp := geom.NewMultiPoint([]geom.Point{ + geom.NewPoint(xy(0, 1)), + geom.NewPoint(xy(2, 3)), + }) + test.NoErr(t, mp.Validate()) + }) + nan := math.NaN() for i, tc := range []struct { reason geom.RuleViolation coords []geom.Coordinates }{ - {"", []geom.Coordinates{xy(0, 1), xy(2, 3)}}, {geom.ViolateNaN, []geom.Coordinates{xy(0, 1), xy(2, nan)}}, {geom.ViolateNaN, []geom.Coordinates{xy(nan, 1), xy(2, 3)}}, } { - t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Run(fmt.Sprintf("invalid_%d", i), func(t *testing.T) { var pts []geom.Point for _, c := range tc.coords { pt := geom.NewPoint(c) pts = append(pts, pt) } mp := geom.NewMultiPoint(pts) - expectValidity(t, mp, tc.reason) + expectRuleViolation(t, mp, tc.reason) }) } } func TestMultiLineStringValidation(t *testing.T) { + newMLS := func(coords [][]float64) geom.MultiLineString { + var lss []geom.LineString + for _, c := range coords { + seq := geom.NewSequence(c, geom.DimXY) + ls := geom.NewLineString(seq) + lss = append(lss, ls) + } + return geom.NewMultiLineString(lss) + } + t.Run("valid_empty", func(t *testing.T) { + test.NoErr(t, newMLS([][]float64{}).Validate()) + }) + t.Run("valid_single", func(t *testing.T) { + test.NoErr(t, newMLS([][]float64{{0, 1, 2, 3}}).Validate()) + }) + nan := math.NaN() for i, tc := range []struct { reason geom.RuleViolation coords [][]float64 }{ - {"", [][]float64{}}, {geom.ViolateTwoPoints, [][]float64{{0, 1}}}, - {"", [][]float64{{0, 1, 2, 3}}}, {geom.ViolateNaN, [][]float64{{0, 1, 2, nan}}}, } { - t.Run(strconv.Itoa(i), func(t *testing.T) { - var lss []geom.LineString - for _, coords := range tc.coords { - seq := geom.NewSequence(coords, geom.DimXY) - ls := geom.NewLineString(seq) - lss = append(lss, ls) - } - mls := geom.NewMultiLineString(lss) - expectValidity(t, mls, tc.reason) + t.Run(fmt.Sprintf("invalid_%d", i), func(t *testing.T) { + expectRuleViolation(t, newMLS(tc.coords), tc.reason) }) } } @@ -261,7 +287,7 @@ func TestMultiPolygonValidation(t *testing.T) { `MULTIPOLYGON(((0 0,0 1,1 1,1 0,0 0)),((2 -1,3 -1,3 0,2 0,2 -1)),((1 1,3 1,3 3,1 3,1 1)))`, } { t.Run(fmt.Sprintf("valid_%d", i), func(t *testing.T) { - geomFromWKT(t, wkt) + test.FromWKT(t, wkt) }) } @@ -312,32 +338,28 @@ func TestMultiPolygonValidation(t *testing.T) { )`, } { t.Run(fmt.Sprintf("invalid_%d", i), func(t *testing.T) { - g := geomFromWKT(t, wkt, geom.NoValidate{}) - expectValidity(t, g, geom.ViolatePolysMultiTouch) + g := test.FromWKT(t, wkt, geom.NoValidate{}) + expectRuleViolation(t, g, geom.ViolatePolysMultiTouch) }) } } func TestMultiPolygonConstraintValidation(t *testing.T) { poly, err := geom.UnmarshalWKT("POLYGON((0 0,1 1,0 1,1 0,0 0))", geom.NoValidate{}) - expectNoErr(t, err) - expectValidity(t, poly, geom.ViolateRingSimple) + test.NoErr(t, err) + expectRuleViolation(t, poly, geom.ViolateRingSimple) mp := geom.NewMultiPolygon([]geom.Polygon{poly.MustAsPolygon()}) - expectValidity(t, mp, geom.ViolateRingSimple) + expectRuleViolation(t, mp, geom.ViolateRingSimple) } func TestGeometryCollectionValidation(t *testing.T) { - for i, tc := range []struct { - reason geom.RuleViolation - wkt string - }{ - {"", "GEOMETRYCOLLECTION(LINESTRING(0 1,2 3))"}, - {geom.ViolateTwoPoints, "GEOMETRYCOLLECTION(LINESTRING(0 1))"}, - } { - t.Run(strconv.Itoa(i), func(t *testing.T) { - gc := geomFromWKT(t, tc.wkt, geom.NoValidate{}) - expectValidity(t, gc, tc.reason) - }) - } + t.Run("valid", func(t *testing.T) { + gc := test.FromWKT(t, "GEOMETRYCOLLECTION(LINESTRING(0 1,2 3))", geom.NoValidate{}) + test.NoErr(t, gc.Validate()) + }) + t.Run("invalid", func(t *testing.T) { + gc := test.FromWKT(t, "GEOMETRYCOLLECTION(LINESTRING(0 1))", geom.NoValidate{}) + expectRuleViolation(t, gc, geom.ViolateTwoPoints) + }) } diff --git a/geom/wkb_test.go b/geom/wkb_test.go index 50b895ee..f1ea4ac2 100644 --- a/geom/wkb_test.go +++ b/geom/wkb_test.go @@ -9,25 +9,9 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) -func hexStringToBytes(tb testing.TB, s string) []byte { - tb.Helper() - s = strings.ReplaceAll(s, " ", "") - if len(s)%2 != 0 { - tb.Fatal("hex string must have even length") - } - var buf []byte - for i := 0; i < len(s); i += 2 { - x, err := strconv.ParseUint(s[i:i+2], 16, 8) - if err != nil { - tb.Fatal(err) - } - buf = append(buf, byte(x)) - } - return buf -} - // Test cases generated from: /* SELECT @@ -390,9 +374,11 @@ func TestWKBParseValid(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - geom, err := geom.UnmarshalWKB(hexStringToBytes(t, tt.wkb)) - expectNoErr(t, err) - expectGeomEq(t, geom, geomFromWKT(t, tt.wkt)) + buf, err := hex.DecodeString(tt.wkb) + test.NoErr(t, err) + geom, err := geom.UnmarshalWKB(buf) + test.NoErr(t, err) + test.ExactEquals(t, geom, test.FromWKT(t, tt.wkt)) }) } } @@ -537,8 +523,9 @@ func TestWKBParserSyntaxError(t *testing.T) { }, } { t.Run(tc.description, func(t *testing.T) { - wkb := hexStringToBytes(t, tc.wkbHex) - _, err := geom.UnmarshalWKB(wkb) + wkb, err := hex.DecodeString(tc.wkbHex) + test.NoErr(t, err) + _, err = geom.UnmarshalWKB(wkb) if err == nil { t.Fatal("expected an error but got nil") } @@ -577,11 +564,11 @@ func TestWKBMarshalValid(t *testing.T) { "GEOMETRYCOLLECTION(POINT(1 2),LINESTRING(1 2,3 4))", } { t.Run(strconv.Itoa(i), func(t *testing.T) { - g := geomFromWKT(t, wkt) + g := test.FromWKT(t, wkt) buf := g.AsBinary() readBackGeom, err := geom.UnmarshalWKB(buf) - expectNoErr(t, err) - expectGeomEq(t, readBackGeom, g) + test.NoErr(t, err) + test.ExactEquals(t, readBackGeom, g) }) } } @@ -669,9 +656,10 @@ func TestWKBMarshalEmptyPoint(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - g := geomFromWKT(t, tt.wkt) + g := test.FromWKT(t, tt.wkt) buf := g.AsBinary() - want := hexStringToBytes(t, tt.hex) + want, err := hex.DecodeString(tt.hex) + test.NoErr(t, err) if !bytes.Equal(want, buf) { t.Logf("wkt: %v", tt.wkt) t.Logf("want:\n%v", hex.Dump(want)) @@ -713,11 +701,12 @@ func TestWKBUnmarshalEndianess(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - want := geomFromWKT(t, tt.wkt) - wkb := hexStringToBytes(t, tt.hex) + want := test.FromWKT(t, tt.wkt) + wkb, err := hex.DecodeString(tt.hex) + test.NoErr(t, err) got, err := geom.UnmarshalWKB(wkb) - expectNoErr(t, err) - expectGeomEq(t, got, want) + test.NoErr(t, err) + test.ExactEquals(t, got, want) }) } } @@ -748,18 +737,20 @@ func TestWKBGeometryCollectionMixedCoordinateTypes(t *testing.T) { {"00000bb9", geom.DimXYZM}, } { t.Run(fmt.Sprintf("gc_%v_point_%v", gc.ct, point.ct), func(t *testing.T) { - hex := bigEndian + + hexStr := bigEndian + gc.hex + u32One + bigEndian + point.hex + strings.Repeat(f64Zero, point.ct.Dimension()) - _, err := geom.UnmarshalWKB(hexStringToBytes(t, hex)) + buf, err := hex.DecodeString(hexStr) + test.NoErr(t, err) + _, err = geom.UnmarshalWKB(buf) if gc.ct == point.ct { - expectNoErr(t, err) + test.NoErr(t, err) } else { wantErr := geom.MismatchedGeometryCollectionDimsError{gc.ct, point.ct} - expectErrIs(t, err, wantErr) + test.ErrIs(t, err, wantErr) } }) } diff --git a/geom/wkt_test.go b/geom/wkt_test.go index 3404aee8..a5dcd653 100644 --- a/geom/wkt_test.go +++ b/geom/wkt_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func TestUnmarshalWKTValidGrammar(t *testing.T) { @@ -173,26 +174,26 @@ func TestUnmarshalWKTSyntaxErrors(t *testing.T) { func TestUnmarshalWKT(t *testing.T) { t.Run("multi line string containing an empty line string", func(t *testing.T) { - g := geomFromWKT(t, "MULTILINESTRING((1 2,3 4),EMPTY,(5 6,7 8))") + g := test.FromWKT(t, "MULTILINESTRING((1 2,3 4),EMPTY,(5 6,7 8))") mls := g.MustAsMultiLineString() - expectIntEq(t, mls.NumLineStrings(), 3) - expectGeomEq(t, + test.Eq(t, mls.NumLineStrings(), 3) + test.ExactEquals(t, mls.LineStringN(0).AsGeometry(), - geomFromWKT(t, "LINESTRING(1 2,3 4)"), + test.FromWKT(t, "LINESTRING(1 2,3 4)"), ) - expectGeomEq(t, + test.ExactEquals(t, mls.LineStringN(1).AsGeometry(), - geomFromWKT(t, "LINESTRING EMPTY"), + test.FromWKT(t, "LINESTRING EMPTY"), ) - expectGeomEq(t, + test.ExactEquals(t, mls.LineStringN(2).AsGeometry(), - geomFromWKT(t, "LINESTRING(5 6,7 8)"), + test.FromWKT(t, "LINESTRING(5 6,7 8)"), ) }) t.Run("multipoints with and without parenthesised points", func(t *testing.T) { - g1 := geomFromWKT(t, "MULTIPOINT((10 40),(40 30),(20 20),(30 10))") - g2 := geomFromWKT(t, "MULTIPOINT(10 40,40 30,20 20,30 10)") - expectGeomEq(t, g1, g2) + g1 := test.FromWKT(t, "MULTIPOINT((10 40),(40 30),(20 20),(30 10))") + g2 := test.FromWKT(t, "MULTIPOINT(10 40,40 30,20 20,30 10)") + test.ExactEquals(t, g1, g2) }) } @@ -211,7 +212,7 @@ func TestAsTextEmpty(t *testing.T) { } { t.Run(strconv.Itoa(i), func(t *testing.T) { got := tt.g.AsText() - expectStringEq(t, got, tt.want) + test.Eq(t, got, tt.want) }) } } @@ -254,11 +255,11 @@ func TestInconsistentDimensionTypeInWKT(t *testing.T) { t.Run(tc.wkt, func(t *testing.T) { _, err := geom.UnmarshalWKT(tc.wkt) if tc.allow { - expectNoErr(t, err) + test.NoErr(t, err) return } want := geom.MismatchedGeometryCollectionDimsError{} - expectErrAs(t, err, &want) + test.ErrAs(t, err, &want) }) } } diff --git a/geom/xy_test.go b/geom/xy_test.go index ae1f052d..52a77e04 100644 --- a/geom/xy_test.go +++ b/geom/xy_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func TestXYUnit(t *testing.T) { @@ -14,42 +15,42 @@ func TestXYUnit(t *testing.T) { for _, tc := range []struct { description string input geom.XY - output geom.XY + want geom.XY }{ { description: "+ve unit in X", input: geom.XY{X: 1}, - output: geom.XY{X: 1}, + want: geom.XY{X: 1}, }, { description: "+ve unit in Y", input: geom.XY{Y: 1}, - output: geom.XY{Y: 1}, + want: geom.XY{Y: 1}, }, { description: "-ve unit in X", input: geom.XY{X: -1}, - output: geom.XY{X: -1}, + want: geom.XY{X: -1}, }, { description: "-ve unit in Y", input: geom.XY{Y: -1}, - output: geom.XY{Y: -1}, + want: geom.XY{Y: -1}, }, { description: "non-aligned unit", input: geom.XY{X: -1 / sqrt2, Y: 1 / sqrt2}, - output: geom.XY{X: -1 / sqrt2, Y: 1 / sqrt2}, + want: geom.XY{X: -1 / sqrt2, Y: 1 / sqrt2}, }, { description: "non-unit", input: geom.XY{X: 1, Y: -2}, - output: geom.XY{X: 1 / sqrt5, Y: -2 / sqrt5}, + want: geom.XY{X: 1 / sqrt5, Y: -2 / sqrt5}, }, } { t.Run(tc.description, func(t *testing.T) { got := tc.input.Unit() - expectXYWithinTolerance(t, got, tc.output, 0.0000001) + test.LT(t, got.Sub(tc.want).Length(), 0.0000001) }) } } @@ -80,12 +81,12 @@ func TestXYCross(t *testing.T) { t.Run(strconv.Itoa(i), func(t *testing.T) { t.Run("fwd", func(t *testing.T) { got := tc.u.Cross(tc.v) - expectFloat64Eq(t, got, tc.want) + test.Eq(t, got, tc.want) }) t.Run("rev", func(t *testing.T) { got := tc.v.Cross(tc.u) want := -tc.want // Cross product is anti-commutative. - expectFloat64Eq(t, got, want) + test.Eq(t, got, want) }) }) } diff --git a/geom/zero_value_test.go b/geom/zero_value_test.go index fd79348a..8f72d372 100644 --- a/geom/zero_value_test.go +++ b/geom/zero_value_test.go @@ -4,75 +4,76 @@ import ( "testing" "github.com/peterstace/simplefeatures/geom" + "github.com/peterstace/simplefeatures/internal/test" ) func TestZeroValueGeometries(t *testing.T) { t.Run("Point", func(t *testing.T) { var pt geom.Point - expectBoolEq(t, pt.IsEmpty(), true) - expectCoordinatesTypeEq(t, pt.CoordinatesType(), geom.DimXY) + test.Eq(t, pt.IsEmpty(), true) + test.Eq(t, pt.CoordinatesType(), geom.DimXY) }) t.Run("LineString", func(t *testing.T) { var ls geom.LineString - expectIntEq(t, ls.Coordinates().Length(), 0) - expectCoordinatesTypeEq(t, ls.CoordinatesType(), geom.DimXY) + test.Eq(t, ls.Coordinates().Length(), 0) + test.Eq(t, ls.CoordinatesType(), geom.DimXY) }) t.Run("Polygon", func(t *testing.T) { var p geom.Polygon - expectBoolEq(t, p.IsEmpty(), true) - expectCoordinatesTypeEq(t, p.CoordinatesType(), geom.DimXY) + test.Eq(t, p.IsEmpty(), true) + test.Eq(t, p.CoordinatesType(), geom.DimXY) }) t.Run("MultiPoint", func(t *testing.T) { var mp geom.MultiPoint - expectIntEq(t, mp.NumPoints(), 0) - expectCoordinatesTypeEq(t, mp.CoordinatesType(), geom.DimXY) + test.Eq(t, mp.NumPoints(), 0) + test.Eq(t, mp.CoordinatesType(), geom.DimXY) }) t.Run("MultiLineString", func(t *testing.T) { var mls geom.MultiLineString - expectIntEq(t, mls.NumLineStrings(), 0) - expectCoordinatesTypeEq(t, mls.CoordinatesType(), geom.DimXY) + test.Eq(t, mls.NumLineStrings(), 0) + test.Eq(t, mls.CoordinatesType(), geom.DimXY) }) t.Run("MultiPolygon", func(t *testing.T) { var mp geom.MultiPolygon - expectIntEq(t, mp.NumPolygons(), 0) - expectCoordinatesTypeEq(t, mp.CoordinatesType(), geom.DimXY) + test.Eq(t, mp.NumPolygons(), 0) + test.Eq(t, mp.CoordinatesType(), geom.DimXY) }) t.Run("GeometryCollection", func(t *testing.T) { var gc geom.GeometryCollection - expectIntEq(t, gc.NumGeometries(), 0) - expectCoordinatesTypeEq(t, gc.CoordinatesType(), geom.DimXY) + test.Eq(t, gc.NumGeometries(), 0) + test.Eq(t, gc.CoordinatesType(), geom.DimXY) }) } func TestEmptySliceConstructors(t *testing.T) { t.Run("Polygon", func(t *testing.T) { p := geom.NewPolygon(nil) - expectNoErr(t, p.Validate()) - expectBoolEq(t, p.IsEmpty(), true) - expectCoordinatesTypeEq(t, p.CoordinatesType(), geom.DimXY) + test.NoErr(t, p.Validate()) + test.Eq(t, p.IsEmpty(), true) + test.Eq(t, p.CoordinatesType(), geom.DimXY) }) t.Run("MultiPoint", func(t *testing.T) { mp := geom.NewMultiPoint(nil) - expectNoErr(t, mp.Validate()) - expectIntEq(t, mp.NumPoints(), 0) - expectCoordinatesTypeEq(t, mp.CoordinatesType(), geom.DimXY) + test.NoErr(t, mp.Validate()) + test.Eq(t, mp.NumPoints(), 0) + test.Eq(t, mp.CoordinatesType(), geom.DimXY) }) t.Run("MultiLineString", func(t *testing.T) { mls := geom.NewMultiLineString(nil) - expectNoErr(t, mls.Validate()) - expectIntEq(t, mls.NumLineStrings(), 0) - expectCoordinatesTypeEq(t, mls.CoordinatesType(), geom.DimXY) + test.NoErr(t, mls.Validate()) + test.Eq(t, mls.NumLineStrings(), 0) + test.Eq(t, mls.CoordinatesType(), geom.DimXY) }) t.Run("MultiPolygon", func(t *testing.T) { mp := geom.NewMultiPolygon(nil) - expectNoErr(t, mp.Validate()) - expectIntEq(t, mp.NumPolygons(), 0) - expectCoordinatesTypeEq(t, mp.CoordinatesType(), geom.DimXY) + test.NoErr(t, mp.Validate()) + test.Eq(t, mp.NumPolygons(), 0) + test.Eq(t, mp.CoordinatesType(), geom.DimXY) }) t.Run("GeometryCollection", func(t *testing.T) { gc := geom.NewGeometryCollection(nil) - expectNoErr(t, gc.Validate()) - expectIntEq(t, gc.NumGeometries(), 0) - expectCoordinatesTypeEq(t, gc.CoordinatesType(), geom.DimXY) + test.NoErr(t, gc.Validate()) + test.Eq(t, gc.NumGeometries(), 0) + test.Eq(t, gc.CoordinatesType(), geom.DimXY) }) } diff --git a/geos/entrypoints_test.go b/geos/entrypoints_test.go index bbea0a2c..7e90b595 100644 --- a/geos/entrypoints_test.go +++ b/geos/entrypoints_test.go @@ -3,52 +3,15 @@ package geos_test import ( "errors" "math" - "os" "strconv" "testing" "github.com/peterstace/simplefeatures/geom" "github.com/peterstace/simplefeatures/geos" "github.com/peterstace/simplefeatures/internal/rawgeos" + "github.com/peterstace/simplefeatures/internal/test" ) -func geomFromWKT(t *testing.T, wkt string, nv ...geom.NoValidate) geom.Geometry { - t.Helper() - geom, err := geom.UnmarshalWKT(wkt, nv...) - if err != nil { - t.Fatalf("could not unmarshal WKT:\n wkt: %s\n err: %v", wkt, err) - } - return geom -} - -func geomFromWKTFile(t *testing.T, path string, nv ...geom.NoValidate) geom.Geometry { - t.Helper() - wkt, err := os.ReadFile(path) - expectNoErr(t, err) - return geomFromWKT(t, string(wkt), nv...) -} - -func expectNoErr(t *testing.T, err error) { - t.Helper() - if err != nil { - t.Fatalf("unexpected error: %v", err) - } -} - -func expectErr(t *testing.T, err error) { - t.Helper() - if err == nil { - t.Fatal("expected error but got nil") - } -} - -func expectGeomEq(t *testing.T, got, want geom.Geometry, opts ...geom.ExactEqualsOption) { - t.Helper() - if !geom.ExactEquals(got, want, opts...) { - t.Errorf("\ngot: %v\nwant: %v\n", got.AsText(), want.AsText()) - } -} - func skipIfUnsupported(tb testing.TB, err error) { tb.Helper() var verErr rawgeos.UnsupportedGEOSVersionError @@ -57,13 +20,6 @@ func skipIfUnsupported(tb testing.TB, err error) { } } -func expectBoolEq(t *testing.T, got, want bool) { - t.Helper() - if got != want { - t.Errorf("got: %v want: %v", got, want) - } -} - // These tests aren't exhaustive, because we are leveraging GEOS. The // testing is just enough to make use confident that we're invoking GEOS // correctly. @@ -358,11 +314,11 @@ func TestRelate(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - g1 := geomFromWKT(t, tt.wkt1) - g2 := geomFromWKT(t, tt.wkt2) + g1 := test.FromWKT(t, tt.wkt1) + g2 := test.FromWKT(t, tt.wkt2) t.Run("Equals", func(t *testing.T) { got, err := geos.Equals(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got != tt.equals { t.Logf("WKT1: %v", tt.wkt1) t.Logf("WKT2: %v", tt.wkt2) @@ -371,7 +327,7 @@ func TestRelate(t *testing.T) { }) t.Run("Disjoint", func(t *testing.T) { got, err := geos.Disjoint(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got != tt.disjoint { t.Logf("WKT1: %v", tt.wkt1) t.Logf("WKT2: %v", tt.wkt2) @@ -380,7 +336,7 @@ func TestRelate(t *testing.T) { }) t.Run("Touches", func(t *testing.T) { got, err := geos.Touches(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got != tt.touches { t.Logf("WKT1: %v", tt.wkt1) t.Logf("WKT2: %v", tt.wkt2) @@ -389,7 +345,7 @@ func TestRelate(t *testing.T) { }) t.Run("Contains", func(t *testing.T) { got, err := geos.Contains(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got != tt.contains { t.Logf("WKT1: %v", tt.wkt1) t.Logf("WKT2: %v", tt.wkt2) @@ -398,7 +354,7 @@ func TestRelate(t *testing.T) { }) t.Run("Covers", func(t *testing.T) { got, err := geos.Covers(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got != tt.covers { t.Logf("WKT1: %v", tt.wkt1) t.Logf("WKT2: %v", tt.wkt2) @@ -407,7 +363,7 @@ func TestRelate(t *testing.T) { }) t.Run("Intersects", func(t *testing.T) { got, err := geos.Intersects(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got != tt.intersects { t.Logf("WKT1: %v", tt.wkt1) t.Logf("WKT2: %v", tt.wkt2) @@ -416,7 +372,7 @@ func TestRelate(t *testing.T) { }) t.Run("Within", func(t *testing.T) { got, err := geos.Within(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got != tt.within { t.Logf("WKT1: %v", tt.wkt1) t.Logf("WKT2: %v", tt.wkt2) @@ -425,7 +381,7 @@ func TestRelate(t *testing.T) { }) t.Run("CoveredBy", func(t *testing.T) { got, err := geos.CoveredBy(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got != tt.coveredBy { t.Logf("WKT1: %v", tt.wkt1) t.Logf("WKT2: %v", tt.wkt2) @@ -514,13 +470,13 @@ func TestCrosses(t *testing.T) { run := func(rev bool) func(*testing.T) { return func(t *testing.T) { t.Helper() - g1 := geomFromWKT(t, tt.wkt1) - g2 := geomFromWKT(t, tt.wkt2) + g1 := test.FromWKT(t, tt.wkt1) + g2 := test.FromWKT(t, tt.wkt2) if rev { g1, g2 = g2, g1 } got, err := geos.Crosses(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got != tt.want { t.Logf("WKT1: %v", tt.wkt1) t.Logf("WKT2: %v", tt.wkt2) @@ -571,13 +527,13 @@ func TestOverlaps(t *testing.T) { run := func(rev bool) func(t *testing.T) { return func(t *testing.T) { t.Helper() - g1 := geomFromWKT(t, tt.wkt1) - g2 := geomFromWKT(t, tt.wkt2) + g1 := test.FromWKT(t, tt.wkt1) + g2 := test.FromWKT(t, tt.wkt2) if rev { g1, g2 = g2, g1 } got, err := geos.Overlaps(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got != tt.want { t.Logf("WKT1: %v", tt.wkt1) t.Logf("WKT2: %v", tt.wkt2) @@ -592,10 +548,10 @@ func TestOverlaps(t *testing.T) { } func TestRelateCode(t *testing.T) { - g1 := geomFromWKT(t, "POLYGON((0 0,0 2,2 2,2 0,0 0))") - g2 := geomFromWKT(t, "POLYGON((1 1,1 3,3 3,3 1,1 1))") + g1 := test.FromWKT(t, "POLYGON((0 0,0 2,2 2,2 0,0 0))") + g2 := test.FromWKT(t, "POLYGON((1 1,1 3,3 3,3 1,1 1))") got, err := geos.Relate(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) const want = "212101212" if got != want { t.Errorf("got: %v want: %v", got, want) @@ -614,15 +570,15 @@ func RunBinaryOperationTest(t *testing.T, fn func(a, b geom.Geometry) (geom.Geom run := func(rev bool) func(t *testing.T) { return func(t *testing.T) { t.Helper() - g1 := geomFromWKT(t, c.In1) - g2 := geomFromWKT(t, c.In2) + g1 := test.FromWKT(t, c.In1) + g2 := test.FromWKT(t, c.In2) if rev { g1, g2 = g2, g1 } t.Logf("WKT1: %v", g1.AsText()) t.Logf("WKT2: %v", g2.AsText()) got, err := fn(g1, g2) - expectNoErr(t, err) + test.NoErr(t, err) if got.IsEmpty() { // Normalise the result to a geometry collection when @@ -631,7 +587,7 @@ func RunBinaryOperationTest(t *testing.T, fn func(a, b geom.Geometry) (geom.Geom // for empty geometries. got = geom.GeometryCollection{}.AsGeometry() } - expectGeomEq(t, got, geomFromWKT(t, c.Out), geom.IgnoreOrder) + test.ExactEquals(t, got, test.FromWKT(t, c.Out), geom.IgnoreOrder) } } t.Run("Forward", run(false)) @@ -762,15 +718,15 @@ func TestBuffer(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - g := geomFromWKT(t, tt.wkt) + g := test.FromWKT(t, tt.wkt) t.Logf("WKT: %v", g.AsText()) got, err := geos.Buffer(g, tt.radius, tt.opts...) - expectNoErr(t, err) + test.NoErr(t, err) - gWant := geomFromWKT(t, tt.want) + gWant := test.FromWKT(t, tt.want) symDiff, err := geom.SymmetricDifference(gWant, got) - expectNoErr(t, err) + test.NoErr(t, err) const threshold = 1e-3 relativeAreaDiff := symDiff.Area() / math.Min(gWant.Area(), got.Area()) if relativeAreaDiff > threshold { @@ -799,11 +755,11 @@ func TestSimplify(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - g := geomFromWKT(t, tt.input) + g := test.FromWKT(t, tt.input) t.Logf("WKT: %v", g.AsText()) got, err := geos.Simplify(g, tt.tolerance) - expectNoErr(t, err) - expectGeomEq(t, got, geomFromWKT(t, tt.output), geom.IgnoreOrder) + test.NoErr(t, err) + test.ExactEquals(t, got, test.FromWKT(t, tt.output), geom.IgnoreOrder) }) } } @@ -813,31 +769,31 @@ func TestTopologyPreserveSimplify(t *testing.T) { input = `POLYGON((0 0,0 1,-0.5 1.5,0 2,0 3,3 3,3 0,0 0),(-0.1 1.5,2 2,2 1,-0.1 1.5))` output = `POLYGON((0 0,-0.5 1.5,0 3,3 3,3 0,0 0),(-0.1 1.5,2 2,2 1,-0.1 1.5))` ) - got, err := geos.TopologyPreserveSimplify(geomFromWKT(t, input), 0.5) - expectNoErr(t, err) - expectGeomEq(t, got, geomFromWKT(t, output), geom.IgnoreOrder) + got, err := geos.TopologyPreserveSimplify(test.FromWKT(t, input), 0.5) + test.NoErr(t, err) + test.ExactEquals(t, got, test.FromWKT(t, output), geom.IgnoreOrder) } func TestDifference(t *testing.T) { - a := geomFromWKT(t, "POLYGON((0 0,0 2,2 2,2 0,0 0))") - b := geomFromWKT(t, "POLYGON((1 1,1 3,3 3,3 1,1 1))") + a := test.FromWKT(t, "POLYGON((0 0,0 2,2 2,2 0,0 0))") + b := test.FromWKT(t, "POLYGON((1 1,1 3,3 3,3 1,1 1))") got, err := geos.Difference(a, b) - expectNoErr(t, err) - want := geomFromWKT(t, "POLYGON((0 0,0 2,1 2,1 1,2 1,2 0,0 0))") + test.NoErr(t, err) + want := test.FromWKT(t, "POLYGON((0 0,0 2,1 2,1 1,2 1,2 0,0 0))") - expectGeomEq(t, got, want, geom.IgnoreOrder) + test.ExactEquals(t, got, want, geom.IgnoreOrder) } func TestSymmetricDifference(t *testing.T) { - a := geomFromWKT(t, "POLYGON((0 0,0 2,2 2,2 0,0 0))") - b := geomFromWKT(t, "POLYGON((1 1,1 3,3 3,3 1,1 1))") + a := test.FromWKT(t, "POLYGON((0 0,0 2,2 2,2 0,0 0))") + b := test.FromWKT(t, "POLYGON((1 1,1 3,3 3,3 1,1 1))") got, err := geos.SymmetricDifference(a, b) - expectNoErr(t, err) - want := geomFromWKT(t, "MULTIPOLYGON(((0 0,0 2,1 2,1 1,2 1,2 0,0 0)),((2 1,3 1,3 3,1 3,1 2,2 2,2 1)))") + test.NoErr(t, err) + want := test.FromWKT(t, "MULTIPOLYGON(((0 0,0 2,1 2,1 1,2 1,2 0,0 0)),((2 1,3 1,3 3,1 3,1 2,2 2,2 1)))") - expectGeomEq(t, got, want, geom.IgnoreOrder) + test.ExactEquals(t, got, want, geom.IgnoreOrder) } func TestMakeValid(t *testing.T) { @@ -852,13 +808,13 @@ func TestMakeValid(t *testing.T) { } { t.Run(strconv.Itoa(i), func(t *testing.T) { _, err := geom.UnmarshalWKT(tt.input) - expectErr(t, err) - in := geomFromWKT(t, tt.input, geom.NoValidate{}) + test.Err(t, err) + in := test.FromWKT(t, tt.input, geom.NoValidate{}) gotGeom, err := geos.MakeValid(in) skipIfUnsupported(t, err) - expectNoErr(t, err) - wantGeom := geomFromWKT(t, tt.wantOutput) - expectGeomEq(t, gotGeom, wantGeom, geom.IgnoreOrder) + test.NoErr(t, err) + wantGeom := test.FromWKT(t, tt.wantOutput) + test.ExactEquals(t, gotGeom, wantGeom, geom.IgnoreOrder) }) } } @@ -874,9 +830,9 @@ func TestUnaryUnion(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - got, err := geos.UnaryUnion(geomFromWKT(t, tt.input)) - expectNoErr(t, err) - expectGeomEq(t, got, geomFromWKT(t, tt.wantOutput), geom.IgnoreOrder) + got, err := geos.UnaryUnion(test.FromWKT(t, tt.input)) + test.NoErr(t, err) + test.ExactEquals(t, got, test.FromWKT(t, tt.wantOutput), geom.IgnoreOrder) }) } } @@ -934,15 +890,15 @@ func TestCoverageUnion(t *testing.T) { }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { - in := geomFromWKT(t, tc.input) + in := test.FromWKT(t, tc.input) gotGeom, err := geos.CoverageUnion(in) skipIfUnsupported(t, err) if tc.wantErr { - expectErr(t, err) + test.Err(t, err) } else { - expectNoErr(t, err) - wantGeom := geomFromWKT(t, tc.output) - expectGeomEq(t, gotGeom, wantGeom, geom.IgnoreOrder) + test.NoErr(t, err) + wantGeom := test.FromWKT(t, tc.output) + test.ExactEquals(t, gotGeom, wantGeom, geom.IgnoreOrder) } }) } @@ -951,15 +907,15 @@ func TestCoverageUnion(t *testing.T) { func TestCoverageSimplifyVW(t *testing.T) { input := geom.NewGeometryCollection( []geom.Geometry{ - geomFromWKTFile(t, "testdata/coverage_simplify_input_birchgrove.wkt"), - geomFromWKTFile(t, "testdata/coverage_simplify_input_balmain.wkt"), + test.FromWKT(t, test.ReadFile(t, "testdata/coverage_simplify_input_birchgrove.wkt")), + test.FromWKT(t, test.ReadFile(t, "testdata/coverage_simplify_input_balmain.wkt")), }, ) got, err := geos.CoverageSimplifyVW(input.AsGeometry(), 0.001, false) skipIfUnsupported(t, err) - expectNoErr(t, err) - want := geomFromWKTFile(t, "testdata/coverage_simplify_output.wkt") - expectGeomEq(t, got, want) + test.NoErr(t, err) + want := test.FromWKT(t, test.ReadFile(t, "testdata/coverage_simplify_output.wkt")) + test.ExactEquals(t, got, want) } func TestCoverageIsValid(t *testing.T) { @@ -1007,12 +963,12 @@ func TestCoverageIsValid(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - inputG := geomFromWKT(t, tc.input) + inputG := test.FromWKT(t, tc.input) valid, badEdges, err := geos.CoverageIsValid(inputG, 0) skipIfUnsupported(t, err) - expectNoErr(t, err) - expectBoolEq(t, valid, tc.wantValid) - expectGeomEq(t, badEdges, geomFromWKT(t, tc.wantBadEdges)) + test.NoErr(t, err) + test.Eq(t, valid, tc.wantValid) + test.ExactEquals(t, badEdges, test.FromWKT(t, tc.wantBadEdges)) }) } } diff --git a/internal/cartodemo/cartodemo_test.go b/internal/cartodemo/cartodemo_test.go index f1938b49..3713234f 100644 --- a/internal/cartodemo/cartodemo_test.go +++ b/internal/cartodemo/cartodemo_test.go @@ -11,7 +11,6 @@ import ( "image" "image/color" "image/draw" - "image/png" "io" "math" "os" @@ -20,6 +19,7 @@ import ( "github.com/peterstace/simplefeatures/carto" "github.com/peterstace/simplefeatures/geom" "github.com/peterstace/simplefeatures/internal/cartodemo/rasterize" + "github.com/peterstace/simplefeatures/internal/test" ) const ( @@ -229,7 +229,7 @@ func (f *worldProjectionFixture) build(t *testing.T, outputPath string) { for _, g := range []*geom.Geometry{&land, &lakes, &glaciers, &iceshelves} { clipped, err := geom.Intersection(*g, f.worldMask.AsGeometry()) *g = clipped - expectNoErr(t, err) + test.NoErr(t, err) } var graticules []geom.LineString @@ -248,7 +248,7 @@ func (f *worldProjectionFixture) build(t *testing.T, outputPath string) { for i := range graticules { clipped, err := geom.Intersection(graticules[i].AsGeometry(), f.worldMask.AsGeometry()) clippedGraticules = append(clippedGraticules, extractLinearParts(clipped)...) - expectNoErr(t, err) + test.NoErr(t, err) } graticules = clippedGraticules @@ -256,7 +256,7 @@ func (f *worldProjectionFixture) build(t *testing.T, outputPath string) { mapMaskRatio := mapMaskEnv.Width() / mapMaskEnv.Height() pxHigh := int(pxWide / mapMaskRatio) mapMaskCenter, ok := mapMaskEnv.Center().XY() - expectTrue(t, ok) + test.True(t, ok) mapUnitsPerPixel := f.mapMask.Envelope().Width() / float64(pxWide) @@ -326,28 +326,20 @@ func (f *worldProjectionFixture) build(t *testing.T, outputPath string) { rast.MultiLineString(mapOutline) rast.Draw(img, img.Bounds(), image.NewUniform(color.Black), image.Point{}) - err := os.WriteFile(outputPath, imageToPNG(t, img), 0o600) - expectNoErr(t, err) -} - -func imageToPNG(t *testing.T, img image.Image) []byte { - t.Helper() - buf := new(bytes.Buffer) - err := png.Encode(buf, img) - expectNoErr(t, err) - return buf.Bytes() + err := os.WriteFile(outputPath, test.ImageToPNG(t, img), 0o600) + test.NoErr(t, err) } func loadGeom(t *testing.T, filename string) geom.Geometry { t.Helper() zippedBuf, err := os.ReadFile(filename) - expectNoErr(t, err) + test.NoErr(t, err) gzipReader, err := gzip.NewReader(bytes.NewReader(zippedBuf)) - expectNoErr(t, err) + test.NoErr(t, err) unzippedBuf, err := io.ReadAll(gzipReader) - expectNoErr(t, err) + test.NoErr(t, err) // TODO: There is currently no way to disable a GeoJSON GeometryCollection // without validation directly. See @@ -359,11 +351,11 @@ func loadGeom(t *testing.T, filename string) geom.Geometry { } `json:"features"` } err = json.Unmarshal(unzippedBuf, &collection) - expectNoErr(t, err) + test.NoErr(t, err) var gs []geom.Geometry for _, rawFeat := range collection.Features { g, err := geom.UnmarshalGeoJSON(rawFeat.Geometry, geom.NoValidate{}) - expectNoErr(t, err) + test.NoErr(t, err) if err := g.Validate(); err != nil { continue } @@ -371,7 +363,7 @@ func loadGeom(t *testing.T, filename string) geom.Geometry { } all, err := geom.UnionMany(gs) - expectNoErr(t, err) + test.NoErr(t, err) return all } @@ -424,17 +416,3 @@ func circle(c geom.XY, r float64) geom.Polygon { func rectangle(tl, br geom.XY) geom.Polygon { return geom.NewEnvelope(tl, br).AsGeometry().MustAsPolygon() } - -func expectNoErr(tb testing.TB, err error) { - tb.Helper() - if err != nil { - tb.Fatalf("unexpected error: %v", err) - } -} - -func expectTrue(tb testing.TB, b bool) { - tb.Helper() - if !b { - tb.Fatalf("expected true, got false") - } -} diff --git a/internal/cartodemo/rasterize/draw_test.go b/internal/cartodemo/rasterize/draw_test.go index 5adc3acc..0d4db5d1 100644 --- a/internal/cartodemo/rasterize/draw_test.go +++ b/internal/cartodemo/rasterize/draw_test.go @@ -8,17 +8,18 @@ import ( "github.com/peterstace/simplefeatures/geom" "github.com/peterstace/simplefeatures/internal/cartodemo/rasterize" + "github.com/peterstace/simplefeatures/internal/test" ) func TestDrawLine(t *testing.T) { g, err := geom.UnmarshalWKT("LINESTRING(4 4, 12 8, 4 12)") - expectNoErr(t, err) + test.NoErr(t, err) img := image.NewRGBA(image.Rect(0, 0, 16, 16)) rast := rasterize.NewRasterizer(16, 16) rast.LineString(g.MustAsLineString()) rast.Draw(img, img.Bounds(), image.NewUniform(color.Black), image.Point{}) - err = os.WriteFile("testdata/line.png", imageToPNG(t, img), 0o600) - expectNoErr(t, err) + err = os.WriteFile("testdata/line.png", test.ImageToPNG(t, img), 0o600) + test.NoErr(t, err) } diff --git a/internal/cartodemo/rasterize/rasterizer_test.go b/internal/cartodemo/rasterize/rasterizer_test.go index 6a051e8a..9df8e8be 100644 --- a/internal/cartodemo/rasterize/rasterizer_test.go +++ b/internal/cartodemo/rasterize/rasterizer_test.go @@ -8,6 +8,7 @@ import ( "github.com/peterstace/simplefeatures/geom" "github.com/peterstace/simplefeatures/internal/cartodemo/rasterize" + "github.com/peterstace/simplefeatures/internal/test" ) func TestRasterizer(t *testing.T) { @@ -15,12 +16,12 @@ func TestRasterizer(t *testing.T) { rast := rasterize.NewRasterizer(sz, sz) ls, err := geom.UnmarshalWKT("LINESTRING(4 4, 12 8, 4 12)") - expectNoErr(t, err) + test.NoErr(t, err) rast.LineString(ls.MustAsLineString()) img := image.NewRGBA(image.Rect(0, 0, sz, sz)) rast.Draw(img, img.Bounds(), image.NewUniform(color.Black), image.Point{}) - err = os.WriteFile("testdata/line.png", imageToPNG(t, img), 0o600) - expectNoErr(t, err) + err = os.WriteFile("testdata/line.png", test.ImageToPNG(t, img), 0o600) + test.NoErr(t, err) } diff --git a/internal/cartodemo/rasterize/util_test.go b/internal/cartodemo/rasterize/util_test.go deleted file mode 100644 index 29539c1e..00000000 --- a/internal/cartodemo/rasterize/util_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package rasterize_test - -import ( - "bytes" - "image" - "image/png" - "testing" -) - -func expectNoErr(tb testing.TB, err error) { - tb.Helper() - if err != nil { - tb.Fatalf("unexpected error: %v", err) - } -} - -func imageToPNG(t *testing.T, img image.Image) []byte { - t.Helper() - buf := new(bytes.Buffer) - err := png.Encode(buf, img) - expectNoErr(t, err) - return buf.Bytes() -} diff --git a/internal/cmprefimpl/cmpgeos/extract_source.go b/internal/cmprefimpl/cmpgeos/extract_source.go index 145314e4..b59bb17a 100644 --- a/internal/cmprefimpl/cmpgeos/extract_source.go +++ b/internal/cmprefimpl/cmpgeos/extract_source.go @@ -2,10 +2,10 @@ package main import ( "bufio" + "encoding/hex" "errors" "os" "sort" - "strconv" "strings" "github.com/peterstace/simplefeatures/geom" @@ -53,7 +53,7 @@ func convertToGeometries(candidates []string) ([]geom.Geometry, error) { oldCount := len(geoms) for _, c := range candidates { - buf, err := hexStringToBytes(c) + buf, err := hex.DecodeString(c) if err != nil { continue } @@ -79,18 +79,3 @@ func convertToGeometries(candidates []string) ([]geom.Geometry, error) { return geoms, nil } - -func hexStringToBytes(s string) ([]byte, error) { - if len(s)%2 != 0 { - return nil, errors.New("hex string must have even length") - } - var buf []byte - for i := 0; i < len(s); i += 2 { - x, err := strconv.ParseUint(s[i:i+2], 16, 8) - if err != nil { - return nil, err - } - buf = append(buf, byte(x)) - } - return buf, nil -} diff --git a/internal/cmprefimpl/cmppg/checks.go b/internal/cmprefimpl/cmppg/checks.go index e8d3bd48..49ca746f 100644 --- a/internal/cmprefimpl/cmppg/checks.go +++ b/internal/cmprefimpl/cmppg/checks.go @@ -3,7 +3,6 @@ package main import ( "bytes" "encoding/hex" - "errors" "fmt" "math" "strconv" @@ -61,7 +60,7 @@ func checkWKTParse(t *testing.T, pg PostGIS, candidates []string) { func checkWKBParse(t *testing.T, pg PostGIS, candidates []string) { var found bool for i, wkb := range candidates { - buf, err := hexStringToBytes(wkb) + buf, err := hex.DecodeString(wkb) if err != nil { continue } @@ -101,21 +100,6 @@ func checkWKBParse(t *testing.T, pg PostGIS, candidates []string) { } } -func hexStringToBytes(s string) ([]byte, error) { - if len(s)%2 != 0 { - return nil, errors.New("hex string must have even length") - } - var buf []byte - for i := 0; i < len(s); i += 2 { - x, err := strconv.ParseUint(s[i:i+2], 16, 8) - if err != nil { - return nil, err - } - buf = append(buf, byte(x)) - } - return buf, nil -} - // isEWKB returns true if the WKB hex string uses EWKB extensions (SRID or // extended dimension flags). PostGIS uses EWKB by default but simplefeatures // only supports standard WKB. diff --git a/internal/cmprefimpl/cmppg/fuzz_test.go b/internal/cmprefimpl/cmppg/fuzz_test.go index 91591944..6eaebd97 100644 --- a/internal/cmprefimpl/cmppg/fuzz_test.go +++ b/internal/cmprefimpl/cmppg/fuzz_test.go @@ -3,6 +3,7 @@ package main import ( "bufio" "database/sql" + "encoding/hex" "fmt" "math" "os" @@ -121,7 +122,7 @@ func convertToGeometries(t *testing.T, candidates []string) []geom.Geometry { oldCount := len(geoms) for _, c := range candidates { - buf, err := hexStringToBytes(c) + buf, err := hex.DecodeString(c) if err != nil { continue } diff --git a/internal/test/test.go b/internal/test/test.go index 28a32261..782cd7b4 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -2,21 +2,39 @@ package test import ( + "bytes" "errors" + "image" + "image/png" "math" + "os" "reflect" "testing" "github.com/peterstace/simplefeatures/geom" ) -func FromWKT(tb testing.TB, wkt string) geom.Geometry { +func FromWKT(tb testing.TB, wkt string, nv ...geom.NoValidate) geom.Geometry { tb.Helper() - g, err := geom.UnmarshalWKT(wkt) + g, err := geom.UnmarshalWKT(wkt, nv...) NoErr(tb, err) return g } +func FromGeoJSON(tb testing.TB, geojson string, nv ...geom.NoValidate) geom.Geometry { + tb.Helper() + g, err := geom.UnmarshalGeoJSON([]byte(geojson), nv...) + NoErr(tb, err) + return g +} + +func ReadFile(tb testing.TB, path string) string { + tb.Helper() + data, err := os.ReadFile(path) + NoErr(tb, err) + return string(data) +} + func Eq[T comparable](tb testing.TB, got, want T) { tb.Helper() if got != want { @@ -24,6 +42,28 @@ func Eq[T comparable](tb testing.TB, got, want T) { } } +// ordered can be replaced with cmp.Ordered when upgrading to Go 1.21. +type ordered interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | + ~float32 | ~float64 | + ~string +} + +func GT[T ordered](tb testing.TB, got, greaterThan T) { + tb.Helper() + if !(got > greaterThan) { + tb.Fatalf("got: %v\ngreater than: %v", got, greaterThan) + } +} + +func LT[T ordered](tb testing.TB, got, lessThan T) { + tb.Helper() + if !(got < lessThan) { + tb.Fatalf("got: %v\nless than: %v", got, lessThan) + } +} + func True(tb testing.TB, cond bool) { tb.Helper() if !cond { @@ -45,6 +85,17 @@ func NoErr(tb testing.TB, err error) { } } +func Panics(tb testing.TB, fn func()) { + tb.Helper() + defer func() { + if r := recover(); r != nil { + return + } + tb.Errorf("didn't panic") + }() + fn() +} + func Err(tb testing.TB, err error) { tb.Helper() if err == nil { @@ -52,6 +103,13 @@ func Err(tb testing.TB, err error) { } } +func ErrIs(tb testing.TB, err, want error) { + tb.Helper() + if !errors.Is(err, want) { + tb.Fatalf("got: %v\nwant: %v", err, want) + } +} + func ErrAs(tb testing.TB, err error, target any) { tb.Helper() if !errors.As(err, target) { @@ -93,6 +151,14 @@ func NotDeepEqual(tb testing.TB, a, b any) { } } +func ImageToPNG(tb testing.TB, img image.Image) []byte { + tb.Helper() + buf := new(bytes.Buffer) + err := png.Encode(buf, img) + NoErr(tb, err) + return buf.Bytes() +} + // Tolerance specifies tolerances for approximate float comparison. type Tolerance struct { Rel float64 // Relative tolerance: diff must be <= Rel * max(|got|, |want|). diff --git a/test-helper-consolidation.md b/test-helper-consolidation.md new file mode 100644 index 00000000..749ef89a --- /dev/null +++ b/test-helper-consolidation.md @@ -0,0 +1,90 @@ +# Test Helper Consolidation + +This document describes the current state of test helper consolidation into the +`internal/test` package, and identifies remaining work. Remove sections from +this document as they are completed. + +## Current state of `internal/test` + +The `internal/test` package provides the following helpers: + +| Helper | Description | +| --- | --- | +| `FromWKT` | Parse WKT string into `geom.Geometry` | +| `FromGeoJSON` | Parse GeoJSON string into `geom.Geometry` | +| `ReadFile` | Read a file, failing on error | +| `Eq[T]` | Assert two comparable values are equal | +| `GT[T]` | Assert a value is greater than another | +| `LT[T]` | Assert a value is less than another | +| `True` | Assert a condition is true | +| `False` | Assert a condition is false | +| `NoErr` | Assert no error | +| `Err` | Assert an error occurred | +| `ErrIs` | Assert `errors.Is` match | +| `ErrAs` | Assert `errors.As` match | +| `Panics` | Assert a function panics | +| `ExactEquals` | Assert two geometries are exactly equal | +| `NotExactEquals` | Assert two geometries are not exactly equal | +| `ExactEqualsWKT` | Assert a geometry exactly equals a WKT string | +| `DeepEqual` | Assert `reflect.DeepEqual` | +| `NotDeepEqual` | Assert not `reflect.DeepEqual` | +| `ApproxEqual` | Assert two float64 values are approximately eq. | +| `ImageToPNG` | Encode an image to PNG bytes | + +## Remaining helpers in the geom package + +The `geom/util_test.go` file was deleted as part of the initial consolidation, +but several helpers remain scattered across other geom test files. + +### Used in a single geom test file + +These are file-local but could be consolidated for consistency: + +| Helper | File | +| --- | --- | +| `expectDumpEqWKT` | `geom/alg_dump_test.go` | +| `upcastPoints/LineStrings/Polygons` | `geom/alg_dump_test.go` | +| `expectSequenceEq` | `geom/dump_coordinates_test.go` | +| `xyCoords` | `geom/accessor_test.go` | +| `xy` | `geom/validation_test.go` | +| `checkSequence` | `geom/type_sequence_test.go` | +| `regularPolygon` | `geom/perf_test.go` | + +### Excluded from consolidation + +The following are trivial local utilities, not reusable test helpers: + +- `maxInt`, `minInt` in `geom/alg_overlay_test.go` +- `minMax` in `geom/twkb_test.go` +- `newSimpleDisjointSet` in `geom/alg_disjoint_set_internal_test.go` + +## Helpers in other packages + +### carto + +File: `carto/projections_test.go` + +| Helper | Notes | +| --- | --- | +| `xy` | Trivial `geom.XY` constructor | +| `expectXYWithinTolerance` | Similar to `test.ApproxEqual` but for `geom.XY` | + +### Duplicated `regularPolygon` + +The `regularPolygon` helper is defined independently in three places: + +- `geom/perf_test.go` +- `internal/perf/util_test.go` +- `internal/rawgeos/benchmark_internal_test.go` + +### Not candidates for consolidation + +The following packages have domain-specific test helpers that are tightly +coupled to their package internals: + +- **rtree**: `testBulkLoad`, `testPopulations`, `checkSearch`, + `checkInvariants`, `randomBox`, `checkNearest`, `checkPrioritySearch` — these + operate on internal rtree types. +- **internal/cmprefimpl/cmppg**: `setupDB`, `loadStringsFromFile`, + `convertToGeometries`, `isMultiPointWithEmptyPoint`, `hasLargeCoordinates` — + these are specific to PostGIS comparison testing. From 9f42f539512328a182c2de37d884d6e95f978fd1 Mon Sep 17 00:00:00 2001 From: Peter Stace Date: Mon, 30 Mar 2026 15:46:09 +1100 Subject: [PATCH 2/3] Remove test-helper-consolidation.md --- test-helper-consolidation.md | 90 ------------------------------------ 1 file changed, 90 deletions(-) delete mode 100644 test-helper-consolidation.md diff --git a/test-helper-consolidation.md b/test-helper-consolidation.md deleted file mode 100644 index 749ef89a..00000000 --- a/test-helper-consolidation.md +++ /dev/null @@ -1,90 +0,0 @@ -# Test Helper Consolidation - -This document describes the current state of test helper consolidation into the -`internal/test` package, and identifies remaining work. Remove sections from -this document as they are completed. - -## Current state of `internal/test` - -The `internal/test` package provides the following helpers: - -| Helper | Description | -| --- | --- | -| `FromWKT` | Parse WKT string into `geom.Geometry` | -| `FromGeoJSON` | Parse GeoJSON string into `geom.Geometry` | -| `ReadFile` | Read a file, failing on error | -| `Eq[T]` | Assert two comparable values are equal | -| `GT[T]` | Assert a value is greater than another | -| `LT[T]` | Assert a value is less than another | -| `True` | Assert a condition is true | -| `False` | Assert a condition is false | -| `NoErr` | Assert no error | -| `Err` | Assert an error occurred | -| `ErrIs` | Assert `errors.Is` match | -| `ErrAs` | Assert `errors.As` match | -| `Panics` | Assert a function panics | -| `ExactEquals` | Assert two geometries are exactly equal | -| `NotExactEquals` | Assert two geometries are not exactly equal | -| `ExactEqualsWKT` | Assert a geometry exactly equals a WKT string | -| `DeepEqual` | Assert `reflect.DeepEqual` | -| `NotDeepEqual` | Assert not `reflect.DeepEqual` | -| `ApproxEqual` | Assert two float64 values are approximately eq. | -| `ImageToPNG` | Encode an image to PNG bytes | - -## Remaining helpers in the geom package - -The `geom/util_test.go` file was deleted as part of the initial consolidation, -but several helpers remain scattered across other geom test files. - -### Used in a single geom test file - -These are file-local but could be consolidated for consistency: - -| Helper | File | -| --- | --- | -| `expectDumpEqWKT` | `geom/alg_dump_test.go` | -| `upcastPoints/LineStrings/Polygons` | `geom/alg_dump_test.go` | -| `expectSequenceEq` | `geom/dump_coordinates_test.go` | -| `xyCoords` | `geom/accessor_test.go` | -| `xy` | `geom/validation_test.go` | -| `checkSequence` | `geom/type_sequence_test.go` | -| `regularPolygon` | `geom/perf_test.go` | - -### Excluded from consolidation - -The following are trivial local utilities, not reusable test helpers: - -- `maxInt`, `minInt` in `geom/alg_overlay_test.go` -- `minMax` in `geom/twkb_test.go` -- `newSimpleDisjointSet` in `geom/alg_disjoint_set_internal_test.go` - -## Helpers in other packages - -### carto - -File: `carto/projections_test.go` - -| Helper | Notes | -| --- | --- | -| `xy` | Trivial `geom.XY` constructor | -| `expectXYWithinTolerance` | Similar to `test.ApproxEqual` but for `geom.XY` | - -### Duplicated `regularPolygon` - -The `regularPolygon` helper is defined independently in three places: - -- `geom/perf_test.go` -- `internal/perf/util_test.go` -- `internal/rawgeos/benchmark_internal_test.go` - -### Not candidates for consolidation - -The following packages have domain-specific test helpers that are tightly -coupled to their package internals: - -- **rtree**: `testBulkLoad`, `testPopulations`, `checkSearch`, - `checkInvariants`, `randomBox`, `checkNearest`, `checkPrioritySearch` — these - operate on internal rtree types. -- **internal/cmprefimpl/cmppg**: `setupDB`, `loadStringsFromFile`, - `convertToGeometries`, `isMultiPointWithEmptyPoint`, `hasLargeCoordinates` — - these are specific to PostGIS comparison testing. From 781ab299501eedec747dbeeec1adc152bc29d97e Mon Sep 17 00:00:00 2001 From: Peter Stace Date: Mon, 30 Mar 2026 16:12:09 +1100 Subject: [PATCH 3/3] Fix ClipByRect test to use consolidated test helpers --- geos/entrypoints_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/geos/entrypoints_test.go b/geos/entrypoints_test.go index 4024f85f..c12cf7b3 100644 --- a/geos/entrypoints_test.go +++ b/geos/entrypoints_test.go @@ -1048,10 +1048,10 @@ func TestClipByRect(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - got, err := geos.ClipByRect(geomFromWKT(t, tc.input), tc.rect) + got, err := geos.ClipByRect(test.FromWKT(t, tc.input), tc.rect) skipIfUnsupported(t, err) - expectNoErr(t, err) - expectGeomEq(t, got, geomFromWKT(t, tc.want), geom.IgnoreOrder) + test.NoErr(t, err) + test.ExactEquals(t, got, test.FromWKT(t, tc.want), geom.IgnoreOrder) }) } }