Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/mrexodia/ida-pro-mcp/llms.txt

Use this file to discover all available pages before exploring further.

IDA Pro MCP ships a custom headless test framework that loads binaries through idalib and runs tests without opening the IDA GUI. Tests live alongside the functions they cover in api_*.py files and in dedicated files under src/ida_pro_mcp/ida_mcp/tests/. The test runner is invoked with uv run ida-mcp-test.

Running the test suite

Two test fixtures are maintained. Run both to get full coverage:
uv run ida-mcp-test tests/crackme03.elf -q
uv run ida-mcp-test tests/typed_fixture.elf -q
The -q flag prints failures and a summary only, suppressing per-test output.

Filtering tests

Run only the tests in a specific API module with -c:
uv run ida-mcp-test tests/crackme03.elf -c api_analysis
Run tests whose names match a glob pattern with -p:
uv run ida-mcp-test tests/typed_fixture.elf -p "*stack*"
Both flags can be combined. The pattern is matched against the test function name.

Running a non-fixture binary

When adding generic tests, verify they are not ELF-specific by running against an unrelated binary:
uv run ida-mcp-test "C:\CodeBlocks\x64dbg\bin\x64\x64dbg.dll" -q

Measuring coverage

Measure coverage across both fixtures using the coverage package:
uv run coverage erase
uv run coverage run -m ida_pro_mcp.test tests/crackme03.elf -q
uv run coverage run --append -m ida_pro_mcp.test tests/typed_fixture.elf -q
uv run coverage report --show-missing
To inspect which lines are uncovered in API modules specifically:
uv run coverage report --show-missing --include="src/ida_pro_mcp/ida_mcp/api_*.py"

Test fixtures

crackme03.elf

Compact general-purpose regression fixture. Most tests that do not require typed data run against this binary.

typed_fixture.elf

Typed globals, structs, local variables, and stack frames. Required for tests that exercise api_types.py and api_stack.py code paths.

Writing tests

The @test decorator

Register a test function with the @test() decorator imported from .tests:
from .tests import test, assert_has_keys

@test()
def test_idb_meta():
    """idb_meta returns valid metadata"""
    meta = idb_meta()
    assert_has_keys(meta, "path", "module", "base")
Place the test immediately after the function it covers. The framework auto-discovers the test and assigns it the category of the enclosing module (e.g., api_core).

Binary-specific tests

Use @test(binary="...") with the executable basename when a test requires properties unique to one fixture:
@test(binary="crackme03.elf")
def test_crackme_entry():
    """Tests a code path that requires the crackme binary structure"""
    ...
Binary-specific tests run only when that exact binary is loaded. Reserve them for edge cases that cannot be triggered with generic inputs — do not use them to assert IDA-specific addresses or values.

Assertion helpers

Import helpers from .tests instead of using bare assert statements:
HelperWhat it checks
assert_has_keys(d, *keys)Dict contains all specified keys
assert_valid_address(addr)String is a valid 0x-prefixed hex address
assert_non_empty(value)Value is not None, "", or []
assert_is_list(value, min_length=0)Value is a list with at least min_length items
assert_all_have_keys(items, *keys)Every dict in the list contains all specified keys

Test data helpers

These helpers return valid inputs without hardcoding addresses:
from .tests import get_any_function, get_any_string, get_first_segment
from .tests import get_n_functions, get_n_strings

fn_addr = get_any_function()
if not fn_addr:
    return  # Skip gracefully when no data is available
get_n_functions() and get_n_strings() return a deterministically sampled list whose size is controlled by the --sample-size flag (default: 5).

Test writing guidelines

Follow these conventions to keep the test suite reliable and maintainable: Prefer semantic assertions. Check that a field has the expected value or shape, not just that it exists. Weak assert "key" in result checks should be replaced with assert_has_keys plus value checks. Use round-trip tests for mutating APIs. Restore the original state in a finally block:
@test()
def test_rename_roundtrip():
    """rename can be undone"""
    fn_addr = get_any_function()
    if not fn_addr:
        return
    original = lookup_funcs(fn_addr)[0]["fn"]["name"]
    try:
        rename({"func": [{"addr": fn_addr, "name": "__test__"}]})
        assert lookup_funcs(fn_addr)[0]["fn"]["name"] == "__test__"
    finally:
        rename({"func": [{"addr": fn_addr, "name": original}]})
Fix wrong behavior instead of weakening tests. If a test reveals an incorrect API result, fix the API rather than loosening the assertion. Skip gracefully when data is unavailable. Return early (without asserting) if get_any_function() or similar helpers return None.
Some IDA and Hex-Rays behaviors vary between IDA versions or binary types. Guarded assertions (if hexrays_ready: ...) and @test(skip=True) are acceptable when the variance is documented and justified.

Coverage targets

ModuleTarget
api_core90%+
api_analysis85%+
api_memory85%+
api_types80%+
api_modify70%+
api_resources85%+
api_stack80%+
api_debugSkip — requires an active debugger
api_pythonSkip — requires special setup

Build docs developers (and LLMs) love