- 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
177 lines
5.9 KiB
Python
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")
|