Maarten Marx

168 papers A* 10A 10B 17C 4Misc 8Journal 73Unranked 43
YearRankTypeTitle / Venue / Authors
2025 J jnl
CoRR
Jonas van Elburg, Peter van der Putten, Maarten Marx
2025 conf
TPDL (Short Papers and Workshops)
Floris Bos, Marc van Opijnen, Maarten Marx
2025 conf
ECIR (1)
Jan Hutter, David Rau, Maarten Marx, Jaap Kamps
2025 J jnl
Int. J. Document Anal. Recognit.
Ruben van Heusden, Kaj Meijer, Maarten Marx
2025 conf
ECIR (5)
Pepijn van Wijk, Maarten Marx
2025 conf
TPDL (Short Papers and Workshops)
Gregory Slager, Maarten Marx
2024 J jnl
Pattern Recognit. Lett.
Ruben van Heusden, Maarten Marx
2024 J jnl
Discov. Comput.
Ruben van Heusden, Jaap Kamps, Maarten Marx
2024 C conf
JURIX
Harry Nan, Maarten Marx, Johan Wolswinkel
2024 J jnl
Data
Gerda Viira, Maarten Marx
2024 conf
TPDL (1)
Ruben van Heusden, Jaap Kamps, Maarten Marx
2024 conf
CASE
Femke Bakker, Ruben van Heusden, Maarten Marx
2023 B conf
TPDL
Ruben van Heusden, Aron de Ruijter, Roderick Majoor, Maarten Marx
2023 conf
ECIR (3)
Maarten Marx, Maik Larooij, Filipp Perasedillo, Jaap Kamps
2023 B conf
TPDL
Ruben van Heusden, Hazel Ling, Lars Nelissen, Maarten Marx
2023 J jnl
Data
Ruben van Heusden, Jaap Kamps, Maarten Marx
2023 J jnl
Lang. Resour. Evaluation
Daan Schueler, Maarten Marx
2023 J jnl
Lang. Resour. Evaluation
Tomaz Erjavec, Maciej Ogrodniczuk, Petya Osenova, Nikola Ljubesic, Kiril Simov, Andrej Pancur, Michal Rudolf, Matyás Kopp, Starkaður Barkarson, Steinþór Steingrímsson, Çagri Çöltekin, Jesse de Does, Katrien Depuydt, Tommaso Agnoloni, Giulia Venturi, María Calzada Pérez, Luciana D. de Macedo, Costanza Navarretta, Giancarlo Luxardo, Matthew Coole, Paul Rayson, Vaidas Morkevicius, Tomas Krilavicius, Roberts Dargis, Orsolya Ring, Ruben van Heusden, Maarten Marx, Darja Fiser
2023 J jnl
Algorithms
Lukas Busch, Ruben van Heusden, Maarten Marx
2022 Misc conf
ICTIR
Ruben van Heusden, Jaap Kamps, Maarten Marx
2022 conf
HR@RecSys
Roan Schellingerhout, Volodymyr Medentsiy, Maarten Marx
2022 J jnl
CoRR
Maarten Marx
2022 Misc conf
ICTIR
Ruben van Heusden, Jaap Kamps, Maarten Marx
2021 J jnl
Nat. Lang. Eng.
Hosein Azarbonyad, Mostafa Dehghani, Maarten Marx, Jaap Kamps
2021 J jnl
Data
Jenny Heddes, Pim Meerdink, Miguel Pieters, Maarten Marx
2019 J jnl
IEEE Trans. Knowl. Data Eng.
Hosein Azarbonyad, Mostafa Dehghani, Tom Kenter, Maarten Marx, Jaap Kamps, Maarten de Rijke
2019 conf
CLEF
Hosein Azarbonyad, Maarten Marx
2019 conf
TREC
Mahsa S. Shahshahani, Jaap Kamps, Maarten Marx
2018 J jnl
CoRR
Hosein Azarbonyad, Mostafa Dehghani, Tom Kenter, Maarten Marx, Jaap Kamps, Maarten de Rijke
2017 J jnl
Inf. Process. Lett.
Evgeny Sherkhonov, Maarten Marx
2017 J jnl
CoRR
Alex Olieman, Kaspar Beelen, Milan van Lange, Jaap Kamps, Maarten Marx
2017 Misc conf
SEMANTiCS
Alex Olieman, Kaspar Beelen, Milan van Lange, Jaap Kamps, Maarten Marx
2017 A conf
ECIR
Hosein Azarbonyad, Mostafa Dehghani, Tom Kenter, Maarten Marx, Jaap Kamps, Maarten de Rijke
2017 J jnl
CoRR
Hosein Azarbonyad, Mostafa Dehghani, Tom Kenter, Maarten Marx, Jaap Kamps, Maarten de Rijke
2017 Misc conf
ICTIR
Mostafa Dehghani, Glorianna Jagfeld, Hosein Azarbonyad, Alex Olieman, Jaap Kamps, Maarten Marx
2017 J jnl
CoRR
Mostafa Dehghani, Glorianna Jagfeld, Hosein Azarbonyad, Alex Olieman, Jaap Kamps, Maarten Marx
2017 B conf
CHIIR
Mostafa Dehghani, Glorianna Jagfeld, Hosein Azarbonyad, Alex Olieman, Jaap Kamps, Maarten Marx
2017 A conf
CIKM
Hosein Azarbonyad, Mostafa Dehghani, Kaspar Beelen, Alexandra Arkut, Maarten Marx, Jaap Kamps
2017 J jnl
CoRR
Hosein Azarbonyad, Mostafa Dehghani, Kaspar Beelen, Alexandra Arkut, Maarten Marx, Jaap Kamps
2016 J jnl
Soc. Netw. Anal. Min.
Steven Laan, Maarten Marx, Robert J. Mokken
2016 J jnl
Inf. Syst.
Maarten Marx, Evgeny Sherkhonov
2016 B conf
CHIIR
Mostafa Dehghani, Hosein Azarbonyad, Jaap Kamps, Maarten Marx
2016 J jnl
CoRR
Mostafa Dehghani, Hosein Azarbonyad, Jaap Kamps, Maarten Marx
2016 A conf
CIKM
Mostafa Dehghani, Hosein Azarbonyad, Jaap Kamps, Djoerd Hiemstra, Maarten Marx
2016 Misc conf
ICTIR
Mostafa Dehghani, Hosein Azarbonyad, Jaap Kamps, Maarten Marx
2016 J jnl
CoRR
Mostafa Dehghani, Hosein Azarbonyad, Jaap Kamps, Maarten Marx
2016 conf
TREC
Mostafa Dehghani, Jaap Kamps, Hosein Azarbonyad, Maarten Marx
2016 conf
CLEF
Mostafa Dehghani, Hosein Azarbonyad, Jaap Kamps, Maarten Marx
2016 A conf
ECAI
Janneke M. van der Zwaan, Maarten Marx, Jaap Kamps
2015 conf
SEMANTiCS (Posters & Demos)
Alex Olieman, Jaap Kamps, Maarten Marx, Arjan Nusselder
2015 J jnl
CoRR
Alex Olieman, Jaap Kamps, Maarten Marx, Arjan Nusselder
2015 conf
CLEF
Hosein Azarbonyad, Ferron Saan, Mostafa Dehghani, Maarten Marx, Jaap Kamps
2015 J jnl
CoRR
Steven Laan, Maarten Marx, Robert J. Mokken
2015 J jnl
Log. Methods Comput. Sci.
Alessandro Facchini, Yoichi Hirai, Maarten Marx, Evgeny Sherkhonov
2015 A conf
ECIR
Mostafa Dehghani, Hosein Azarbonyad, Maarten Marx, Jaap Kamps
2015 A* conf
SIGIR
Hosein Azarbonyad, Mostafa Dehghani, Maarten Marx, Jaap Kamps
2014 J jnl
Inf. Process. Manag.
Suzan Verberne, Eva D'hondt, Antal van den Bosch, Maarten Marx
2014 conf
ERD@SIGIR
Alex Olieman, Hosein Azarbonyad, Mostafa Dehghani, Jaap Kamps, Maarten Marx
2014 J jnl
ERCIM News
Maarten Marx
2014 J jnl
Digit. Humanit. Q.
Hinke Piersma, Ismee Tames, Lars Buitinck, Johan van Doornik, Maarten Marx
2013 conf
WebDB
Evgeny Sherkhonov, Maarten Marx
2013 B conf
K-CAP
Victor de Boer, Johan van Doornik, Lars Buitinck, Maarten Marx, Tim Veken, Kees Ribbens
2013 conf
ESWC (Satellite Events)
Tatiana Tarasova, Maarten Marx
2013 B conf
TPDL
Bart de Goede, Justin van Wees, Maarten Marx, Ridho Reinanda
2013 conf
DIR
Bart de Goede, Justin van Wees, Maarten Marx
2013 conf
KESW
Tatiana Tarasova, Massimo Argenti, Maarten Marx
2013 J jnl
J. Web Semant.
Steven Grijzenhout, Maarten Marx
2012 conf
CLEF (Online Working Notes/Labs/Workshop)
Qiuyue Wang, Jaap Kamps, Georgina Ramírez Camps, Maarten Marx, Anne Schuth, Martin Theobald, Sairam Gurajada, Arunav Mishra
2012 J jnl
SIGIR Forum
Patrice Bellot, Timothy Chappell, Antoine Doucet, Shlomo Geva, Jaap Kamps, Gabriella Kazai, Marijn Koolen, Monica Landoni, Maarten Marx, Véronique Moriceau, Josiane Mothe, Georgina Ramírez, Mark Sanderson, Eric SanJuan, Falk Scholer, Xavier Tannier, Martin Theobald, Matthew Trappett, Andrew Trotman, Qiuyue Wang
2012 J jnl
SIGIR Forum
Patrice Bellot, Timothy Chappell, Antoine Doucet, Shlomo Geva, Sairam Gurajada, Jaap Kamps, Gabriella Kazai, Marijn Koolen, Monica Landoni, Maarten Marx, Arunav Mishra, Véronique Moriceau, Josiane Mothe, Michael Preminger, Georgina Ramírez, Mark Sanderson, Eric SanJuan, Falk Scholer, A. Schuh, Xavier Tannier, Martin Theobald, Matthew Trappett, Andrew Trotman, Qiuyue Wang
2012 C conf
NLDB
Lars Buitinck, Maarten Marx
2011 J jnl
SIGMOD Rec.
Tom J. Ameloot, Maarten Marx, Wim Martens, Frank Neven, Justin van Wees
2011 J jnl
Int. J. Document Anal. Recognit.
Maarten Marx, Tim Gielissen
2011 conf
CLEF
Anne Schuth, Maarten Marx
2011 conf
INEX
Qiuyue Wang, Georgina Ramírez, Maarten Marx, Martin Theobald, Jaap Kamps
2011 conf
HT
Bart de Goede, Maarten Marx, Arjan Nusselder, Justin van Wees
2011 A conf
CIKM
Steven Grijzenhout, Maarten Marx
2011 conf
INEX
Anne Schuth, Maarten Marx
2010 J jnl
J. Appl. Log.
Balder ten Cate, Tadeusz Litak, Maarten Marx
2010 conf
DG.O
Maarten Marx, Nelleke Aders, Anne Schuth
2010 B conf
LREC
Maarten Marx, Anne Schuth
2010 J jnl
Inf. Retr.
Rianne Kaptein, Maarten Marx
2010 conf
ESAIR
Maarten Marx
2010 conf
WebDB
Maria-Hendrike Peetz, Maarten Marx
2010 conf
OKCon
Maarten Marx, Arjan Nusselder
2009 J jnl
J. Digit. Inf.
Maarten Marx
2009 J jnl
Theory Comput. Syst.
Balder ten Cate, Maarten Marx
2009 conf
AND
Tim Gielissen, Maarten Marx
2009 A conf
CIKM
Arjan Nusselder, Hendrike Peetz, Anne Schuth, Maarten Marx
2009 conf
Reasoning Web
Maarten Marx
2009 B conf
EDBT
Loredana Afanasiev, Torsten Grust, Maarten Marx, Jan Rittinger, Jens Teubner
2009 A* conf
SIGIR
Rianne Kaptein, Maarten Marx, Jaap Kamps
2008 A* conf
ICDE
Loredana Afanasiev, Torsten Grust, Maarten Marx, Jan Rittinger, Jens Teubner
2008 J jnl
Inf. Syst.
Loredana Afanasiev, Maarten Marx
2008 conf
AND
Valentin Jijkoun, Mahboob Alam Khalid, Maarten Marx, Maarten de Rijke
2008 conf
Description Logics
Maarten Marx
2007 J jnl
CoRR
Loredana Afanasiev, Torsten Grust, Maarten Marx, Jan Rittinger, Jens Teubner
2007 A conf
ICDT
Balder ten Cate, Maarten Marx
2007 ch.
Handbook of Modal Logic
Maarten Marx
2007 A* conf
WWW
Valentin Jijkoun, Maarten Marx, Maarten de Rijke, Frank van Waveren
2007 conf
WIDM
Anne Schuth, Maarten Marx, Maarten de Rijke
2007 book
Erich Grädel, Phokion G. Kolaitis, Leonid Libkin, Maarten Marx, Joel Spencer, Moshe Y. Vardi, Yde Venema, Scott Weinstein
2007 J jnl
SIGMOD Rec.
Balder ten Cate, Maarten Marx
2007 A* conf
PODS
Maarten Marx
2006 conf
ExpDB
Loredana Afanasiev, Maarten Marx
2006 J jnl
ACM Trans. Inf. Syst.
Jaap Kamps, Maarten Marx, Maarten de Rijke, Börkur Sigurbjörnsson
2006 A* conf
KR
Balder ten Cate, Willem Conradie, Maarten Marx, Yde Venema
2006 J jnl
Bull. EATCS
Maarten Marx
2006 A* conf
VLDB
Loredana Afanasiev, Massimo Franceschet, Maarten Marx, Enrico Zimuel
2005 J jnl
ACM Trans. Database Syst.
Maarten Marx
2005 A conf
ICDT
Maarten Marx
2005 J jnl
Log. J. IGPL
Balder ten Cate, Maarten Marx, Jorge Petrucio Viana
2005 A* conf
LICS
Evan Goris, Maarten Marx
2005 J jnl
J. Appl. Non Class. Logics
Loredana Afanasiev, Patrick Blackburn, Ioanna Dimitriou, Bertrand Gaiffe, Evan Goris, Maarten Marx, Maarten de Rijke
2005 J jnl
SIGMOD Rec.
Maarten Marx, Maarten de Rijke
2005 A conf
CIKM
Jaap Kamps, Maarten Marx, Maarten de Rijke, Börkur Sigurbjörnsson
2005 J jnl
J. Log. Lang. Inf.
Dirk Leinders, Maarten Marx, Jerzy Tyszkiewicz, Jan Van den Bussche
2005 B conf
CSL
Maarten Marx
2004 conf
WebDB
Jaap Kamps, Maarten Marx, Maarten de Rijke, Börkur Sigurbjörnsson
2004 C conf
TIME
Loredana Afanasiev, Massimo Franceschet, Maarten Marx, Maarten de Rijke
2004 A* conf
PODS
Maarten Marx
2004 Misc conf
ISWC
Willem Robert van Hage, Maarten de Rijke, Maarten Marx
2004 conf
TDM
Maarten Marx, Maarten de Rijke
2004 C conf
SACMAT
Irini Fundulaki, Maarten Marx
2004 B conf
LREC
Jaap Kamps, Maarten Marx, Robert J. Mokken, Maarten de Rijke
2004 B conf
EDBT
Maarten Marx
2003 J jnl
Stud Logica
Nick Bezhanishvili, Maarten Marx
2003 J jnl
J. Symb. Log.
Patrick Blackburn, Maarten Marx
2003 conf
Description Logics
Carlos Areces, Patrick Blackburn, Bernadette Martínez Hernández, Maarten Marx
2003 conf
SWDB
Irini Fundulaki, Maarten Marx
2003 J jnl
Soc. Networks
Maarten Marx, Michael Masuch
2003 J jnl
Ann. Pure Appl. Log.
Carlos Areces, Patrick Blackburn, Maarten Marx
2003 A* conf
SIGIR
Jaap Kamps, Maarten Marx, Maarten de Rijke, Börkur Sigurbjörnsson
2003 B conf
TABLEAUX
Maarten Marx
2002 J jnl
Stud Logica
Maarten Marx, Szabolcs Mikulás
2002 conf
EurAsia-ICT
Maarten Marx, Mehdi Dastani
2002 J jnl
Stud Logica
Maarten Marx
2002 J jnl
Stud Logica
Eva Hoogland, Maarten Marx
2002 conf
Description Logics
Maarten Marx
2002 Misc conf
ISWC
Jaap Kamps, Maarten Marx
2002 J jnl
J. Philos. Log.
Patrick Blackburn, Maarten Marx
2002 B conf
TABLEAUX
Patrick Blackburn, Maarten Marx
2002 conf
INEX Workshop
Jaap Kamps, Maarten Marx, Maarten de Rijke, Börkur Sigurbjörnsson
2001 J jnl
J. Symb. Log.
Carlos Areces, Patrick Blackburn, Maarten Marx
2001 J jnl
Log. J. IGPL
Maarten Marx, Szabolcs Mikulás
2001 J jnl
J. Log. Comput.
Maarten Marx
2001 Misc conf
EPIA
Patrick Blackburn, Jaap Kamps, Maarten Marx
2001 J jnl
Log. J. IGPL
Patrick Blackburn, Maarten Marx
2001 J jnl
J. Log. Lang. Inf.
Maarten Marx
2000 J jnl
Log. J. IGPL
Carlos Areces, Patrick Blackburn, Maarten Marx
2000 B conf
TABLEAUX
Maarten Marx, Szabolcs Mikulás, Mark Reynolds
1999 B conf
CSL
Carlos Areces, Patrick Blackburn, Maarten Marx
1999 B conf
LPAR
Eva Hoogland, Maarten Marx, Martin Otto
1999 J jnl
J. Log. Comput.
Maarten Marx
1999 J jnl
J. Symb. Log.
Maarten Marx, Szabolcs Mikulás
1999 J jnl
Inf. Sci.
Maarten Marx
1999 J jnl
Log. J. IGPL
Maarten Marx, Szabolcs Mikulás, Stefan Schlobach
1999 J jnl
J. Log. Lang. Inf.
Maarten Marx
1999 J jnl
J. Log. Comput.
Maarten Marx, Mark Reynolds
1999 J jnl
J. Symb. Log.
Szabolcs Mikulás, Maarten Marx
1998 J jnl
J. Symb. Log.
Maarten Marx
1998 J jnl
Notre Dame J. Formal Log.
Maarten Marx, Carlos Areces
1998 conf
AMAST
Maarten Marx
1997 book
Multi-dimensional modal logic.
Maarten Marx, Yde Venema
1997 conf
RelMiCS
Maarten Marx
1996 B conf
Advances in Modal Logic
Maarten Marx
1996 J jnl
Notre Dame J. Formal Log.
Maarten Marx
1995 J jnl
J. Log. Lang. Inf.
Maarten Marx, Szabolcs Mikulás, István Németi
tests/unit/test_decompile_modules.py
← Index tests/unit/test_decompile_modules.py python
"""
Unit tests for --decompile-modules flag.

Tests that:
1. CLI argument parsing and validation works correctly
2. decompile_modules is threaded through the entire call chain
3. BinaryNinjaDecompiler.analyze_binary() gates extractions per selected modules
4. DecompileBinja.prepare_export_data() filters DB tables per selected modules
"""
import os
import pytest
from unittest.mock import Mock, patch, MagicMock

pytestmark = [pytest.mark.unit]


# ============================================================================
# CLI Argument Parsing Tests
# ============================================================================

class TestDecompileModulesCliArgument:
    """Tests for --decompile-modules CLI argument parsing in start.py."""

    def _parse_args(self, args_list):
        """Helper to parse CLI args without running main()."""
        import argparse

        parser = argparse.ArgumentParser()
        input_group = parser.add_mutually_exclusive_group(required=True)
        input_group.add_argument("--path")
        input_group.add_argument("--s3", action="store_true")
        input_group.add_argument("--s3-solo", metavar="S3_KEY")
        input_group.add_argument("--nomad-job", action="store_true")
        input_group.add_argument("--date", metavar="YYYY-MM-DD")
        input_group.add_argument("--range", nargs=2)
        input_group.add_argument("--analyzed", action="store_true")

        parser.add_argument("--repo")
        parser.add_argument("--index_prefix", default="redb")
        parser.add_argument("-d", "--decompile", action="store_true")
        parser.add_argument("-m", "--modules", default="all")
        parser.add_argument("--decompile-modules", default="all")
        parser.add_argument("--force", action="store_true")
        parser.add_argument("--dry-run", action="store_true")

        return parser.parse_args(args_list)

    def test_decompile_modules_default_all(self):
        """--decompile-modules defaults to 'all'."""
        args = self._parse_args(["--path", "/tmp/test", "--repo", "test"])
        assert args.decompile_modules == "all"

    def test_decompile_modules_single(self):
        """--decompile-modules accepts a single module."""
        args = self._parse_args([
            "--path", "/tmp/test", "--repo", "test",
            "-d", "--decompile-modules", "strings"
        ])
        assert args.decompile_modules == "strings"

    def test_decompile_modules_comma_separated(self):
        """--decompile-modules accepts comma-separated modules."""
        args = self._parse_args([
            "--path", "/tmp/test", "--repo", "test",
            "-d", "--decompile-modules", "cfg,llil,strings"
        ])
        assert args.decompile_modules == "cfg,llil,strings"

    def test_decompile_modules_with_decompile_flag(self):
        """--decompile-modules works with -d flag."""
        args = self._parse_args([
            "--path", "/tmp/test", "--repo", "test",
            "-d", "--decompile-modules", "disassembly"
        ])
        assert args.decompile is True
        assert args.decompile_modules == "disassembly"


# ============================================================================
# Validation Tests
# ============================================================================

class TestDecompileModulesValidation:
    """Tests for --decompile-modules validation logic in start.py."""

    def test_valid_modules_set_parsing(self):
        """Valid comma-separated modules are parsed into a set."""
        VALID = {"all", "decompilation", "disassembly", "cfg", "llil", "strings"}
        raw = "cfg,llil,strings"
        modules_set = {m.strip() for m in raw.split(",")}
        invalid = modules_set - VALID
        assert invalid == set()
        assert modules_set == {"cfg", "llil", "strings"}

    def test_invalid_module_detected(self):
        """Invalid module names are detected."""
        VALID = {"all", "decompilation", "disassembly", "cfg", "llil", "strings"}
        raw = "cfg,invalid_module"
        modules_set = {m.strip() for m in raw.split(",")}
        invalid = modules_set - VALID
        assert invalid == {"invalid_module"}

    def test_all_keyword_as_set(self):
        """'all' is parsed into {'all'} set."""
        raw = "all"
        if raw == "all":
            modules_set = {"all"}
        assert modules_set == {"all"}

    def test_whitespace_handling(self):
        """Whitespace around module names is stripped."""
        raw = " cfg , llil , strings "
        modules_set = {m.strip() for m in raw.split(",")}
        assert modules_set == {"cfg", "llil", "strings"}


# ============================================================================
# Ingestor Parameter Storage Tests
# ============================================================================

class TestIngestorDecompileModules:
    """Tests that Ingestor stores decompile_modules correctly."""

    @patch('builtins.open', MagicMock())
    @patch('redb.ingestor.multiprocessing')
    @patch('redb.ingestor.datetime')
    def test_ingestor_stores_decompile_modules(self, mock_datetime, mock_mp):
        """Ingestor stores the decompile_modules set."""
        mock_mp.Manager.return_value.dict = Mock(side_effect=[{}, {}])
        mock_datetime.today.return_value.strftime.return_value = "20260222"

        from redb.ingestor import Ingestor
        ingestor = Ingestor(
            path="/tmp/test", repository="test", index_prefix="redb",
            decompile_modules={"cfg", "strings"}
        )
        assert ingestor.decompile_modules == {"cfg", "strings"}

    @patch('builtins.open', MagicMock())
    @patch('redb.ingestor.multiprocessing')
    @patch('redb.ingestor.datetime')
    def test_ingestor_defaults_to_all(self, mock_datetime, mock_mp):
        """Ingestor defaults decompile_modules to {'all'} when not provided."""
        mock_mp.Manager.return_value.dict = Mock(side_effect=[{}, {}])
        mock_datetime.today.return_value.strftime.return_value = "20260222"

        from redb.ingestor import Ingestor
        ingestor = Ingestor(
            path="/tmp/test", repository="test", index_prefix="redb"
        )
        assert ingestor.decompile_modules == {"all"}

    @patch('builtins.open', MagicMock())
    @patch('redb.ingestor.multiprocessing')
    @patch('redb.ingestor.datetime')
    def test_ingestor_none_defaults_to_all(self, mock_datetime, mock_mp):
        """Ingestor converts None decompile_modules to {'all'}."""
        mock_mp.Manager.return_value.dict = Mock(side_effect=[{}, {}])
        mock_datetime.today.return_value.strftime.return_value = "20260222"

        from redb.ingestor import Ingestor
        ingestor = Ingestor(
            path="/tmp/test", repository="test", index_prefix="redb",
            decompile_modules=None
        )
        assert ingestor.decompile_modules == {"all"}


# ============================================================================
# Function Chain Threading Tests
# ============================================================================

class TestDecompileModulesThreading:
    """Tests that decompile_modules is correctly threaded through the call chain."""

    @patch('redb.workers.process_binary_file')
    @patch('redb.workers.is_binary_file', return_value=True)
    def test_process_file_internal_passes_decompile_modules(self, mock_is_binary, mock_process):
        """_process_file_internal passes decompile_modules to process_binary_file."""
        from redb.ingestor import _process_file_internal

        mock_process.return_value = (Mock(), "pebin")
        logger = Mock()
        modules = {"cfg", "strings"}

        _process_file_internal(
            "/tmp/test.bin", False, "redb", logger,
            decompile_modules=modules
        )

        mock_process.assert_called_once()
        call_kwargs = mock_process.call_args[1]
        assert call_kwargs["decompile_modules"] == modules

    @patch('redb.workers.process_binary_file')
    @patch('redb.workers.is_binary_file', return_value=True)
    def test_process_file_internal_default_decompile_modules_none(self, mock_is_binary, mock_process):
        """_process_file_internal defaults decompile_modules to None."""
        from redb.ingestor import _process_file_internal

        mock_process.return_value = (Mock(), "pebin")
        logger = Mock()

        _process_file_internal("/tmp/test.bin", False, "redb", logger)

        call_kwargs = mock_process.call_args[1]
        assert call_kwargs["decompile_modules"] is None

    @patch('redb.workers._process_file_internal')
    def test_process_file_passes_decompile_modules(self, mock_internal):
        """process_file passes decompile_modules to _process_file_internal."""
        from redb.workers import process_file

        mock_internal.return_value = (Mock(), "pebin")
        modules = {"llil", "disassembly"}

        with patch('redb.workers.setup_direct_logger', return_value=Mock()):
            process_file(
                "/tmp/test.bin", False, "redb", "/tmp/log", 1, 1,
                decompile_modules=modules
            )

        mock_internal.assert_called_once()
        call_kwargs = mock_internal.call_args[1]
        assert call_kwargs["decompile_modules"] == modules

    @patch('redb.workers._process_file_internal')
    def test_process_s3_file_passes_decompile_modules(self, mock_internal):
        """process_s3_file passes decompile_modules to _process_file_internal."""
        from redb.workers import process_s3_file

        mock_internal.return_value = (Mock(), "pebin")
        modules = {"cfg"}

        with patch('redb.workers.setup_direct_logger', return_value=Mock()), \
             patch('redb.workers.download_s3_object', return_value="/tmp/downloaded.bin"):
            process_s3_file(
                "bucket", "key", "/tmp", False, "redb", "/tmp/log", 1, 1,
                decompile_modules=modules
            )

        mock_internal.assert_called_once()
        call_kwargs = mock_internal.call_args[1]
        assert call_kwargs["decompile_modules"] == modules

    @patch('redb.workers.process_binary_file')
    def test_process_zip_passes_decompile_modules(self, mock_process):
        """process_zip_file passes decompile_modules to process_binary_file."""
        from redb.workers import process_zip_file

        mock_process.return_value = (Mock(), "pebin")
        logger = Mock()
        modules = {"strings", "decompilation"}

        import tempfile
        import zipfile

        with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp:
            tmp_path = tmp.name

        try:
            with zipfile.ZipFile(tmp_path, 'w') as zf:
                zf.writestr("test.bin", b"\x00" * 100)

            process_zip_file(
                tmp_path, False, "redb", logger, "all",
                decompile_modules=modules
            )

            mock_process.assert_called_once()
            call_kwargs = mock_process.call_args[1]
            assert call_kwargs["decompile_modules"] == modules
        finally:
            os.unlink(tmp_path)

    @patch('redb.workers.process_binary_file')
    def test_process_7zip_passes_decompile_modules(self, mock_process):
        """process_7zip_file passes decompile_modules to process_binary_file."""
        from redb.workers import process_7zip_file

        mock_process.return_value = (Mock(), "pebin")
        logger = Mock()
        modules = {"cfg", "llil"}

        with patch('redb.workers.py7zr') as mock_py7zr:
            mock_zf = Mock()
            mock_py7zr.SevenZipFile.return_value.__enter__ = Mock(return_value=mock_zf)
            mock_py7zr.SevenZipFile.return_value.__exit__ = Mock(return_value=False)

            def fake_extractall(path):
                dest = os.path.join(path, "test.bin")
                with open(dest, "wb") as f:
                    f.write(b"\x00" * 100)

            mock_zf.extractall = fake_extractall

            process_7zip_file(
                "/tmp/test.7z", False, "redb", logger, "all",
                decompile_modules=modules
            )

            if mock_process.called:
                call_kwargs = mock_process.call_args[1]
                assert call_kwargs["decompile_modules"] == modules

    def test_worker_unpacks_decompile_modules(self):
        """worker() correctly unpacks decompile_modules from args tuple."""
        from redb.workers import worker

        modules = {"strings", "cfg"}

        with patch('redb.workers.process_file') as mock_pf, \
             patch('redb.workers.signal'), \
             patch('redb.workers.gc'):
            mock_pf.return_value = (Mock(), "pebin")

            args = (
                "/tmp/test.bin",  # filepath
                True,             # decompile
                "redb",           # index_prefix
                "/tmp/log",       # log_file
                1,                # file_number
                10,               # total_files
                "all",            # selected_modules
                False,            # dry_run
                False,            # yara_scan
                False,            # with_yara
                False,            # force
                modules,          # decompile_modules
            )

            worker(args)

            mock_pf.assert_called_once()
            call_kwargs = mock_pf.call_args[1]
            assert call_kwargs["decompile_modules"] == modules


# ============================================================================
# BinaryNinjaDecompiler._module_selected() Tests
# ============================================================================

class TestModuleSelected:
    """Tests for BinaryNinjaDecompiler._module_selected() helper."""

    def _make_decompiler(self, decompile_modules):
        """Create a minimal BinaryNinjaDecompiler-like object for testing _module_selected."""

        class FakeDecompiler:
            def __init__(self, modules):
                self.decompile_modules = modules

            def _module_selected(self, module_name):
                return "all" in self.decompile_modules or module_name in self.decompile_modules

        return FakeDecompiler(decompile_modules)

    def test_all_selects_everything(self):
        d = self._make_decompiler({"all"})
        assert d._module_selected("strings") is True
        assert d._module_selected("decompilation") is True
        assert d._module_selected("disassembly") is True
        assert d._module_selected("cfg") is True
        assert d._module_selected("llil") is True

    def test_specific_module_selected(self):
        d = self._make_decompiler({"cfg", "strings"})
        assert d._module_selected("cfg") is True
        assert d._module_selected("strings") is True
        assert d._module_selected("decompilation") is False
        assert d._module_selected("disassembly") is False
        assert d._module_selected("llil") is False

    def test_empty_set_selects_nothing(self):
        d = self._make_decompiler(set())
        assert d._module_selected("strings") is False
        assert d._module_selected("decompilation") is False

    def test_single_module(self):
        d = self._make_decompiler({"llil"})
        assert d._module_selected("llil") is True
        assert d._module_selected("cfg") is False
        assert d._module_selected("strings") is False


# ============================================================================
# analyze_binary() Selective Execution Tests
# ============================================================================

class TestAnalyzeBinaryGating:
    """Tests that analyze_binary() gates extraction per decompile_modules.

    Uses a FakeDecompiler to test the gating logic without importing binaryninja.
    """

    def _make_analyze_binary_runner(self, decompile_modules):
        """Create a test harness that simulates the gating logic of analyze_binary()."""
        run_all = "all" in decompile_modules
        run_strings = run_all or "strings" in decompile_modules
        run_decompilation = run_all or "decompilation" in decompile_modules
        run_disassembly = run_all or "disassembly" in decompile_modules
        run_llil = run_all or "llil" in decompile_modules
        run_cfg = run_all or "cfg" in decompile_modules
        need_per_function = run_decompilation or run_disassembly or run_llil or run_cfg

        return {
            "run_strings": run_strings,
            "run_decompilation": run_decompilation,
            "run_disassembly": run_disassembly,
            "run_llil": run_llil,
            "run_cfg": run_cfg,
            "need_per_function": need_per_function,
        }

    def test_all_runs_everything(self):
        r = self._make_analyze_binary_runner({"all"})
        assert r["run_strings"] is True
        assert r["run_decompilation"] is True
        assert r["run_disassembly"] is True
        assert r["run_llil"] is True
        assert r["run_cfg"] is True
        assert r["need_per_function"] is True

    def test_strings_only_skips_per_function_loop(self):
        """Selecting only 'strings' should skip the per-function loop entirely."""
        r = self._make_analyze_binary_runner({"strings"})
        assert r["run_strings"] is True
        assert r["run_decompilation"] is False
        assert r["run_disassembly"] is False
        assert r["run_llil"] is False
        assert r["run_cfg"] is False
        assert r["need_per_function"] is False

    def test_cfg_only_runs_cfg_and_disassembly_in_loop(self):
        """Selecting only 'cfg' should run the per-function loop but skip decompilation/llil/strings."""
        r = self._make_analyze_binary_runner({"cfg"})
        assert r["run_strings"] is False
        assert r["run_decompilation"] is False
        assert r["run_disassembly"] is False  # Not selected (but backbone runs anyway in real code)
        assert r["run_llil"] is False
        assert r["run_cfg"] is True
        assert r["need_per_function"] is True

    def test_decompilation_only(self):
        r = self._make_analyze_binary_runner({"decompilation"})
        assert r["run_decompilation"] is True
        assert r["run_strings"] is False
        assert r["run_cfg"] is False
        assert r["run_llil"] is False
        assert r["need_per_function"] is True

    def test_multiple_modules(self):
        r = self._make_analyze_binary_runner({"cfg", "llil", "strings"})
        assert r["run_strings"] is True
        assert r["run_cfg"] is True
        assert r["run_llil"] is True
        assert r["run_decompilation"] is False
        assert r["run_disassembly"] is False
        assert r["need_per_function"] is True

    def test_disassembly_only(self):
        r = self._make_analyze_binary_runner({"disassembly"})
        assert r["run_disassembly"] is True
        assert r["run_decompilation"] is False
        assert r["run_strings"] is False
        assert r["run_cfg"] is False
        assert r["run_llil"] is False
        assert r["need_per_function"] is True


# ============================================================================
# prepare_export_data() Table Filtering Tests
# ============================================================================

class TestPrepareExportDataFiltering:
    """Tests that prepare_export_data() only includes tables for selected modules.

    Simulates the filtering logic from DecompileBinja.prepare_export_data()
    without importing binaryninja.
    """

    def _get_export_tables(self, decompile_modules):
        """Simulate the table selection logic from prepare_export_data()."""
        run_all = "all" in decompile_modules
        run_decompilation = run_all or "decompilation" in decompile_modules
        run_disassembly = run_all or "disassembly" in decompile_modules
        run_llil = run_all or "llil" in decompile_modules
        run_cfg = run_all or "cfg" in decompile_modules
        run_strings = run_all or "strings" in decompile_modules

        tables = set()
        if run_decompilation:
            tables.update(["decompiled_content", "decompiled_refs"])
        if run_disassembly:
            tables.update(["disassembled_content", "disassembled_refs", "function_similarity_metrics"])
        if run_llil:
            tables.update(["llil_content", "llil_refs"])
        if run_cfg:
            tables.update(["cfg_functions"])
        if run_strings:
            tables.update(["strings_raw"])
        # Errors always included if per-function loop ran
        if run_decompilation or run_disassembly or run_llil or run_cfg:
            tables.add("function_analysis_errors")

        return tables

    def test_all_includes_all_tables(self):
        tables = self._get_export_tables({"all"})
        assert "decompiled_content" in tables
        assert "decompiled_refs" in tables
        assert "disassembled_content" in tables
        assert "disassembled_refs" in tables
        assert "function_similarity_metrics" in tables
        assert "llil_content" in tables
        assert "llil_refs" in tables
        assert "cfg_functions" in tables
        assert "strings_raw" in tables
        assert "function_analysis_errors" in tables

    def test_strings_only_includes_only_strings(self):
        tables = self._get_export_tables({"strings"})
        assert tables == {"strings_raw"}

    def test_cfg_only_includes_cfg_and_errors(self):
        tables = self._get_export_tables({"cfg"})
        assert tables == {"cfg_functions", "function_analysis_errors"}

    def test_decompilation_only_tables(self):
        tables = self._get_export_tables({"decompilation"})
        assert tables == {"decompiled_content", "decompiled_refs", "function_analysis_errors"}

    def test_disassembly_only_tables(self):
        tables = self._get_export_tables({"disassembly"})
        assert tables == {
            "disassembled_content", "disassembled_refs",
            "function_similarity_metrics", "function_analysis_errors"
        }

    def test_llil_only_tables(self):
        tables = self._get_export_tables({"llil"})
        assert tables == {"llil_content", "llil_refs", "function_analysis_errors"}

    def test_combined_cfg_strings_tables(self):
        tables = self._get_export_tables({"cfg", "strings"})
        assert tables == {"cfg_functions", "strings_raw", "function_analysis_errors"}

    def test_no_errors_table_for_strings_only(self):
        """Error table should NOT be included when only strings is selected (no per-function loop)."""
        tables = self._get_export_tables({"strings"})
        assert "function_analysis_errors" not in tables


# ============================================================================
# DecompileBinja Parameter Acceptance Tests
# ============================================================================

class TestDecompileBinjaDecompileModules:
    """Tests that DecompileBinja accepts and stores decompile_modules.

    Since DecompileBinja imports binaryninja (which requires blake3 etc),
    we test the default/init logic in isolation.
    """

    def test_default_decompile_modules(self):
        """decompile_modules defaults to {'all'} when None."""
        modules = None
        result = modules or {"all"}
        assert result == {"all"}

    def test_explicit_decompile_modules(self):
        """Explicit decompile_modules is preserved."""
        modules = {"cfg", "llil"}
        result = modules or {"all"}
        assert result == {"cfg", "llil"}

    def test_empty_set_becomes_all(self):
        """Empty set is falsy and defaults to {'all'}."""
        modules = set()
        result = modules or {"all"}
        assert result == {"all"}


# ============================================================================
# Integration-Style: analyze_binary() Per-Function Gating (Logic Tests)
# ============================================================================

class TestAnalyzeBinaryPerFunctionGating:
    """Test that the per-function extraction gating produces correct results structures.

    These test the logic patterns from analyze_binary() using mock data,
    verifying that:
    - Unselected modules produce empty result lists
    - Selected modules produce populated result lists
    - Disassembly always runs when any per-function module is selected
    - Cross-linkage None handling works when counterparts are skipped
    """

    def _simulate_per_function_results(self, decompile_modules, num_functions=3):
        """Simulate the per-function extraction loop from analyze_binary().

        Returns results dict with the same structure as analyze_binary().
        """
        run_all = "all" in decompile_modules
        run_strings = run_all or "strings" in decompile_modules
        run_decompilation = run_all or "decompilation" in decompile_modules
        run_disassembly = run_all or "disassembly" in decompile_modules
        run_llil = run_all or "llil" in decompile_modules
        run_cfg = run_all or "cfg" in decompile_modules
        need_per_function = run_decompilation or run_disassembly or run_llil or run_cfg

        results = {
            "decompiled": [],
            "disassembled": [],
            "cfg": [],
            "llil": [],
            "errors": [],
            "strings": [],
        }

        if run_strings:
            results["strings"] = [{"string": f"str_{i}"} for i in range(5)]

        if not need_per_function:
            return results

        for i in range(num_functions):
            hlil_json = {"decompiled_function_hash": f"hlil_{i}"} if run_decompilation else None
            disass_json = {"disassembled_function_hash": f"disass_{i}", "disassembled_function_name": f"func_{i}", "disassembled_function_address": i * 100}  # always
            cfg_json = {"function_address": i * 100, "cyclomatic_complexity": 5} if run_cfg else None
            lowlevel_json = {"sha256_llil": f"llil_{i}", "tlsh_llil": "def", "minhash": [1, 2]} if run_llil else None

            # Simulate cross-linkage (same pattern as real code)
            if hlil_json and disass_json:
                hlil_json["disassembled_function_hash"] = disass_json["disassembled_function_hash"]
                disass_json["decompiled_function_hash"] = hlil_json["decompiled_function_hash"]
                results["decompiled"].append(hlil_json)
                if run_disassembly:
                    results["disassembled"].append(disass_json)
            elif hlil_json:
                hlil_json["disassembled_function_hash"] = None
                results["decompiled"].append(hlil_json)
            elif disass_json:
                disass_json["decompiled_function_hash"] = None
                if run_disassembly:
                    results["disassembled"].append(disass_json)

            if lowlevel_json and disass_json:
                lowlevel_json["disassembled_function_hash"] = disass_json["disassembled_function_hash"]
                disass_json["tlsh_llil"] = lowlevel_json.get("tlsh_llil")
                disass_json["minhash"] = lowlevel_json.get("minhash")
            elif lowlevel_json:
                lowlevel_json["disassembled_function_hash"] = None
            elif disass_json:
                disass_json["tlsh_llil"] = None
                disass_json["minhash"] = None

            if lowlevel_json:
                results["llil"].append(lowlevel_json)

            if cfg_json and disass_json:
                cfg_json["disassembled_function_hash"] = disass_json["disassembled_function_hash"]
                disass_json["cyclomatic_complexity"] = cfg_json.get("cyclomatic_complexity")
                results["cfg"].append(cfg_json)
            elif cfg_json:
                cfg_json["disassembled_function_hash"] = None
                results["cfg"].append(cfg_json)

            if disass_json and "cyclomatic_complexity" not in disass_json:
                disass_json["cyclomatic_complexity"] = None

        return results

    def test_all_populates_everything(self):
        r = self._simulate_per_function_results({"all"}, 3)
        assert len(r["decompiled"]) == 3
        assert len(r["disassembled"]) == 3
        assert len(r["cfg"]) == 3
        assert len(r["llil"]) == 3
        assert len(r["strings"]) == 5

    def test_strings_only_populates_strings(self):
        r = self._simulate_per_function_results({"strings"}, 3)
        assert len(r["strings"]) == 5
        assert len(r["decompiled"]) == 0
        assert len(r["disassembled"]) == 0
        assert len(r["cfg"]) == 0
        assert len(r["llil"]) == 0

    def test_cfg_only_populates_cfg_no_disassembly_export(self):
        """With only 'cfg' selected, cfg list is populated but disassembled is empty
        (disassembly runs internally but not exported)."""
        r = self._simulate_per_function_results({"cfg"}, 3)
        assert len(r["cfg"]) == 3
        assert len(r["disassembled"]) == 0  # Not selected for export
        assert len(r["decompiled"]) == 0
        assert len(r["llil"]) == 0
        assert len(r["strings"]) == 0

    def test_cfg_has_disassembled_function_hash_linkage(self):
        """CFG entries get disassembled_function_hash from backbone disassembly."""
        r = self._simulate_per_function_results({"cfg"}, 2)
        for cfg in r["cfg"]:
            assert cfg["disassembled_function_hash"] is not None
            assert cfg["disassembled_function_hash"].startswith("disass_")

    def test_decompilation_only_has_none_disassembly_linkage_when_no_disasm(self):
        """When only decompilation selected, disassembled still runs (backbone)
        so linkage is still available."""
        r = self._simulate_per_function_results({"decompilation"}, 2)
        assert len(r["decompiled"]) == 2
        # Disassembly runs as backbone, so linkage hash is present
        for d in r["decompiled"]:
            assert d["disassembled_function_hash"] is not None

    def test_disassembly_without_llil_has_none_fuzzy_hashes(self):
        """When LLIL is not selected, disassembly records have None fuzzy hashes."""
        r = self._simulate_per_function_results({"disassembly"}, 2)
        for d in r["disassembled"]:
            assert d["tlsh_llil"] is None
            assert d["minhash"] is None

    def test_disassembly_without_decompilation_has_none_decompiled_hash(self):
        """When decompilation is not selected, disassembly has None decompiled_function_hash."""
        r = self._simulate_per_function_results({"disassembly"}, 2)
        for d in r["disassembled"]:
            assert d["decompiled_function_hash"] is None

    def test_disassembly_without_cfg_has_none_cyclomatic(self):
        """When CFG is not selected, disassembly has None cyclomatic_complexity."""
        r = self._simulate_per_function_results({"disassembly"}, 2)
        for d in r["disassembled"]:
            assert d["cyclomatic_complexity"] is None

    def test_all_linkage_populated(self):
        """When all modules run, all cross-linkage fields are populated."""
        r = self._simulate_per_function_results({"all"}, 2)
        for d in r["disassembled"]:
            assert d["decompiled_function_hash"] is not None
            assert d["tlsh_llil"] is not None
            assert d["cyclomatic_complexity"] is not None
        for h in r["decompiled"]:
            assert h["disassembled_function_hash"] is not None
        for c in r["cfg"]:
            assert c["disassembled_function_hash"] is not None
        for ll in r["llil"]:
            assert ll["disassembled_function_hash"] is not None


# ============================================================================
# IOC Extraction Gating by decompile_modules
# ============================================================================

class TestIOCExtractionGating:
    """Tests that IOC extraction only runs when its data sources are present.

    IOCExtractorFromResults consumes in-memory analysis_results['strings']
    and analysis_results['decompiled']. It runs automatically (implicit
    dependency) when 'decompilation' or 'strings' modules produce data.
    It is NOT a standalone selectable module.
    """

    @staticmethod
    def _is_ioc_relevant(decompile_modules):
        """Replicate the gating logic from workers.py."""
        return (
            not decompile_modules
            or "all" in decompile_modules
            or "decompilation" in decompile_modules
            or "strings" in decompile_modules
        )

    def test_all_triggers_ioc(self):
        assert self._is_ioc_relevant({"all"}) is True

    def test_decompilation_triggers_ioc(self):
        assert self._is_ioc_relevant({"decompilation"}) is True

    def test_strings_triggers_ioc(self):
        assert self._is_ioc_relevant({"strings"}) is True

    def test_decompilation_and_strings_triggers_ioc(self):
        assert self._is_ioc_relevant({"decompilation", "strings"}) is True

    def test_cfg_only_skips_ioc(self):
        assert self._is_ioc_relevant({"cfg"}) is False

    def test_disassembly_only_skips_ioc(self):
        assert self._is_ioc_relevant({"disassembly"}) is False

    def test_llil_only_skips_ioc(self):
        assert self._is_ioc_relevant({"llil"}) is False

    def test_cfg_and_disassembly_skips_ioc(self):
        assert self._is_ioc_relevant({"cfg", "disassembly"}) is False

    def test_cfg_and_llil_skips_ioc(self):
        assert self._is_ioc_relevant({"cfg", "llil"}) is False

    def test_cfg_and_decompilation_triggers_ioc(self):
        """If decompilation is included alongside cfg, IOC should run."""
        assert self._is_ioc_relevant({"cfg", "decompilation"}) is True

    def test_disassembly_and_strings_triggers_ioc(self):
        """If strings is included alongside disassembly, IOC should run."""
        assert self._is_ioc_relevant({"disassembly", "strings"}) is True

    def test_none_modules_triggers_ioc(self):
        """When decompile_modules is None (default), IOC should run."""
        assert self._is_ioc_relevant(None) is True

    def test_empty_set_triggers_ioc(self):
        """When decompile_modules is empty set, IOC should run (falsy)."""
        assert self._is_ioc_relevant(set()) is True