"""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]"