James A. Smith

40 papers A* 1C 3Journal 31Unranked 5
YearRankTypeTitle / Venue / Authors
2024 conf
MWSCAS
Kamil Agi, Ramiro Jordan, James A. Smith, Lok-kun Tsui, Robert Ian, Sleight Halley, Fernando Garzon, Donna Koechner, Tony Quinones
2023 conf
I2MTC
James A. Smith, Jon Quinn, Arvin B. Cunningham
2023 J jnl
IEEE Trans. Instrum. Meas.
James A. Smith, Vivek Agarwal
2023 J jnl
IEEE Instrum. Meas. Mag.
James A. Smith, Helena Maria Geirinhas Ramos
2023 J jnl
PLoS Comput. Biol.
Verena Heise, Constance Holman, Hung Lo, Ekaterini Maria Lyras, Mark Christopher Adkins, Maria Raisa Jessica Aquino, Konstantinos I. Bougioukas, Katherine O. Bray, Martyna Gajos, Xuanzong Guo, Corinna Hartling, Rodrigo Huerta-Gutierrez, Miroslava Jindrová, Joanne P. M. Kenney, Adrianna P. Kepinska, Laura Kneller, Elena Lopez-Rodriguez, Felix Mühlensiepen, Angela Richards, Gareth Richards, Maximilian Siebert, James A. Smith, Natalie Smith, Nicolai Stransky, Sirpa Tarvainen, Daniela Sofia Valdes, Kayleigh L. Warrington, Nina-Maria Wilpert, Disa Witkowska, Mirela Zaneva, Jeanette Zanker, Tracey L. Weissgerber
2018 J jnl
NeuroImage
Glyn S. Spencer, James A. Smith, Muhammad Enamul Hoque Chowdhury, Richard Bowtell, Karen J. Mullinger
2013 J jnl
Nucleic Acids Res.
Wanjuan Yang, Jorge Soares, Patricia Greninger, Elena J. Edelman, Howard Lightfoot, Simon A. Forbes, Nidhi Bindal, David Beare, James A. Smith, I. Richard Thompson, Sridhar Ramaswamy, P. Andrew Futreal, Daniel A. Haber, Michael R. Stratton, Cyril Benes, Ultan McDermott, Mathew Garnett
2013 J jnl
IEEE Instrum. Meas. Mag.
Randall A. Ali, Steven L. Garrett, James A. Smith, Dale K. Kotter
2012 J jnl
Proc. IEEE
Kun-Shan Chen, Sebastiano B. Serpico, James A. Smith
2011 J jnl
J. Frankl. Inst.
James A. Smith, Tullis C. Onstott
2010 J jnl
Nucleic Acids Res.
Paul Flicek, Bronwen L. Aken, Benoît Ballester, Kathryn Beal, Eugene Bragin, Simon Brent, Yuan Chen, Peter Clapham, Guy Coates, Susan Fairley, Stephen Fitzgerald, Julio Fernandez-Banet, Leo Gordon, Stefan Gräf, Syed Haider, Martin Hammond, Kerstin Howe, Andrew M. Jenkinson, Nathan Johnson, Andreas Kähäri, Damian Keefe, Stephen Keenan, Rhoda Kinsella, Felix Kokocinski, Gautier Koscielny, Eugene Kulesha, Daniel Lawson, Ian Longden, Tim Massingham, William M. McLaren, Karine Megy, Bert Overduin, Bethan Pritchard, Daniel Rios, Magali Ruffier, Michael Schuster, Guy Slater, Damian Smedley, Giulietta Spudich, Y. Amy Tang, Stephen J. Trevanion, Albert J. Vilella, Jan Vogel, Simon White, Steven P. Wilder, Amonida Zadissa, Ewan Birney, Fiona Cunningham, Ian Dunham, Richard Durbin, Xosé M. Fernández-Suárez, Javier Herrero, Tim J. P. Hubbard, Anne Parker, Glenn Proctor, James A. Smith, Stephen M. J. Searle
2010 J jnl
BMC Bioinform.
Anne Parker, Eugene Bragin, Simon Brent, Bethan Pritchard, James A. Smith, Stephen J. Trevanion
2009 J jnl
Nucleic Acids Res.
Tim J. P. Hubbard, Bronwen L. Aken, Sarah C. Ayling, Benoît Ballester, Kathryn Beal, Eugene Bragin, Simon Brent, Yuan Chen, Peter Clapham, Laura Clarke, Guy Coates, Susan Fairley, Stephen Fitzgerald, Julio Fernandez-Banet, Leo Gordon, Stefan Gräf, Syed Haider, Martin Hammond, Richard C. G. Holland, Kevin L. Howe, Andrew M. Jenkinson, Nathan Johnson, Andreas Kähäri, Damian Keefe, Stephen Keenan, Rhoda Kinsella, Felix Kokocinski, Eugene Kulesha, Daniel Lawson, Ian Longden, Karine Megy, Patrick Meidl, Bert Overduin, Anne Parker, Bethan Pritchard, Daniel Rios, Michael Schuster, Guy Slater, Damian Smedley, William Spooner, Giulietta Spudich, Stephen J. Trevanion, Albert J. Vilella, Jan Vogel, Simon White, Steven P. Wilder, Arek Zadissa, Ewan Birney, Fiona Cunningham, Val Curwen, Richard Durbin, Xosé M. Fernández-Suárez, Javier Herrero, Arek Kasprzyk, Glenn Proctor, James A. Smith, Stephen M. J. Searle, Paul Flicek
2008 J jnl
Nucleic Acids Res.
Paul Flicek, Bronwen L. Aken, Kathryn Beal, Benoît Ballester, Mario Cáccamo, Yuan Chen, Laura Clarke, Guy Coates, Fiona Cunningham, Tim Cutts, Thomas A. Down, S. C. Dyer, T. Eyre, Stephen Fitzgerald, Julio Fernandez-Banet, Stefan Gräf, Syed Haider, Martin Hammond, Richard C. G. Holland, Kevin L. Howe, Kerstin Howe, Nathan Johnson, Andrew M. Jenkinson, Andreas Kähäri, Damian Keefe, Felix Kokocinski, Eugene Kulesha, Daniel Lawson, Ian Longden, Karine Megy, Patrick Meidl, Bert Overduin, Anne Parker, Bethan Pritchard, Andreas Prlic, S. Rice, Daniel Rios, Michael Schuster, Ian Sealy, Guy Slater, Damian Smedley, Giulietta Spudich, Stephen J. Trevanion, Albert J. Vilella, Jan Vogel, Simon White, M. Wood, Ewan Birney, Tony Cox, Val Curwen, Richard Durbin, Xosé M. Fernández-Suárez, Javier Herrero, Tim J. P. Hubbard, Arek Kasprzyk, Glenn Proctor, James A. Smith, Abel Ureta-Vidal, Stephen M. J. Searle
2008 conf
IGARSS (4)
James A. Smith, Jill L. Deppe
2007 J jnl
J. Intell. Inf. Syst.
John L. Schnase, Judy Cushing, James A. Smith
2007 J jnl
Nucleic Acids Res.
Tim J. P. Hubbard, Bronwen L. Aken, Kathryn Beal, Benoît Ballester, Mario Cáccamo, Yuan Chen, Laura Clarke, Guy Coates, Fiona Cunningham, Tim Cutts, Thomas A. Down, S. C. Dyer, Stephen Fitzgerald, Julio Fernandez-Banet, Stefan Gräf, Syed Haider, Martin Hammond, Javier Herrero, Richard C. G. Holland, Kevin L. Howe, Kerstin Howe, Nathan Johnson, Andreas Kähäri, Damian Keefe, Felix Kokocinski, Eugene Kulesha, Daniel Lawson, Ian Longden, Craig Melsopp, Karine Megy, Patrick Meidl, Bert Overduin, Anne Parker, Andreas Prlic, S. Rice, Daniel Rios, Michael Schuster, Ian Sealy, Jessica Severin, Guy Slater, Damian Smedley, Giulietta Spudich, Stephen J. Trevanion, Albert J. Vilella, Jan Vogel, Simon White, M. Wood, Tony Cox, Val Curwen, Richard Durbin, Xosé M. Fernández-Suárez, Paul Flicek, Arek Kasprzyk, Glenn Proctor, Stephen M. J. Searle, James A. Smith, Abel Ureta-Vidal, Ewan Birney
2006 J jnl
Nucleic Acids Res.
Ewan Birney, T. Daniel Andrews, Mario Cáccamo, Yuan Chen, Laura Clarke, Guy Coates, Tony Cox, Fiona Cunningham, Val Curwen, Tim Cutts, Thomas A. Down, Richard Durbin, Xosé M. Fernández-Suárez, Paul Flicek, Stefan Gräf, Martin Hammond, Javier Herrero, Kevin L. Howe, Vivek Iyer, Kerstin Jekosch, Andreas Kähäri, Arek Kasprzyk, Damian Keefe, Felix Kokocinski, Eugene Kulesha, D. London, Ian Longden, Craig Melsopp, Patrick Meidl, Bert Overduin, Anne Parker, Glenn Proctor, Andreas Prlic, Mark Rae, Daniel Rios, Seth Redmond, Michael Schuster, Ian Sealy, Stephen M. J. Searle, Jessica Severin, Guy Slater, Damian Smedley, James A. Smith, Arne Stabenau, Jim Stalker, Stephen J. Trevanion, Abel Ureta-Vidal, Jan Vogel, Simon White, Cara Woodwark, Tim J. P. Hubbard
2006 conf
DILS
Andreas Prlic, Ewan Birney, Tony Cox, Thomas A. Down, Robert D. Finn, Stefan Gräf, David K. Jackson, Andreas Kähäri, Eugene Kulesha, Roger Pettett, James A. Smith, Jim Stalker, Tim J. P. Hubbard
2005 J jnl
Nucleic Acids Res.
Tim J. P. Hubbard, D. Andrews, Mario Cáccamo, Graham Cameron, Yuan Chen, Michele E. Clamp, Laura Clarke, Guy Coates, Tony Cox, Fiona Cunningham, Val Curwen, Tim Cutts, Thomas A. Down, Richard Durbin, Xosé M. Fernández-Suárez, James G. R. Gilbert, Martin Hammond, Javier Herrero, H. Hotz, Kevin L. Howe, Vivek Iyer, Kerstin Jekosch, Andreas Kähäri, Arek Kasprzyk, Damian Keefe, Stephen Keenan, Felix Kokocinski, D. London, Ian Longden, Graham P. McVicker, Craig Melsopp, Patrick Meidl, Simon C. Potter, Glenn Proctor, Mark Rae, Daniel Rios, Michael Schuster, Stephen M. J. Searle, Jessica Severin, Guy Slater, Damian Smedley, James A. Smith, William Spooner, Arne Stabenau, Jim Stalker, Roy Storey, Stephen J. Trevanion, Abel Ureta-Vidal, Jan Vogel, Simon White, Cara Woodwark, Ewan Birney
2005 J jnl
IEEE Trans. Geosci. Remote. Sens.
Werner Wiesbeck, R. Keith Raney, Kamal Sarabandi, Kiyo Tomiyasu, James A. Smith
2005 conf
Computer Graphics and Imaging
James A. Smith
2004 J jnl
Nucleic Acids Res.
Ewan Birney, T. Daniel Andrews, Paul Bevan, Mario Cáccamo, Graham Cameron, Yuan Chen, Laura Clarke, Guy Coates, Tony Cox, James A. Cuff, Val Curwen, Tim Cutts, Thomas A. Down, Richard Durbin, Eduardo Eyras, Xosé M. Fernández-Suárez, Paul J. Gane, B. Gibbins, James G. R. Gilbert, Martin Hammond, H. Hotz, Vivek Iyer, Andreas Kähäri, Kerstin Jekosch, Arek Kasprzyk, Damian Keefe, Stephen Keenan, Heikki Lehväslaiho, Graham P. McVicker, Craig Melsopp, Patrick Meidl, Emmanuel Mongin, Roger Pettett, Simon C. Potter, Glenn Proctor, Mark Rae, Stephen M. J. Searle, Guy Slater, Damian Smedley, James A. Smith, William Spooner, Arne Stabenau, Jim Stalker, Roy Storey, Abel Ureta-Vidal, Cara Woodwark, Michele E. Clamp, Tim J. P. Hubbard
2004 J jnl
IEEE Trans. Geosci. Remote. Sens.
Werner Wiesbeck, R. Keith Raney, Kamal Sarabandi, Kiyo Tomiyasu, James A. Smith
2004 C conf
IGARSS
James A. Smith
2003 J jnl
Nucleic Acids Res.
Michele E. Clamp, T. Daniel Andrews, Daniel Barker, Paul Bevan, Graham Cameron, Yuan Chen, Laura Clarke, Tony Cox, James A. Cuff, Val Curwen, Thomas A. Down, Richard Durbin, Eduardo Eyras, James G. R. Gilbert, Martin Hammond, Tim J. P. Hubbard, Arek Kasprzyk, Damian Keefe, Heikki Lehväslaiho, Vivek Iyer, Craig Melsopp, Emmanuel Mongin, Roger Pettett, Simon C. Potter, Alistair G. Rust, Esther Schmidt, Stephen M. J. Searle, Guy Slater, James A. Smith, William Spooner, Arne Stabenau, Jim Stalker, Elia Stupka, Abel Ureta-Vidal, Imre Vastrik, Ewan Birney
2003 J jnl
IEEE Trans. Geosci. Remote. Sens.
Kamal Sarabandi, Werner Wiesbeck, R. Keith Raney, Kiyo Tomiyasu, James A. Smith
2002 C conf
IGARSS
Jerrell R. Ballard Jr., James A. Smith
2002 C conf
IGARSS
John L. Schnase, James A. Smith, Thomas J. Stohlgren, Sara J. Graves, Charles Trees
2002 J jnl
IEEE Trans. Geosci. Remote. Sens.
Kamal Sarabandi, Werner Wiesbeck, R. Keith Raney, Haruhisa Shimoda, Kiyo Tomiyasu, James A. Smith
2002 J jnl
IEEE Trans. Geosci. Remote. Sens.
Chein-I Chang, Shao-Shan Chiang, James A. Smith, Irving W. Ginsberg
2002 J jnl
Nucleic Acids Res.
Tim J. P. Hubbard, Daniel Barker, Ewan Birney, Graham Cameron, Yuan Chen, Laura Clarke, Tony Cox, James A. Cuff, Val Curwen, Thomas A. Down, Richard Durbin, Eduardo Eyras, James G. R. Gilbert, Martin Hammond, Lukasz Huminiecki, Arek Kasprzyk, Heikki Lehväslaiho, Philip Lijnzaad, Craig Melsopp, Emmanuel Mongin, Roger Pettett, Matthew R. Pocock, Simon C. Potter, Alistair G. Rust, Esther Schmidt, Stephen M. J. Searle, Guy Slater, James A. Smith, William Spooner, Arne Stabenau, Jim Stalker, Elia Stupka, Abel Ureta-Vidal, Imre Vastrik, Michele E. Clamp
2001 J jnl
IEEE Trans. Geosci. Remote. Sens.
Kamal Sarabandi, Werner Wiesbeck, R. Keith Raney, Haruhisa Shimoda, Kiyo Tomiyasu, James A. Smith
1999 J jnl
IEEE Trans. Geosci. Remote. Sens.
James A. Smith, S. M. Goltz
1997 J jnl
IEEE Trans. Geosci. Remote. Sens.
James A. Smith, Narinder S. Chauhan, Thomas J. Schmugge, Jerrell R. Ballard Jr.
1994 J jnl
IEEE Trans. Geosci. Remote. Sens.
James A. Smith, S. M. Goltz
1993 J jnl
IEEE Trans. Geosci. Remote. Sens.
James A. Smith
1991 J jnl
IEEE Trans. Geosci. Remote. Sens.
Yosio Edemir Shimabukuro, James A. Smith
1976 A* conf
DAC
James A. Smith, James G. Linders
1976 J jnl
ACM SIGCSE Bull.
James A. Smith
tests/unit/test_cfg_features.py
← Index tests/unit/test_cfg_features.py python
"""
Unit tests for cfg_features.py — all new CFG feature computations.

These tests use plain Python data structures (index-based adjacency lists)
and require no Binary Ninja dependency.
"""
import pytest

from redb.extractors.decompiler.bninja.analysis.cfg_features import (
    bfs_order,
    bfs_max_depth,
    count_back_edges,
    compute_topology_hash,
    compute_md_index_topdown,
    compute_md_index_bottomup,
    compute_prime_product,
    build_block_features,
    compute_cfg_feature_tlsh,
    compute_wl_minhash,
    pack_adjacency,
    LLIL_OP_CATEGORIES,
    CAT_ARITHMETIC,
    CAT_LOGIC,
    CAT_CALL,
    CAT_MEMORY,
    NUM_WL_MINHASH_PERMS,
)


# ===================================================================
# Helper: common graph topologies
# ===================================================================

def _linear_chain(n):
    """0 -> 1 -> 2 -> ... -> (n-1)"""
    return [[i + 1] if i < n - 1 else [] for i in range(n)]


def _diamond():
    """
    0 -> 1, 0 -> 2, 1 -> 3, 2 -> 3
    (classic if/else diamond)
    """
    return [[1, 2], [3], [3], []]


def _predecessors_from_successors(successors, n):
    preds = [[] for _ in range(n)]
    for src, targets in enumerate(successors):
        for tgt in targets:
            preds[tgt].append(src)
    return preds


# ===================================================================
# TestBfsOrder
# ===================================================================

class TestBfsOrder:
    def test_empty_graph(self):
        assert bfs_order([], 0) == []

    def test_single_node(self):
        assert bfs_order([[]], 1) == [0]

    def test_linear_chain(self):
        succs = _linear_chain(4)
        assert bfs_order(succs, 4) == [0, 1, 2, 3]

    def test_diamond(self):
        succs = _diamond()
        order = bfs_order(succs, 4)
        assert order[0] == 0
        assert order[-1] == 3
        assert set(order) == {0, 1, 2, 3}

    def test_unreachable_nodes(self):
        # 0 -> 1, node 2 is unreachable
        succs = [[1], [], []]
        order = bfs_order(succs, 3)
        assert order[:2] == [0, 1]
        assert 2 in order  # unreachable appended

    def test_all_nodes_visited(self):
        succs = _diamond()
        order = bfs_order(succs, 4)
        assert len(order) == 4


# ===================================================================
# TestBfsMaxDepth
# ===================================================================

class TestBfsMaxDepth:
    def test_empty_graph(self):
        assert bfs_max_depth([], 0) == 0

    def test_single_block(self):
        assert bfs_max_depth([[]], 1) == 0

    def test_linear_chain(self):
        succs = _linear_chain(5)
        assert bfs_max_depth(succs, 5) == 4

    def test_diamond(self):
        succs = _diamond()
        assert bfs_max_depth(succs, 4) == 2

    def test_wide_graph(self):
        # 0 -> 1, 0 -> 2, 0 -> 3 (all at depth 1)
        succs = [[1, 2, 3], [], [], []]
        assert bfs_max_depth(succs, 4) == 1


# ===================================================================
# TestCountBackEdges
# ===================================================================

class TestCountBackEdges:
    def test_empty_graph(self):
        assert count_back_edges([], 0) == 0

    def test_no_loops(self):
        succs = _linear_chain(3)
        assert count_back_edges(succs, 3) == 0

    def test_single_loop(self):
        # 0 -> 1 -> 2 -> 0 (one back edge: 2->0)
        succs = [[1], [2], [0]]
        assert count_back_edges(succs, 3) == 1

    def test_nested_loops(self):
        # 0 -> 1 -> 2 -> 1 (inner), 2 -> 3 -> 0 (outer)
        succs = [[1], [2], [1, 3], [0]]
        assert count_back_edges(succs, 4) == 2

    def test_self_loop(self):
        # 0 -> 0 (self-loop)
        succs = [[0]]
        assert count_back_edges(succs, 1) == 1

    def test_diamond_no_loops(self):
        succs = _diamond()
        assert count_back_edges(succs, 4) == 0

    def test_single_node_no_loop(self):
        succs = [[]]
        assert count_back_edges(succs, 1) == 0


# ===================================================================
# TestTopologyHash
# ===================================================================

class TestTopologyHash:
    def test_same_graph_same_hash(self):
        succs = _diamond()
        bfs = bfs_order(succs, 4)
        h1 = compute_topology_hash(succs, bfs, 4)
        h2 = compute_topology_hash(succs, bfs, 4)
        assert h1 == h2

    def test_different_graphs_different_hash(self):
        succs1 = _linear_chain(3)
        bfs1 = bfs_order(succs1, 3)
        h1 = compute_topology_hash(succs1, bfs1, 3)

        succs2 = _diamond()
        bfs2 = bfs_order(succs2, 4)
        h2 = compute_topology_hash(succs2, bfs2, 4)

        assert h1 != h2

    def test_returns_16_bytes(self):
        succs = _diamond()
        bfs = bfs_order(succs, 4)
        h = compute_topology_hash(succs, bfs, 4)
        assert isinstance(h, bytes)
        assert len(h) == 16

    def test_isomorphic_graphs_same_hash(self):
        # Graph A: 0->1, 0->2, 1->3, 2->3 (diamond with successors [1,2])
        succs_a = [[1, 2], [3], [3], []]
        # Graph B: same structure but successors listed as [2,1]
        # BFS from 0 will visit them in different order, but after remapping
        # the canonical form should be identical for isomorphic graphs
        succs_b = [[2, 1], [3], [3], []]

        bfs_a = bfs_order(succs_a, 4)
        bfs_b = bfs_order(succs_b, 4)

        h_a = compute_topology_hash(succs_a, bfs_a, 4)
        h_b = compute_topology_hash(succs_b, bfs_b, 4)
        assert h_a == h_b

    def test_empty_graph(self):
        h = compute_topology_hash([], [], 0)
        assert h == b'\x00' * 16

    def test_single_node(self):
        succs = [[]]
        bfs = bfs_order(succs, 1)
        h = compute_topology_hash(succs, bfs, 1)
        assert isinstance(h, bytes)
        assert len(h) == 16


# ===================================================================
# TestMdIndex
# ===================================================================

class TestMdIndex:
    def test_single_block_topdown(self):
        succs = [[]]
        preds = [[]]
        bfs = [0]
        result = compute_md_index_topdown(succs, preds, bfs)
        assert isinstance(result, int)
        assert result > 0

    def test_single_block_bottomup(self):
        succs = [[]]
        preds = [[]]
        result = compute_md_index_bottomup(succs, preds, 1)
        assert isinstance(result, int)
        assert result > 0

    def test_linear_chain_topdown_vs_bottomup(self):
        succs = _linear_chain(4)
        preds = _predecessors_from_successors(succs, 4)
        bfs = bfs_order(succs, 4)
        td = compute_md_index_topdown(succs, preds, bfs)
        bu = compute_md_index_bottomup(succs, preds, 4)
        # Top-down and bottom-up should be different for a linear chain
        # (entry has in_deg=0, exit has out_deg=0, so the sequences differ)
        assert td != bu

    def test_deterministic(self):
        succs = _diamond()
        preds = _predecessors_from_successors(succs, 4)
        bfs = bfs_order(succs, 4)
        td1 = compute_md_index_topdown(succs, preds, bfs)
        td2 = compute_md_index_topdown(succs, preds, bfs)
        assert td1 == td2

    def test_different_graphs_different_index(self):
        succs1 = _linear_chain(3)
        preds1 = _predecessors_from_successors(succs1, 3)
        bfs1 = bfs_order(succs1, 3)
        td1 = compute_md_index_topdown(succs1, preds1, bfs1)

        succs2 = _diamond()
        preds2 = _predecessors_from_successors(succs2, 4)
        bfs2 = bfs_order(succs2, 4)
        td2 = compute_md_index_topdown(succs2, preds2, bfs2)

        assert td1 != td2

    def test_topdown_empty(self):
        assert compute_md_index_topdown([], [], []) == 0

    def test_bottomup_empty(self):
        assert compute_md_index_bottomup([], [], 0) == 0


# ===================================================================
# TestPrimeProduct
# ===================================================================

class TestPrimeProduct:
    def test_empty(self):
        assert compute_prime_product([]) == 0

    def test_known_sequence(self):
        # Use actual LLIL enum values from conftest_binja_stubs:
        # LLIL_NOP=0 -> prime 1, LLIL_LOAD=4 -> prime 5
        from redb.extractors.decompiler.bninja.analysis.cfg_features import LLIL_OP_PRIMES
        nop_val = 0   # LLIL_NOP
        load_val = 4  # LLIL_LOAD
        expected = LLIL_OP_PRIMES.get(nop_val, 1) * LLIL_OP_PRIMES.get(load_val, 1)
        result = compute_prime_product([nop_val, load_val])
        assert result == expected

    def test_order_independence(self):
        # LLIL_LOAD=4, LLIL_STORE=5, LLIL_ADD=13
        ops_a = [4, 5, 13]
        ops_b = [13, 4, 5]
        assert compute_prime_product(ops_a) == compute_prime_product(ops_b)

    def test_unknown_ops_map_to_1(self):
        # Unknown ops get prime 1, so they don't change the product
        result_known = compute_prime_product([4])  # LLIL_LOAD -> 5
        result_with_unknown = compute_prime_product([4, 9999])  # LOAD * unknown(1)
        assert result_known == result_with_unknown

    def test_mod_2_64(self):
        # Product should be mod 2^64
        result = compute_prime_product([4] * 1000)  # LLIL_LOAD
        assert 0 <= result < 2**64

    def test_single_op(self):
        # LLIL_STORE=5 -> prime 7
        assert compute_prime_product([5]) == 7


# ===================================================================
# TestBuildBlockFeatures
# ===================================================================

class TestBuildBlockFeatures:
    def test_empty_llil(self):
        succs = [[1], []]
        features = build_block_features([[], []], succs, 2)
        assert len(features) == 2
        # All zeros except successor_count
        assert features[0] == [0, 0, 0, 0, 0, 0, 0, 1]  # 1 successor
        assert features[1] == [0, 0, 0, 0, 0, 0, 0, 0]  # 0 successors

    def test_correct_categorization(self):
        # Set up categories for testing
        import redb.extractors.decompiler.bninja.analysis.cfg_features as cf
        old_cats = cf.LLIL_OP_CATEGORIES.copy()
        cf.LLIL_OP_CATEGORIES.update({
            100: CAT_ARITHMETIC,
            101: CAT_ARITHMETIC,
            200: CAT_LOGIC,
            300: CAT_CALL,
            400: CAT_MEMORY,
        })
        try:
            block_ops = [[100, 101, 200, 300, 400]]
            succs = [[]]
            features = build_block_features(block_ops, succs, 1)
            assert features[0][0] == 5   # instr_count
            assert features[0][1] == 2   # arithmetic
            assert features[0][2] == 1   # logic
            assert features[0][4] == 1   # call
            assert features[0][6] == 1   # memory
        finally:
            cf.LLIL_OP_CATEGORIES.clear()
            cf.LLIL_OP_CATEGORIES.update(old_cats)

    def test_cap_at_65535(self):
        # More than 65535 ops in one block
        huge_ops = [0] * 70000  # NOP x 70000
        succs = [[]]
        features = build_block_features([huge_ops], succs, 1)
        assert features[0][0] == 65535  # capped

    def test_missing_block_ops(self):
        # block_llil_ops shorter than n
        succs = [[1], [2], []]
        features = build_block_features([[1, 2]], succs, 3)
        assert len(features) == 3
        # Block 1 and 2 get empty ops since block_llil_ops only has 1 entry
        assert features[1] == [0, 0, 0, 0, 0, 0, 0, 1]
        assert features[2] == [0, 0, 0, 0, 0, 0, 0, 0]


# ===================================================================
# TestCfgFeatureTlsh
# ===================================================================

class TestCfgFeatureTlsh:
    def test_too_few_blocks_returns_none(self):
        # 5 blocks = 5 * 9 bytes = 45 < 50
        bb_features = [[10, 1, 0, 2, 0, 1, 1, 2]] * 5
        bfs = list(range(5))
        result = compute_cfg_feature_tlsh(bb_features, bfs)
        assert result is None

    def test_uniform_data_returns_none(self):
        # 7 identical blocks — TLSH returns TNULL for low-entropy input
        bb_features = [[10, 1, 0, 2, 0, 1, 1, 2]] * 7
        bfs = list(range(7))
        result = compute_cfg_feature_tlsh(bb_features, bfs)
        assert result is None

    def test_varied_data_returns_string(self):
        # 20 blocks with varied features — enough entropy for TLSH
        bb_features = [
            [i * 7 + 3, (i * 13) % 50, (i * 17) % 30, (i * 23) % 40,
             (i * 11) % 20, (i * 7) % 25, (i * 19) % 35, (i * 3) % 10]
            for i in range(20)
        ]
        bfs = list(range(20))
        result = compute_cfg_feature_tlsh(bb_features, bfs)
        assert isinstance(result, str)
        assert len(result) > 0
        assert result.startswith("T1")


# ===================================================================
# TestWlMinhash
# ===================================================================

class TestWlMinhash:
    def test_empty_function(self):
        result = compute_wl_minhash([], [], [], 0)
        assert result == [255] * NUM_WL_MINHASH_PERMS

    def test_returns_128_elements(self):
        succs = _diamond()
        preds = _predecessors_from_successors(succs, 4)
        bb_feats = [[5, 1, 0, 2, 0, 1, 1, 2]] * 4
        result = compute_wl_minhash(succs, preds, bb_feats, 4)
        assert len(result) == 128

    def test_all_uint8(self):
        succs = _linear_chain(3)
        preds = _predecessors_from_successors(succs, 3)
        bb_feats = [[3, 1, 0, 1, 0, 0, 1, 1]] * 3
        result = compute_wl_minhash(succs, preds, bb_feats, 3)
        assert all(0 <= v <= 255 for v in result)

    def test_identical_graphs_same_signature(self):
        succs = _diamond()
        preds = _predecessors_from_successors(succs, 4)
        bb_feats = [[5, 1, 0, 2, 0, 1, 1, 2]] * 4
        sig1 = compute_wl_minhash(succs, preds, bb_feats, 4)
        sig2 = compute_wl_minhash(succs, preds, bb_feats, 4)
        assert sig1 == sig2

    def test_different_graphs_different_signatures(self):
        # Graph 1: linear chain
        succs1 = _linear_chain(4)
        preds1 = _predecessors_from_successors(succs1, 4)
        bb_feats1 = [[5, 1, 0, 2, 0, 1, 1, i] for i in range(4)]
        sig1 = compute_wl_minhash(succs1, preds1, bb_feats1, 4)

        # Graph 2: diamond
        succs2 = _diamond()
        preds2 = _predecessors_from_successors(succs2, 4)
        bb_feats2 = [[10, 3, 2, 1, 0, 0, 0, i] for i in range(4)]
        sig2 = compute_wl_minhash(succs2, preds2, bb_feats2, 4)

        assert sig1 != sig2

    def test_single_node(self):
        succs = [[]]
        preds = [[]]
        bb_feats = [[1, 0, 0, 0, 0, 0, 0, 0]]
        result = compute_wl_minhash(succs, preds, bb_feats, 1)
        assert len(result) == 128


# ===================================================================
# TestPackAdjacency
# ===================================================================

class TestPackAdjacency:
    def test_empty(self):
        assert pack_adjacency([]) == []

    def test_single_edge(self):
        succs = [[1], []]
        edges = pack_adjacency(succs)
        assert len(edges) == 1
        assert edges[0] == (0 << 16) | 1

    def test_correct_packing(self):
        succs = _diamond()
        edges = pack_adjacency(succs)
        assert len(edges) == 4
        # 0->1, 0->2, 1->3, 2->3
        expected = {
            (0 << 16) | 1,
            (0 << 16) | 2,
            (1 << 16) | 3,
            (2 << 16) | 3,
        }
        assert set(edges) == expected

    def test_roundtrip(self):
        """Unpack edges and verify source/target pairs."""
        succs = [[1, 2], [3], [3], []]
        edges = pack_adjacency(succs)
        unpacked = [(e >> 16, e & 0xFFFF) for e in edges]
        expected = [(0, 1), (0, 2), (1, 3), (2, 3)]
        assert sorted(unpacked) == sorted(expected)

    def test_large_index_filtered(self):
        # Create a successor list where index >= 65536
        succs = [[] for _ in range(65537)]
        succs[0] = [65536]  # target is exactly 65536 — should be filtered
        edges = pack_adjacency(succs)
        assert len(edges) == 0

    def test_max_valid_index(self):
        # Index 65535 is the maximum valid
        succs = [[] for _ in range(65536)]
        succs[0] = [65535]
        edges = pack_adjacency(succs)
        assert len(edges) == 1
        assert edges[0] == (0 << 16) | 65535