mcilspy/tests/test_ilspy_wrapper.py
Ryan Malloy 609d97b86d fix: correct ilspycmd output parsing and heap access
- Fix _TYPE_LINE_PATTERN regex to match ilspycmd format (no colon)
- Fix UserStringHeap data access using getattr instead of name mangling
- Add _decompile_to_stdout method for simple decompilation
- Fix decompile to not always use -o flag when stdout is desired
- Update test data to match actual ilspycmd output format
2026-02-08 13:33:06 -07:00

177 lines
5.9 KiB
Python

"""Tests for mcilspy.ilspy_wrapper module."""
import pytest
from mcilspy.ilspy_wrapper import ILSpyWrapper
from mcilspy.models import DecompileRequest, ListTypesRequest
class TestILSpyWrapperInit:
"""Tests for ILSpyWrapper initialization."""
def test_init_finds_ilspycmd_or_raises(self):
"""Test that init either finds ilspycmd or raises RuntimeError."""
try:
wrapper = ILSpyWrapper()
# If we get here, ilspycmd was found
assert wrapper.ilspycmd_path is not None
except RuntimeError as e:
# ilspycmd not installed - that's okay for testing
assert "ILSpyCmd not found" in str(e)
class TestILSpyWrapperTypeParsing:
"""Tests for type name parsing utilities."""
def test_parse_types_output_empty(self):
"""Test parsing empty output."""
# Create wrapper only if ilspycmd is available
try:
wrapper = ILSpyWrapper()
except RuntimeError:
pytest.skip("ilspycmd not installed")
types = wrapper._parse_types_output("")
assert types == []
def test_parse_types_output_single_type(self):
"""Test parsing single type."""
try:
wrapper = ILSpyWrapper()
except RuntimeError:
pytest.skip("ilspycmd not installed")
output = "Class MyNamespace.MyClass"
types = wrapper._parse_types_output(output)
assert len(types) == 1
assert types[0].name == "MyClass"
assert types[0].namespace == "MyNamespace"
assert types[0].kind == "Class"
assert types[0].full_name == "MyNamespace.MyClass"
def test_parse_types_output_multiple_types(self):
"""Test parsing multiple types."""
try:
wrapper = ILSpyWrapper()
except RuntimeError:
pytest.skip("ilspycmd not installed")
output = """Class NS.ClassA
Interface NS.IService
Struct NS.MyStruct
Enum NS.MyEnum
Delegate NS.MyDelegate"""
types = wrapper._parse_types_output(output)
assert len(types) == 5
# Check kinds
kinds = [t.kind for t in types]
assert "Class" in kinds
assert "Interface" in kinds
assert "Struct" in kinds
assert "Enum" in kinds
assert "Delegate" in kinds
def test_parse_types_output_nested_type(self):
"""Test parsing nested types."""
try:
wrapper = ILSpyWrapper()
except RuntimeError:
pytest.skip("ilspycmd not installed")
output = "Class MyNamespace.Outer+Nested"
types = wrapper._parse_types_output(output)
assert len(types) == 1
assert types[0].name == "Outer+Nested"
assert types[0].namespace == "MyNamespace"
def test_parse_types_output_no_namespace(self):
"""Test parsing type with no namespace."""
try:
wrapper = ILSpyWrapper()
except RuntimeError:
pytest.skip("ilspycmd not installed")
output = "Class MyClass"
types = wrapper._parse_types_output(output)
assert len(types) == 1
assert types[0].name == "MyClass"
assert types[0].namespace is None
def test_parse_types_output_skips_invalid_lines(self):
"""Test that invalid lines are skipped."""
try:
wrapper = ILSpyWrapper()
except RuntimeError:
pytest.skip("ilspycmd not installed")
output = """Class NS.Valid
This is not a valid line
Another invalid line
Interface NS.AlsoValid"""
types = wrapper._parse_types_output(output)
assert len(types) == 2
assert types[0].full_name == "NS.Valid"
assert types[1].full_name == "NS.AlsoValid"
class TestILSpyWrapperDecompile:
"""Tests for decompilation functionality."""
@pytest.mark.asyncio
async def test_decompile_nonexistent_assembly(self):
"""Test decompiling nonexistent assembly."""
try:
wrapper = ILSpyWrapper()
except RuntimeError:
pytest.skip("ilspycmd not installed")
request = DecompileRequest(assembly_path="/nonexistent/assembly.dll")
response = await wrapper.decompile(request)
assert response.success is False
assert "not found" in response.error_message.lower()
@pytest.mark.asyncio
async def test_list_types_nonexistent_assembly(self):
"""Test listing types from nonexistent assembly."""
try:
wrapper = ILSpyWrapper()
except RuntimeError:
pytest.skip("ilspycmd not installed")
request = ListTypesRequest(assembly_path="/nonexistent/assembly.dll")
response = await wrapper.list_types(request)
assert response.success is False
assert "not found" in response.error_message.lower()
class TestILSpyWrapperHelpers:
"""Tests for ILSpyWrapper helper methods."""
def test_split_type_name_simple(self):
"""Test splitting simple type names."""
assert ILSpyWrapper._split_type_name("MyClass") == ("MyClass", None)
assert ILSpyWrapper._split_type_name("NS.MyClass") == ("MyClass", "NS")
assert ILSpyWrapper._split_type_name("Deep.NS.MyClass") == ("MyClass", "Deep.NS")
def test_split_type_name_nested(self):
"""Test splitting nested type names."""
assert ILSpyWrapper._split_type_name("NS.Outer+Nested") == ("Outer+Nested", "NS")
assert ILSpyWrapper._split_type_name("Outer+Nested") == ("Outer+Nested", None)
assert ILSpyWrapper._split_type_name("NS.A+B+C") == ("A+B+C", "NS")
def test_split_type_name_deep_namespace(self):
"""Test splitting types with deep namespaces."""
assert ILSpyWrapper._split_type_name("A.B.C.D.MyClass") == ("MyClass", "A.B.C.D")
def test_split_type_name_nested_with_deep_namespace(self):
"""Test nested types with deep namespaces."""
assert ILSpyWrapper._split_type_name("A.B.C.Outer+Inner") == ("Outer+Inner", "A.B.C")