From aab2927f9cabcab8e3ef7d5059050b89037a721e Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 27 Sep 2026 20:15:02 +0000 Subject: [PATCH 1/2] test(map): add v1 map test fixtures and snapshot tests --- tests/map/__snapshots__/test_map_parser.ambr | 133 ++++++++++++++++++ tests/map/test_map_parser.py | 110 ++++++++++++++- tests/map/testdata/v1/README.md | 20 +++ ...with_forbidden_zones_and_virtual_walls.bin | Bin 0 -> 62768 bytes .../v1/s5_fw1886_with_goto_target.bin | Bin 0 -> 69226 bytes .../testdata/v1/s5_fw2008_with_segments.bin | Bin 0 -> 65600 bytes ...52_with_active_segment_and_no_mop_zone.bin | Bin 0 -> 138625 bytes 7 files changed, 259 insertions(+), 4 deletions(-) create mode 100644 tests/map/__snapshots__/test_map_parser.ambr create mode 100644 tests/map/testdata/v1/README.md create mode 100644 tests/map/testdata/v1/s5_fw1886_with_forbidden_zones_and_virtual_walls.bin create mode 100644 tests/map/testdata/v1/s5_fw1886_with_goto_target.bin create mode 100644 tests/map/testdata/v1/s5_fw2008_with_segments.bin create mode 100644 tests/map/testdata/v1/s6_fw2652_with_active_segment_and_no_mop_zone.bin diff --git a/tests/map/__snapshots__/test_map_parser.ambr b/tests/map/__snapshots__/test_map_parser.ambr new file mode 100644 index 000000000..fbf583c89 --- /dev/null +++ b/tests/map/__snapshots__/test_map_parser.ambr @@ -0,0 +1,133 @@ +# serializer version: 1 +# name: test_parse_v1_fixtures[s5_fw1886_with_forbidden_zones_and_virtual_walls.bin] + dict({ + 'additional_parameters': dict({ + 'is_valid': True, + 'major_version': 1, + 'map_index': 875, + 'map_sequence': 64, + 'minor_version': 0, + }), + 'charger': Point(x=25612, y=25254, a=None), + 'goto_path_points': 0, + 'image_format': 'PNG', + 'image_size': tuple( + 1048, + 952, + ), + 'no_go_areas': list([ + Area(x0=27000, y0=28750, x1=28850, y1=28750, x2=28850, y2=28100, x3=27000, y3=28100), + ]), + 'no_mopping_areas': None, + 'path_points': 55, + 'predicted_path_points': 0, + 'rooms': dict({ + }), + 'vacuum_position': Point(x=28697, y=26764, a=0), + 'walls': list([ + Wall(x0=25300, y0=27850, x1=25300, y1=26850), + ]), + 'zones': list([ + Zone(x0=27289, y0=25480, x1=28919, y1=27358), + ]), + }) +# --- +# name: test_parse_v1_fixtures[s5_fw1886_with_goto_target.bin] + dict({ + 'additional_parameters': dict({ + 'is_valid': True, + 'major_version': 1, + 'map_index': 876, + 'map_sequence': 84, + 'minor_version': 0, + }), + 'charger': Point(x=25612, y=25254, a=None), + 'goto_path_points': 0, + 'image_format': 'PNG', + 'image_size': tuple( + 1052, + 1048, + ), + 'no_go_areas': list([ + ]), + 'no_mopping_areas': None, + 'path_points': 32, + 'predicted_path_points': 3, + 'rooms': dict({ + }), + 'vacuum_position': Point(x=26139, y=26459, a=0), + 'walls': list([ + ]), + 'zones': None, + }) +# --- +# name: test_parse_v1_fixtures[s5_fw2008_with_segments.bin] + dict({ + 'additional_parameters': dict({ + 'is_valid': True, + 'major_version': 1, + 'map_index': 919, + 'map_sequence': 814, + 'minor_version': 1, + }), + 'charger': Point(x=25652, y=25222, a=98), + 'goto_path_points': 0, + 'image_format': 'PNG', + 'image_size': tuple( + 1024, + 964, + ), + 'no_go_areas': list([ + ]), + 'no_mopping_areas': None, + 'path_points': 933, + 'predicted_path_points': 0, + 'rooms': dict({ + 1: Room(x0=23250, y0=24950, x1=26550, y1=26400, number=1, name=None, pos_x=None, pos_y=None), + 2: Room(x0=25150, y0=26400, x1=26600, y1=29950, number=2, name=None, pos_x=None, pos_y=None), + }), + 'vacuum_position': Point(x=25642, y=25401, a=-88), + 'walls': list([ + ]), + 'zones': list([ + ]), + }) +# --- +# name: test_parse_v1_fixtures[s6_fw2652_with_active_segment_and_no_mop_zone.bin] + dict({ + 'additional_parameters': dict({ + 'is_valid': True, + 'major_version': 1, + 'map_index': 804, + 'map_sequence': 43, + 'minor_version': 0, + }), + 'charger': Point(x=25273, y=25603, a=-1), + 'goto_path_points': 0, + 'image_format': 'PNG', + 'image_size': tuple( + 1644, + 1344, + ), + 'no_go_areas': list([ + Area(x0=25000, y0=28000, x1=27200, y1=28000, x2=27200, y2=27100, x3=25000, y3=27100), + ]), + 'no_mopping_areas': list([ + Area(x0=29750, y0=27400, x1=32400, y1=27400, x2=32400, y2=25950, x3=29750, y3=25950), + ]), + 'path_points': 78, + 'predicted_path_points': 0, + 'rooms': dict({ + 16: Room(x0=29550, y0=29550, x1=30450, y1=30900, number=16, name=None, pos_x=None, pos_y=None), + 17: Room(x0=29000, y0=24550, x1=33100, y1=29400, number=17, name=None, pos_x=None, pos_y=None), + 18: Room(x0=30550, y0=29400, x1=34750, y1=31250, number=18, name=None, pos_x=None, pos_y=None), + 19: Room(x0=25050, y0=24550, x1=28950, y1=29650, number=19, name=None, pos_x=None, pos_y=None), + 20: Room(x0=25150, y0=29350, x1=28150, y1=30950, number=20, name=None, pos_x=None, pos_y=None), + 21: Room(x0=33350, y0=24500, x1=35300, y1=29400, number=21, name=None, pos_x=None, pos_y=None), + }), + 'vacuum_position': Point(x=30550, y=26455, a=-92), + 'walls': list([ + ]), + 'zones': None, + }) +# --- diff --git a/tests/map/test_map_parser.py b/tests/map/test_map_parser.py index 4c6489727..904d9163d 100644 --- a/tests/map/test_map_parser.py +++ b/tests/map/test_map_parser.py @@ -1,22 +1,124 @@ """Tests for the map parser.""" +import io from pathlib import Path +from typing import Any +from unittest.mock import MagicMock, patch import pytest +from PIL import Image +from syrupy.assertion import SnapshotAssertion +from vacuum_map_parser_base.config.drawable import Drawable from roborock.exceptions import RoborockException -from roborock.map.map_parser import MapParser, MapParserConfig +from roborock.map.map_parser import MapParser, MapParserConfig, ParsedMapData, _create_image_generator -MAP_DATA_FILE = Path(__file__).parent / "raw_map_data" +V1_FIXTURES_DIR = Path(__file__).resolve().parent / "testdata" / "v1" DEFAULT_MAP_CONFIG = MapParserConfig() +def _map_data_to_snapshot(result: ParsedMapData) -> dict[str, Any]: + md = result.map_data + assert md is not None + assert result.image_content is not None + image = Image.open(io.BytesIO(result.image_content)) + return { + "image_size": image.size, + "image_format": image.format, + "charger": md.charger, + "vacuum_position": md.vacuum_position, + "rooms": md.rooms, + "walls": md.walls, + "no_go_areas": md.no_go_areas, + "no_mopping_areas": md.no_mopping_areas, + "zones": md.zones, + "path_points": len(md.path.path[0]) if md.path and md.path.path else 0, + "goto_path_points": len(md.goto_path.path[0]) if md.goto_path and md.goto_path.path else 0, + "predicted_path_points": len(md.predicted_path.path[0]) if md.predicted_path and md.predicted_path.path else 0, + "additional_parameters": md.additional_parameters, + } + + @pytest.mark.parametrize("map_content", [b"", b"12345"]) -def test_invalid_map_content(map_content: bytes): +def test_invalid_map_content(map_content: bytes) -> None: """Test that parsing map data returns the expected image and data.""" parser = MapParser(DEFAULT_MAP_CONFIG) with pytest.raises(RoborockException, match="Failed to parse map data"): parser.parse(map_content) -# We can add additional tests here in the future that actually parse valid map data +@pytest.mark.parametrize( + "filename", + [ + "s5_fw2008_with_segments.bin", + "s5_fw1886_with_forbidden_zones_and_virtual_walls.bin", + "s5_fw1886_with_goto_target.bin", + "s6_fw2652_with_active_segment_and_no_mop_zone.bin", + ], +) +def test_parse_v1_fixtures( + filename: str, + snapshot: SnapshotAssertion, +) -> None: + """Test that parsing valid V1 fixtures matches expected snapshot.""" + raw_data = (V1_FIXTURES_DIR / filename).read_bytes() + parser = MapParser(DEFAULT_MAP_CONFIG) + result = parser.parse(raw_data) + + assert result is not None + assert result.image_content is not None + assert result.image_content.startswith(b"\x89PNG\r\n\x1a\n") + assert _map_data_to_snapshot(result) == snapshot + + +def test_map_parser_config_variants() -> None: + """Test map parser with different display flags and scales.""" + raw_data = (V1_FIXTURES_DIR / "s6_fw2652_with_active_segment_and_no_mop_zone.bin").read_bytes() + + config_custom = MapParserConfig( + show_background=False, + show_walls=False, + show_rooms=False, + map_scale=2, + ) + parser_custom = MapParser(config_custom) + result_custom = parser_custom.parse(raw_data) + assert result_custom is not None + assert result_custom.image_content is not None + + parser_scaled = MapParser(MapParserConfig(map_scale=1)) + result_scaled = parser_scaled.parse(raw_data) + assert result_scaled is not None + assert result_scaled.image_content is not None + + img_scale2 = Image.open(io.BytesIO(result_custom.image_content)) + img_scale1 = Image.open(io.BytesIO(result_scaled.image_content)) + assert img_scale2.width > img_scale1.width + assert img_scale2.height > img_scale1.height + + +def test_map_parser_rendering_failure() -> None: + """Test that failure to generate an image raises RoborockException.""" + raw_data = (V1_FIXTURES_DIR / "s5_fw2008_with_segments.bin").read_bytes() + parser = MapParser(DEFAULT_MAP_CONFIG) + + mock_map_data = MagicMock() + mock_map_data.image = None + + with ( + patch.object(parser._map_parser, "parse", return_value=mock_map_data), + pytest.raises(RoborockException, match="Failed to render map image"), + ): + parser.parse(raw_data) + + +def test_create_image_generator() -> None: + """Test image generator creation with default and custom drawables.""" + generator = _create_image_generator(DEFAULT_MAP_CONFIG) + assert generator is not None + + custom_generator = _create_image_generator( + DEFAULT_MAP_CONFIG, + drawables=[Drawable.PATH, Drawable.CHARGER], + ) + assert custom_generator is not None diff --git a/tests/map/testdata/v1/README.md b/tests/map/testdata/v1/README.md new file mode 100644 index 000000000..f7456f706 --- /dev/null +++ b/tests/map/testdata/v1/README.md @@ -0,0 +1,20 @@ +# V1 Map Test Data Fixtures + +These binary test files contain real raw Roborock V1 map payloads (`RRMap` format) used for unit testing and performance benchmarking of the Roborock V1 map parser (`roborock.map.map_parser`). + +## Source & Attribution + +The fixtures in this directory are adapted from the [Valetudo](https://github.com/Hypfer/Valetudo) project: +- **Repository**: https://github.com/Hypfer/Valetudo +- **Path in Source**: `backend/test/lib/robots/roborock/res/map/` +- **License**: Apache License 2.0 (compatible with `python-roborock`) +- **Author/Project**: Hypfer and Valetudo contributors + +## Fixture Descriptions + +| File Name | Original Source File | Model & Firmware | Features Exercised | +| :--- | :--- | :--- | :--- | +| `s5_fw2008_with_segments.bin` | `S5_FW2008_with_segments.bin` | Roborock S5 (fw 2008) | Multi-room segmentation (rooms 1 & 2), charger dock location, vacuum position, and path coordinates. | +| `s5_fw1886_with_forbidden_zones_and_virtual_walls.bin` | `S5_FW1886_with_forbidden_zones_and_virtual_walls_and_currently_cleaned_zones.bin` | Roborock S5 (fw 1886) | Virtual walls (`walls`), forbidden/no-go zones (`no_go_areas`), and currently cleaned zones (`zones`). | +| `s5_fw1886_with_goto_target.bin` | `S5_FW1886_with_goto_target.bin` | Roborock S5 (fw 1886) | Pinpoint target location (`goto_target`) and computed path to target (`goto_path`). | +| `s6_fw2652_with_active_segment_and_no_mop_zone.bin` | `S6_FW2652_with_active_segment_and_no_mop_zone.bin` | Roborock S6 (fw 2652) | Complex layout with 6 segmented rooms (16, 17, 18, 19, 20, 21), active cleaning segment, and no-mop zone. | diff --git a/tests/map/testdata/v1/s5_fw1886_with_forbidden_zones_and_virtual_walls.bin b/tests/map/testdata/v1/s5_fw1886_with_forbidden_zones_and_virtual_walls.bin new file mode 100644 index 0000000000000000000000000000000000000000..663a94f98dfd9b91ece122039144c8da361e87c6 GIT binary patch literal 62768 zcmeI*Pl()99Ki86wk%sIRA>)Y>cNXWNl~bOP^ex!h!g}-L@%8rn@naVn^uG#dhj9? z-Ckr%@vw+cdhsCa#gn2?FGbx;LDW@HPZe5(q80ozzCZb&*`3izao^;dl0Wa|ZOHGF z-+Rf-&U80^C!@nC$%7c9dxH7vsFu!7R`g~ z=p!lwV_@$%(OO9ch(qT{M7q?w`i2%8boK+Jk81vv_o28HrZsh*)G>vCYr zBscNo9Xv~bO4gWm$#PB=3f(;qHv`G5N(y;5vMedK4y+dGb`|O{qHtqrrGmay$eX5R z!Dpx2P%fA}K|wj#tFYb3Z?5q4OKTO>V^b+iQ!3Y$8f;UrSfTg=Nk%x6g2fE~?YfUm z2^16y3eq5d?9T4Q)@(G^BdkVfPQjWBUq8{t5bPZl)QDDjU>H02PgQB}*eDH9P#pvX z6w-mRvEk9)aY6NnsfNBO8lj+&P(UH=EaZZ{xuA!|wvUcYJ~rx3VP%hK?3pnc7wmjt zjY6YO&S@^B52IGCJMseuO-@!03(Y+_+nozdCW1UxOLAetaN1IZib_iqsyzsWsz`gr zg)xyAt6JJ4iUj0BqCqYw2oz9AJB01Ipgs~_x%nrd{fm{IZ!hGpru6yG%wVyCPJ-n9 zfElyFW`*KABC|2rt56=$+NqF#A^`RXp;} z-mZPaM+R=9t)2H2zt^@@K}J@MHVL*K(EKXMx1g)xs$eS}#X!xkf{eJD3rX*;rK0RE)C5%b;E+rDX zaIl_b8d#SRQK1PuFf>gj*{$tXe7L@c(Gz%H)1??(yNKOKryzl+y{qhzpIc4IC3X^) zi_1@4JnaVOknNcs72oOFk{SVD(GYeFYCi^l;p1NAN(9mseXMP)e-T)BS>ql>-ed=a zo#J_wt%Uk#LAio!EzQ)uweQYtqv%?aGKK60*u}f*nr~XtcW8?~)_4C4yb6V5aDU=y zwXF=p(2~L6Arvz#+%Z>a48^{B8RT<@yj?VkUsqbKoLbt+j42i9})xi`LkENRy-P`N0zEN?{;Gd;ekGS^;#SGGhi(zDM zgiCEy&ahOBOYEeJA$se{IDD`Lz)pIW{^yX660ow5Tj<0zl0;t0PbmM z)C|%r+W1G#Fxapu3@LQpFo$MP`kW1eA2|)q5XNJ=mM}Eg5W2R3mO6XLaK9V~LG2g0 zD~}frXhmbmhLWKukAjdK9?<3`2Z{ zr=GzFP>h9Qt}nf!w1@PK!lyy|`9MA-w#*k*cYyXl6lKG>{hK#{C2N;?j;r1Ja*0Hp-fHWPNZ163jzi;p(3V$!@ zebLL1DYON5ugG|s?FZd0-AyJv-Da+PoQ0PxD@LHuduiK?eo=J_PgqBdv$u*zPA0oJvrO8|IDu0>+>6St3FeITYpkN zz4JqTe&@Q(f7CbYzw4*!f9ucfkM(c!$Lj~?FWJ+xcjlM2ugyN4{<{5Bb!_`Yb#{7K zYU%27?2UPKr_}GLtZ&I2>UV1AR{fE^Ro^`KYIV1NSKgHAo@Ct|IwD6J^WK9mKKbay Vci(*W%Haj!Bvju`6MS4w?A_zRB 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 UeSKr0Q+s3I&ZWCc|6RNLe?mH?R{#J2 literal 0 HcmV?d00001 From 53530a7223bfb619dfec360c6791d71ea47cc147 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 27 Sep 2026 23:07:40 +0000 Subject: [PATCH 2/2] test(map): assert goto target in v1 map snapshots and fix fixture doc --- tests/map/__snapshots__/test_map_parser.ambr | 4 ++++ tests/map/test_map_parser.py | 1 + tests/map/testdata/v1/README.md | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/map/__snapshots__/test_map_parser.ambr b/tests/map/__snapshots__/test_map_parser.ambr index fbf583c89..d2b18aac0 100644 --- a/tests/map/__snapshots__/test_map_parser.ambr +++ b/tests/map/__snapshots__/test_map_parser.ambr @@ -9,6 +9,7 @@ 'minor_version': 0, }), 'charger': Point(x=25612, y=25254, a=None), + 'goto': None, 'goto_path_points': 0, 'image_format': 'PNG', 'image_size': tuple( @@ -42,6 +43,7 @@ 'minor_version': 0, }), 'charger': Point(x=25612, y=25254, a=None), + 'goto': Point(x=25874, y=26218, a=None), 'goto_path_points': 0, 'image_format': 'PNG', 'image_size': tuple( @@ -71,6 +73,7 @@ 'minor_version': 1, }), 'charger': Point(x=25652, y=25222, a=98), + 'goto': None, 'goto_path_points': 0, 'image_format': 'PNG', 'image_size': tuple( @@ -103,6 +106,7 @@ 'minor_version': 0, }), 'charger': Point(x=25273, y=25603, a=-1), + 'goto': None, 'goto_path_points': 0, 'image_format': 'PNG', 'image_size': tuple( diff --git a/tests/map/test_map_parser.py b/tests/map/test_map_parser.py index 904d9163d..52499480a 100644 --- a/tests/map/test_map_parser.py +++ b/tests/map/test_map_parser.py @@ -27,6 +27,7 @@ def _map_data_to_snapshot(result: ParsedMapData) -> dict[str, Any]: "image_format": image.format, "charger": md.charger, "vacuum_position": md.vacuum_position, + "goto": md.goto, "rooms": md.rooms, "walls": md.walls, "no_go_areas": md.no_go_areas, diff --git a/tests/map/testdata/v1/README.md b/tests/map/testdata/v1/README.md index f7456f706..7359239e5 100644 --- a/tests/map/testdata/v1/README.md +++ b/tests/map/testdata/v1/README.md @@ -16,5 +16,5 @@ The fixtures in this directory are adapted from the [Valetudo](https://github.co | :--- | :--- | :--- | :--- | | `s5_fw2008_with_segments.bin` | `S5_FW2008_with_segments.bin` | Roborock S5 (fw 2008) | Multi-room segmentation (rooms 1 & 2), charger dock location, vacuum position, and path coordinates. | | `s5_fw1886_with_forbidden_zones_and_virtual_walls.bin` | `S5_FW1886_with_forbidden_zones_and_virtual_walls_and_currently_cleaned_zones.bin` | Roborock S5 (fw 1886) | Virtual walls (`walls`), forbidden/no-go zones (`no_go_areas`), and currently cleaned zones (`zones`). | -| `s5_fw1886_with_goto_target.bin` | `S5_FW1886_with_goto_target.bin` | Roborock S5 (fw 1886) | Pinpoint target location (`goto_target`) and computed path to target (`goto_path`). | +| `s5_fw1886_with_goto_target.bin` | `S5_FW1886_with_goto_target.bin` | Roborock S5 (fw 1886) | Pinpoint target location (`goto`) and predicted navigation path to target (`predicted_path`). | | `s6_fw2652_with_active_segment_and_no_mop_zone.bin` | `S6_FW2652_with_active_segment_and_no_mop_zone.bin` | Roborock S6 (fw 2652) | Complex layout with 6 segmented rooms (16, 17, 18, 19, 20, 21), active cleaning segment, and no-mop zone. |