🎉 Initial commit
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
"""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"""<?xml version="1.0" encoding="utf-16"?>
|
||||
<project xmlns="{NS}">
|
||||
<instances>
|
||||
<configurations>
|
||||
<configuration name="">
|
||||
<globalVars name="Global">
|
||||
<variable name="Always_ON" address="SM400">
|
||||
<type><BOOL/></type>
|
||||
<addData>
|
||||
<data name="http://www.mitsubishielectric.com/xml/variableLineNumber"
|
||||
handleUnknown="implementation">
|
||||
<variableLineNumber number="3"/>
|
||||
</data>
|
||||
</addData>
|
||||
</variable>
|
||||
<variable name="version" address="D450">
|
||||
<type><string length="12"/></type>
|
||||
</variable>
|
||||
<variable name="PLC_FEL">
|
||||
<type><derived name="FB_Din"/></type>
|
||||
<addData>
|
||||
<data name="http://www.mitsubishielectric.com/xml/variableComments"
|
||||
handleUnknown="implementation">
|
||||
<variableComments>
|
||||
<comment number="1">
|
||||
<html xml:space="preserve"
|
||||
xmlns="http://www.w3.org/1999/xhtml">PLC error active</html>
|
||||
</comment>
|
||||
</variableComments>
|
||||
</data>
|
||||
</addData>
|
||||
</variable>
|
||||
<variable name="PLC_FEL_STA">
|
||||
<type><derived name="ST_Din_STA"/></type>
|
||||
<addData>
|
||||
<data name="http://www.mitsubishielectric.com/xml/variableStructDeviceAssignment"
|
||||
handleUnknown="implementation">
|
||||
<variableStructDeviceAssignment>
|
||||
<member name="Value" address="D0.0"/>
|
||||
<member name="Status" address="D1"/>
|
||||
</variableStructDeviceAssignment>
|
||||
</data>
|
||||
</addData>
|
||||
</variable>
|
||||
</globalVars>
|
||||
<globalVars name="Global" retain="true">
|
||||
<variable name="PulseTot" address="D600">
|
||||
<type><WORD/></type>
|
||||
</variable>
|
||||
</globalVars>
|
||||
</configuration>
|
||||
</configurations>
|
||||
</instances>
|
||||
</project>
|
||||
"""
|
||||
|
||||
|
||||
@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 <addData> (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]"
|
||||
Reference in New Issue
Block a user