diff --git a/python/sedonadb/python/sedonadb/testing.py b/python/sedonadb/python/sedonadb/testing.py index 990c5d940..de00446bd 100644 --- a/python/sedonadb/python/sedonadb/testing.py +++ b/python/sedonadb/python/sedonadb/testing.py @@ -63,6 +63,31 @@ def _env_no_skip(): return env_no_skip in ("true", "1") +def _normalize_geometry_wkt_columns(rows, geometry_indexes): + """Canonicalize WKT only for geometry-typed tuple values.""" + import shapely + + geometry_indexes = set(geometry_indexes) + normalized_rows = [] + + for row in rows: + normalized_row = list(row) + + for i in geometry_indexes: + value = normalized_row[i] + if not isinstance(value, str): + continue + + try: + normalized_row[i] = shapely.to_wkt(shapely.from_wkt(value)) + except (ValueError, TypeError, shapely.errors.GEOSException): + pass + + normalized_rows.append(tuple(normalized_row)) + + return normalized_rows + + class DBEngine: """Engine-agnostic catalog and SQL engine @@ -288,7 +313,18 @@ def assert_result(self, result, expected, **kwargs) -> "DBEngine": result_pandas = self.result_to_pandas(result) pandas.testing.assert_frame_equal(result_pandas, expected, **kwargs) elif isinstance(expected, list): + result_table = self.result_to_table(result) + geometry_indexes = { + i + for i, col in enumerate(result_table.columns) + if _type_is_geoarrow(col.type) + } result_tuples = self.result_to_tuples(result, **kwargs) + if geometry_indexes: + result_tuples = _normalize_geometry_wkt_columns( + result_tuples, geometry_indexes + ) + expected = _normalize_geometry_wkt_columns(expected, geometry_indexes) if result_tuples != expected: raise AssertionError( f"Expected:\n {expected}\nGot:\n {result_tuples}" diff --git a/python/sedonadb/tests/functions/test_functions.py b/python/sedonadb/tests/functions/test_functions.py index 95c36c0ae..407d9bdad 100644 --- a/python/sedonadb/tests/functions/test_functions.py +++ b/python/sedonadb/tests/functions/test_functions.py @@ -1221,11 +1221,11 @@ def test_st_unaryunion_zm(eng, geom, expected): elif is_postgis and ("M(" in expected or "M (" in expected): pytest.skip("PostGIS doesn't support M dimensions") else: - # Test for exact string equality - # Remove all spaces from both the actual and expected results to ignore formatting differences + # Compare the geometry result directly so the shared test harness can + # normalize WKT formatting for geometry-typed outputs only. eng.assert_query_result( - f"SELECT replace(ST_AsText(ST_UnaryUnion({geom_or_null(geom)})), ' ', '')", - expected.replace(" ", ""), + f"SELECT ST_UnaryUnion({geom_or_null(geom)})", + expected, ) @@ -2309,7 +2309,7 @@ def test_st_length(eng, geom, expected): ("geom", "expected"), [ (None, None), - ("POINT EMPTY", "POINT EMPTY"), + ("POINT EMPTY", "POINT (nan nan)"), ("LINESTRING EMPTY", "LINESTRING EMPTY"), ("POLYGON EMPTY", "POLYGON EMPTY"), ("MULTIPOINT EMPTY", "MULTIPOINT EMPTY"), @@ -2371,20 +2371,12 @@ def test_st_normalize(eng, geom, expected): expected = "POLYGON Z ((0 0 5, 0 1 5, 1 1 5, 1 0 5, 0 0 5))" if isinstance(eng, PostGIS) and expected is not None: - # Normalize expected WKT to PostGIS's compact ST_AsText formatting. - expected = expected.replace(", ", ",") - expected = expected.replace(" (", "(") + # Normalize expected WKT to PostGIS's formatting. expected = expected.replace(r"ZM(", r"ZM (") expected = expected.replace(r"M(", r"M (") expected = expected.replace(r"Z(", r"Z (") - if isinstance(eng, SedonaDB) and expected is not None: - expected = expected.replace(", ", ",") - expected = expected.replace(" (", "(") - - eng.assert_query_result( - f"SELECT ST_AsText(ST_Normalize({geom_or_null(geom)}))", expected - ) + eng.assert_query_result(f"SELECT ST_Normalize({geom_or_null(geom)})", expected) @pytest.mark.parametrize("eng", [SedonaDB, PostGIS]) diff --git a/python/sedonadb/tests/test_testing.py b/python/sedonadb/tests/test_testing.py index 42edf1f6d..9fbdf4269 100644 --- a/python/sedonadb/tests/test_testing.py +++ b/python/sedonadb/tests/test_testing.py @@ -129,6 +129,21 @@ def test_assert_result_spatial(eng): ) +@pytest.mark.parametrize("eng", [SedonaDB, PostGIS, DuckDB]) +def test_assert_result_wkt_normalizes_geometry_only(eng): + with eng.create_or_skip() as eng: + eng.assert_query_result( + "SELECT ST_GeomFromText('POLYGON Z ((0 0 5, 0 1 5, 1 1 5, 1 0 5, 0 0 5))') as geom", + "POLYGON Z ((0 0 5,0 1 5,1 1 5,1 0 5,0 0 5))", + ) + + with pytest.raises(AssertionError): + eng.assert_query_result( + "SELECT 'POLYGON Z ((0 0 5, 0 1 5, 1 1 5, 1 0 5, 0 0 5))' as geom", + "POLYGON Z ((0 0 5,0 1 5,1 1 5,1 0 5,0 0 5))", + ) + + @pytest.mark.parametrize("eng", [SedonaDB, PostGIS, DuckDB]) def test_table_arrow_no_crs(eng): with eng.create_or_skip() as eng: