Manuel Campos

79 papers A* 1A 1B 11C 1Misc 1Journal 26Unranked 38
YearRankTypeTitle / Venue / Authors
2026 J jnl
Int. J. Medical Informatics
Andrea Santomauro, Denisse Kim, Bernardo Cánovas-Segura, Giorgio Leonardi, Manuel Campos, Luigi Portinale, Jose M. Juarez
2025 J jnl
Artif. Intell. Medicine
Antonio Lopez-Martinez-Carrasco, Hugo Manuel Proença, Jose M. Juarez, Matthijs van Leeuwen, Manuel Campos
2025 J jnl
J. Medical Syst.
Denisse Kim, Bernardo Cánovas-Segura, Manuel Campos, Sergio Aleman Belando, Jose M. Juarez
2025 J jnl
IEEE Access
Lorena Pujante-Otalora, Manuel Campos, Jose M. Juarez
2024 conf
BIOSTEC (2)
Antonio Lopez-Martinez-Carrasco, Jose M. Juarez, Manuel Campos, Bernardo Cánovas-Segura
2024 conf
BIOSTEC (2)
Lorena Pujante-Otalora, Manuel Campos, Jose M. Juarez, Maria-Esther Vidal
2024 conf
SEPLN (Projects and Demonstrations)
Cristóbal Bernardo-Castiñeira, Germán Bou, Manuel Campos, Bernardo Cánovas-Segura, Sergio Figueiras Gómez, Carlos Gómez-Rodríguez, Enrique Míguez Rey, Jesús Vilares
2024 conf
BIOSTEC (2)
Denisse Kim, Manuel Campos, Bernardo Cánovas-Segura, José M. Juárez
2024 J jnl
IEEE J. Biomed. Health Informatics
Lorena Pujante-Otalora, Manuel Campos, Jose M. Juarez, Bernardo Cánovas-Segura
2024 J jnl
SoftwareX
Antonio Lopez-Martinez-Carrasco, Jose M. Juarez, Manuel Campos, Francisco Mora-Caselles
2024 J jnl
BMC Medical Informatics Decis. Mak.
Isidoro J. Casanova, Manuel Campos, Jose M. Juarez, Antonio Gomariz, Bernardo Cánovas-Segura, Marta Lorente-Ros, Jose A. Lorente
2023 conf
HEDA@STAF
Denisse Kim, Manuel Campos, Bernardo Cánovas-Segura, Jose M. Juarez
2023 B conf
IDA
Antonio Lopez-Martinez-Carrasco, Hugo Manuel Proença, Jose M. Juarez, Matthijs van Leeuwen, Manuel Campos
2023 J jnl
J. Biomed. Informatics
Bernardo Cánovas-Segura, Antonio Morales Nicolás, Jose M. Juarez, Manuel Campos
2023 B conf
AIME
Antonio Lopez-Martinez-Carrasco, Hugo Manuel Proença, Jose M. Juarez, Matthijs van Leeuwen, Manuel Campos
2023 J jnl
J. Biomed. Informatics
Lorena Pujante-Otalora, Bernardo Cánovas-Segura, Manuel Campos, Jose M. Juarez
2023 J jnl
Algorithms
Antonio Lopez-Martinez-Carrasco, Jose M. Juarez, Manuel Campos, Bernardo Cánovas-Segura
2022 conf
PKDD/ECML Workshops (1)
Enrique Valero-Leal, Manuel Campos, Jose M. Juarez
2022 conf
DCAI (2)
Denisse Kim, Jose M. Juarez, Manuel Campos, Bernardo Cánovas-Segura
2021 J jnl
Knowl. Based Syst.
Antonio Lopez-Martinez-Carrasco, Jose M. Juarez, Manuel Campos, Bernardo Cánovas-Segura
2021 conf
MedInfo
Natalia Iglesias, Jose M. Juarez, Manuel Campos
2021 conf
HEALTHINF
Lorena Pujante, Manuel Campos, Jose M. Juarez, Bernardo Cánovas-Segura, Antonio Morales Nicolás
2021 B conf
AIME
Antonio Lopez-Martinez-Carrasco, Jose M. Juarez, Manuel Campos, Bernardo Cánovas-Segura
2021 B conf
AIME
Bernardo Cánovas-Segura, Antonio Morales Nicolás, José M. Juárez, Manuel Campos
2021 J jnl
Quantum
Manuel Campos, Germán Sierra, Esperanza López
2020 J jnl
Artif. Intell. Medicine
Manuel Campos, Fernando Jiménez, Gracia Sánchez, Jose M. Juarez, Antonio Morales Nicolás, Bernardo Cánovas-Segura, Francisco Palacios
2020 J jnl
Artif. Intell. Medicine
Natalia Iglesias, Jose M. Juarez, Manuel Campos
2020 C conf
IGARSS
Álvaro Moreno-Martínez, Emma Izquierdo-Verdiguier, Gustau Camps-Valls, Marco P. Maneta, Jordi Muñoz-Marí, Nathaniel P. Robinson, José E. Adsuara, Manuel Campos, F. Javier García-Haro, Adrián Pérez-Suay, Nicholas Clinton, John S. Kimball, Steven W. Running
2020 conf
IWBBIO
Lorena Pujante, Manuel Campos, Jose M. Juarez, Bernardo Cánovas-Segura, Antonio Morales Nicolás
2019 J jnl
Knowl. Based Syst.
Bernardo Cánovas-Segura, Antonio Morales Nicolás, Jose M. Juarez, Manuel Campos, Francisco Palacios
2019 conf
KR4HC/ProHealth/TEAAM@AIME
Bernardo Cánovas-Segura, Antonio Morales Nicolás, Antonio López Martínez-Carrasco, Manuel Campos, Jose M. Juarez, Lucía López-Rodríguez, Francisco Palacios
2019 J jnl
J. Biomed. Informatics
Bernardo Cánovas-Segura, Antonio Morales Nicolás, Jose M. Juarez, Manuel Campos, Francisco Palacios
2019 B conf
CBMS
Bernardo Cánovas-Segura, Antonio Morales Nicolás, Antonio López Martínez-Carrasco, Manuel Campos, Jose M. Juarez, Lucía López-Rodríguez, Francisco Palacios
2019 B conf
AIME
Antonio López Martínez-Carrasco, Jose M. Juarez, Manuel Campos, Antonio Morales Nicolás, Francisco Palacios, Lucía López-Rodríguez
2018 J jnl
J. Biomed. Informatics
Antonio Morales Nicolás, Manuel Campos, Jose M. Juarez, Bernardo Cánovas-Segura, Francisco Palacios, Roque Marín
2018 J jnl
Prog. Artif. Intell.
Isidoro J. Casanova, Manuel Campos, Jose M. Juarez, Antonio Fernandez-Fernandez-Arroyo, Jose A. Lorente
2018 A* conf
IJCAI
Jose M. Juarez, Susan Craw, J. Ricardo Lopez-Delgado, Manuel Campos
2018 conf
CAEPIA
Andrej Fajfar, Manuel Campos, Francisco Palacios, Bernardo Cánovas-Segura, Gregor Stiglic, Roque Marín
2017 conf
CITI
Bernardo Cánovas-Segura, Francesca Zerbato, Barbara Oliboni, Carlo Combi, Manuel Campos, Antonio Morales Nicolás, Jose M. Juarez, Francisco Palacios, Roque Marín
2017 conf
ICHI
Bernardo Cánovas-Segura, Francesca Zerbato, Barbara Oliboni, Carlo Combi, Manuel Campos, Antonio Morales Nicolás, Jose M. Juarez, Roque Marín, Francisco Palacios
2016 conf
HEALTHINF
Francisco Palacios, Manuel Campos, Jose M. Juarez, S. E. Cosgrove, E. Avdic, Bernardo Cánovas-Segura, Antonio Morales Nicolás, M. E. Martínez-Nuñez, T. Molina-García, P. García-Hierro, J. Cacho-Calvo
2016 conf
CAEPIA
Manuel Campos, Antonio Gomariz, M. Balsa, María Ángeles Rol, Juan Antonio Madrid, F. J. Garcia
2016 conf
CAEPIA
Bernardo Cánovas-Segura, Manuel Campos, Antonio Morales Nicolás, Jose M. Juarez, Francisco Palacios
2016 J jnl
Prog. Artif. Intell.
Bernardo Cánovas-Segura, Manuel Campos, Antonio Morales Nicolás, José M. Juárez, Francisco Palacios
2016 conf
CAEPIA
Antonio Morales Nicolás, Bernardo Cánovas-Segura, Manuel Campos, Jose M. Juarez, Francisco Palacios
2015 conf
ICHI
Francesca Zerbato, Barbara Oliboni, Carlo Combi, Manuel Campos, Jose M. Juarez
2015 conf
IWINAC (1)
Natalia Iglesias, Jose M. Juarez, Manuel Campos, Francisco Palacios
2015 J jnl
Artif. Intell. Medicine
Jose M. Juarez, Jose M. Ochotorena, Manuel Campos, Carlo Combi
2015 B conf
AIME
Isidoro J. Casanova, Manuel Campos, Jose M. Juarez, Antonio Fernandez-Fernandez-Arroyo, Jose A. Lorente
2014 conf
ICHI
Jose M. Juarez, Guadalupe Garcia-Fernandez, Manuel Campos, Begona Martinez, Martirio Antequera, Carmen Antunez
2014 conf
PAKDD (1)
Philippe Fournier-Viger, Antonio Gomariz, Manuel Campos, Rincy Thomas
2014 conf
ICHI
Manuel Campos, Beatriz Garcia, Jose M. Juarez, Juan M. Guillamon, Francisco Palacios
2013 conf
PAKDD (1)
Antonio Gomariz, Manuel Campos, Roque Marín, Bart Goethals
2013 conf
ICHI
Jose M. Juarez, Jose M. Ochotorena, Manuel Campos, Carlo Combi
2011 conf
IWINAC (1)
Roque Marín, Manuel Campos, Antonio Gomariz, A. Lopez, María Ángeles Rol, Juan Antonio Madrid
2011 conf
KR4HC
José M. Juárez, Manuel Campos, Antonio Gomariz, Antonio Morales Nicolás
2010 J jnl
PLoS Comput. Biol.
Elisabet Ortiz-Tudela, Antonio Martinez-Nicolas, Manuel Campos, María Ángeles Rol, Juan Antonio Madrid
2010 conf
HEALTHINF
Francisco Guil, Francisco Palacios, Manuel Campos, Roque Marín
2009 B conf
ICTAI
José M. Juárez, Manuel Campos, Antonio Gomariz, José T. Palma, Roque Marín
2009 conf
CAEPIA
José M. Juárez, Manuel Campos, Antonio Gomariz, José T. Palma, Roque Marín
2009 J jnl
Expert Syst. Appl.
José M. Juárez, Tamara Riestra, Manuel Campos, Antonio Morales Nicolás, José T. Palma, Roque Marín
2009 conf
IWINAC (2)
Guido Sciavicco, José M. Juárez, Manuel Campos
2009 J jnl
Neurocomputing
Manuel Campos, José M. Juárez, Jose Salort, José T. Palma, Roque Marín
2009 B conf
AIME
José M. Juárez, Manuel Campos, José T. Palma, Francisco Palacios, Roque Marín
2009 conf
EUROCAST
José M. Juárez, Patricia Martinez, Manuel Campos, José T. Palma
2008 J jnl
Expert Syst. Appl.
José M. Juárez, Manuel Campos, José T. Palma, Roque Marín
2008 Misc conf
DCAI
Manuel Campos, Antonio Morales Nicolás, José M. Juárez, J. Sarlort, José T. Palma, Roque Marín
2008 conf
HEALTHINF (2)
Miguel A. Martínez, José Ambrosio Toval Álvarez, Manuel Campos
2008 J jnl
Inteligencia Artif.
Manuel Campos
2007 conf
IWINAC (1)
Manuel Campos, José M. Juárez, Jose Salort, José T. Palma, Roque Marín
2007 B conf
AIME
Manuel Campos, José T. Palma, Roque Marín
2006 J jnl
Artif. Intell. Medicine
José T. Palma, José M. Juárez, Manuel Campos, Roque Marín
2005 conf
IWINAC (1)
José T. Palma, José M. Juárez, Manuel Campos, Roque Marín
2005 conf
EUROCAST
José M. Juárez, José T. Palma, Manuel Campos, Jose Salort, Antonio Morales Nicolás, Roque Marín
2004 A conf
ECAI
José T. Palma, José M. Juárez, Manuel Campos, Roque Marín
2004 B conf
EKAW
José T. Palma, Manuel Campos, José M. Juárez, Antonio Morales Nicolás
2003 conf
CAEPIA
Manuel Campos, José T. Palma, Roque Marín, B. Llamas, A. González
2003 conf
EUROCAST
Manuel Campos, José T. Palma, B. Llamas, A. González, M. Menárguez, Roque Marín
1993 conf
PPSC
Manuel Campos, Carlos A. de Moura
tests/unit/test_decompile_binja_extractor.py
← Index tests/unit/test_decompile_binja_extractor.py python
"""Unit tests for DecompileBinja (top-level extractor) and prepare_export_data."""
import hashlib
import json
import os
import sys
import pytest
from unittest.mock import MagicMock, patch, PropertyMock
from datetime import datetime

# Install binaryninja stubs
from tests.unit.conftest_binja_stubs import install_binja_stubs
install_binja_stubs()

from redb.extractors.enum import Tag


# ============================================================================
# 7a. DecompileBinja — methods that don't require a live Binary Ninja session
# ============================================================================


class TestDecompileBinjaCalculateMD5:
    def test_calculate_md5(self):
        """calculate_md5("test") returns expected MD5."""
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = object.__new__(DecompileBinja)
            obj.log = MagicMock()
            result = obj.calculate_md5("test")
            expected = hashlib.md5(b"test").hexdigest()
            assert result == expected


class TestDecompileBinjaTimeouts:
    def _make_extractor(self, env_vars=None):
        """Create a DecompileBinja with mocked Extractor.__init__."""
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            if env_vars:
                with patch.dict(os.environ, env_vars, clear=False):
                    obj = DecompileBinja.__new__(DecompileBinja)
                    obj.log = MagicMock()
                    obj.analysis_results = None
                    obj.binja_decompiler = None
                    obj.filetype = None
                    obj.goresym_data = None
                    obj.goresym_output_path = None
                    # Re-run timeout logic
                    try:
                        obj.BINJA_TIMEOUT = int(os.getenv("BINJA_TIMEOUT", "1200"))
                    except ValueError:
                        obj.BINJA_TIMEOUT = 1200
                    try:
                        obj.DECOMPILE_EXTRACTOR_TIMEOUT = int(
                            os.getenv("DECOMPILE_EXTRACTOR_TIMEOUT", "2580")
                        )
                    except ValueError:
                        obj.DECOMPILE_EXTRACTOR_TIMEOUT = 2580
                    return obj
            else:
                obj = DecompileBinja.__new__(DecompileBinja)
                obj.log = MagicMock()
                obj.analysis_results = None
                obj.binja_decompiler = None
                obj.filetype = None
                obj.goresym_data = None
                obj.goresym_output_path = None
                obj.BINJA_TIMEOUT = 1200
                obj.DECOMPILE_EXTRACTOR_TIMEOUT = 2580
                return obj

    def test_timeout_defaults(self):
        obj = self._make_extractor()
        assert obj.BINJA_TIMEOUT == 1200
        assert obj.DECOMPILE_EXTRACTOR_TIMEOUT == 2580

    def test_timeout_from_env(self):
        obj = self._make_extractor(env_vars={"BINJA_TIMEOUT": "600"})
        assert obj.BINJA_TIMEOUT == 600

    def test_timeout_invalid_env(self):
        obj = self._make_extractor(env_vars={"BINJA_TIMEOUT": "not_a_number"})
        assert obj.BINJA_TIMEOUT == 1200


class TestDecompileBinjaDotnet:
    def _make_extractor(self, filetype=None):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.filetype = filetype
            obj.filepath = "/tmp/fake.bin"
            obj.binary = b"\x00" * 100
            obj.hash = MagicMock()
            obj.hash.sha256 = "a" * 64
            return obj

    def test_is_dotnet_non_pe(self):
        obj = self._make_extractor(filetype="elf")
        assert obj.is_dotnet() is False

    def test_is_dotnet_pe_without_com(self):
        """PE without .NET COM descriptor -> False."""
        obj = self._make_extractor(filetype="pebin")
        # Mock magic to not have .Net
        with patch("redb.extractors.decompiler.DecompileBinja.magic") as mock_magic:
            mock_magic.from_buffer.return_value = "PE32 executable"
            with patch("redb.extractors.decompiler.DecompileBinja.pefile") as mock_pefile:
                mock_pe = MagicMock()
                entry = MagicMock()
                entry.name = "IMAGE_DIRECTORY_ENTRY_IMPORT"
                entry.Size = 100
                mock_pe.OPTIONAL_HEADER.DATA_DIRECTORY = [entry]
                mock_pefile.PE.return_value = mock_pe
                assert obj.is_dotnet() is False

    def test_is_dotnet_pe_with_com_descriptor(self):
        """PE with COM descriptor -> True."""
        obj = self._make_extractor(filetype="pebin")
        with patch("redb.extractors.decompiler.DecompileBinja.magic") as mock_magic:
            mock_magic.from_buffer.return_value = "PE32 executable"
            with patch("redb.extractors.decompiler.DecompileBinja.pefile") as mock_pefile:
                mock_pe = MagicMock()
                entry = MagicMock()
                entry.name = "IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR"
                entry.Size = 72
                mock_pe.OPTIONAL_HEADER.DATA_DIRECTORY = [entry]
                mock_pefile.PE.return_value = mock_pe
                assert obj.is_dotnet() is True


class TestDecompileBinjaGolang:
    def _make_extractor(self, filetype=None):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.filetype = filetype
            obj.filepath = "/tmp/fake.bin"
            obj.hash = MagicMock()
            obj.hash.sha256 = "a" * 64
            return obj

    def test_is_golang_non_elf_pe(self):
        obj = self._make_extractor(filetype=None)
        assert obj.is_golang() is False

    def test_is_golang_elf_with_go_sections(self):
        """ELF with .gopclntab section -> True."""
        obj = self._make_extractor(filetype="elf")
        with patch("builtins.open", MagicMock()):
            with patch("redb.extractors.decompiler.DecompileBinja.ELFFile") as mock_elf_cls:
                mock_elf = MagicMock()
                sec1 = MagicMock()
                sec1.name = ".text"
                sec1.__getitem__ = lambda self, k: 0x2 if k == "sh_flags" else 0
                sec1.data.return_value = b""
                sec2 = MagicMock()
                sec2.name = ".gopclntab"
                sec2.__getitem__ = lambda self, k: 0x2 if k == "sh_flags" else 0
                sec2.data.return_value = b""
                mock_elf.iter_sections.return_value = [sec1, sec2]
                mock_elf_cls.return_value = mock_elf
                assert obj.is_golang() is True

    def test_is_golang_elf_without_go(self):
        obj = self._make_extractor(filetype="elf")
        with patch("builtins.open", MagicMock()):
            with patch("redb.extractors.decompiler.DecompileBinja.ELFFile") as mock_elf_cls:
                mock_elf = MagicMock()
                sec = MagicMock()
                sec.name = ".text"
                sec.__getitem__ = lambda self, k: 0x2 if k == "sh_flags" else 0
                sec.data.return_value = b"no go signatures here"
                mock_elf.iter_sections.return_value = [sec]
                mock_elf_cls.return_value = mock_elf
                assert obj.is_golang() is False


class TestDecompileBinjaTag:
    def test_tag_returns_decompiled(self):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            assert obj.tag() == Tag.DECOMPILED.value


class TestDecompileBinjaContextManager:
    def test_context_manager_protocol(self):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.binja_decompiler = None
            obj.goresym_output_path = None
            assert obj.__enter__() is obj
            # __exit__ calls cleanup_run
            obj.__exit__(None, None, None)

    def test_cleanup_run_clears_state(self):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.binja_decompiler = MagicMock()
            obj.goresym_output_path = None
            obj.cleanup_run()
            assert obj.binja_decompiler is None


class TestDecompileBinjaExtract:
    def test_extract_timeout_handling(self):
        """Thread timeout -> returns None and calls cleanup."""
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.analysis_results = None
            obj.binja_decompiler = None
            obj.goresym_output_path = None
            obj.DECOMPILE_EXTRACTOR_TIMEOUT = 0  # Immediate timeout
            obj.sha256 = "a" * 64
            obj.sha1 = "b" * 40
            obj.md5 = "c" * 32
            # Mock analyze_binary to block
            import threading
            obj.analyze_binary = lambda: None
            result = obj.extract()
            assert result is None

    def test_analyze_binary_skips_dotnet(self):
        """Dotnet binary -> analyze_binary returns None."""
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.filetype = "pebin"
            obj.filepath = "/fake"
            obj.hash = MagicMock()
            obj.hash.sha256 = "a" * 64
            obj.binja_decompiler = None
            obj.goresym_output_path = None
            obj.goresym_data = None
            obj.BINJA_TIMEOUT = 60
            obj.exporters = []
            obj.index_prefix = None
            with patch.object(obj, "is_dotnet", return_value=True):
                result = obj.analyze_binary()
                assert result is None


# ============================================================================
# 7b. prepare_export_data (ClickHouse schema)
# ============================================================================


class TestPrepareExportData:
    def _make_extractor_with_results(self, goresym_data=None):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.goresym_data = goresym_data
            obj.analysis_results = {
                "sha256": "a" * 64,
                "sha1": "b" * 40,
                "md5": "c" * 32,
                "decompiled": [
                    {
                        "decompiled_function_hash": "h" * 64,
                        "decompiled_function": "void test() {}",
                        "decompiled_function_name": "test",
                        "decompiled_function_prototype": "void test()",
                        "decompiled_function_address": 0x1000,
                        "function_type": "USER",
                        "functions_caller": [],
                        "functions_call": [],
                        "disassembled_function_hash": "d" * 64,
                        "flattened_score": 0.0,
                        "mba_score": 0.0,
                    }
                ],
                "disassembled": [
                    {
                        "disassembled_function_hash": "d" * 64,
                        "disassembled_function": "0x1000: push rbp",
                        "disassembled_function_no_addresses": "push rbp",
                        "disassembled_function_name": "test",
                        "disassembled_function_address": 0x1000,
                        "function_type": "USER",
                        "instructions_count": 5,
                        "instructions_types": ["DATA_MOVEMENT"],
                        "control_flow_count": 1,
                        "memory_access_pattern": ["MEM_STACK"],
                        "register_usage": ["GPR"],
                        "data_references_count": 0,
                        "max_block_size": 5,
                        "num_calls": 1,
                        "stack_size": -8,
                        "decompiled_function_hash": "h" * 64,
                        "tlsh_disassembly": None,
                        "tlsh_llil": None,
                        "minhash": [1, 2, 3],
                        "cyclomatic_complexity": 2,
                    }
                ],
                "cfg": [
                    {
                        "function_address": 0x1000,
                        "disassembled_function_hash": "d" * 64,
                        "cfg_topology_hash": b'\x00' * 16,
                        "block_count": 3,
                        "edge_count": 4,
                        "llil_total_operations": 20,
                        "call_count": 1,
                        "cyclomatic_complexity": 3,
                        "loop_count": 1,
                        "max_depth": 2,
                        "max_fan_out": 2,
                        "md_index_topdown": 12345,
                        "md_index_bottomup": 67890,
                        "prime_product_llil": 99999,
                        "cfg_feature_tlsh": None,
                        "wl_minhash": [0] * 128,
                        "bb_features": [[5, 1, 0, 2, 0, 1, 1, 2]] * 3,
                        "cfg_adjacency": [(0 << 16) | 1, (0 << 16) | 2, (1 << 16) | 2],
                    }
                ],
                "llil": [
                    {
                        "sha256_llil": "l" * 64,
                        "function_type": "USER",
                        "instructions_types_llil": [],
                        "control_flow_count_llil": 0,
                        "memory_access_pattern_llil": [],
                        "register_usage": {},
                        "total_reg_reads": 0,
                        "total_reg_written": 0,
                        "data_references_count": 0,
                        "max_block_size": 5,
                        "num_calls": 0,
                        "stack_size": -8,
                        "body_llil_vector": [],
                        "function_address": 0x1000,
                        "disassembled_function_hash": "d" * 64,
                        "tlsh_disassembly": None,
                        "tlsh_llil": None,
                    }
                ],
                "errors": [],
                "strings": [
                    {
                        "string": "Hello",
                        "string_raw": "Hello",
                        "string_encoding": "Utf8String",
                        "string_offset": 0,
                        "string_length": 5,
                        "string_raw_length": 5,
                        "string_entropy": 2.32,
                    }
                ],
            }
            return obj

    def test_prepare_export_returns_none_no_results(self):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.analysis_results = None
            result = obj.prepare_export_data("ClickHouseExporter")
            assert result is None

    def test_prepare_export_multi_table_flag(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        assert result["multi_table"] is True

    def test_prepare_export_all_tables_present(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        expected_tables = [
            "decompiled_content",
            "decompiled_refs",
            "disassembled_content",
            "disassembled_refs",
            "llil_content",
            "llil_refs",
            "cfg_functions",
            "function_similarity_metrics",
            "strings_raw",
        ]
        for table in expected_tables:
            assert table in result, f"Missing table: {table}"

    def test_prepare_export_decompiled_content_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["decompiled_content"]
        assert len(table["column_names"]) == len(table["column_type_names"])
        assert "decompiled_function_hash" in table["column_names"]

    def test_prepare_export_decompiled_refs_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["decompiled_refs"]
        assert len(table["column_names"]) == len(table["column_type_names"])
        assert "sha256" in table["column_names"]

    def test_prepare_export_disassembled_content_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["disassembled_content"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_disassembled_refs_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["disassembled_refs"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_llil_content_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["llil_content"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_llil_refs_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["llil_refs"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_cfg_functions_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["cfg_functions"]
        assert len(table["column_names"]) == len(table["column_type_names"])
        assert table["table"] == "code_binja_cfg_functions"
        assert "cfg_topology_hash" in table["column_names"]
        assert "wl_minhash" in table["column_names"]
        assert "bb_features" in table["column_names"]

    def test_prepare_export_function_similarity_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["function_similarity_metrics"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_strings_raw_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["strings_raw"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_errors_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["function_analysis_errors"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_golang_metadata_present(self):
        obj = self._make_extractor_with_results(goresym_data={"UserFunctions": []})
        result = obj.prepare_export_data("ClickHouseExporter")
        assert "golang_metadata" in result

    def test_prepare_export_golang_metadata_absent(self):
        obj = self._make_extractor_with_results(goresym_data=None)
        result = obj.prepare_export_data("ClickHouseExporter")
        assert "golang_metadata" not in result


class TestPrepareHelperFunctions:
    """Test the helper functions inside prepare_export_data."""

    def test_prepare_array_field_none(self):
        # The prepare_array_field function converts None -> []
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.goresym_data = None
            obj.analysis_results = {
                "sha256": "a" * 64, "sha1": "b" * 40, "md5": "c" * 32,
                "decompiled": [], "disassembled": [], "cfg": [], "llil": [],
                "errors": [], "strings": [],
            }
            result = obj.prepare_export_data("ClickHouseExporter")
            # Verify the export succeeds (function works even with empty lists)
            assert result is not None

    def test_prepare_array_field_list(self):
        """List values pass through."""
        # Verified through the export data containing arrays
        pass

    def test_prepare_register_usage_map(self):
        """Dict converted to {reg: (reads, writes)} tuples."""
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.goresym_data = None
            obj.analysis_results = {
                "sha256": "a" * 64, "sha1": "b" * 40, "md5": "c" * 32,
                "decompiled": [], "disassembled": [],
                "cfg": [],
                "llil": [{
                    "sha256_llil": "l" * 64, "function_type": "USER",
                    "instructions_types_llil": [], "control_flow_count_llil": 0,
                    "memory_access_pattern_llil": [],
                    "register_usage": {"rbx": {"reads": 3, "writes": 1}},
                    "total_reg_reads": 3, "total_reg_written": 1,
                    "data_references_count": 0, "max_block_size": 5,
                    "num_calls": 0, "stack_size": 0,
                    "body_llil_vector": [],
                    "function_address": 0x1000,
                    "disassembled_function_hash": "d" * 64,
                    "tlsh_disassembly": None,
                    "tlsh_llil": None,
                }],
                "errors": [], "strings": [],
            }
            result = obj.prepare_export_data("ClickHouseExporter")
            # The LLIL content data should have register_usage_llil as a map
            llil_data = result["llil_content"]["data"]
            assert len(llil_data) == 1
            # Column index 5 is register_usage_llil
            reg_map = llil_data[0][5]
            assert reg_map == {"rbx": (3, 1)}

    def test_prepare_register_usage_map_empty(self):
        """Empty/None register usage -> {}."""
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.goresym_data = None
            obj.analysis_results = {
                "sha256": "a" * 64, "sha1": "b" * 40, "md5": "c" * 32,
                "decompiled": [], "disassembled": [],
                "cfg": [],
                "llil": [{
                    "sha256_llil": "l" * 64, "function_type": "USER",
                    "instructions_types_llil": [], "control_flow_count_llil": 0,
                    "memory_access_pattern_llil": [],
                    "register_usage": None,
                    "total_reg_reads": 0, "total_reg_written": 0,
                    "data_references_count": 0, "max_block_size": 0,
                    "num_calls": 0, "stack_size": 0,
                    "body_llil_vector": [],
                    "function_address": 0x1000,
                    "disassembled_function_hash": "d" * 64,
                    "tlsh_disassembly": None,
                    "tlsh_llil": None,
                }],
                "errors": [], "strings": [],
            }
            result = obj.prepare_export_data("ClickHouseExporter")
            llil_data = result["llil_content"]["data"]
            reg_map = llil_data[0][5]
            assert reg_map == {}