"""Tests for the PLCopen XML parser.""" from __future__ import annotations from pathlib import Path import pytest from mitsubishi_label_converter.parser import ( parse_global_vars, parse_project_metadata, ) NS = "http://www.plcopen.org/xml/tc6_0201" SAMPLE_XML = f""" PLC error active """ @pytest.fixture() def sample_file(tmp_path: Path) -> Path: """Write the sample XML as UTF-16 LE, like GX Works3 does.""" xml_path = tmp_path / "Global.xml" xml_path.write_bytes(SAMPLE_XML.encode("utf-16")) return xml_path def test_parses_two_sections(sample_file: Path) -> None: sections = parse_global_vars(sample_file) assert len(sections) == 2 assert all(section.name == "Global" for section in sections) def test_retain_flag_is_parsed(sample_file: Path) -> None: sections = parse_global_vars(sample_file) assert sections[0].retain is False assert sections[1].retain is True def test_primitive_type(sample_file: Path) -> None: sections = parse_global_vars(sample_file) first = sections[0].variables[0] assert first.name == "Always_ON" assert first.address == "SM400" assert first.type_name == "BOOL" assert first.comment == "" def test_string_type_includes_length(sample_file: Path) -> None: sections = parse_global_vars(sample_file) version = sections[0].variables[1] assert version.name == "version" assert version.type_name == "STRING[12]" def test_derived_type_uses_name(sample_file: Path) -> None: sections = parse_global_vars(sample_file) plc_fel = sections[0].variables[2] assert plc_fel.address == "" # derived FB variable without device address assert plc_fel.type_name == "FB_Din" sta = sections[0].variables[3] assert sta.type_name == "ST_Din_STA" assert [(m.name, m.address) for m in sta.members] == [ ("Value", "D0.0"), ("Status", "D1"), ] def test_comment_extraction(sample_file: Path) -> None: sections = parse_global_vars(sample_file) plc_fel = sections[0].variables[2] assert plc_fel.comment == "PLC error active" def test_struct_members_are_parsed(sample_file: Path) -> None: sections = parse_global_vars(sample_file) # PLC_FEL (plain derived FB) has no member assignments assert sections[0].variables[2].members == () def test_struct_members_from_real_file() -> None: asset = Path(__file__).parents[1] / "assets" / "Global.xml" if not asset.is_file(): pytest.skip("assets/Global.xml not present") sections = parse_global_vars(asset) structured = [v for s in sections for v in s.variables if v.members] assert len(structured) == 189 sta = next(v for v in structured if v.name == "PLC_FEL_STA") assert sta.type_name == "ST_Din_STA" assert sta.line_number == 39 assert (sta.members[0].name, sta.members[0].address) == ("Value", "D0.0") def test_project_metadata_from_real_file() -> None: asset = Path(__file__).parents[1] / "assets" / "Global.xml" if not asset.is_file(): pytest.skip("assets/Global.xml not present") meta = parse_project_metadata(asset) assert meta.file_header["productName"] == "GX Works3" assert meta.content_header["name"] == "21014JON-B14_20260930" assert meta.project_add_data["projectPlcType"] == "FX5U" def test_line_number_is_parsed(sample_file: Path) -> None: sections = parse_global_vars(sample_file) always_on = sections[0].variables[0] assert always_on.line_number == 3 def test_real_world_global_xml() -> None: """End-to-end check against the real asset in the repository.""" asset = Path(__file__).parents[1] / "assets" / "Global.xml" if not asset.is_file(): pytest.skip("assets/Global.xml not present") sections = parse_global_vars(asset) assert len(sections) == 2 # 174/118 raw child elements include one (varListProperties) each assert [len(s.variables) for s in sections] == [173, 117] version_row = sections[0].variables[0] assert version_row.name == "version" assert version_row.address == "D450" assert version_row.type_name == "STRING[12]"