From ef42718a2b5ca941931d49221928d88ce24206c9 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 7 Nov 2024 20:40:56 +0000 Subject: [PATCH] Used assertRaisesMessage and subTest where appropriate in GEOS tests. --- tests/gis_tests/data/geometries.json | 12 +- tests/gis_tests/geos_tests/test_geos.py | 838 ++++++++++-------- .../geos_tests/test_geos_mutation.py | 188 ++-- tests/gis_tests/geos_tests/test_io.py | 33 +- .../gis_tests/geos_tests/test_mutable_list.py | 290 +++--- 5 files changed, 754 insertions(+), 607 deletions(-) diff --git a/tests/gis_tests/data/geometries.json b/tests/gis_tests/data/geometries.json index eb6c64b6d9..6856ac793a 100644 --- a/tests/gis_tests/data/geometries.json +++ b/tests/gis_tests/data/geometries.json @@ -11,12 +11,12 @@ {"wkt": "MULTIPOLYGON (((180 60, 240 160, 300 60, 180 60)), ((80 80, 180 60, 160 140, 240 160, 360 140, 300 60, 420 100, 320 280, 120 260, 80 80)))", "valid": true, "num_geom": 2, "n_p": 14} ], "errors": [ - {"wkt": "GEOMETR##!@#%#............a32515", "bad": true, "hex": false}, - {"wkt": "Foo.Bar", "bad": true, "hex": false}, - {"wkt": "POINT (5, 23)", "bad": true, "hex": false}, - {"wkt": "AAABBBDDDAAD##@#1113511111-098111111111111111533333333333333", "bad": true, "hex": true}, - {"wkt": "FFFFFFFFFFFFFFFFF1355555555555555555565111", "bad": true, "hex": true}, - {"wkt": "", "bad": true, "hex": false} + {"wkt": "GEOMETR##!@#%#............a32515", "bad": true, "hex": false, "msg": "String input unrecognized as WKT EWKT, and HEXEWKB."}, + {"wkt": "Foo.Bar", "bad": true, "hex": false, "msg": "String input unrecognized as WKT EWKT, and HEXEWKB."}, + {"wkt": "POINT (5, 23)", "bad": true, "hex": false, "msg": "Error encountered checking Geometry returned from GEOS C function \"GEOSWKTReader_read_r\"."}, + {"wkt": "AAABBBDDDAAD##@#1113511111-098111111111111111533333333333333", "bad": true, "hex": true, "msg": "String input unrecognized as WKT EWKT, and HEXEWKB."}, + {"wkt": "FFFFFFFFFFFFFFFFF1355555555555555555565111", "bad": true, "hex": true, "msg": "Error encountered checking Geometry returned from GEOS C function \"GEOSWKBReader_readHEX_r\"."}, + {"wkt": "", "bad": true, "hex": false, "msg": "String input unrecognized as WKT EWKT, and HEXEWKB."} ], "wkt_out": [ {"wkt": "POINT (110 130)", "ewkt": "POINT (110 130)", "kml": "110.0,130.0,0", "gml": "110,130"}, diff --git a/tests/gis_tests/geos_tests/test_geos.py b/tests/gis_tests/geos_tests/test_geos.py index 724555560b..88d0344972 100644 --- a/tests/gis_tests/geos_tests/test_geos.py +++ b/tests/gis_tests/geos_tests/test_geos.py @@ -28,17 +28,34 @@ from django.contrib.gis.shortcuts import numpy from django.template import Context from django.template.engine import Engine from django.test import SimpleTestCase +from django.utils.version import PY312 from ..test_data import TestDataMixin class GEOSTest(SimpleTestCase, TestDataMixin): + + error_checking_geom = ( + 'Error encountered checking Geometry returned from GEOS C function "{}".' + ) + + def assertArgumentTypeError(self, i, bad_type): + if PY312: + msg = ( + f"argument {i}: TypeError: '{bad_type}' object cannot be interpreted " + "as an integer" + ) + else: + msg = f"argument {i}: TypeError: wrong type" + return self.assertRaisesMessage(ctypes.ArgumentError, msg) + def test_wkt(self): "Testing WKT output." for g in self.geometries.wkt_out: - geom = fromstr(g.wkt) - if geom.hasz: - self.assertEqual(g.ewkt, geom.wkt) + with self.subTest(g=g): + geom = fromstr(g.wkt) + if geom.hasz: + self.assertEqual(g.ewkt, geom.wkt) def test_wkt_invalid(self): msg = "String input unrecognized as WKT EWKT, and HEXEWKB." @@ -50,8 +67,9 @@ class GEOSTest(SimpleTestCase, TestDataMixin): def test_hex(self): "Testing HEX output." for g in self.geometries.hex_wkt: - geom = fromstr(g.wkt) - self.assertEqual(g.hex, geom.hex.decode()) + with self.subTest(g=g): + geom = fromstr(g.wkt) + self.assertEqual(g.hex, geom.hex.decode()) def test_hexewkb(self): "Testing (HEX)EWKB output." @@ -92,28 +110,35 @@ class GEOSTest(SimpleTestCase, TestDataMixin): geom = fromstr(tg.wkt) kml = getattr(tg, "kml", False) if kml: - self.assertEqual(kml, geom.kml) + with self.subTest(tg=tg): + self.assertEqual(kml, geom.kml) def test_errors(self): "Testing the Error handlers." # string-based for err in self.geometries.errors: - with self.assertRaises((GEOSException, ValueError)): + with ( + self.subTest(err=err.wkt), + self.assertRaisesMessage((GEOSException, ValueError), err.msg), + ): fromstr(err.wkt) # Bad WKB - with self.assertRaises(GEOSException): + with self.assertRaisesMessage( + GEOSException, self.error_checking_geom.format("GEOSWKBReader_read_r") + ): GEOSGeometry(memoryview(b"0")) class NotAGeometry: pass - # Some other object - with self.assertRaises(TypeError): - GEOSGeometry(NotAGeometry()) - # None - with self.assertRaises(TypeError): - GEOSGeometry(None) + for geom in (NotAGeometry(), None): + msg = f"Improper geometry input type: {type(geom)}" + with ( + self.subTest(geom=geom), + self.assertRaisesMessage(TypeError, msg), + ): + GEOSGeometry(geom) def test_wkb(self): "Testing WKB output." @@ -128,7 +153,8 @@ class GEOSTest(SimpleTestCase, TestDataMixin): geom_h = GEOSGeometry(g.hex) # we need to do this so decimal places get normalized geom_t = fromstr(g.wkt) - self.assertEqual(geom_t.wkt, geom_h.wkt) + with self.subTest(g=g): + self.assertEqual(geom_t.wkt, geom_h.wkt) def test_create_wkb(self): "Testing creation from WKB." @@ -137,7 +163,8 @@ class GEOSTest(SimpleTestCase, TestDataMixin): geom_h = GEOSGeometry(wkb) # we need to do this so decimal places get normalized geom_t = fromstr(g.wkt) - self.assertEqual(geom_t.wkt, geom_h.wkt) + with self.subTest(g=g): + self.assertEqual(geom_t.wkt, geom_h.wkt) def test_ewkt(self): "Testing EWKT." @@ -146,19 +173,21 @@ class GEOSTest(SimpleTestCase, TestDataMixin): for p in self.geometries.polygons: ewkt = "SRID=%d;%s" % (srid, p.wkt) poly = fromstr(ewkt) - self.assertEqual(srid, poly.srid) - self.assertEqual(srid, poly.shell.srid) - self.assertEqual(srid, fromstr(poly.ewkt).srid) # Checking export + with self.subTest(p=p): + self.assertEqual(srid, poly.srid) + self.assertEqual(srid, poly.shell.srid) + self.assertEqual(srid, fromstr(poly.ewkt).srid) # Checking export def test_json(self): "Testing GeoJSON input/output (via GDAL)." for g in self.geometries.json_geoms: geom = GEOSGeometry(g.wkt) - if not hasattr(g, "not_equal"): - # Loading jsons to prevent decimal differences - self.assertEqual(json.loads(g.json), json.loads(geom.json)) - self.assertEqual(json.loads(g.json), json.loads(geom.geojson)) - self.assertEqual(GEOSGeometry(g.wkt, 4326), GEOSGeometry(geom.json)) + with self.subTest(g=g): + if not hasattr(g, "not_equal"): + # Loading jsons to prevent decimal differences + self.assertEqual(json.loads(g.json), json.loads(geom.json)) + self.assertEqual(json.loads(g.json), json.loads(geom.geojson)) + self.assertEqual(GEOSGeometry(g.wkt, 4326), GEOSGeometry(geom.json)) def test_json_srid(self): geojson_data = { @@ -187,7 +216,8 @@ class GEOSTest(SimpleTestCase, TestDataMixin): for fh in (wkt_f, wkb_f): fh.seek(0) pnt = fromfile(fh) - self.assertEqual(ref_pnt, pnt) + with self.subTest(fh=fh): + self.assertEqual(ref_pnt, pnt) def test_eq(self): "Testing equivalence." @@ -201,9 +231,10 @@ class GEOSTest(SimpleTestCase, TestDataMixin): # Error shouldn't be raise on equivalence testing with # an invalid type. for g in (p, ls): - self.assertIsNotNone(g) - self.assertNotEqual(g, {"foo": "bar"}) - self.assertIsNot(g, False) + with self.subTest(g=g): + self.assertIsNotNone(g) + self.assertNotEqual(g, {"foo": "bar"}) + self.assertIsNot(g, False) def test_hash(self): point_1 = Point(5, 23) @@ -331,55 +362,56 @@ class GEOSTest(SimpleTestCase, TestDataMixin): for p in self.geometries.points: # Creating the point from the WKT pnt = fromstr(p.wkt) - self.assertEqual(pnt.geom_type, "Point") - self.assertEqual(pnt.geom_typeid, 0) - self.assertEqual(pnt.dims, 0) - self.assertEqual(p.x, pnt.x) - self.assertEqual(p.y, pnt.y) - self.assertEqual(pnt, fromstr(p.wkt)) - self.assertIs(pnt == prev, False) # Use assertIs() to test __eq__. + with self.subTest(p=p): + self.assertEqual(pnt.geom_type, "Point") + self.assertEqual(pnt.geom_typeid, 0) + self.assertEqual(pnt.dims, 0) + self.assertEqual(p.x, pnt.x) + self.assertEqual(p.y, pnt.y) + self.assertEqual(pnt, fromstr(p.wkt)) + self.assertIs(pnt == prev, False) # Use assertIs() to test __eq__. - # Making sure that the point's X, Y components are what we expect - self.assertAlmostEqual(p.x, pnt.tuple[0], 9) - self.assertAlmostEqual(p.y, pnt.tuple[1], 9) + # Making sure that the point's X, Y components are what we expect + self.assertAlmostEqual(p.x, pnt.tuple[0], 9) + self.assertAlmostEqual(p.y, pnt.tuple[1], 9) - # Testing the third dimension, and getting the tuple arguments - if hasattr(p, "z"): - self.assertIs(pnt.hasz, True) - self.assertEqual(p.z, pnt.z) - self.assertEqual(p.z, pnt.tuple[2], 9) - tup_args = (p.x, p.y, p.z) - set_tup1 = (2.71, 3.14, 5.23) - set_tup2 = (5.23, 2.71, 3.14) - else: - self.assertIs(pnt.hasz, False) - self.assertIsNone(pnt.z) - tup_args = (p.x, p.y) - set_tup1 = (2.71, 3.14) - set_tup2 = (3.14, 2.71) + # Testing the third dimension, and getting the tuple arguments + if hasattr(p, "z"): + self.assertIs(pnt.hasz, True) + self.assertEqual(p.z, pnt.z) + self.assertEqual(p.z, pnt.tuple[2], 9) + tup_args = (p.x, p.y, p.z) + set_tup1 = (2.71, 3.14, 5.23) + set_tup2 = (5.23, 2.71, 3.14) + else: + self.assertIs(pnt.hasz, False) + self.assertIsNone(pnt.z) + tup_args = (p.x, p.y) + set_tup1 = (2.71, 3.14) + set_tup2 = (3.14, 2.71) - # Centroid operation on point should be point itself - self.assertEqual(p.centroid, pnt.centroid.tuple) + # Centroid operation on point should be point itself + self.assertEqual(p.centroid, pnt.centroid.tuple) - # Now testing the different constructors - pnt2 = Point(tup_args) # e.g., Point((1, 2)) - pnt3 = Point(*tup_args) # e.g., Point(1, 2) - self.assertEqual(pnt, pnt2) - self.assertEqual(pnt, pnt3) + # Now testing the different constructors + pnt2 = Point(tup_args) # e.g., Point((1, 2)) + pnt3 = Point(*tup_args) # e.g., Point(1, 2) + self.assertEqual(pnt, pnt2) + self.assertEqual(pnt, pnt3) - # Now testing setting the x and y - pnt.y = 3.14 - pnt.x = 2.71 - self.assertEqual(3.14, pnt.y) - self.assertEqual(2.71, pnt.x) + # Now testing setting the x and y + pnt.y = 3.14 + pnt.x = 2.71 + self.assertEqual(3.14, pnt.y) + self.assertEqual(2.71, pnt.x) - # Setting via the tuple/coords property - pnt.tuple = set_tup1 - self.assertEqual(set_tup1, pnt.tuple) - pnt.coords = set_tup2 - self.assertEqual(set_tup2, pnt.coords) + # Setting via the tuple/coords property + pnt.tuple = set_tup1 + self.assertEqual(set_tup1, pnt.tuple) + pnt.coords = set_tup2 + self.assertEqual(set_tup2, pnt.coords) - prev = pnt # setting the previous geometry + prev = pnt # setting the previous geometry def test_point_reverse(self): point = GEOSGeometry("POINT(144.963 -37.8143)", 4326) @@ -391,56 +423,64 @@ class GEOSTest(SimpleTestCase, TestDataMixin): "Testing MultiPoint objects." for mp in self.geometries.multipoints: mpnt = fromstr(mp.wkt) - self.assertEqual(mpnt.geom_type, "MultiPoint") - self.assertEqual(mpnt.geom_typeid, 4) - self.assertEqual(mpnt.dims, 0) + with self.subTest(mp=mp): + self.assertEqual(mpnt.geom_type, "MultiPoint") + self.assertEqual(mpnt.geom_typeid, 4) + self.assertEqual(mpnt.dims, 0) - self.assertAlmostEqual(mp.centroid[0], mpnt.centroid.tuple[0], 9) - self.assertAlmostEqual(mp.centroid[1], mpnt.centroid.tuple[1], 9) + self.assertAlmostEqual(mp.centroid[0], mpnt.centroid.tuple[0], 9) + self.assertAlmostEqual(mp.centroid[1], mpnt.centroid.tuple[1], 9) - with self.assertRaises(IndexError): - mpnt.__getitem__(len(mpnt)) - self.assertEqual(mp.centroid, mpnt.centroid.tuple) - self.assertEqual(mp.coords, tuple(m.tuple for m in mpnt)) - for p in mpnt: - self.assertEqual(p.geom_type, "Point") - self.assertEqual(p.geom_typeid, 0) - self.assertIs(p.empty, False) - self.assertIs(p.valid, True) + mpnt_len = len(mpnt) + msg = f"invalid index: {mpnt_len}" + with self.assertRaisesMessage(IndexError, msg): + mpnt.__getitem__(mpnt_len) + self.assertEqual(mp.centroid, mpnt.centroid.tuple) + self.assertEqual(mp.coords, tuple(m.tuple for m in mpnt)) + for p in mpnt: + self.assertEqual(p.geom_type, "Point") + self.assertEqual(p.geom_typeid, 0) + self.assertIs(p.empty, False) + self.assertIs(p.valid, True) def test_linestring(self): "Testing LineString objects." prev = fromstr("POINT(0 0)") for line in self.geometries.linestrings: ls = fromstr(line.wkt) - self.assertEqual(ls.geom_type, "LineString") - self.assertEqual(ls.geom_typeid, 1) - self.assertEqual(ls.dims, 1) - self.assertIs(ls.empty, False) - self.assertIs(ls.ring, False) - if hasattr(line, "centroid"): - self.assertEqual(line.centroid, ls.centroid.tuple) - if hasattr(line, "tup"): - self.assertEqual(line.tup, ls.tuple) + with self.subTest(line=line): + self.assertEqual(ls.geom_type, "LineString") + self.assertEqual(ls.geom_typeid, 1) + self.assertEqual(ls.dims, 1) + self.assertIs(ls.empty, False) + self.assertIs(ls.ring, False) + if hasattr(line, "centroid"): + self.assertEqual(line.centroid, ls.centroid.tuple) + if hasattr(line, "tup"): + self.assertEqual(line.tup, ls.tuple) - self.assertEqual(ls, fromstr(line.wkt)) - self.assertIs(ls == prev, False) # Use assertIs() to test __eq__. - with self.assertRaises(IndexError): - ls.__getitem__(len(ls)) - prev = ls + self.assertEqual(ls, fromstr(line.wkt)) + self.assertIs(ls == prev, False) # Use assertIs() to test __eq__. + ls_len = len(ls) + msg = f"invalid index: {ls_len}" + with self.assertRaisesMessage(IndexError, msg): + ls.__getitem__(ls_len) + prev = ls - # Creating a LineString from a tuple, list, and numpy array - self.assertEqual(ls, LineString(ls.tuple)) # tuple - self.assertEqual(ls, LineString(*ls.tuple)) # as individual arguments - self.assertEqual(ls, LineString([list(tup) for tup in ls.tuple])) # as list - # Point individual arguments - self.assertEqual( - ls.wkt, LineString(*tuple(Point(tup) for tup in ls.tuple)).wkt - ) - if numpy: + # Creating a LineString from a tuple, list, and numpy array + self.assertEqual(ls, LineString(ls.tuple)) # tuple + self.assertEqual(ls, LineString(*ls.tuple)) # as individual arguments self.assertEqual( - ls, LineString(numpy.array(ls.tuple)) - ) # as numpy array + ls, LineString([list(tup) for tup in ls.tuple]) + ) # as list + # Point individual arguments + self.assertEqual( + ls.wkt, LineString(*tuple(Point(tup) for tup in ls.tuple)).wkt + ) + if numpy: + self.assertEqual( + ls, LineString(numpy.array(ls.tuple)) + ) # as numpy array with self.assertRaisesMessage( TypeError, "Each coordinate should be a sequence (list or tuple)" @@ -499,46 +539,52 @@ class GEOSTest(SimpleTestCase, TestDataMixin): prev = fromstr("POINT(0 0)") for line in self.geometries.multilinestrings: ml = fromstr(line.wkt) - self.assertEqual(ml.geom_type, "MultiLineString") - self.assertEqual(ml.geom_typeid, 5) - self.assertEqual(ml.dims, 1) + with self.subTest(line=line): + self.assertEqual(ml.geom_type, "MultiLineString") + self.assertEqual(ml.geom_typeid, 5) + self.assertEqual(ml.dims, 1) - self.assertAlmostEqual(line.centroid[0], ml.centroid.x, 9) - self.assertAlmostEqual(line.centroid[1], ml.centroid.y, 9) + self.assertAlmostEqual(line.centroid[0], ml.centroid.x, 9) + self.assertAlmostEqual(line.centroid[1], ml.centroid.y, 9) - self.assertEqual(ml, fromstr(line.wkt)) - self.assertIs(ml == prev, False) # Use assertIs() to test __eq__. - prev = ml + self.assertEqual(ml, fromstr(line.wkt)) + self.assertIs(ml == prev, False) # Use assertIs() to test __eq__. + prev = ml - for ls in ml: - self.assertEqual(ls.geom_type, "LineString") - self.assertEqual(ls.geom_typeid, 1) - self.assertIs(ls.empty, False) + for ls in ml: + self.assertEqual(ls.geom_type, "LineString") + self.assertEqual(ls.geom_typeid, 1) + self.assertIs(ls.empty, False) - with self.assertRaises(IndexError): - ml.__getitem__(len(ml)) - self.assertEqual(ml.wkt, MultiLineString(*tuple(s.clone() for s in ml)).wkt) - self.assertEqual( - ml, MultiLineString(*tuple(LineString(s.tuple) for s in ml)) - ) + ml_len = len(ml) + msg = f"invalid index: {ml_len}" + with self.assertRaisesMessage(IndexError, msg): + ml.__getitem__(ml_len) + self.assertEqual( + ml.wkt, MultiLineString(*tuple(s.clone() for s in ml)).wkt + ) + self.assertEqual( + ml, MultiLineString(*tuple(LineString(s.tuple) for s in ml)) + ) def test_linearring(self): "Testing LinearRing objects." for rr in self.geometries.linearrings: lr = fromstr(rr.wkt) - self.assertEqual(lr.geom_type, "LinearRing") - self.assertEqual(lr.geom_typeid, 2) - self.assertEqual(lr.dims, 1) - self.assertEqual(rr.n_p, len(lr)) - self.assertIs(lr.valid, True) - self.assertIs(lr.empty, False) + with self.subTest(rr=rr): + self.assertEqual(lr.geom_type, "LinearRing") + self.assertEqual(lr.geom_typeid, 2) + self.assertEqual(lr.dims, 1) + self.assertEqual(rr.n_p, len(lr)) + self.assertIs(lr.valid, True) + self.assertIs(lr.empty, False) - # Creating a LinearRing from a tuple, list, and numpy array - self.assertEqual(lr, LinearRing(lr.tuple)) - self.assertEqual(lr, LinearRing(*lr.tuple)) - self.assertEqual(lr, LinearRing([list(tup) for tup in lr.tuple])) - if numpy: - self.assertEqual(lr, LinearRing(numpy.array(lr.tuple))) + # Creating a LinearRing from a tuple, list, and numpy array + self.assertEqual(lr, LinearRing(lr.tuple)) + self.assertEqual(lr, LinearRing(*lr.tuple)) + self.assertEqual(lr, LinearRing([list(tup) for tup in lr.tuple])) + if numpy: + self.assertEqual(lr, LinearRing(numpy.array(lr.tuple))) with self.assertRaisesMessage( ValueError, "LinearRing requires at least 4 points, got 3." @@ -582,66 +628,77 @@ class GEOSTest(SimpleTestCase, TestDataMixin): for p in self.geometries.polygons: # Creating the Polygon, testing its properties. poly = fromstr(p.wkt) - self.assertEqual(poly.geom_type, "Polygon") - self.assertEqual(poly.geom_typeid, 3) - self.assertEqual(poly.dims, 2) - self.assertIs(poly.empty, False) - self.assertIs(poly.ring, False) - self.assertEqual(p.n_i, poly.num_interior_rings) - self.assertEqual(p.n_i + 1, len(poly)) # Testing __len__ - self.assertEqual(p.n_p, poly.num_points) + with self.subTest(p=p): + self.assertEqual(poly.geom_type, "Polygon") + self.assertEqual(poly.geom_typeid, 3) + self.assertEqual(poly.dims, 2) + self.assertIs(poly.empty, False) + self.assertIs(poly.ring, False) + self.assertEqual(p.n_i, poly.num_interior_rings) + self.assertEqual(p.n_i + 1, len(poly)) # Testing __len__ + self.assertEqual(p.n_p, poly.num_points) - # Area & Centroid - self.assertAlmostEqual(p.area, poly.area, 9) - self.assertAlmostEqual(p.centroid[0], poly.centroid.tuple[0], 9) - self.assertAlmostEqual(p.centroid[1], poly.centroid.tuple[1], 9) + # Area & Centroid + self.assertAlmostEqual(p.area, poly.area, 9) + self.assertAlmostEqual(p.centroid[0], poly.centroid.tuple[0], 9) + self.assertAlmostEqual(p.centroid[1], poly.centroid.tuple[1], 9) - # Testing the geometry equivalence - self.assertEqual(poly, fromstr(p.wkt)) - # Should not be equal to previous geometry - self.assertIs(poly == prev, False) # Use assertIs() to test __eq__. - self.assertIs(poly != prev, True) # Use assertIs() to test __ne__. + # Testing the geometry equivalence + self.assertEqual(poly, fromstr(p.wkt)) + # Should not be equal to previous geometry + self.assertIs(poly == prev, False) # Use assertIs() to test __eq__. + self.assertIs(poly != prev, True) # Use assertIs() to test __ne__. - # Testing the exterior ring - ring = poly.exterior_ring - self.assertEqual(ring.geom_type, "LinearRing") - self.assertEqual(ring.geom_typeid, 2) - if p.ext_ring_cs: - self.assertEqual(p.ext_ring_cs, ring.tuple) - self.assertEqual(p.ext_ring_cs, poly[0].tuple) # Testing __getitem__ + # Testing the exterior ring + ring = poly.exterior_ring + self.assertEqual(ring.geom_type, "LinearRing") + self.assertEqual(ring.geom_typeid, 2) + if p.ext_ring_cs: + self.assertEqual(p.ext_ring_cs, ring.tuple) + self.assertEqual( + p.ext_ring_cs, poly[0].tuple + ) # Testing __getitem__ - # Testing __getitem__ and __setitem__ on invalid indices - with self.assertRaises(IndexError): - poly.__getitem__(len(poly)) - with self.assertRaises(IndexError): - poly.__setitem__(len(poly), False) - with self.assertRaises(IndexError): - poly.__getitem__(-1 * len(poly) - 1) + # Testing __getitem__ and __setitem__ on invalid indices + poly_len = len(poly) + msg = f"invalid index: {poly_len}" + with self.assertRaisesMessage(IndexError, msg): + poly.__getitem__(poly_len) + with self.assertRaisesMessage(IndexError, msg): + poly.__setitem__(poly_len, False) + negative_index = -1 * poly_len - 1 + msg = f"invalid index: {negative_index}" + with self.assertRaisesMessage(IndexError, msg): + poly.__getitem__(negative_index) - # Testing __iter__ - for r in poly: - self.assertEqual(r.geom_type, "LinearRing") - self.assertEqual(r.geom_typeid, 2) + # Testing __iter__ + for r in poly: + self.assertEqual(r.geom_type, "LinearRing") + self.assertEqual(r.geom_typeid, 2) - # Testing polygon construction. - with self.assertRaises(TypeError): - Polygon(0, [1, 2, 3]) - with self.assertRaises(TypeError): - Polygon("foo") + # Testing polygon construction. + msg = ( + "Parameter must be a sequence of LinearRings or " + "objects that can initialize to LinearRings" + ) + with self.assertRaisesMessage(TypeError, msg): + Polygon(0, [1, 2, 3]) + with self.assertRaisesMessage(TypeError, msg): + Polygon("foo") - # Polygon(shell, (hole1, ... holeN)) - ext_ring, *int_rings = poly - self.assertEqual(poly, Polygon(ext_ring, int_rings)) + # Polygon(shell, (hole1, ... holeN)) + ext_ring, *int_rings = poly + self.assertEqual(poly, Polygon(ext_ring, int_rings)) - # Polygon(shell_tuple, hole_tuple1, ... , hole_tupleN) - ring_tuples = tuple(r.tuple for r in poly) - self.assertEqual(poly, Polygon(*ring_tuples)) + # Polygon(shell_tuple, hole_tuple1, ... , hole_tupleN) + ring_tuples = tuple(r.tuple for r in poly) + self.assertEqual(poly, Polygon(*ring_tuples)) - # Constructing with tuples of LinearRings. - self.assertEqual(poly.wkt, Polygon(*tuple(r for r in poly)).wkt) - self.assertEqual( - poly.wkt, Polygon(*tuple(LinearRing(r.tuple) for r in poly)).wkt - ) + # Constructing with tuples of LinearRings. + self.assertEqual(poly.wkt, Polygon(*tuple(r for r in poly)).wkt) + self.assertEqual( + poly.wkt, Polygon(*tuple(LinearRing(r.tuple) for r in poly)).wkt + ) def test_polygons_templates(self): # Accessing Polygon attributes in templates should work. @@ -667,24 +724,28 @@ class GEOSTest(SimpleTestCase, TestDataMixin): fromstr("POINT (0 0)") for mp in self.geometries.multipolygons: mpoly = fromstr(mp.wkt) - self.assertEqual(mpoly.geom_type, "MultiPolygon") - self.assertEqual(mpoly.geom_typeid, 6) - self.assertEqual(mpoly.dims, 2) - self.assertEqual(mp.valid, mpoly.valid) + with self.subTest(mp=mp): + self.assertEqual(mpoly.geom_type, "MultiPolygon") + self.assertEqual(mpoly.geom_typeid, 6) + self.assertEqual(mpoly.dims, 2) + self.assertEqual(mp.valid, mpoly.valid) - if mp.valid: - self.assertEqual(mp.num_geom, mpoly.num_geom) - self.assertEqual(mp.n_p, mpoly.num_coords) - self.assertEqual(mp.num_geom, len(mpoly)) - with self.assertRaises(IndexError): - mpoly.__getitem__(len(mpoly)) - for p in mpoly: - self.assertEqual(p.geom_type, "Polygon") - self.assertEqual(p.geom_typeid, 3) - self.assertIs(p.valid, True) - self.assertEqual( - mpoly.wkt, MultiPolygon(*tuple(poly.clone() for poly in mpoly)).wkt - ) + if mp.valid: + mpoly_len = len(mpoly) + self.assertEqual(mp.num_geom, mpoly.num_geom) + self.assertEqual(mp.n_p, mpoly.num_coords) + self.assertEqual(mp.num_geom, mpoly_len) + msg = f"invalid index: {mpoly_len}" + with self.assertRaisesMessage(IndexError, msg): + mpoly.__getitem__(mpoly_len) + for p in mpoly: + self.assertEqual(p.geom_type, "Polygon") + self.assertEqual(p.geom_typeid, 3) + self.assertIs(p.valid, True) + self.assertEqual( + mpoly.wkt, + MultiPolygon(*tuple(poly.clone() for poly in mpoly)).wkt, + ) def test_memory_hijinks(self): "Testing Geometry __del__() on rings and polygons." @@ -713,46 +774,49 @@ class GEOSTest(SimpleTestCase, TestDataMixin): def test_coord_seq(self): "Testing Coordinate Sequence objects." for p in self.geometries.polygons: - if p.ext_ring_cs: - # Constructing the polygon and getting the coordinate sequence - poly = fromstr(p.wkt) - cs = poly.exterior_ring.coord_seq + with self.subTest(p=p): + if p.ext_ring_cs: + # Constructing the polygon and getting the coordinate sequence + poly = fromstr(p.wkt) + cs = poly.exterior_ring.coord_seq - self.assertEqual( - p.ext_ring_cs, cs.tuple - ) # done in the Polygon test too. - self.assertEqual( - len(p.ext_ring_cs), len(cs) - ) # Making sure __len__ works + self.assertEqual( + p.ext_ring_cs, cs.tuple + ) # done in the Polygon test too. + self.assertEqual( + len(p.ext_ring_cs), len(cs) + ) # Making sure __len__ works - # Checks __getitem__ and __setitem__ - for i in range(len(p.ext_ring_cs)): - c1 = p.ext_ring_cs[i] # Expected value - c2 = cs[i] # Value from coordseq - self.assertEqual(c1, c2) + # Checks __getitem__ and __setitem__ + for i in range(len(p.ext_ring_cs)): + c1 = p.ext_ring_cs[i] # Expected value + c2 = cs[i] # Value from coordseq + self.assertEqual(c1, c2) - # Constructing the test value to set the coordinate sequence with - if len(c1) == 2: - tset = (5, 23) - else: - tset = (5, 23, 8) - cs[i] = tset - - # Making sure every set point matches what we expect - for j in range(len(tset)): + # Construct the test value to set the coordinate sequence with + if len(c1) == 2: + tset = (5, 23) + else: + tset = (5, 23, 8) cs[i] = tset - self.assertEqual(tset[j], cs[i][j]) + + # Making sure every set point matches what we expect + for j in range(len(tset)): + cs[i] = tset + self.assertEqual(tset[j], cs[i][j]) def test_relate_pattern(self): "Testing relate() and relate_pattern()." g = fromstr("POINT (0 0)") - with self.assertRaises(GEOSException): + msg = "invalid intersection matrix pattern" + with self.assertRaisesMessage(GEOSException, msg): g.relate_pattern(0, "invalid pattern, yo") for rg in self.geometries.relate_geoms: a = fromstr(rg.wkt_a) b = fromstr(rg.wkt_b) - self.assertEqual(rg.result, a.relate_pattern(b, rg.pattern)) - self.assertEqual(rg.pattern, a.relate(b)) + with self.subTest(rg=rg): + self.assertEqual(rg.result, a.relate_pattern(b, rg.pattern)) + self.assertEqual(rg.pattern, a.relate(b)) def test_intersection(self): "Testing intersects() and intersection()." @@ -760,12 +824,13 @@ class GEOSTest(SimpleTestCase, TestDataMixin): a = fromstr(self.geometries.topology_geoms[i].wkt_a) b = fromstr(self.geometries.topology_geoms[i].wkt_b) i1 = fromstr(self.geometries.intersect_geoms[i].wkt) - self.assertIs(a.intersects(b), True) i2 = a.intersection(b) - self.assertTrue(i1.equals(i2)) - self.assertTrue(i1.equals(a & b)) # __and__ is intersection operator - a &= b # testing __iand__ - self.assertTrue(i1.equals(a)) + with self.subTest(i=i): + self.assertIs(a.intersects(b), True) + self.assertTrue(i1.equals(i2)) + self.assertTrue(i1.equals(a & b)) # __and__ is intersection operator + a &= b # testing __iand__ + self.assertTrue(i1.equals(a)) def test_union(self): "Testing union()." @@ -774,10 +839,11 @@ class GEOSTest(SimpleTestCase, TestDataMixin): b = fromstr(self.geometries.topology_geoms[i].wkt_b) u1 = fromstr(self.geometries.union_geoms[i].wkt) u2 = a.union(b) - self.assertTrue(u1.equals(u2)) - self.assertTrue(u1.equals(a | b)) # __or__ is union operator - a |= b # testing __ior__ - self.assertTrue(u1.equals(a)) + with self.subTest(i=i): + self.assertTrue(u1.equals(u2)) + self.assertTrue(u1.equals(a | b)) # __or__ is union operator + a |= b # testing __ior__ + self.assertTrue(u1.equals(a)) def test_unary_union(self): "Testing unary_union." @@ -786,7 +852,8 @@ class GEOSTest(SimpleTestCase, TestDataMixin): b = fromstr(self.geometries.topology_geoms[i].wkt_b) u1 = fromstr(self.geometries.union_geoms[i].wkt) u2 = GeometryCollection(a, b).unary_union - self.assertTrue(u1.equals(u2)) + with self.subTest(i=i): + self.assertTrue(u1.equals(u2)) def test_difference(self): "Testing difference()." @@ -795,10 +862,11 @@ class GEOSTest(SimpleTestCase, TestDataMixin): b = fromstr(self.geometries.topology_geoms[i].wkt_b) d1 = fromstr(self.geometries.diff_geoms[i].wkt) d2 = a.difference(b) - self.assertTrue(d1.equals(d2)) - self.assertTrue(d1.equals(a - b)) # __sub__ is difference operator - a -= b # testing __isub__ - self.assertTrue(d1.equals(a)) + with self.subTest(i=i): + self.assertTrue(d1.equals(d2)) + self.assertTrue(d1.equals(a - b)) # __sub__ is difference operator + a -= b # testing __isub__ + self.assertTrue(d1.equals(a)) def test_symdifference(self): "Testing sym_difference()." @@ -807,19 +875,20 @@ class GEOSTest(SimpleTestCase, TestDataMixin): b = fromstr(self.geometries.topology_geoms[i].wkt_b) d1 = fromstr(self.geometries.sdiff_geoms[i].wkt) d2 = a.sym_difference(b) - self.assertTrue(d1.equals(d2)) - self.assertTrue( - d1.equals(a ^ b) - ) # __xor__ is symmetric difference operator - a ^= b # testing __ixor__ - self.assertTrue(d1.equals(a)) + with self.subTest(i=i): + self.assertTrue(d1.equals(d2)) + self.assertTrue( + d1.equals(a ^ b) + ) # __xor__ is symmetric difference operator + a ^= b # testing __ixor__ + self.assertTrue(d1.equals(a)) def test_buffer(self): bg = self.geometries.buffer_geoms[0] g = fromstr(bg.wkt) # Can't use a floating-point for the number of quadsegs. - with self.assertRaises(ctypes.ArgumentError): + with self.assertArgumentTypeError(4, "float"): g.buffer(bg.width, quadsegs=1.1) self._test_buffer(self.geometries.buffer_geoms, "buffer") @@ -829,21 +898,22 @@ class GEOSTest(SimpleTestCase, TestDataMixin): g = fromstr(bg.wkt) # Can't use a floating-point for the number of quadsegs. - with self.assertRaises(ctypes.ArgumentError): + with self.assertArgumentTypeError(4, "float"): g.buffer_with_style(bg.width, quadsegs=1.1) # Can't use a floating-point for the end cap style. - with self.assertRaises(ctypes.ArgumentError): + with self.assertArgumentTypeError(5, "float"): g.buffer_with_style(bg.width, end_cap_style=1.2) # Can't use a end cap style that is not in the enum. - with self.assertRaises(GEOSException): + msg = self.error_checking_geom.format("GEOSBufferWithStyle_r") + with self.assertRaisesMessage(GEOSException, msg): g.buffer_with_style(bg.width, end_cap_style=55) # Can't use a floating-point for the join style. - with self.assertRaises(ctypes.ArgumentError): + with self.assertArgumentTypeError(6, "float"): g.buffer_with_style(bg.width, join_style=1.3) # Can't use a join style that is not in the enum. - with self.assertRaises(GEOSException): + with self.assertRaisesMessage(GEOSException, msg): g.buffer_with_style(bg.width, join_style=66) self._test_buffer( @@ -873,19 +943,20 @@ class GEOSTest(SimpleTestCase, TestDataMixin): if hasattr(bg, kwarg_name) } buf = getattr(g, buffer_method_name)(**buf_kwargs) - self.assertEqual(exp_buf.num_coords, buf.num_coords) - self.assertEqual(len(exp_buf), len(buf)) + with self.subTest(bg=bg): + self.assertEqual(exp_buf.num_coords, buf.num_coords) + self.assertEqual(len(exp_buf), len(buf)) - # Now assuring that each point in the buffer is almost equal - for j in range(len(exp_buf)): - exp_ring = exp_buf[j] - buf_ring = buf[j] - self.assertEqual(len(exp_ring), len(buf_ring)) - for k in range(len(exp_ring)): - # Asserting the X, Y of each point are almost equal (due to - # floating point imprecision). - self.assertAlmostEqual(exp_ring[k][0], buf_ring[k][0], 9) - self.assertAlmostEqual(exp_ring[k][1], buf_ring[k][1], 9) + # Now assuring that each point in the buffer is almost equal + for j in range(len(exp_buf)): + exp_ring = exp_buf[j] + buf_ring = buf[j] + self.assertEqual(len(exp_ring), len(buf_ring)) + for k in range(len(exp_ring)): + # Asserting the X, Y of each point are almost equal (due to + # floating point imprecision). + self.assertAlmostEqual(exp_ring[k][0], buf_ring[k][0], 9) + self.assertAlmostEqual(exp_ring[k][1], buf_ring[k][1], 9) def test_covers(self): poly = Polygon(((0, 0), (0, 10), (10, 10), (10, 0), (0, 0))) @@ -905,14 +976,15 @@ class GEOSTest(SimpleTestCase, TestDataMixin): self.assertEqual(4326, pnt.srid) pnt.srid = 3084 self.assertEqual(3084, pnt.srid) - with self.assertRaises(ctypes.ArgumentError): + with self.assertArgumentTypeError(3, "str"): pnt.srid = "4326" # Testing SRID keyword on fromstr(), and on Polygon rings. poly = fromstr(self.geometries.polygons[1].wkt, srid=4269) self.assertEqual(4269, poly.srid) for ring in poly: - self.assertEqual(4269, ring.srid) + with self.subTest(ring=ring): + self.assertEqual(4269, ring.srid) poly.srid = 4326 self.assertEqual(4326, poly.shell.srid) @@ -922,7 +994,8 @@ class GEOSTest(SimpleTestCase, TestDataMixin): ) self.assertEqual(32021, gc.srid) for i in range(len(gc)): - self.assertEqual(32021, gc[i].srid) + with self.subTest(i=i): + self.assertEqual(32021, gc[i].srid) # GEOS may get the SRID from HEXEWKB # 'POINT(5 23)' at SRID=4326 in hex form -- obtained from PostGIS @@ -956,49 +1029,54 @@ class GEOSTest(SimpleTestCase, TestDataMixin): """Test with a null srid and a srid unknown to GDAL.""" for srid in [None, 999999]: pnt = Point(111200, 220900, srid=srid) - self.assertTrue( - pnt.ewkt.startswith( - ("SRID=%s;" % srid if srid else "") + "POINT (111200" + with self.subTest(srid=srid): + self.assertTrue( + pnt.ewkt.startswith( + ("SRID=%s;" % srid if srid else "") + "POINT (111200" + ) ) - ) - self.assertIsInstance(pnt.ogr, gdal.OGRGeometry) - self.assertIsNone(pnt.srs) + self.assertIsInstance(pnt.ogr, gdal.OGRGeometry) + self.assertIsNone(pnt.srs) - # Test conversion from custom to a known srid - c2w = gdal.CoordTransform( - gdal.SpatialReference( - "+proj=mill +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +R_A +datum=WGS84 " - "+units=m +no_defs" - ), - gdal.SpatialReference(4326), - ) - new_pnt = pnt.transform(c2w, clone=True) - self.assertEqual(new_pnt.srid, 4326) - self.assertAlmostEqual(new_pnt.x, 1, 1) - self.assertAlmostEqual(new_pnt.y, 2, 1) + # Test conversion from custom to a known srid + c2w = gdal.CoordTransform( + gdal.SpatialReference( + "+proj=mill +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +R_A +datum=WGS84 " + "+units=m +no_defs" + ), + gdal.SpatialReference(4326), + ) + new_pnt = pnt.transform(c2w, clone=True) + self.assertEqual(new_pnt.srid, 4326) + self.assertAlmostEqual(new_pnt.x, 1, 1) + self.assertAlmostEqual(new_pnt.y, 2, 1) def test_mutable_geometries(self): "Testing the mutability of Polygons and Geometry Collections." # ### Testing the mutability of Polygons ### for p in self.geometries.polygons: poly = fromstr(p.wkt) + msg = ( + "Parameter must be a sequence of LinearRings or objects that can " + "initialize to LinearRings" + ) + with self.subTest(p=p): + # Should only be able to use __setitem__ with LinearRing geometries. + with self.assertRaisesMessage(TypeError, msg): + poly.__setitem__(0, LineString((1, 1), (2, 2))) - # Should only be able to use __setitem__ with LinearRing geometries. - with self.assertRaises(TypeError): - poly.__setitem__(0, LineString((1, 1), (2, 2))) + # Construct the new shell by adding 500 to every point in the old shell. + shell_tup = poly.shell.tuple + new_coords = [] + for point in shell_tup: + new_coords.append((point[0] + 500.0, point[1] + 500.0)) + new_shell = LinearRing(*tuple(new_coords)) - # Constructing the new shell by adding 500 to every point in the old shell. - shell_tup = poly.shell.tuple - new_coords = [] - for point in shell_tup: - new_coords.append((point[0] + 500.0, point[1] + 500.0)) - new_shell = LinearRing(*tuple(new_coords)) - - # Assigning polygon's exterior ring w/the new shell - poly.exterior_ring = new_shell - str(new_shell) # new shell is still accessible - self.assertEqual(poly.exterior_ring, new_shell) - self.assertEqual(poly[0], new_shell) + # Assigning polygon's exterior ring w/the new shell + poly.exterior_ring = new_shell + str(new_shell) # new shell is still accessible + self.assertEqual(poly.exterior_ring, new_shell) + self.assertEqual(poly[0], new_shell) # ### Testing the mutability of Geometry Collections for tg in self.geometries.multipoints: @@ -1007,12 +1085,13 @@ class GEOSTest(SimpleTestCase, TestDataMixin): # Creating a random point. pnt = mp[i] new = Point(random.randint(21, 100), random.randint(21, 100)) - # Testing the assignment - mp[i] = new - str(new) # what was used for the assignment is still accessible - self.assertEqual(mp[i], new) - self.assertEqual(mp[i].wkt, new.wkt) - self.assertNotEqual(pnt, mp[i]) + with self.subTest(tg=tg, i=i): + # Testing the assignment + mp[i] = new + str(new) # what was used for the assignment is still accessible + self.assertEqual(mp[i], new) + self.assertEqual(mp[i].wkt, new.wkt) + self.assertNotEqual(pnt, mp[i]) # MultiPolygons involve much more memory management because each # Polygon w/in the collection has its own rings. @@ -1028,12 +1107,13 @@ class GEOSTest(SimpleTestCase, TestDataMixin): r[k] = (r[k][0] + 500.0, r[k][1] + 500.0) poly[j] = r - self.assertNotEqual(mpoly[i], poly) - # Testing the assignment - mpoly[i] = poly - str(poly) # Still accessible - self.assertEqual(mpoly[i], poly) - self.assertNotEqual(mpoly[i], old_poly) + with self.subTest(tg=tg, i=i, j=j): + self.assertNotEqual(mpoly[i], poly) + # Testing the assignment + mpoly[i] = poly + str(poly) # Still accessible + self.assertEqual(mpoly[i], poly) + self.assertNotEqual(mpoly[i], old_poly) # Extreme (!!) __setitem__ -- no longer works, have to detect # in the first object that __setitem__ is called in the subsequent @@ -1056,9 +1136,9 @@ class GEOSTest(SimpleTestCase, TestDataMixin): p[:] = (1, 2) self.assertEqual(p.wkt, Point(1, 2)) - with self.assertRaises(ValueError): + with self.assertRaisesMessage(ValueError, "Must have at least 2 items"): p[:] = (1,) - with self.assertRaises(ValueError): + with self.assertRaisesMessage(ValueError, "Cannot have more than 3 items"): p[:] = (1, 2, 3, 4, 5) def test_linestring_list_assignment(self): @@ -1070,7 +1150,7 @@ class GEOSTest(SimpleTestCase, TestDataMixin): ls[:] = ((0, 0), (1, 1), (2, 2)) self.assertEqual(ls, LineString((0, 0), (1, 1), (2, 2))) - with self.assertRaises(ValueError): + with self.assertRaisesMessage(ValueError, "Must have at least 2 items"): ls[:] = (1,) def test_linearring_list_assignment(self): @@ -1082,7 +1162,7 @@ class GEOSTest(SimpleTestCase, TestDataMixin): ls[:] = ((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)) self.assertEqual(ls, LinearRing((0, 0), (0, 1), (1, 1), (1, 0), (0, 0))) - with self.assertRaises(ValueError): + with self.assertRaisesMessage(ValueError, "Must have at least 4 items"): ls[:] = ((0, 0), (1, 1), (2, 2)) def test_polygon_list_assignment(self): @@ -1114,7 +1194,8 @@ class GEOSTest(SimpleTestCase, TestDataMixin): # Testing a 3D Point pnt = Point(2, 3, 8) self.assertEqual((2.0, 3.0, 8.0), pnt.coords) - with self.assertRaises(TypeError): + msg = "Dimension of value does not match." + with self.assertRaisesMessage(TypeError, msg): pnt.tuple = (1.0, 2.0) pnt.coords = (1.0, 2.0, 3.0) self.assertEqual((1.0, 2.0, 3.0), pnt.coords) @@ -1122,7 +1203,7 @@ class GEOSTest(SimpleTestCase, TestDataMixin): # Testing a 3D LineString ls = LineString((2.0, 3.0, 8.0), (50.0, 250.0, -117.0)) self.assertEqual(((2.0, 3.0, 8.0), (50.0, 250.0, -117.0)), ls.tuple) - with self.assertRaises(TypeError): + with self.assertRaisesMessage(TypeError, msg): ls.__setitem__(0, (1.0, 2.0)) ls[0] = (1.0, 2.0, 3.0) self.assertEqual((1.0, 2.0, 3.0), ls[0]) @@ -1186,36 +1267,40 @@ class GEOSTest(SimpleTestCase, TestDataMixin): geoms.append(LineString(numpy.array([]))) for g in geoms: - self.assertIs(g.empty, True) + with self.subTest(g=g): + self.assertIs(g.empty, True) - # Testing len() and num_geom. - if isinstance(g, Polygon): - self.assertEqual(1, len(g)) # Has one empty linear ring - self.assertEqual(1, g.num_geom) - self.assertEqual(0, len(g[0])) - elif isinstance(g, (Point, LineString)): - self.assertEqual(1, g.num_geom) - self.assertEqual(0, len(g)) - else: - self.assertEqual(0, g.num_geom) - self.assertEqual(0, len(g)) + # Testing len() and num_geom. + if isinstance(g, Polygon): + self.assertEqual(1, len(g)) # Has one empty linear ring + self.assertEqual(1, g.num_geom) + self.assertEqual(0, len(g[0])) + elif isinstance(g, (Point, LineString)): + self.assertEqual(1, g.num_geom) + self.assertEqual(0, len(g)) + else: + self.assertEqual(0, g.num_geom) + self.assertEqual(0, len(g)) - # Testing __getitem__ (doesn't work on Point or Polygon) - if isinstance(g, Point): - # IndexError is not raised in GEOS 3.8.0. - if geos_version_tuple() != (3, 8, 0): - with self.assertRaises(IndexError): - g.x - elif isinstance(g, Polygon): - lr = g.shell - self.assertEqual("LINEARRING EMPTY", lr.wkt) - self.assertEqual(0, len(lr)) - self.assertIs(lr.empty, True) - with self.assertRaises(IndexError): - lr.__getitem__(0) - else: - with self.assertRaises(IndexError): - g.__getitem__(0) + # Testing __getitem__ (doesn't work on Point or Polygon) + if isinstance(g, Point): + # IndexError is not raised in GEOS 3.8.0. + if geos_version_tuple() != (3, 8, 0): + msg = "invalid GEOS Geometry index:" + with self.assertRaisesMessage(IndexError, msg): + g.x + elif isinstance(g, Polygon): + lr = g.shell + self.assertEqual("LINEARRING EMPTY", lr.wkt) + self.assertEqual(0, len(lr)) + self.assertIs(lr.empty, True) + msg = "invalid index: 0" + with self.assertRaisesMessage(IndexError, msg): + lr.__getitem__(0) + else: + msg = "invalid index: 0" + with self.assertRaisesMessage(IndexError, msg): + g.__getitem__(0) def test_collection_dims(self): gc = GeometryCollection([]) @@ -1306,8 +1391,9 @@ class GEOSTest(SimpleTestCase, TestDataMixin): # correct as having a 1 meter accuracy. prec = -1 for p in (t1, t2, t3, k2): - self.assertAlmostEqual(trans.x, p.x, prec) - self.assertAlmostEqual(trans.y, p.y, prec) + with self.subTest(p=p): + self.assertAlmostEqual(trans.x, p.x, prec) + self.assertAlmostEqual(trans.y, p.y, prec) def test_transform_3d(self): p3d = GEOSGeometry("POINT (5 23 100)", 4326) @@ -1332,22 +1418,14 @@ class GEOSTest(SimpleTestCase, TestDataMixin): def test_transform_nosrid(self): """Testing `transform` method (no SRID or negative SRID)""" - - g = GEOSGeometry("POINT (-104.609 38.255)", srid=None) - with self.assertRaises(GEOSException): - g.transform(2774) - - g = GEOSGeometry("POINT (-104.609 38.255)", srid=None) - with self.assertRaises(GEOSException): - g.transform(2774, clone=True) - - g = GEOSGeometry("POINT (-104.609 38.255)", srid=-1) - with self.assertRaises(GEOSException): - g.transform(2774) - - g = GEOSGeometry("POINT (-104.609 38.255)", srid=-1) - with self.assertRaises(GEOSException): - g.transform(2774, clone=True) + msg = "Calling transform() with no SRID set is not supported" + for srid, clone in itertools.product((None, -1), (True, False)): + g = GEOSGeometry("POINT (-104.609 38.255)", srid=srid) + with ( + self.subTest(srid=srid, clone=clone), + self.assertRaisesMessage(GEOSException, msg), + ): + g.transform(2774, clone=clone) def test_extent(self): "Testing `extent` method." @@ -1382,8 +1460,9 @@ class GEOSTest(SimpleTestCase, TestDataMixin): for geom in tgeoms: s1 = pickle.dumps(geom) g1 = pickle.loads(s1) - self.assertEqual(geom, g1) - self.assertEqual(geom.srid, g1.srid) + with self.subTest(geom=geom): + self.assertEqual(geom, g1) + self.assertEqual(geom.srid, g1.srid) def test_prepared(self): "Testing PreparedGeometry support." @@ -1397,9 +1476,10 @@ class GEOSTest(SimpleTestCase, TestDataMixin): pnts = [Point(5, 5), Point(7.5, 7.5), Point(2.5, 7.5)] for pnt in pnts: # Results should be the same (but faster) - self.assertEqual(mpoly.contains(pnt), prep.contains(pnt)) - self.assertEqual(mpoly.intersects(pnt), prep.intersects(pnt)) - self.assertEqual(mpoly.covers(pnt), prep.covers(pnt)) + with self.subTest(pnt=pnt): + self.assertEqual(mpoly.contains(pnt), prep.contains(pnt)) + self.assertEqual(mpoly.intersects(pnt), prep.intersects(pnt)) + self.assertEqual(mpoly.covers(pnt), prep.covers(pnt)) self.assertTrue(prep.crosses(fromstr("LINESTRING(1 1, 15 15)"))) self.assertTrue(prep.disjoint(Point(-5, -5))) @@ -1643,5 +1723,5 @@ class GEOSTest(SimpleTestCase, TestDataMixin): # Step into CoordSeq iterator. next(it) ls[:] = [] - with self.assertRaises(IndexError): + with self.assertRaisesMessage(IndexError, "invalid index: 1"): next(it) diff --git a/tests/gis_tests/geos_tests/test_geos_mutation.py b/tests/gis_tests/geos_tests/test_geos_mutation.py index c239ab3efe..eada4d664b 100644 --- a/tests/gis_tests/geos_tests/test_geos_mutation.py +++ b/tests/gis_tests/geos_tests/test_geos_mutation.py @@ -2,8 +2,6 @@ # Modified from original contribution by Aryeh Leib Taurog, which was # released under the New BSD license. -import unittest - from django.contrib.gis.geos import ( LinearRing, LineString, @@ -12,6 +10,7 @@ from django.contrib.gis.geos import ( Polygon, fromstr, ) +from django.test import SimpleTestCase def api_get_distance(x): @@ -77,7 +76,7 @@ geos_function_tests = [ ] -class GEOSMutationTest(unittest.TestCase): +class GEOSMutationTest(SimpleTestCase): """ Tests Pythonic Mutability of Python GEOS geometry wrappers get/set/delitem on a slice, normal list methods @@ -87,33 +86,41 @@ class GEOSMutationTest(unittest.TestCase): "Testing Geometry IndexError" p = Point(1, 2) for i in range(-2, 2): - p._checkindex(i) - with self.assertRaises(IndexError): - p._checkindex(2) - with self.assertRaises(IndexError): - p._checkindex(-3) + with self.subTest(i=i): + p._checkindex(i) + + for i in (2, -3): + with ( + self.subTest(i=i), + self.assertRaisesMessage(IndexError, f"invalid index: {i}"), + ): + p._checkindex(i) def test01_PointMutations(self): "Testing Point mutations" for p in (Point(1, 2, 3), fromstr("POINT (1 2 3)")): - self.assertEqual( - p._get_single_external(1), 2.0, "Point _get_single_external" - ) + with self.subTest(p=p): + self.assertEqual( + p._get_single_external(1), 2.0, "Point _get_single_external" + ) - # _set_single - p._set_single(0, 100) - self.assertEqual(p.coords, (100.0, 2.0, 3.0), "Point _set_single") + # _set_single + p._set_single(0, 100) + self.assertEqual(p.coords, (100.0, 2.0, 3.0), "Point _set_single") - # _set_list - p._set_list(2, (50, 3141)) - self.assertEqual(p.coords, (50.0, 3141.0), "Point _set_list") + # _set_list + p._set_list(2, (50, 3141)) + self.assertEqual(p.coords, (50.0, 3141.0), "Point _set_list") def test02_PointExceptions(self): "Testing Point exceptions" - with self.assertRaises(TypeError): - Point(range(1)) - with self.assertRaises(TypeError): - Point(range(4)) + msg = "Invalid parameters given for Point initialization." + for i in (range(1), range(4)): + with ( + self.subTest(i=i), + self.assertRaisesMessage(TypeError, msg), + ): + Point(i) def test03_PointApi(self): "Testing Point API" @@ -121,7 +128,8 @@ class GEOSMutationTest(unittest.TestCase): for p in (Point(1, 2, 3), fromstr("POINT (1 2 3)")): p[0:2] = [4, 5] for f in geos_function_tests: - self.assertEqual(f(q), f(p), "Point " + f.__name__) + with self.subTest(p=p, f=f): + self.assertEqual(f(q), f(p), "Point " + f.__name__) def test04_LineStringMutations(self): "Testing LineString mutations" @@ -129,29 +137,31 @@ class GEOSMutationTest(unittest.TestCase): LineString((1, 0), (4, 1), (6, -1)), fromstr("LINESTRING (1 0,4 1,6 -1)"), ): - self.assertEqual( - ls._get_single_external(1), - (4.0, 1.0), - "LineString _get_single_external", - ) + with self.subTest(ls=ls): + self.assertEqual( + ls._get_single_external(1), + (4.0, 1.0), + "LineString _get_single_external", + ) - # _set_single - ls._set_single(0, (-50, 25)) - self.assertEqual( - ls.coords, - ((-50.0, 25.0), (4.0, 1.0), (6.0, -1.0)), - "LineString _set_single", - ) + # _set_single + ls._set_single(0, (-50, 25)) + self.assertEqual( + ls.coords, + ((-50.0, 25.0), (4.0, 1.0), (6.0, -1.0)), + "LineString _set_single", + ) - # _set_list - ls._set_list(2, ((-50.0, 25.0), (6.0, -1.0))) - self.assertEqual( - ls.coords, ((-50.0, 25.0), (6.0, -1.0)), "LineString _set_list" - ) + # _set_list + ls._set_list(2, ((-50.0, 25.0), (6.0, -1.0))) + self.assertEqual( + ls.coords, ((-50.0, 25.0), (6.0, -1.0)), "LineString _set_list" + ) - lsa = LineString(ls.coords) - for f in geos_function_tests: - self.assertEqual(f(lsa), f(ls), "LineString " + f.__name__) + lsa = LineString(ls.coords) + for f in geos_function_tests: + with self.subTest(f=f): + self.assertEqual(f(lsa), f(ls), "LineString " + f.__name__) def test05_Polygon(self): "Testing Polygon mutations" @@ -162,37 +172,45 @@ class GEOSMutationTest(unittest.TestCase): ), fromstr("POLYGON ((1 0,4 1,6 -1,8 10,1 0),(5 4,6 4,6 3,5 4))"), ): - self.assertEqual( - pg._get_single_external(0), - LinearRing((1, 0), (4, 1), (6, -1), (8, 10), (1, 0)), - "Polygon _get_single_external(0)", - ) - self.assertEqual( - pg._get_single_external(1), - LinearRing((5, 4), (6, 4), (6, 3), (5, 4)), - "Polygon _get_single_external(1)", - ) + with self.subTest(pg=pg): + self.assertEqual( + pg._get_single_external(0), + LinearRing((1, 0), (4, 1), (6, -1), (8, 10), (1, 0)), + "Polygon _get_single_external(0)", + ) + self.assertEqual( + pg._get_single_external(1), + LinearRing((5, 4), (6, 4), (6, 3), (5, 4)), + "Polygon _get_single_external(1)", + ) - # _set_list - pg._set_list( - 2, - ( - ((1, 2), (10, 0), (12, 9), (-1, 15), (1, 2)), - ((4, 2), (5, 2), (5, 3), (4, 2)), - ), - ) - self.assertEqual( - pg.coords, - ( - ((1.0, 2.0), (10.0, 0.0), (12.0, 9.0), (-1.0, 15.0), (1.0, 2.0)), - ((4.0, 2.0), (5.0, 2.0), (5.0, 3.0), (4.0, 2.0)), - ), - "Polygon _set_list", - ) + # _set_list + pg._set_list( + 2, + ( + ((1, 2), (10, 0), (12, 9), (-1, 15), (1, 2)), + ((4, 2), (5, 2), (5, 3), (4, 2)), + ), + ) + self.assertEqual( + pg.coords, + ( + ( + (1.0, 2.0), + (10.0, 0.0), + (12.0, 9.0), + (-1.0, 15.0), + (1.0, 2.0), + ), + ((4.0, 2.0), (5.0, 2.0), (5.0, 3.0), (4.0, 2.0)), + ), + "Polygon _set_list", + ) - lsa = Polygon(*pg.coords) - for f in geos_function_tests: - self.assertEqual(f(lsa), f(pg), "Polygon " + f.__name__) + lsa = Polygon(*pg.coords) + for f in geos_function_tests: + with self.subTest(f=f): + self.assertEqual(f(lsa), f(pg), "Polygon " + f.__name__) def test06_Collection(self): "Testing Collection mutations" @@ -201,17 +219,21 @@ class GEOSMutationTest(unittest.TestCase): fromstr("MULTIPOINT (3 4,-1 2,5 -4,2 8)"), ) for mp in points: - self.assertEqual( - mp._get_single_external(2), - Point(5, -4), - "Collection _get_single_external", - ) + with self.subTest(mp=mp): + self.assertEqual( + mp._get_single_external(2), + Point(5, -4), + "Collection _get_single_external", + ) - mp._set_list(3, map(Point, ((5, 5), (3, -2), (8, 1)))) - self.assertEqual( - mp.coords, ((5.0, 5.0), (3.0, -2.0), (8.0, 1.0)), "Collection _set_list" - ) + mp._set_list(3, map(Point, ((5, 5), (3, -2), (8, 1)))) + self.assertEqual( + mp.coords, + ((5.0, 5.0), (3.0, -2.0), (8.0, 1.0)), + "Collection _set_list", + ) - lsa = MultiPoint(*map(Point, ((5, 5), (3, -2), (8, 1)))) - for f in geos_function_tests: - self.assertEqual(f(lsa), f(mp), "MultiPoint " + f.__name__) + lsa = MultiPoint(*map(Point, ((5, 5), (3, -2), (8, 1)))) + for f in geos_function_tests: + with self.subTest(f=f): + self.assertEqual(f(lsa), f(mp), "MultiPoint " + f.__name__) diff --git a/tests/gis_tests/geos_tests/test_io.py b/tests/gis_tests/geos_tests/test_io.py index cab4b0ed5b..35cf0c11ad 100644 --- a/tests/gis_tests/geos_tests/test_io.py +++ b/tests/gis_tests/geos_tests/test_io.py @@ -25,7 +25,8 @@ class GEOSIOTest(SimpleTestCase): g2 = wkt_r.read(wkt) for geom in (g1, g2): - self.assertEqual(ref, geom) + with self.subTest(geom=geom): + self.assertEqual(ref, geom) # Should only accept string objects. with self.assertRaises(TypeError): @@ -67,7 +68,8 @@ class GEOSIOTest(SimpleTestCase): g2 = wkb_r.read(hex_bin) g3 = wkb_r.read(hex_str) for geom in (g1, g2, g3): - self.assertEqual(ref, geom) + with self.subTest(geom=geom): + self.assertEqual(ref, geom) bad_input = (1, 5.23, None, False) for bad_wkb in bad_input: @@ -89,9 +91,13 @@ class GEOSIOTest(SimpleTestCase): self.assertEqual(wkb1, wkb_w.write(g)) # Ensuring bad byteorders are not accepted. + msg = "Byte order parameter must be 0 (Big Endian) or 1 (Little Endian)." for bad_byteorder in (-1, 2, 523, "foo", None): # Equivalent of `wkb_w.byteorder = bad_byteorder` - with self.assertRaises(ValueError): + with ( + self.subTest(bad_byteorder=bad_byteorder), + self.assertRaisesMessage(ValueError, msg), + ): wkb_w._set_byteorder(bad_byteorder) # Setting the byteorder to 0 (for Big Endian) @@ -114,9 +120,11 @@ class GEOSIOTest(SimpleTestCase): wkb3d_srid = memoryview(binascii.a2b_hex(hex3d_srid)) # Ensuring bad output dimensions are not accepted + msg = "WKB output dimension must be 2 or 3" for bad_outdim in (-1, 0, 1, 4, 423, "foo", None): - with self.assertRaisesMessage( - ValueError, "WKB output dimension must be 2 or 3" + with ( + self.subTest(bad_outdim=bad_outdim), + self.assertRaisesMessage(ValueError, msg), ): wkb_w.outdim = bad_outdim @@ -221,9 +229,12 @@ class GEOSIOTest(SimpleTestCase): wkb_w.byteorder = byteorder for srid, hex in enumerate(hexes): wkb_w.srid = srid - self.assertEqual(wkb_w.write_hex(p), hex) - self.assertEqual( - GEOSGeometry(wkb_w.write_hex(p)), p if srid else p_no_srid - ) - self.assertEqual(wkb_w.write(p), memoryview(binascii.a2b_hex(hex))) - self.assertEqual(GEOSGeometry(wkb_w.write(p)), p if srid else p_no_srid) + with self.subTest(byteorder=byteorder, hexes=hexes): + self.assertEqual(wkb_w.write_hex(p), hex) + self.assertEqual( + GEOSGeometry(wkb_w.write_hex(p)), p if srid else p_no_srid + ) + self.assertEqual(wkb_w.write(p), memoryview(binascii.a2b_hex(hex))) + self.assertEqual( + GEOSGeometry(wkb_w.write(p)), p if srid else p_no_srid + ) diff --git a/tests/gis_tests/geos_tests/test_mutable_list.py b/tests/gis_tests/geos_tests/test_mutable_list.py index a092f603cb..1a66812d2b 100644 --- a/tests/gis_tests/geos_tests/test_mutable_list.py +++ b/tests/gis_tests/geos_tests/test_mutable_list.py @@ -4,9 +4,8 @@ # Modified from original contribution by Aryeh Leib Taurog, which was # released under the New BSD license. -import unittest - from django.contrib.gis.geos.mutable_list import ListMixin +from django.test import SimpleTestCase class UserListA(ListMixin): @@ -54,7 +53,7 @@ def nextRange(length): nextRange.start = 0 -class ListMixinTest(unittest.TestCase): +class ListMixinTest(SimpleTestCase): """ Tests base class ListMixin by comparing a list clone which is a ListMixin subclass with a real Python list. @@ -79,22 +78,24 @@ class ListMixinTest(unittest.TestCase): "Slice retrieval" pl, ul = self.lists_of_len() for i in self.limits_plus(1): - self.assertEqual(pl[i:], ul[i:], "slice [%d:]" % (i)) - self.assertEqual(pl[:i], ul[:i], "slice [:%d]" % (i)) + with self.subTest(i=i): + self.assertEqual(pl[i:], ul[i:], "slice [%d:]" % (i)) + self.assertEqual(pl[:i], ul[:i], "slice [:%d]" % (i)) + + for j in self.limits_plus(1): + self.assertEqual(pl[i:j], ul[i:j], "slice [%d:%d]" % (i, j)) + for k in self.step_range(): + self.assertEqual( + pl[i:j:k], ul[i:j:k], "slice [%d:%d:%d]" % (i, j, k) + ) - for j in self.limits_plus(1): - self.assertEqual(pl[i:j], ul[i:j], "slice [%d:%d]" % (i, j)) for k in self.step_range(): - self.assertEqual( - pl[i:j:k], ul[i:j:k], "slice [%d:%d:%d]" % (i, j, k) - ) - - for k in self.step_range(): - self.assertEqual(pl[i::k], ul[i::k], "slice [%d::%d]" % (i, k)) - self.assertEqual(pl[:i:k], ul[:i:k], "slice [:%d:%d]" % (i, k)) + self.assertEqual(pl[i::k], ul[i::k], "slice [%d::%d]" % (i, k)) + self.assertEqual(pl[:i:k], ul[:i:k], "slice [:%d:%d]" % (i, k)) for k in self.step_range(): - self.assertEqual(pl[::k], ul[::k], "slice [::%d]" % (k)) + with self.subTest(k=k): + self.assertEqual(pl[::k], ul[::k], "slice [::%d]" % (k)) def test02_setslice(self): "Slice assignment" @@ -105,120 +106,135 @@ class ListMixinTest(unittest.TestCase): pl, ul = self.lists_of_len() for slen in range(self.limit + 1): ssl = nextRange(slen) - ul[:] = ssl - pl[:] = ssl - self.assertEqual(pl, ul[:], "set slice [:]") + with self.subTest(slen=slen): + ul[:] = ssl + pl[:] = ssl + self.assertEqual(pl, ul[:], "set slice [:]") - for i in self.limits_plus(1): - ssl = nextRange(slen) - ul[i:] = ssl - pl[i:] = ssl - self.assertEqual(pl, ul[:], "set slice [%d:]" % (i)) - - ssl = nextRange(slen) - ul[:i] = ssl - pl[:i] = ssl - self.assertEqual(pl, ul[:], "set slice [:%d]" % (i)) - - for j in self.limits_plus(1): + for i in self.limits_plus(1): ssl = nextRange(slen) - ul[i:j] = ssl - pl[i:j] = ssl - self.assertEqual(pl, ul[:], "set slice [%d:%d]" % (i, j)) + ul[i:] = ssl + pl[i:] = ssl + self.assertEqual(pl, ul[:], "set slice [%d:]" % (i)) + + ssl = nextRange(slen) + ul[:i] = ssl + pl[:i] = ssl + self.assertEqual(pl, ul[:], "set slice [:%d]" % (i)) + + for j in self.limits_plus(1): + ssl = nextRange(slen) + ul[i:j] = ssl + pl[i:j] = ssl + self.assertEqual(pl, ul[:], "set slice [%d:%d]" % (i, j)) + + for k in self.step_range(): + ssl = nextRange(len(ul[i:j:k])) + ul[i:j:k] = ssl + pl[i:j:k] = ssl + self.assertEqual( + pl, ul[:], "set slice [%d:%d:%d]" % (i, j, k) + ) + + sliceLen = len(ul[i:j:k]) + msg = ( + f"attempt to assign sequence of size {sliceLen + 1} " + f"to extended slice of size {sliceLen}" + ) + with self.assertRaisesMessage(ValueError, msg): + setfcn(ul, i, j, k, sliceLen + 1) + if sliceLen > 2: + msg = ( + f"attempt to assign sequence of size {sliceLen - 1}" + f" to extended slice of size {sliceLen}" + ) + with self.assertRaisesMessage(ValueError, msg): + setfcn(ul, i, j, k, sliceLen - 1) for k in self.step_range(): - ssl = nextRange(len(ul[i:j:k])) - ul[i:j:k] = ssl - pl[i:j:k] = ssl - self.assertEqual(pl, ul[:], "set slice [%d:%d:%d]" % (i, j, k)) + ssl = nextRange(len(ul[i::k])) + ul[i::k] = ssl + pl[i::k] = ssl + self.assertEqual(pl, ul[:], "set slice [%d::%d]" % (i, k)) - sliceLen = len(ul[i:j:k]) - with self.assertRaises(ValueError): - setfcn(ul, i, j, k, sliceLen + 1) - if sliceLen > 2: - with self.assertRaises(ValueError): - setfcn(ul, i, j, k, sliceLen - 1) + ssl = nextRange(len(ul[:i:k])) + ul[:i:k] = ssl + pl[:i:k] = ssl + self.assertEqual(pl, ul[:], "set slice [:%d:%d]" % (i, k)) for k in self.step_range(): - ssl = nextRange(len(ul[i::k])) - ul[i::k] = ssl - pl[i::k] = ssl - self.assertEqual(pl, ul[:], "set slice [%d::%d]" % (i, k)) - - ssl = nextRange(len(ul[:i:k])) - ul[:i:k] = ssl - pl[:i:k] = ssl - self.assertEqual(pl, ul[:], "set slice [:%d:%d]" % (i, k)) - - for k in self.step_range(): - ssl = nextRange(len(ul[::k])) - ul[::k] = ssl - pl[::k] = ssl - self.assertEqual(pl, ul[:], "set slice [::%d]" % (k)) + ssl = nextRange(len(ul[::k])) + ul[::k] = ssl + pl[::k] = ssl + self.assertEqual(pl, ul[:], "set slice [::%d]" % (k)) def test03_delslice(self): "Delete slice" for Len in range(self.limit): pl, ul = self.lists_of_len(Len) - del pl[:] - del ul[:] - self.assertEqual(pl[:], ul[:], "del slice [:]") - for i in range(-Len - 1, Len + 1): - pl, ul = self.lists_of_len(Len) - del pl[i:] - del ul[i:] - self.assertEqual(pl[:], ul[:], "del slice [%d:]" % (i)) - pl, ul = self.lists_of_len(Len) - del pl[:i] - del ul[:i] - self.assertEqual(pl[:], ul[:], "del slice [:%d]" % (i)) - for j in range(-Len - 1, Len + 1): + with self.subTest(Len=Len): + del pl[:] + del ul[:] + self.assertEqual(pl[:], ul[:], "del slice [:]") + for i in range(-Len - 1, Len + 1): pl, ul = self.lists_of_len(Len) - del pl[i:j] - del ul[i:j] - self.assertEqual(pl[:], ul[:], "del slice [%d:%d]" % (i, j)) + del pl[i:] + del ul[i:] + self.assertEqual(pl[:], ul[:], "del slice [%d:]" % (i)) + pl, ul = self.lists_of_len(Len) + del pl[:i] + del ul[:i] + self.assertEqual(pl[:], ul[:], "del slice [:%d]" % (i)) + for j in range(-Len - 1, Len + 1): + pl, ul = self.lists_of_len(Len) + del pl[i:j] + del ul[i:j] + self.assertEqual(pl[:], ul[:], "del slice [%d:%d]" % (i, j)) + for k in [*range(-Len - 1, 0), *range(1, Len)]: + pl, ul = self.lists_of_len(Len) + del pl[i:j:k] + del ul[i:j:k] + self.assertEqual( + pl[:], ul[:], "del slice [%d:%d:%d]" % (i, j, k) + ) + for k in [*range(-Len - 1, 0), *range(1, Len)]: pl, ul = self.lists_of_len(Len) - del pl[i:j:k] - del ul[i:j:k] - self.assertEqual( - pl[:], ul[:], "del slice [%d:%d:%d]" % (i, j, k) - ) + del pl[:i:k] + del ul[:i:k] + self.assertEqual(pl[:], ul[:], "del slice [:%d:%d]" % (i, k)) + + pl, ul = self.lists_of_len(Len) + del pl[i::k] + del ul[i::k] + self.assertEqual(pl[:], ul[:], "del slice [%d::%d]" % (i, k)) for k in [*range(-Len - 1, 0), *range(1, Len)]: pl, ul = self.lists_of_len(Len) - del pl[:i:k] - del ul[:i:k] - self.assertEqual(pl[:], ul[:], "del slice [:%d:%d]" % (i, k)) - - pl, ul = self.lists_of_len(Len) - del pl[i::k] - del ul[i::k] - self.assertEqual(pl[:], ul[:], "del slice [%d::%d]" % (i, k)) - - for k in [*range(-Len - 1, 0), *range(1, Len)]: - pl, ul = self.lists_of_len(Len) - del pl[::k] - del ul[::k] - self.assertEqual(pl[:], ul[:], "del slice [::%d]" % (k)) + del pl[::k] + del ul[::k] + self.assertEqual(pl[:], ul[:], "del slice [::%d]" % (k)) def test04_get_set_del_single(self): "Get/set/delete single item" pl, ul = self.lists_of_len() for i in self.limits_plus(0): - self.assertEqual(pl[i], ul[i], "get single item [%d]" % i) + with self.subTest(i=i): + self.assertEqual(pl[i], ul[i], "get single item [%d]" % i) for i in self.limits_plus(0): pl, ul = self.lists_of_len() pl[i] = 100 ul[i] = 100 - self.assertEqual(pl[:], ul[:], "set single item [%d]" % i) + with self.subTest(i=i): + self.assertEqual(pl[:], ul[:], "set single item [%d]" % i) for i in self.limits_plus(0): pl, ul = self.lists_of_len() del pl[i] del ul[i] - self.assertEqual(pl[:], ul[:], "del single item [%d]" % i) + with self.subTest(i=i): + self.assertEqual(pl[:], ul[:], "del single item [%d]" % i) def test05_out_of_range_exceptions(self): "Out of range exceptions" @@ -234,12 +250,14 @@ class ListMixinTest(unittest.TestCase): pl, ul = self.lists_of_len() for i in (-1 - self.limit, self.limit): - with self.assertRaises(IndexError): # 'set index %d' % i) - setfcn(ul, i) - with self.assertRaises(IndexError): # 'get index %d' % i) - getfcn(ul, i) - with self.assertRaises(IndexError): # 'del index %d' % i) - delfcn(ul, i) + msg = f"invalid index: {i}" + with self.subTest(i=i): + with self.assertRaisesMessage(IndexError, msg): + setfcn(ul, i) + with self.assertRaisesMessage(IndexError, msg): + getfcn(ul, i) + with self.assertRaisesMessage(IndexError, msg): + delfcn(ul, i) def test06_list_methods(self): "List methods" @@ -260,12 +278,14 @@ class ListMixinTest(unittest.TestCase): pl, ul = self.lists_of_len() pl.insert(i, 50) ul.insert(i, 50) - self.assertEqual(pl[:], ul[:], "insert at %d" % i) + with self.subTest(i=i): + self.assertEqual(pl[:], ul[:], "insert at %d" % i) for i in self.limits_plus(0): pl, ul = self.lists_of_len() - self.assertEqual(pl.pop(i), ul.pop(i), "popped value at %d" % i) - self.assertEqual(pl[:], ul[:], "after pop at %d" % i) + with self.subTest(i=i): + self.assertEqual(pl.pop(i), ul.pop(i), "popped value at %d" % i) + self.assertEqual(pl[:], ul[:], "after pop at %d" % i) pl, ul = self.lists_of_len() self.assertEqual(pl.pop(), ul.pop(i), "popped value") @@ -276,23 +296,26 @@ class ListMixinTest(unittest.TestCase): def popfcn(x, i): x.pop(i) - with self.assertRaises(IndexError): + with self.assertRaisesMessage(IndexError, "invalid index: 3"): popfcn(ul, self.limit) - with self.assertRaises(IndexError): + with self.assertRaisesMessage(IndexError, "invalid index: -4"): popfcn(ul, -1 - self.limit) pl, ul = self.lists_of_len() for val in range(self.limit): - self.assertEqual(pl.index(val), ul.index(val), "index of %d" % val) + with self.subTest(val=val): + self.assertEqual(pl.index(val), ul.index(val), "index of %d" % val) for val in self.limits_plus(2): - self.assertEqual(pl.count(val), ul.count(val), "count %d" % val) + with self.subTest(val=val): + self.assertEqual(pl.count(val), ul.count(val), "count %d" % val) for val in range(self.limit): pl, ul = self.lists_of_len() pl.remove(val) ul.remove(val) - self.assertEqual(pl[:], ul[:], "after remove val %d" % val) + with self.subTest(val=val): + self.assertEqual(pl[:], ul[:], "after remove val %d" % val) def indexfcn(x, v): return x.index(v) @@ -300,9 +323,10 @@ class ListMixinTest(unittest.TestCase): def removefcn(x, v): return x.remove(v) - with self.assertRaises(ValueError): + msg = "40 not found in object" + with self.assertRaisesMessage(ValueError, msg): indexfcn(ul, 40) - with self.assertRaises(ValueError): + with self.assertRaisesMessage(ValueError, msg): removefcn(ul, 40) def test07_allowed_types(self): @@ -315,9 +339,10 @@ class ListMixinTest(unittest.TestCase): def setfcn(x, i, v): x[i] = v - with self.assertRaises(TypeError): + msg = "Invalid type encountered in the arguments." + with self.assertRaisesMessage(TypeError, msg): setfcn(ul, 2, "hello") - with self.assertRaises(TypeError): + with self.assertRaisesMessage(TypeError, msg): setfcn(ul, slice(0, 3, 2), ("hello", "goodbye")) def test08_min_length(self): @@ -331,17 +356,20 @@ class ListMixinTest(unittest.TestCase): def setfcn(x, i): x[:i] = [] + msg = "Must have at least 3 items" for i in range(len(ul) - ul._minlength + 1, len(ul)): - with self.assertRaises(ValueError): - delfcn(ul, i) - with self.assertRaises(ValueError): - setfcn(ul, i) + with self.subTest(i=i): + with self.assertRaisesMessage(ValueError, msg): + delfcn(ul, i) + with self.assertRaisesMessage(ValueError, msg): + setfcn(ul, i) del ul[: len(ul) - ul._minlength] ul._maxlength = 4 for i in range(0, ul._maxlength - len(ul)): - ul.append(i) - with self.assertRaises(ValueError): + with self.subTest(i=i): + ul.append(i) + with self.assertRaisesMessage(ValueError, "Cannot have more than 4 items"): ul.append(10) def test09_iterable_check(self): @@ -351,22 +379,28 @@ class ListMixinTest(unittest.TestCase): def setfcn(x, i, v): x[i] = v - with self.assertRaises(TypeError): + with self.assertRaisesMessage( + TypeError, "can only assign an iterable to a slice" + ): setfcn(ul, slice(0, 3, 2), 2) def test10_checkindex(self): "Index check" pl, ul = self.lists_of_len() for i in self.limits_plus(0): - if i < 0: - self.assertEqual( - ul._checkindex(i), i + self.limit, "_checkindex(neg index)" - ) - else: - self.assertEqual(ul._checkindex(i), i, "_checkindex(pos index)") + with self.subTest(i=i): + if i < 0: + self.assertEqual( + ul._checkindex(i), i + self.limit, "_checkindex(neg index)" + ) + else: + self.assertEqual(ul._checkindex(i), i, "_checkindex(pos index)") for i in (-self.limit - 1, self.limit): - with self.assertRaises(IndexError): + with ( + self.subTest(i=i), + self.assertRaisesMessage(IndexError, f"invalid index: {i}"), + ): ul._checkindex(i) def test_11_sorting(self):