Wei Lu

169 papers A* 37A 2B 1Journal 68Unranked 58
YearRankTypeTitle / Venue / Authors
2025 J jnl
IEEE Trans. Neural Networks Learn. Syst.
Pengzhou Cheng, Zongru Wu, Wei Du, Haodong Zhao, Wei Lu, Gongshen Liu
2025 J jnl
CoRR
Renjie Luo, Zichen Liu, Xiangyan Liu, Chao Du, Min Lin, Wenhu Chen, Wei Lu, Tianyu Pang
2025 J jnl
CoRR
Sicong Leng, Jing Wang, Jiaxi Li, Hao Zhang, Zhiqiang Hu, Boqiang Zhang, Yuming Jiang, Hang Zhang, Xin Li, Lidong Bing, Deli Zhao, Wei Lu, Yu Rong, Aixin Sun, Shijian Lu
2025 A conf
CIKM
Hongcheng Guo, Zheyong Xie, Shaosheng Cao, Boyang Wang, Weiting Liu, Zheyu Ye, Zhoujun Li, Zuozhu Liu, Wei Lu
2025 J jnl
CoRR
Longxu Dou, Qian Liu, Fan Zhou, Changyu Chen, Zili Wang, Ziqi Jin, Zichen Liu, Tongyao Zhu, Cunxiao Du, Penghui Yang, Haonan Wang, Jiaheng Liu, Yongchi Zhao, Xiachong Feng, Xin Mao, Man Tsung Yeung, Kunat Pipatanakul, Fajri Koto, Min Si Thu, Hynek Kydlícek, Zeyi Liu, Qunshu Lin, Sittipong Sripaisarnmongkol, Kridtaphad Sae-Khow, Nirattisai Thongchim, Taechawat Konkaew, Narong Borijindargoon, Anh Dao, Matichon Maneegard, Phakphum Artkaew, Zheng-Xin Yong, Quan Nguyen, Wannaphong Phatthiyaphaibun, Hoang H. Tran, Mike Zhang, Shiqi Chen, Tianyu Pang, Chao Du, Xinyi Wan, Wei Lu, Min Lin
2025 conf
NAACL (Long Papers)
Ziqi Jin, Wei Lu
2025 J jnl
Trans. Mach. Learn. Res.
Haoran Li, Qingxiu Dong, Zhengyang Tang, Chaojun Wang, Xingxing Zhang, Haoyang Huang, Shaohan Huang, Xiaolong Huang, Zeqiang Huang, Dongdong Zhang, Yuxian Gu, Xin Cheng, Xun Wang, Si-Qing Chen, Li Dong, Wei Lu, Zhifang Sui, Benyou Wang, Wai Lam, Furu Wei
2025 J jnl
CoRR
Jiaxi Li, Xingxing Zhang, Xun Wang, Xiaolong Huang, Li Dong, Liang Wang, Si-Qing Chen, Wei Lu, Furu Wei
2025 conf
ACL (3)
Zheyong Xie, Shaosheng Cao, Zuozhu Liu, Zheyu Ye, Zihan Niu, Chonggang Lu, Tong Xu, Enhong Chen, Zhe Xu, Yao Hu, Wei Lu
2024 A* conf
CVPR
Mohammed Haroon Dupty, Yanfei Dong, Sicong Leng, Guoji Fu, Yong Liang Goh, Wei Lu, Wee Sun Lee
2024 J jnl
CoRR
Mohammed Haroon Dupty, Yanfei Dong, Sicong Leng, Guoji Fu, Yong Liang Goh, Wei Lu, Wee Sun Lee
2024 J jnl
CoRR
Aditya Desu, Xuanli He, Qiongkai Xu, Wei Lu
2024 J jnl
Trans. Mach. Learn. Res.
Aditya Desu, Xuanli He, Qiongkai Xu, Wei Lu
2024 conf
EMNLP (System Demonstrations)
Longxu Dou, Qian Liu, Guangtao Zeng, Jia Guo, Jiahui Zhou, Xin Mao, Ziqi Jin, Wei Lu, Min Lin
2024 J jnl
CoRR
Longxu Dou, Qian Liu, Guangtao Zeng, Jia Guo, Jiahui Zhou, Wei Lu, Min Lin
2024 J jnl
CoRR
Ziqi Jin, Wei Lu
2024 J jnl
CoRR
Haoran Li, Qingxiu Dong, Zhengyang Tang, Chaojun Wang, Xingxing Zhang, Haoyang Huang, Shaohan Huang, Xiaolong Huang, Zeqiang Huang, Dongdong Zhang, Yuxian Gu, Xin Cheng, Xun Wang, Si-Qing Chen, Li Dong, Wei Lu, Zhifang Sui, Benyou Wang, Wai Lam, Furu Wei
2023 J jnl
CoRR
Lu Xu, Lidong Bing, Wei Lu
2023 conf
ACL (1)
Jiaxi Li, Wei Lu
2023 J jnl
CoRR
Jiaxi Li, Wei Lu
2023 conf
EMNLP (Findings)
Yao Xiao, Lu Xu, Jiaxi Li, Wei Lu, Xiaoli Li
2023 J jnl
CoRR
Yao Xiao, Lu Xu, Jiaxi Li, Wei Lu, Xiaoli Li
2023 conf
DASFAA (4)
Ang Li, Jian Hu, Wei Lu, Ke Ding, Xiaolu Zhang, Jun Zhou, Yong He, Liang Zhang, Lihong Gu
2023 conf
ACL (2)
Tianduo Wang, Wei Lu
2023 J jnl
CoRR
Tianduo Wang, Wei Lu
2023 conf
ACL (Findings)
Zhanming Jie, Wei Lu
2023 J jnl
CoRR
Zhanming Jie, Wei Lu
2023 conf
ACL (1)
Guangtao Zeng, Peiyuan Zhang, Wei Lu
2023 J jnl
CoRR
Guangtao Zeng, Peiyuan Zhang, Wei Lu
2023 J jnl
CoRR
Guoxiu He, Aixin Sun, Wei Lu
2023 conf
ACL (Findings)
Lu Xu, Lidong Bing, Wei Lu
2023 conf
ACL (Findings)
Ziqi Jin, Wei Lu
2023 J jnl
CoRR
Ziqi Jin, Wei Lu
2023 conf
ACL (1)
Sicong Leng, Yang Zhou, Mohammed Haroon Dupty, Wee Sun Lee, Sam Joyce, Wei Lu
2023 J jnl
CoRR
Sicong Leng, Yang Zhou, Mohammed Haroon Dupty, Wee Sun Lee, Sam Conrad Joyce, Wei Lu
2023 J jnl
IEEE Trans. Knowl. Data Eng.
Jiale Han, Bo Cheng, Zhiguo Wan, Wei Lu
2023 conf
EMNLP (Findings)
Haoran Li, Yiran Liu, Xingxing Zhang, Wei Lu, Furu Wei
2023 J jnl
CoRR
Haoran Li, Yiran Liu, Xingxing Zhang, Wei Lu, Furu Wei
2023 A* conf
EMNLP
Xiaobing Sun, Jiaxi Li, Wei Lu
2023 J jnl
CoRR
Xiaobing Sun, Jiaxi Li, Wei Lu
2022 A* conf
EMNLP
Peiyuan Zhang, Wei Lu
2022 J jnl
CoRR
Peiyuan Zhang, Wei Lu
2022 conf
SemEval@NAACL
Xinyu Wang, Yongliang Shen, Jiong Cai, Tao Wang, Xiaobin Wang, Pengjun Xie, Fei Huang, Weiming Lu, Yueting Zhuang, Kewei Tu, Wei Lu, Yong Jiang
2022 J jnl
CoRR
Xinyu Wang, Yongliang Shen, Jiong Cai, Tao Wang, Xiaobin Wang, Pengjun Xie, Fei Huang, Weiming Lu, Yueting Zhuang, Kewei Tu, Wei Lu, Yong Jiang
2022 A* conf
EMNLP
Tianduo Wang, Wei Lu
2022 J jnl
CoRR
Tianduo Wang, Wei Lu
2022 conf
EMNLP (Findings)
Jiale Han, Shuai Zhao, Bo Cheng, Shengkun Ma, Wei Lu
2022 J jnl
CoRR
Jiale Han, Shuai Zhao, Bo Cheng, Shengkun Ma, Wei Lu
2022 conf
NAACL-HLT
Xiaobing Sun, Wei Lu
2022 conf
ACL (1)
Zhanming Jie, Jierui Li, Wei Lu
2022 J jnl
CoRR
Zhanming Jie, Jierui Li, Wei Lu
2022 conf
EMNLP (Findings)
Xinyu Wang, Jiong Cai, Yong Jiang, Pengjun Xie, Kewei Tu, Wei Lu
2022 J jnl
CoRR
Xinyu Wang, Jiong Cai, Yong Jiang, Pengjun Xie, Kewei Tu, Wei Lu
2022 ed.
NLPCC (1)
Wei Lu, Shujian Huang, Yu Hong, Xiabing Zhou
2022 ed.
NLPCC (2)
Wei Lu, Shujian Huang, Yu Hong, Xiabing Zhou
2022 A* conf
EMNLP
Guangtao Zeng, Wei Lu
2022 J jnl
CoRR
Guangtao Zeng, Wei Lu
2021 conf
NAACL-HLT
Lu Xu, Zhanming Jie, Wei Lu, Lidong Bing
2021 J jnl
CoRR
Lu Xu, Zhanming Jie, Wei Lu, Lidong Bing
2021 conf
ACL/IJCNLP (Findings)
Hao Zhou, Weidong Ren, Gongshen Liu, Bo Su, Wei Lu
2021 conf
EMNLP (1)
Jiale Han, Bo Cheng, Wei Lu
2021 J jnl
CoRR
Jiale Han, Bo Cheng, Wei Lu
2021 A* conf
CVPR
Guoshun Nan, Rui Qiao, Yao Xiao, Jun Liu, Sicong Leng, Hao Zhang, Wei Lu
2021 J jnl
CoRR
Guoshun Nan, Rui Qiao, Yao Xiao, Jun Liu, Sicong Leng, Hao Zhang, Wei Lu
2021 A* conf
ICML
Haoran Li, Wei Lu
2021 J jnl
CoRR
Haoran Li, Wei Lu
2021 conf
EMNLP (1)
Yuxiang Zhou, Lejian Liao, Yang Gao, Zhanming Jie, Wei Lu
2021 J jnl
CoRR
Yuxiang Zhou, Lejian Liao, Yang Gao, Zhanming Jie, Wei Lu
2021 conf
EMNLP (1)
Guoshun Nan, Jiaqi Zeng, Rui Qiao, Zhijiang Guo, Wei Lu
2021 J jnl
CoRR
Guoshun Nan, Jiaqi Zeng, Rui Qiao, Zhijiang Guo, Wei Lu
2020 conf
EMNLP (1)
Liying Cheng, Lidong Bing, Qian Yu, Wei Lu, Luo Si
2020 conf
EMNLP (1)
Lu Xu, Lidong Bing, Wei Lu, Fei Huang
2020 J jnl
CoRR
Lu Xu, Lidong Bing, Wei Lu, Fei Huang
2020 A* conf
CVPR
Thilini Cooray, Ngai-Man Cheung, Wei Lu
2020 conf
EMNLP (1)
Liying Cheng, Dekun Wu, Lidong Bing, Yan Zhang, Zhanming Jie, Wei Lu, Luo Si
2020 A* conf
AAAI
Haiyun Peng, Lu Xu, Lidong Bing, Fei Huang, Wei Lu, Luo Si
2020 J jnl
CoRR
Liying Cheng, Yan Zhang, Dekun Wu, Zhanming Jie, Lidong Bing, Wei Lu, Luo Si
2020 A* conf
IJCAI
Zhijiang Guo, Guoshun Nan, Wei Lu, Shay B. Cohen
2020 conf
EMNLP (1)
Yan Zhang, Zhijiang Guo, Zhiyang Teng, Wei Lu, Shay B. Cohen, Zuozhu Liu, Lidong Bing
2020 J jnl
CoRR
Yan Zhang, Zhijiang Guo, Zhiyang Teng, Wei Lu, Shay B. Cohen, Zuozhu Liu, Lidong Bing
2020 conf
EMNLP (1)
Lu Xu, Hao Li, Wei Lu, Lidong Bing
2020 J jnl
CoRR
Lu Xu, Hao Li, Wei Lu, Lidong Bing
2020 conf
EMNLP (1)
Yanyan Zou, Xingxing Zhang, Wei Lu, Furu Wei, Ming Zhou
2020 conf
EMNLP (1)
Wenqiang Lei, Weixin Wang, Zhixin Ma, Tian Gan, Wei Lu, Min-Yen Kan, Tat-Seng Chua
2020 A* conf
ACL
Guoshun Nan, Zhijiang Guo, Ivan Sekulic, Wei Lu
2020 J jnl
CoRR
Guoshun Nan, Zhijiang Guo, Ivan Sekulic, Wei Lu
2020 J jnl
CoRR
Yanyan Zou, Xingxing Zhang, Wei Lu, Furu Wei, Ming Zhou
2020 conf
EMNLP (1)
Jue Wang, Wei Lu
2020 J jnl
CoRR
Jue Wang, Wei Lu
2020 A* conf
ACL
Xiaobing Sun, Wei Lu
2019 conf
ACL (1)
Ruixue Ding, Pengjun Xie, Xiaoyan Zhang, Wei Lu, Linlin Li, Luo Si
2019 J jnl
CoRR
Wei Yang, Wei Lu, Vincent W. Zheng
2019 conf
EMNLP/IJCNLP (1)
Hsiu-Wei Yang, Yanyan Zou, Peng Shi, Wei Lu, Jimmy Lin, Xu Sun
2019 J jnl
CoRR
Hsiu-Wei Yang, Yanyan Zou, Peng Shi, Wei Lu, Jimmy Lin, Xu Sun
2019 conf
ACL (1)
Zhijiang Guo, Yan Zhang, Wei Lu
2019 J jnl
CoRR
Zhijiang Guo, Yan Zhang, Wei Lu
2019 conf
NAACL-HLT (1)
Zhanming Jie, Pengjun Xie, Wei Lu, Ruixue Ding, Linlin Li
2019 conf
EMNLP/IJCNLP (1)
Bailin Wang, Wei Lu
2019 J jnl
CoRR
Bailin Wang, Wei Lu
2019 J jnl
CoRR
Zhijiang Guo, Yan Zhang, Zhiyang Teng, Wei Lu
2019 J jnl
Trans. Assoc. Comput. Linguistics
Zhijiang Guo, Yan Zhang, Zhiyang Teng, Wei Lu
2019 conf
EMNLP/IJCNLP (1)
Zhanming Jie, Wei Lu
2019 J jnl
CoRR
Zhanming Jie, Wei Lu
2019 conf
NAACL-HLT (1)
Yanyan Zou, Wei Lu
2019 J jnl
CoRR
Yanyan Zou, Wei Lu
2019 J jnl
CoRR
Haiyun Peng, Lu Xu, Lidong Bing, Fei Huang, Wei Lu, Luo Si
2019 conf
EMNLP/IJCNLP (1)
Hao Li, Wei Lu
2019 J jnl
CoRR
Hao Li, Wei Lu
2019 conf
NAACL-HLT (1)
Hao Li, Wei Lu, Pengjun Xie, Linlin Li
2019 conf
ACL (1)
Yanyan Zou, Wei Lu
2019 J jnl
CoRR
Yanyan Zou, Wei Lu
2019 conf
PAKDD (1)
Aman Ahuja, Ashish Baghudana, Wei Lu, Edward A. Fox, Chandan K. Reddy
2019 conf
EMNLP/IJCNLP (1)
Yanyan Zou, Wei Lu
2019 J jnl
CoRR
Yanyan Zou, Wei Lu
2019 conf
ACL (1)
Jiaqi Pan, Rishabh Bhardwaj, Wei Lu, Hai Leong Chieu, Xinghao Pan, Ni Yi Puay
2018 A* conf
EMNLP
Bailin Wang, Wei Lu, Yu Wang, Hongxia Jin
2018 J jnl
CoRR
Bailin Wang, Wei Lu, Yu Wang, Hongxia Jin
2018 A* conf
EMNLP
Zhijiang Guo, Wei Lu
2018 A* conf
EMNLP
Zhanming Jie, Wei Lu
2018 J jnl
CoRR
Zhanming Jie, Wei Lu
2018 J jnl
CoRR
Zhanming Jie, Aldrian Obaja Muis, Wei Lu
2018 J jnl
CoRR
Aldrian Obaja Muis, Wei Lu
2018 conf
ACL (2)
Yanyan Zou, Wei Lu
2018 J jnl
CoRR
Yanyan Zou, Wei Lu
2018 A* conf
AAAI
Bailin Wang, Wei Lu
2018 J jnl
CoRR
Aldrian Obaja Muis, Wei Lu
2018 conf
ACL (2)
Hao Li, Wei Lu
2018 A* conf
EMNLP
Bill Yuchen Lin, Wei Lu
2018 J jnl
CoRR
Bill Yuchen Lin, Wei Lu
2018 A* conf
EMNLP
Bailin Wang, Wei Lu
2018 J jnl
CoRR
Bailin Wang, Wei Lu
2018 ed.
EMNLP (Demonstration)
Eduardo Blanco, Wei Lu
2018 conf
SemEval@NAACL-HLT
Peter Phandi, Amila Silva, Wei Lu
2018 J jnl
CoRR
Aldrian Obaja Muis, Wei Lu
2018 A* conf
AAAI
Shaosheng Cao, Wei Lu, Jun Zhou, Xiaolong Li
2017 A* conf
ICDM
Aman Ahuja, Wei Wei, Wei Lu, Kathleen M. Carley, Chandan K. Reddy
2017 A* conf
EMNLP
Wei Yang, Wei Lu, Vincent W. Zheng
2017 A* conf
AAAI
Zhanming Jie, Aldrian Obaja Muis, Wei Lu
2017 A* conf
AAAI
Shaosheng Cao, Wei Lu
2017 conf
ISWC (Posters, Demos & Industry Tracks)
Shaosheng Cao, Wei Lu, Jun Zhou, Xiaolong Li
2017 A* conf
EMNLP
Aldrian Obaja Muis, Wei Lu
2017 A* conf
AAAI
Hao Li, Wei Lu
2017 conf
ACL (1)
Swee Kiat Lim, Aldrian Obaja Muis, Wei Lu, Ong Chen Hui
2017 conf
ACL (2)
Raymond Hendy Susanto, Wei Lu
2017 A* conf
AAAI
Raymond Hendy Susanto, Wei Lu
2017 conf
ACL (1)
Hesam Amoualian, Wei Lu, Éric Gaussier, Georgios Balikas, Massih-Reza Amini, Marianne Clausel
2016 A* conf
EMNLP
Wei Lu, Hai Leong Chieu, Jonathan Löfgren
2016 A* conf
AAAI
Shaosheng Cao, Wei Lu, Qiongkai Xu
2016 J jnl
ACM Trans. Asian Low Resour. Lang. Inf. Process.
Junhui Li, Muhua Zhu, Wei Lu, Guodong Zhou
2016 A* conf
EMNLP
Raymond Hendy Susanto, Hai Leong Chieu, Wei Lu
2016 A* conf
EMNLP
Aldrian Obaja Muis, Wei Lu
2016 conf
HLT-NAACL
Aldrian Obaja Muis, Wei Lu
2015 conf
ACL (2)
Wei Lu
2015 A conf
CIKM
Shaosheng Cao, Wei Lu, Qiongkai Xu
2015 A* conf
EMNLP
Junhui Li, Muhua Zhu, Wei Lu, Guodong Zhou
2015 A* conf
EMNLP
Wei Lu, Dan Roth
2015 J jnl
Comput. Linguistics
Wei Lu
2014 B conf
COLING
Zhanming Jie, Wei Lu
2014 A* conf
EMNLP
Wei Lu
2012 conf
ACL (1)
Wei Lu, Dan Roth
2012 conf
EMNLP-CoNLL
Quang Do, Wei Lu, Dan Roth
2011 A* conf
EMNLP
Wei Lu, Hwee Tou Ng
2010 A* conf
EMNLP
Wei Lu, Hwee Tou Ng
2009 A* conf
EMNLP
Wei Lu, Hwee Tou Ng, Wee Sun Lee
2009 conf
IWSLT
Preslav Nakov, Chang Liu, Wei Lu, Hwee Tou Ng
2009 conf
IWSLT (Evaluation Campaign)
Preslav Nakov, Chang Liu, Wei Lu, Hwee Tou Ng
2008 A* conf
EMNLP
Wei Lu, Hwee Tou Ng, Wee Sun Lee, Luke S. Zettlemoyer
2007 J jnl
Inf. Process. Manag.
Wei Lu, Min-Yen Kan
2005 conf
AIRS
Wei Lu, Min-Yen Kan
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