Files
2026-10-02 06:10:55 +02:00

154 lines
5.1 KiB
Python

"""Tests for the XML writer (Excel data -> GX Works3 XML)."""
from __future__ import annotations
from pathlib import Path
from mitsubishi_label_converter.models import (
GlobalVarSection,
ProjectMetadata,
StructMember,
Variable,
)
from mitsubishi_label_converter.parser import (
parse_global_vars,
parse_project_metadata,
)
from mitsubishi_label_converter.xml_writer import write_xml
def make_sections() -> list[GlobalVarSection]:
return [
GlobalVarSection(
name="Global",
retain=False,
variables=[
Variable("Always_ON", "SM400", "BOOL", "", line_number=3),
Variable("version", "D450", "STRING[12]", ""),
Variable(
"PLC_FEL_STA",
"",
"ST_Din_STA",
"",
line_number=39,
members=(
StructMember("Value", "D0.0"),
StructMember("Status", "D1"),
),
),
Variable(
"Switch_status",
"SD200",
"INT",
"CPU switch status (0: RUN, 1: STOP)",
line_number=14,
),
],
),
GlobalVarSection(
name="Global",
retain=True,
variables=[
Variable(
"PulseTot", "D600", "WORD", "", line_number=31, retain=True
),
],
),
]
def make_metadata() -> ProjectMetadata:
return ProjectMetadata(
file_header={
"companyName": "MITSUBISHI ELECTRIC CORPORATION",
"companyURL": "http://www.mitsubishielectric.com",
"productName": "GX Works3",
"productVersion": "1.126G",
"creationDateTime": "2026-10-01T20:59:19+02:00",
},
content_header={
"name": "21014JON-B14_20260930",
"modificationDateTime": "2026-10-01T20:57:58+02:00",
},
project_add_data={"projectPlcType": "FX5U"},
)
def test_full_round_trip(tmp_path: Path) -> None:
"""XML -> sections -> XML: the parsed data must be identical."""
out = tmp_path / "regenerated.xml"
sections = make_sections()
write_xml(sections, make_metadata(), out)
# The regenerated file must parse back to the same sections.
reparsed = parse_global_vars(out)
assert reparsed == sections
meta = parse_project_metadata(out)
assert meta.file_header == make_metadata().file_header
assert meta.content_header == make_metadata().content_header
assert meta.project_add_data == make_metadata().project_add_data
def test_output_is_utf16_le(tmp_path: Path) -> None:
out = tmp_path / "regenerated.xml"
write_xml(make_sections(), make_metadata(), out)
raw = out.read_bytes()
# UTF-16 LE BOM
assert raw[:2] == b"\xff\xfe"
# XML declaration must declare utf-16
assert raw.decode("utf-16").startswith('<?xml version="1.0" encoding="utf-16"?>')
def test_retain_flag_written(tmp_path: Path) -> None:
out = tmp_path / "regenerated.xml"
write_xml(make_sections(), make_metadata(), out)
text = out.read_text(encoding="utf-16")
assert '<globalVars name="Global" retain="true">' in text
# the plain section: retain attribute must NOT be present
assert '<globalVars name="Global">' in text
def test_member_addresses_written(tmp_path: Path) -> None:
out = tmp_path / "regenerated.xml"
write_xml(make_sections(), make_metadata(), out)
text = out.read_text(encoding="utf-16")
assert 'member name="Value" address="D0.0"' in text
assert 'member name="Status" address="D1"' in text
def test_comment_written(tmp_path: Path) -> None:
out = tmp_path / "regenerated.xml"
write_xml(make_sections(), make_metadata(), out)
reparsed = parse_global_vars(out)
switch_status = reparsed[0].variables[3]
assert switch_status.comment == "CPU switch status (0: RUN, 1: STOP)"
def test_line_numbers_written(tmp_path: Path) -> None:
out = tmp_path / "regenerated.xml"
write_xml(make_sections(), make_metadata(), out)
reparsed = parse_global_vars(out)
assert [v.line_number for v in reparsed[0].variables] == [3, None, 39, 14]
def test_real_file_full_round_trip(tmp_path: Path) -> None:
"""The real asset: XML -> sections -> XML -> sections must be stable."""
asset = Path(__file__).parents[1] / "assets" / "Global.xml"
if not asset.is_file():
import pytest
pytest.skip("assets/Global.xml not present")
original_sections = parse_global_vars(asset)
original_meta = parse_project_metadata(asset)
out = tmp_path / "regenerated.xml"
write_xml(original_sections, original_meta, out)
reparsed_sections = parse_global_vars(out)
reparsed_meta = parse_project_metadata(out)
assert reparsed_sections == original_sections
assert reparsed_meta.file_header == original_meta.file_header
assert reparsed_meta.content_header == original_meta.content_header
assert reparsed_meta.project_add_data == original_meta.project_add_data