mcilspy/tests/test_ilspy_wrapper.py
Ryan Malloy 7d784af17c refactor: address major code review findings
- Use importlib.metadata for dynamic version (single source in pyproject.toml)
- Clean up duplicate `import re` statements across modules
- Add missing type hints to all public methods
- Fix PATH auto-discovery for ilspycmd (~/.dotnet/tools)
- Add pytest test suite with 35 tests covering models, metadata reader, wrapper
- Bump version to 0.2.0, add CHANGELOG.md
2026-02-07 02:05:57 -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")