Majid Nili Ahmadabadi

158 papers A* 8A 24B 10C 3Misc 2Journal 80Unranked 31
YearRankTypeTitle / Venue / Authors
2026 J jnl
CoRR
Erfan Mirzaei, Seyed Pooya Shariatpanahi, Alireza Tavakoli, Reshad Hosseini, Majid Nili Ahmadabadi
2025 J jnl
CoRR
Amir Mohammad Salehoof, Ali Ramezani, Yadollah Yaghoobzadeh, Majid Nili Ahmadabadi
2025 J jnl
CoRR
Ali Khoramfar, Ali Ramezani, Mohammad Mahdi Mohajeri, Mohammad Javad Dousti, Majid Nili Ahmadabadi, Heshaam Faili
2025 J jnl
Neurocomputing
Milad Ghorbani, Reshad Hosseini, Seyed Pooya Shariatpanahi, Majid Nili Ahmadabadi
2025 J jnl
Cogn. Comput.
Fatemeh Mortazavi, Abdol-Hossein Vahabie, Majid Nili Ahmadabadi, Hadi Moradi
2024 J jnl
CoRR
Alireza Rashidi Laleh, Majid Nili Ahmadabadi
2024 J jnl
CoRR
Mrzieh VatandoustMohammadieh, Mohammad Mahdi Mohajeri, Ali Keramati, Majid Nili Ahmadabadi
2024 J jnl
IEEE Trans. Instrum. Meas.
Zahra Sarayloo, Nasser Masoumi, Ehsan Haj Mirza Alian, Naimeh Ghafarian, Mohammad-Reza Nezhad-Ahmadi, Majid Nili Ahmadabadi, Safieddin Safavi-Naeini
2024 J jnl
CoRR
Mohammad Mahdi Mohajeri, Mohammad Javad Dousti, Majid Nili Ahmadabadi
2024 J jnl
CoRR
Javad Seraj, Mohammad Mahdi Mohajeri, Mohammad Javad Dousti, Majid Nili Ahmadabadi
2024 J jnl
CoRR
Hafez Ghaemi, Shirin Jamshidi, Mohammad Mashreghi, Majid Nili Ahmadabadi, Hamed Kebriaei
2024 A conf
AAMAS
Hafez Ghaemi, Hamed Kebriaei, Alireza Ramezani Moghaddam, Majid Nili Ahmadabadi
2024 J jnl
CoRR
Amirhossein Mesbah, Reshad Hosseini, Seyed Pooya Shariatpanahi, Majid Nili Ahmadabadi
2023 J jnl
Commun. Stat. Simul. Comput.
Majid Nili Ahmadabadi, Mohammad Nili Ahmadabadi
2022 J jnl
IEEE Robotics Autom. Lett.
Rezvan Nasiri, Hamidreza Aftabi, Majid Nili Ahmadabadi
2022 J jnl
IEEE Trans. Cogn. Dev. Syst.
Mohammad-Ali Nikouei Mahani, Karin Maria Bausenhart, Rolf Ulrich, Majid Nili Ahmadabadi
2021 J jnl
Comput. Ind. Eng.
Pegah Rokhforoz, Hamed Kebriaei, Majid Nili Ahmadabadi
2021 J jnl
Robotics Auton. Syst.
Omid Mohseni, Majid Abedinzadeh Shahri, Ayoob Davoodi, Majid Nili Ahmadabadi
2021 J jnl
Syst. Control. Lett.
Pegah Rokhforoz, Hamed Kebriaei, Majid Nili Ahmadabadi
2020 J jnl
Evol. Syst.
Maryam Hashemzadeh, Reshad Hosseini, Majid Nili Ahmadabadi
2020 J jnl
J. Robotics
Nafee Mourad, Ali Ezzeddine, Babak Nadjar Araabi, Majid Nili Ahmadabadi
2020 J jnl
CoRR
Milad Ghorbani, Reshad Hosseini, Seyed Pooya Shariatpanahi, Majid Nili Ahmadabadi
2019 A conf
IROS
Majid Abedinzadeh Shahri, Ali Rouhollahi, Majid Nili Ahmadabadi
2019 J jnl
IEEE Trans. Neural Networks Learn. Syst.
Maryam Hashemzadeh, Reshad Hosseini, Majid Nili Ahmadabadi
2019 J jnl
CoRR
Saeed Masoudnia, Omid Mersa, Babak Nadjar Araabi, AbdolHossein Vahabie, Mohammad Amin Sadeghi, Majid Nili Ahmadabadi
2019 J jnl
Expert Syst. Appl.
Saeed Masoudnia, Omid Mersa, Babak Nadjar Araabi, AbdolHossein Vahabie, Mohammad Amin Sadeghi, Majid Nili Ahmadabadi
2018 A conf
IROS
Javad Khaghani, Mahdiar Nekoui, Rezvan Nasiri, Majid Nili Ahmadabadi
2018 J jnl
Expert Syst. Appl.
Ali Ezzeddine, Nafee Mourad, Babak Nadjar Araabi, Majid Nili Ahmadabadi
2018 J jnl
J. Comput. Neurosci.
Safura Rashid Shomali, Majid Nili Ahmadabadi, Hideaki Shimazaki, Seyyed Nader Rasuli
2018 A conf
IROS
Mahdiar Nekoui, Javad Khaghani, Rezvan Nasiri, Majid Nili Ahmadabadi
2017 J jnl
IEEE Trans. Robotics
Mina Alibeigi, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2017 J jnl
Robotics Auton. Syst.
Mahdi Khoramshahi, Rezvan Nasiri, Mohammad Shushtari, Auke Jan Ijspeert, Majid Nili Ahmadabadi
2017 B conf
CogSci
Saeed Masoudnia, AbdolHossein Vahabie, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2017 J jnl
IEEE Trans. Robotics
Hamed Jalaly Bidgoly, Atoosa Parsa, Mohammad Javad Yazdanpanah, Majid Nili Ahmadabadi
2017 J jnl
Robotica
Mohammad Shushtari, Rezvan Nasiri, Mohammad Javad Yazdanpanah, Majid Nili Ahmadabadi
2017 J jnl
CoRR
Maryam Hashemzadeh, Reshad Hosseini, Majid Nili Ahmadabadi
2017 J jnl
J. Vis.
Gholamhossein Taherian, Majid Nili Ahmadabadi, Mohammad Hassan Karimi, MohammadReza Tavakoli
2017 J jnl
CoRR
Mina Alibeigi, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2017 J jnl
J. Intell. Robotic Syst.
Mina Alibeigi, Sadegh Rabiee, Majid Nili Ahmadabadi
2017 J jnl
Wirel. Networks
Hamed Nazaktabar, Kambiz Badie, Majid Nili Ahmadabadi
2016 J jnl
Int. J. Syst. Sci.
Zahra Gharaee, Alireza Fatehi, Maryam S. Mirian, Majid Nili Ahmadabadi
2016 J jnl
Inf. Sci.
Hamidreza Jamalabadi, Hossein Nasrollahi, Sarah Alizadeh, Babak Nadjar Araabi, Majid Nili Ahmadabadi
2016 A conf
IROS
Hamed Jalaly Bidgoly, Majid Nili Ahmadabadi, Mohammad Reza Zakerzadeh
2016 A conf
IROS
Rezvan Nasiri, Mahdi Khoramshahi, Majid Nili Ahmadabadi
2016 J jnl
IEEE Trans. Fuzzy Syst.
Sarah Alizadeh, Ahmad Kalhor, Hamidreza Jamalabadi, Babak Nadjar Araabi, Majid Nili Ahmadabadi
2015 J jnl
Comput. Intell. Neurosci.
Zahra Sadeghi, Babak Nadjar Araabi, Majid Nili Ahmadabadi
2015 conf
AAAI Workshop: Knowledge, Skill, and Behavior Transfer in Autonomous Robots
Amin Mousavi, Babak Nadjar Araabi, Majid Nili Ahmadabadi
2015 J jnl
Appl. Soft Comput.
Faezeh Farivar, Majid Nili Ahmadabadi
2015 J jnl
IEEE Trans. Syst. Man Cybern. Syst.
Hamed Kebriaei, Ashkan Rahimi-Kian, Majid Nili Ahmadabadi
2015 J jnl
IEEE Trans. Robotics
Ehsan Noohi, Hadi Moradi, Sina Parastegari, Majid Nili Ahmadabadi
2014 conf
ICARSC
Kourosh Naderi, Khalil Taheri, Hadi Moradi, Majid Nili Ahmadabadi
2014 J jnl
Expert Syst. Appl.
Mir Hesam Hajimiri, Majid Nili Ahmadabadi, Ashkan Rahimi-Kian
2014 J jnl
Neurocomputing
Mohammad Hassan Zokaei Ashtiani, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2014 J jnl
Comput. Intell. Neurosci.
Amin Mousavi, Babak Nadjar Araabi, Majid Nili Ahmadabadi
2014 A* conf
ICRA
Mahdi Khoramshahi, Atoosa Parsa, Auke Jan Ijspeert, Majid Nili Ahmadabadi
2014 J jnl
Appl. Artif. Intell.
Hamed Kebriaei, Majid Nili Ahmadabadi, Ashkan Rahimi-Kian
2013 A* conf
ICRA
Mahdi Khoramshahi, Alexander Spröwitz, Alexandre Tuleu, Majid Nili Ahmadabadi, Auke Jan Ijspeert
2013 A conf
IROS
Maziar Ahmad Sharbafi, Majid Nili Ahmadabadi, Mohammad Javad Yazdanpanah, Aida Mohammadi Nejad, André Seyfarth
2013 J jnl
IEEE Trans. Auton. Ment. Dev.
Hossein Hajimirsadeghi, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2013 conf
ASCC
Mojtaba Rostami Kandroodi, Farshad Shirani, Babak Nadjar Araabi, Majid Nili Ahmadabadi, Maisam Mansoob Bassiri
2013 J jnl
Robotics Auton. Syst.
Mahdi Khoramshahi, Hamed Jalaly Bidgoly, Soroosh Shafiee, Ali Asaei, Auke Jan Ijspeert, Majid Nili Ahmadabadi
2013 B conf
CogSci
Ali Mahmoodi, Majid Nili Ahmadabadi, Bahador Bahrami
2012 conf
ICONIP (3)
Maryam Tavakol, Majid Nili Ahmadabadi, Maryam S. Mirian, Masoud Asadpour
2012 conf
ICONIP (2)
Hamidreza Jamalabadi, Hossein Nasrollahi, Majid Nili Ahmadabadi, Babak Nadjar Araabi, AbdolHossein Vahabie, Mohammad-Reza Abolghasemi Dehaqani
2012 J jnl
Robotica
Ali Sadeghi, Hadi Moradi, Majid Nili Ahmadabadi
2012 conf
ICONIP (2)
Sepehr Eghbali, Mohammad Hassan Zokaei Ashtiani, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2012 conf
ICONIP (1)
Farbod Farshidian, Zeinab Talebpour, Majid Nili Ahmadabadi
2012 J jnl
J. Comput. Neurosci.
Reyhaneh Bakhtiari, Nazanin Mohammadi Sephavand, Majid Nili Ahmadabadi, Babak Nadjar Araabi, Hossein Esteky
2012 J jnl
ACM Trans. Intell. Syst. Technol.
Hossein Hajimirsadeghi, Majid Nili Ahmadabadi, Babak Nadjar Araabi, Hadi Moradi
2012 A conf
IROS
Maziar Ahmad Sharbafi, Christophe Maufroy, Horst Moritz Maus, André Seyfarth, Majid Nili Ahmadabadi, Mohammad Javad Yazdanpanah
2012 J jnl
Robotics Auton. Syst.
Mostafa Ajallooeian, Majid Nili Ahmadabadi, Babak Nadjar Araabi, Hadi Moradi
2012 conf
ICONIP (2)
Hamid Aghaie Moghanjooghi, Babak Nadjar Araabi, Majid Nili Ahmadabadi
2012 C conf
IJCCI
Safa Yaghini Bonabi, Hassan Asgharian, Reyhaneh Bakhtiari, Saeed Safari, Majid Nili Ahmadabadi
2012 conf
ICONIP (1)
Safa Yaghini Bonabi, Hassan Asgharian, Reyhaneh Bakhtiari, Saeed Safari, Majid Nili Ahmadabadi
2012 J jnl
IEEE Trans. Auton. Ment. Dev.
Hadi Firouzi, Majid Nili Ahmadabadi, Babak Nadjar Araabi, Saeed Amizadeh, Maryam S. Mirian, Roland Siegwart
2012 conf
ICONIP (3)
Sepehr Eghbali, Majid Nili Ahmadabadi, Babak Nadjar Araabi, Maryam S. Mirian
2012 J jnl
J. Intell. Fuzzy Syst.
Maryam S. Mirian, Babak Nadjar Araabi, Majid Nili Ahmadabadi, Roland Siegwart
2012 conf
ROBIO
Sina Parastegari, Majid Nili Ahmadabadi, Ehsan Noohi, Hadi Moradi
2011 conf
ROBIO
Atabak Nejadfard, Hadi Moradi, Majid Nili Ahmadabadi
2011 J jnl
Adv. Robotics
Adel Akbarimajd, Majid Nili Ahmadabadi, Auke Jan Ijspeert
2011 J jnl
Mach. Vis. Appl.
Ehsan Norouzi, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2011 J jnl
Mach. Vis. Appl.
Ali Borji, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2011 conf
ROBIO
Mohammad Hasan H. Kani, Mostafa Derafshian, Hamed Jalaly Bidgoly, Majid Nili Ahmadabadi
2011 conf
CIMSIVP
Amirhossein Habibian, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2011 conf
ROBIO
S. A. Ghazvini, Hadi Moradi, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2011 A conf
IROS
Maziar Ahmad Sharbafi, Mohammad Javad Yazdanpanah, Majid Nili Ahmadabadi
2011 J jnl
Neural Comput.
Maryam S. Mirian, Majid Nili Ahmadabadi, Babak Nadjar Araabi, Roland Siegwart
2011 J jnl
Robotics Auton. Syst.
Ehsan Noohi, Hadi Moradi, Narges Noori, Majid Nili Ahmadabadi
2010 J jnl
Aust. J. Intell. Inf. Process. Syst.
Amirhossein Habibian, Ali Borji, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2010 J jnl
Expert Syst. Appl.
Arash Arami, Caro Lucas, Majid Nili Ahmadabadi
2010 C conf
ACML
Hossein Hajimirsadeghi, Majid Nili Ahmadabadi, Mostafa Ajallooeian, Babak Nadjar Araabi, Hadi Moradi
2010 B conf
ICPR
Mehrtash Tafazzoli Harandi, Majid Nili Ahmadabadi, Babak Nadjar Araabi, Abbas Bigdeli, Brian C. Lovell
2010 J jnl
Fuzzy Sets Syst.
Vali Derhami, Vahid Johari Majd, Majid Nili Ahmadabadi
2010 J jnl
IEEE Trans. Evol. Comput.
Amir Massoud Farahmand, Majid Nili Ahmadabadi, Caro Lucas, Babak Nadjar Araabi
2010 conf
EWDTS
Niki Shakeri, Nastaran Nemati, Majid Nili Ahmadabadi, Zainalabedin Navabi
2010 J jnl
Image Vis. Comput.
Ali Borji, Majid Nili Ahmadabadi, Babak Nadjar Araabi, Mandana Hamidi
2010 A conf
IROS
Ali Borji, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2010 J jnl
Adv. Robotics
Ehsan Noohi, Seyedeh Sara Mahdavi, Ali Baghani, Majid Nili Ahmadabadi
2009 A conf
IROS
Mostafa Ajallooeian, Majid Nili Ahmadabadi, Babak Nadjar Araabi, Hadi Moradi
2009 conf
FSEN
Razieh Behjati, Marjan Sirjani, Majid Nili Ahmadabadi
2009 B conf
RO-MAN
Mostafa Ajallooeian, Ali Borji, Babak Nadjar Araabi, Majid Nili Ahmadabadi, Hadi Moradi
2009 A* conf
ICRA
Ali Borji, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2009 J jnl
Int. J. Comput. Vis.
Mehrtash Tafazzoli Harandi, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2009 conf
CIRA
Narges Noori, Majid Nili Ahmadabadi, Maryam S. Mirian, Babak Nadjar Araabi
2008 J jnl
Robotics Auton. Syst.
Borhan Beigzadeh, Majid Nili Ahmadabadi, Ali Meghdari, Adel Akbarimajd
2008 A conf
IROS
A. Sadeqi, Hadi Moradi, Majid Nili Ahmadabadi
2008 J jnl
Expert Syst. Appl.
S. Motlaghi, F. Jalali, Majid Nili Ahmadabadi
2008 conf
WAPCV
Maryam S. Mirian, Majid Nili Ahmadabadi, Babak Nadjar Araabi, Roland Siegwart
2008 conf
CIG
Mostafa Sahraei-Ardakani, Ashkan Rahimi-Kian, Majid Nili Ahmadabadi
2008 A conf
IROS
Adel Akbarimajd, Majid Nili Ahmadabadi
2007 conf
WAPCV
Saied Haidarian Shahri, Majid Nili Ahmadabadi
2007 J jnl
Appl. Artif. Intell.
Hossein Mobahi, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2007 C conf
AICCSA
Mehrtash Tafazzoli Harandi, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2007 J jnl
IEEE Trans. Syst. Man Cybern. Part B
Babak Nadjar Araabi, S. Mastoureshgh, Majid Nili Ahmadabadi
2007 J jnl
Int. J. Intell. Syst. Technol. Appl.
Reyhaneh Bakhtiari, Babak Nadjar Araabi, Majid Nili Ahmadabadi
2007 conf
ISSPA
Mehrtash Tafazzoli Harandi, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2007 conf
WAPCV
Hadi Fatemi Shariatpanahi, Majid Nili Ahmadabadi
2007 J jnl
Robotics Auton. Syst.
Adel Akbarimajd, Majid Nili Ahmadabadi, Borhan Beigzadeh
2007 conf
ICINCO-RA (1)
Foad Ghaderi, Majid Nili Ahmadabadi
2007 J jnl
IEEE Syst. J.
Ahad Harati, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2006 B conf
IEEE Congress on Evolutionary Computation
Mehdi Salmani Jelodar, S. Najmeh Fakhraie, Faezeh Montazeri, Sied Mehdi Fakhraie, Majid Nili Ahmadabadi
2006 B conf
IJCNN
Hadi Fatemi Shariatpanahi, Nematollah Batmanghelich, Amir R. M. Kermani, Majid Nili Ahmadabadi, Hamid Soltanian-Zadeh
2006 A conf
IROS
Masoud Asadpour, Majid Nili Ahmadabadi, Roland Siegwart
2006 B conf
IEEE Congress on Evolutionary Computation
Amir Massoud Farahmand, Majid Nili Ahmadabadi, Caro Lucas, Babak Nadjar Araabi
2006 A conf
IROS
Majid Nili Ahmadabadi, Ahmad Imanipour, Babak Nadjar Araabi, Masoud Asadpour, Roland Siegwart
2006 A conf
IROS
Hossein Aminaiee, Majid Nili Ahmadabadi
2006 B conf
IJCNN
Mohammad G. Azar, Majid Nili Ahmadabadi, Amir Massoud Farahmand, Babak Nadjar Araabi
2006 conf
ROBIO
Seyedeh Sara Mahdavi, Ehsan Noohi, Majid Nili Ahmadabadi
2006 conf
EUROS
Masoud Asadpour, Majid Nili Ahmadabadi, Roland Siegwart
2006 B conf
IEEE Congress on Evolutionary Computation
Mehdi Salmani Jelodar, Mehdi Kamal, Sied Mehdi Fakhraie, Majid Nili Ahmadabadi
2006 J jnl
Complex.
Hossein Mobahi, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2005 conf
ALaRT
Reyhaneh Bakhtiari, Babak Nadjar Araabi, Majid Nili Ahmadabadi
2005 Misc conf
IC-AI
Reyhaneh Bakhtiari, Babak Nadjar Araabi, Majid Nili Ahmadabadi
2005 A* conf
ICRA
Hossein Mobahi, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2005 J jnl
Robotics Auton. Syst.
Aram Zaerpoora, Majid Nili Ahmadabadi, Mohammadreza Barouni-Ebrahimi, Zhi Dong Wang
2005 A* conf
ICRA
Ali Baghani, Majid Nili Ahmadabadi, Ahad Harati
2005 Misc conf
IC-AI
Reyhaneh Bakhtiari, Babak Nadjar Araabi, Majid Nili Ahmadabadi, Aras Adhami
2004 conf
International Conference on Computational Intelligence
Mehdi Salmani Jelodar, Seid Mehdi Fakhraie, Majid Nili Ahmadabadi
2004 A conf
IROS
Amir Massoud Farahmand, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2004 J jnl
Adv. Robotics
Majid Nili Ahmadabadi, Foad Ghaderi
2004 A conf
IROS
Amir Hossein Elahibakhsh, Majid Nili Ahmadabadi, Farrokh Janabi-Sharifi, Babak Nadjar Araabi
2004 B conf
ICIP
Mehrtash Tafazzoli Harandi, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2004 A conf
IROS
Hossein Mobahi, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2004 conf
SMC (6)
Ali Akhavan Bitaghsir, Amir Moghimi, Mohsen Lesani, Mohammad Mehdi Keramati, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2003 conf
Engineering of Reconfigurable Systems and Algorithms
Maryam S. Mirian, Majid Nili Ahmadabadi, Babak Nadjar Araabi
2003 J jnl
Adv. Robotics
Majid Nili Ahmadabadi, Mohammadreza Barouni-Ebrahimi, Eiji Nakano
2002 A conf
IROS
Foad Ghaderi, Majid Nili Ahmadabadi
2002 J jnl
IEEE Trans. Syst. Man Cybern. Part B
Majid Nili Ahmadabadi, Masoud Asadpour
2001 J jnl
IEEE Trans. Robotics Autom.
Majid Nili Ahmadabadi, Eiji Nakano
2001 J jnl
Adv. Robotics
Majid Nili Ahmadabadi, Masoud Asadpour, Eiji Nakano
2000 A conf
IROS
Majid Nili Ahmadabadi, Shaahin M. Rushan, Zhi Dong Wang, Eiji Nakano
2000 A conf
IROS
Majid Nili Ahmadabadi, Masoud Asadpour, Seyyed H. Khodanbakhsh, Eiji Nakano
1999 A* conf
ICRA
Zhi Dong Wang, Majid Nili Ahmadabadi, Eiji Nakano, Takayuki Takahashi
1998 A* conf
ICRA
Majid Nili Ahmadabadi, Eiji Nakano
1997 A* conf
ICRA
Majid Nili Ahmadabadi, Eiji Nakano
1997 A conf
IROS
Majid Nili Ahmadabadi, Eiji Nakano
1996 A conf
IROS
Majid Nili Ahmadabadi, Eiji Nakano
1996 conf
DARS
Majid Nili Ahmadabadi, Eiji Nakano
tests/unit/test_apk_code_analyzer.py
← Index tests/unit/test_apk_code_analyzer.py python
"""
Unit tests for APK code analysis components.

Tests APKCodeAnalyzer, LibraryFilter, SmaliParser, method enumeration,
library filtering, and method key matching. All external tools are mocked.
"""
import os
import tempfile
import textwrap

import pytest
from unittest.mock import MagicMock, patch, PropertyMock

pytestmark = [pytest.mark.unit, pytest.mark.apk, pytest.mark.decompile]


def _has_mmh3():
    try:
        import mmh3  # noqa: F401
        return True
    except ImportError:
        return False


# ============================================================================
# LibraryFilter Tests
# ============================================================================

class TestLibraryFilter:
    """Tests for package-based library filtering."""

    def test_default_prefixes_loaded(self):
        from redb.extractors.decompiler.apk.library_filter import LibraryFilter
        lf = LibraryFilter()
        assert lf.is_library("android.app.Activity")
        assert lf.is_library("androidx.core.app.NotificationCompat")

    def test_user_class_not_filtered(self):
        from redb.extractors.decompiler.apk.library_filter import LibraryFilter
        lf = LibraryFilter()
        assert not lf.is_library("com.example.myapp.MainActivity")
        assert not lf.is_library("org.myorg.MyClass")

    def test_dalvik_descriptor_format(self):
        from redb.extractors.decompiler.apk.library_filter import LibraryFilter
        lf = LibraryFilter()
        assert lf.is_library("Landroid/app/Activity;")
        assert lf.is_library("Landroidx/core/app/NotificationCompat;")
        assert not lf.is_library("Lcom/example/myapp/MainActivity;")

    def test_custom_prefixes(self):
        from redb.extractors.decompiler.apk.library_filter import LibraryFilter
        lf = LibraryFilter(prefixes=["com.custom."])
        assert lf.is_library("com.custom.SomeClass")
        assert not lf.is_library("android.app.Activity")

    def test_env_var_override(self):
        from redb.extractors.decompiler.apk.library_filter import LibraryFilter
        with patch.dict(os.environ, {"APK_LIBRARY_PREFIXES": "my.lib.,other.lib."}):
            lf = LibraryFilter()
            assert lf.is_library("my.lib.SomeClass")
            assert lf.is_library("other.lib.AnotherClass")
            assert not lf.is_library("android.app.Activity")

    def test_filter_stats(self):
        from redb.extractors.decompiler.apk.library_filter import LibraryFilter
        lf = LibraryFilter()
        lf.is_library("android.app.Activity")
        lf.is_library("com.example.MyClass")
        lf.is_library("kotlin.Unit")
        stats = lf.get_filter_stats()
        assert stats["library"] == 2
        assert stats["user"] == 1

    def test_normalize_class_name(self):
        from redb.extractors.decompiler.apk.library_filter import LibraryFilter
        assert LibraryFilter._normalize_class_name("Lcom/example/Foo;") == "com.example.Foo"
        assert LibraryFilter._normalize_class_name("com.example.Foo") == "com.example.Foo"
        assert LibraryFilter._normalize_class_name("com/example/Foo") == "com.example.Foo"

    def test_google_libraries_filtered(self):
        from redb.extractors.decompiler.apk.library_filter import LibraryFilter
        lf = LibraryFilter()
        assert lf.is_library("com.google.android.gms.ads.AdView")
        assert lf.is_library("com.google.firebase.messaging.FirebaseMessagingService")
        assert lf.is_library("com.google.gson.Gson")

    def test_kotlin_filtered(self):
        from redb.extractors.decompiler.apk.library_filter import LibraryFilter
        lf = LibraryFilter()
        assert lf.is_library("kotlin.Unit")
        assert lf.is_library("kotlinx.coroutines.CoroutineScope")

    def test_third_party_libs_filtered(self):
        from redb.extractors.decompiler.apk.library_filter import LibraryFilter
        lf = LibraryFilter()
        assert lf.is_library("com.squareup.okhttp3.OkHttpClient")
        assert lf.is_library("io.reactivex.Observable")
        assert lf.is_library("org.apache.commons.io.IOUtils")

    def test_empty_class_name(self):
        from redb.extractors.decompiler.apk.library_filter import LibraryFilter
        lf = LibraryFilter()
        assert not lf.is_library("")


# ============================================================================
# SmaliParser Tests
# ============================================================================

class TestSmaliParser:
    """Tests for smali file parsing."""

    SAMPLE_SMALI = textwrap.dedent("""\
        .class public Lcom/example/MyClass;
        .super Ljava/lang/Object;

        .method public constructor <init>()V
            .registers 1
            invoke-direct {p0}, Ljava/lang/Object;-><init>()V
            return-void
        .end method

        .method public onCreate(Landroid/os/Bundle;)V
            .registers 4
            .param p1, "savedInstanceState"

            .line 10
            invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
            const/high16 v0, 0x7f090000
            invoke-virtual {p0, v0}, Lcom/example/MyClass;->setContentView(I)V
            const-string v1, "hello"
            invoke-virtual {p0, v1}, Lcom/example/MyClass;->log(Ljava/lang/String;)V
            return-void
        .end method

        .method public abstract doSomething()V
        .end method

        .method public native nativeMethod()V
        .end method
    """)

    def test_parse_smali_content(self):
        from redb.extractors.decompiler.apk.smali_parser import SmaliParser
        methods = SmaliParser._parse_smali_content(self.SAMPLE_SMALI)
        # abstract and native should be skipped
        assert len(methods) == 2

    def test_method_names_extracted(self):
        from redb.extractors.decompiler.apk.smali_parser import SmaliParser
        methods = SmaliParser._parse_smali_content(self.SAMPLE_SMALI)
        names = {m.method_name for m in methods}
        assert "<init>" in names
        assert "onCreate" in names

    def test_class_name_extracted(self):
        from redb.extractors.decompiler.apk.smali_parser import SmaliParser
        methods = SmaliParser._parse_smali_content(self.SAMPLE_SMALI)
        for m in methods:
            assert m.class_name == "Lcom/example/MyClass;"

    def test_method_signature(self):
        from redb.extractors.decompiler.apk.smali_parser import SmaliParser
        methods = SmaliParser._parse_smali_content(self.SAMPLE_SMALI)
        oncreate = [m for m in methods if m.method_name == "onCreate"][0]
        assert oncreate.method_signature == "(Landroid/os/Bundle;)V"

    def test_instruction_count(self):
        from redb.extractors.decompiler.apk.smali_parser import SmaliParser
        methods = SmaliParser._parse_smali_content(self.SAMPLE_SMALI)
        oncreate = [m for m in methods if m.method_name == "onCreate"][0]
        # invoke-super, const/high16, invoke-virtual, const-string, invoke-virtual, return-void
        assert oncreate.instruction_count == 6

    def test_register_count(self):
        from redb.extractors.decompiler.apk.smali_parser import SmaliParser
        methods = SmaliParser._parse_smali_content(self.SAMPLE_SMALI)
        oncreate = [m for m in methods if m.method_name == "onCreate"][0]
        assert oncreate.register_count == 4

    def test_register_count_locals_directive(self):
        """apktool outputs .locals by default, not .registers."""
        from redb.extractors.decompiler.apk.smali_parser import SmaliParser
        smali_with_locals = textwrap.dedent("""\
            .class public Lcom/example/Locals;
            .super Ljava/lang/Object;

            .method public doWork()V
                .locals 3
                const/4 v0, 0x0
                const/4 v1, 0x1
                add-int v2, v0, v1
                return-void
            .end method
        """)
        methods = SmaliParser._parse_smali_content(smali_with_locals)
        assert len(methods) == 1
        assert methods[0].register_count == 3

    def test_abstract_native_skipped(self):
        from redb.extractors.decompiler.apk.smali_parser import SmaliParser
        methods = SmaliParser._parse_smali_content(self.SAMPLE_SMALI)
        names = {m.method_name for m in methods}
        assert "doSomething" not in names
        assert "nativeMethod" not in names

    def test_normalize_smali_body(self):
        from redb.extractors.decompiler.apk.smali_parser import SmaliParser
        body = textwrap.dedent("""\
            .registers 4
            .line 10
            # comment
            invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V

            const-string v1, "hello"
        """)
        normalized = SmaliParser.normalize_smali_body(body)
        assert ".line" not in normalized
        assert "# comment" not in normalized
        assert "invoke-super" in normalized
        assert "const-string" in normalized

    def test_count_instructions(self):
        from redb.extractors.decompiler.apk.smali_parser import SmaliParser
        body = textwrap.dedent("""\
            .registers 4
            .param p1, "x"
            invoke-virtual {p0}, Lcom/example/Foo;->bar()V
            const/4 v0, 0x0
            :label_0
            if-eqz v0, :label_1
            return-void
            :label_1
            goto :label_0
        """)
        assert SmaliParser.count_instructions(body) == 5

    def test_make_method_key(self):
        from redb.extractors.decompiler.apk.smali_parser import SmaliParser
        key = SmaliParser.make_method_key(
            "Lcom/example/Foo;", "bar", "(I)V"
        )
        assert key == "Lcom/example/Foo;->bar(I)V"

    def test_parse_smali_file(self):
        from redb.extractors.decompiler.apk.smali_parser import SmaliParser
        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".smali", delete=False
        ) as f:
            f.write(self.SAMPLE_SMALI)
            f.flush()
            try:
                methods = SmaliParser.parse_smali_file(f.name)
                assert len(methods) == 2
            finally:
                os.unlink(f.name)

    def test_parse_smali_directory(self):
        from redb.extractors.decompiler.apk.smali_parser import SmaliParser
        with tempfile.TemporaryDirectory() as tmpdir:
            # Create nested dir structure
            pkg_dir = os.path.join(tmpdir, "com", "example")
            os.makedirs(pkg_dir)
            with open(os.path.join(pkg_dir, "MyClass.smali"), "w") as f:
                f.write(self.SAMPLE_SMALI)

            result = SmaliParser.parse_smali_directory(tmpdir)
            assert len(result) == 2
            # Check keys are in expected format
            for key in result:
                assert "->" in key

    def test_empty_smali_file(self):
        from redb.extractors.decompiler.apk.smali_parser import SmaliParser
        methods = SmaliParser._parse_smali_content("")
        assert methods == []

    def test_class_only_no_methods(self):
        from redb.extractors.decompiler.apk.smali_parser import SmaliParser
        content = ".class public Lcom/example/Empty;\n.super Ljava/lang/Object;\n"
        methods = SmaliParser._parse_smali_content(content)
        assert methods == []


# ============================================================================
# MethodExtractor Tests (Hashing, Obfuscation, Type Conversion)
# ============================================================================

class TestMethodExtractor:
    """Tests for method-level content extraction and hashing."""

    def test_compute_sha256(self):
        from redb.extractors.decompiler.apk.method_extractor import compute_sha256
        h = compute_sha256("test content")
        assert len(h) == 64
        assert h == compute_sha256("test content")  # deterministic

    def test_compute_sha256_different_input(self):
        from redb.extractors.decompiler.apk.method_extractor import compute_sha256
        assert compute_sha256("a") != compute_sha256("b")

    def test_compute_ssdeep_short_input(self):
        from redb.extractors.decompiler.apk.method_extractor import compute_ssdeep
        result = compute_ssdeep("short")
        assert result is None

    def test_compute_ssdeep_long_input(self):
        from redb.extractors.decompiler.apk.method_extractor import compute_ssdeep
        result = compute_ssdeep("x" * 200)
        # May be None depending on entropy, but shouldn't crash
        assert result is None or isinstance(result, str)

    def test_compute_tlsh_short_input(self):
        from redb.extractors.decompiler.apk.method_extractor import compute_tlsh
        result = compute_tlsh("short")
        assert result is None

    def test_compute_tlsh_long_input(self):
        from redb.extractors.decompiler.apk.method_extractor import compute_tlsh
        data = "".join(chr(i % 256) for i in range(200))
        result = compute_tlsh(data)
        assert result is None or isinstance(result, str)

    def test_compute_minhash_returns_none_without_mmh3(self):
        from unittest.mock import patch
        from redb.extractors.decompiler.apk.method_extractor import compute_minhash
        with patch.dict("sys.modules", {"mmh3": None}):
            # Force reimport to hit ImportError
            import importlib
            import redb.extractors.decompiler.apk.method_extractor as mod
            importlib.reload(mod)
            result = mod.compute_minhash("line1\nline2\nline3\nline4")
            # mmh3 may or may not be available; just check it doesn't crash
            assert result is None or isinstance(result, list)
            importlib.reload(mod)  # restore

    def test_compute_minhash_too_few_lines(self):
        from redb.extractors.decompiler.apk.method_extractor import compute_minhash
        result = compute_minhash("invoke-direct {v0}, Lfoo;->bar()V")
        # Only 1 instruction line, need at least 3 for 3-grams
        assert result is None

    @pytest.mark.skipif(
        not _has_mmh3(), reason="mmh3 not installed"
    )
    def test_compute_minhash_deterministic(self):
        from redb.extractors.decompiler.apk.method_extractor import compute_minhash
        smali = (
            "invoke-direct {v1}, Ljava/lang/Object;-><init>()V\n"
            "const-string v0, \"hello\"\n"
            "iput-object v0, v1, LA;->a:Ljava/lang/String;\n"
            "return-void"
        )
        sig1 = compute_minhash(smali)
        sig2 = compute_minhash(smali)
        assert sig1 is not None
        assert sig1 == sig2

    @pytest.mark.skipif(
        not _has_mmh3(), reason="mmh3 not installed"
    )
    def test_compute_minhash_signature_length(self):
        from redb.extractors.decompiler.apk.method_extractor import compute_minhash
        smali = (
            "invoke-direct {v1}, Ljava/lang/Object;-><init>()V\n"
            "const-string v0, \"hello\"\n"
            "iput-object v0, v1, LA;->a:Ljava/lang/String;\n"
            "return-void"
        )
        sig = compute_minhash(smali)
        assert sig is not None
        assert len(sig) == 64  # SIGNATURE_LENGTH

    @pytest.mark.skipif(
        not _has_mmh3(), reason="mmh3 not installed"
    )
    def test_compute_minhash_value_range(self):
        from redb.extractors.decompiler.apk.method_extractor import compute_minhash
        smali = (
            "invoke-direct {v1}, Ljava/lang/Object;-><init>()V\n"
            "const-string v0, \"hello\"\n"
            "iput-object v0, v1, LA;->a:Ljava/lang/String;\n"
            "return-void"
        )
        sig = compute_minhash(smali)
        assert sig is not None
        for val in sig:
            assert 0 <= val < 256  # 8-bit values

    @pytest.mark.skipif(
        not _has_mmh3(), reason="mmh3 not installed"
    )
    def test_compute_minhash_different_inputs_differ(self):
        from redb.extractors.decompiler.apk.method_extractor import compute_minhash
        smali_a = (
            "invoke-direct {v1}, Ljava/lang/Object;-><init>()V\n"
            "const-string v0, \"hello\"\n"
            "iput-object v0, v1, LA;->a:Ljava/lang/String;\n"
            "return-void"
        )
        smali_b = (
            "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;\n"
            "const-string v1, \"world\"\n"
            "invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V\n"
            "return-void"
        )
        sig_a = compute_minhash(smali_a)
        sig_b = compute_minhash(smali_b)
        assert sig_a is not None
        assert sig_b is not None
        assert sig_a != sig_b

    @pytest.mark.skipif(
        not _has_mmh3(), reason="mmh3 not installed"
    )
    def test_compute_minhash_skips_directives_and_labels(self):
        from redb.extractors.decompiler.apk.method_extractor import compute_minhash
        # Directives and labels should be filtered out
        smali = (
            ".registers 3\n"
            ":start\n"
            "invoke-direct {v1}, Ljava/lang/Object;-><init>()V\n"
            ".line 10\n"
            "const-string v0, \"hello\"\n"
            "iput-object v0, v1, LA;->a:Ljava/lang/String;\n"
            "return-void"
        )
        smali_clean = (
            "invoke-direct {v1}, Ljava/lang/Object;-><init>()V\n"
            "const-string v0, \"hello\"\n"
            "iput-object v0, v1, LA;->a:Ljava/lang/String;\n"
            "return-void"
        )
        sig_with_directives = compute_minhash(smali)
        sig_clean = compute_minhash(smali_clean)
        assert sig_with_directives == sig_clean

    def test_dalvik_to_java_class(self):
        from redb.extractors.decompiler.apk.method_extractor import dalvik_to_java_class
        assert dalvik_to_java_class("Lcom/example/Foo;") == "com.example.Foo"
        assert dalvik_to_java_class("com.example.Foo") == "com.example.Foo"

    def test_dalvik_type_to_java(self):
        from redb.extractors.decompiler.apk.method_extractor import dalvik_type_to_java
        assert dalvik_type_to_java("V") == "void"
        assert dalvik_type_to_java("I") == "int"
        assert dalvik_type_to_java("Z") == "boolean"
        assert dalvik_type_to_java("Ljava/lang/String;") == "String"
        assert dalvik_type_to_java("[I") == "int[]"
        assert dalvik_type_to_java("[Ljava/lang/String;") == "String[]"

    def test_dalvik_to_java_prototype(self):
        from redb.extractors.decompiler.apk.method_extractor import dalvik_to_java_prototype
        result = dalvik_to_java_prototype("onCreate", "(Landroid/os/Bundle;)V")
        assert result == "void onCreate(Bundle)"

    def test_dalvik_to_java_prototype_no_params(self):
        from redb.extractors.decompiler.apk.method_extractor import dalvik_to_java_prototype
        result = dalvik_to_java_prototype("toString", "()Ljava/lang/String;")
        assert result == "String toString()"

    def test_dalvik_to_java_prototype_multiple_params(self):
        from redb.extractors.decompiler.apk.method_extractor import dalvik_to_java_prototype
        result = dalvik_to_java_prototype("foo", "(ILjava/lang/String;Z)V")
        assert result == "void foo(int, String, boolean)"

    def test_detect_obfuscation_short_name(self):
        from redb.extractors.decompiler.apk.method_extractor import detect_obfuscation_indicators
        result = detect_obfuscation_indicators("a", "Lcom/example/Foo;", "", 10)
        assert result["short_method_name"] is True
        result = detect_obfuscation_indicators("onCreate", "Lcom/example/Foo;", "", 10)
        assert result["short_method_name"] is False

    def test_detect_obfuscation_short_class(self):
        from redb.extractors.decompiler.apk.method_extractor import detect_obfuscation_indicators
        result = detect_obfuscation_indicators("foo", "Lcom/example/a;", "", 10)
        assert result["short_class_name"] is True
        result = detect_obfuscation_indicators("foo", "Lcom/example/MainActivity;", "", 10)
        assert result["short_class_name"] is False

    def test_detect_reflection_calls(self):
        from redb.extractors.decompiler.apk.method_extractor import detect_obfuscation_indicators
        smali = "invoke-virtual {v0}, Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
        result = detect_obfuscation_indicators("foo", "Lcom/Foo;", smali, 10)
        assert result["has_reflection_calls"] is True

    def test_detect_no_reflection(self):
        from redb.extractors.decompiler.apk.method_extractor import detect_obfuscation_indicators
        smali = "invoke-virtual {p0}, Lcom/example/Foo;->bar()V"
        result = detect_obfuscation_indicators("foo", "Lcom/Foo;", smali, 10)
        assert result["has_reflection_calls"] is False

    def test_detect_string_encryption(self):
        from redb.extractors.decompiler.apk.method_extractor import detect_obfuscation_indicators
        smali = 'const-string v0, "encrypted"\n    invoke-static {v0}, Lcom/example/Crypto;->decrypt(Ljava/lang/String;)Ljava/lang/String;'
        result = detect_obfuscation_indicators("foo", "Lcom/Foo;", smali, 10)
        assert result["has_string_encryption"] is True

    def test_detect_excessive_goto(self):
        from redb.extractors.decompiler.apk.method_extractor import detect_obfuscation_indicators
        smali = "\n".join(["goto :label_0"] * 20)
        result = detect_obfuscation_indicators("foo", "Lcom/Foo;", smali, 30)
        # threshold = max(5, 30*0.15) = 5, 20 > 5
        assert result["excessive_goto_count"] is True

    def test_detect_no_excessive_goto(self):
        from redb.extractors.decompiler.apk.method_extractor import detect_obfuscation_indicators
        smali = "goto :label_0\nreturn-void"
        result = detect_obfuscation_indicators("foo", "Lcom/Foo;", smali, 100)
        assert result["excessive_goto_count"] is False

    def test_parse_dalvik_params_empty(self):
        from redb.extractors.decompiler.apk.method_extractor import _parse_dalvik_params
        assert _parse_dalvik_params("") == []

    def test_parse_dalvik_params_primitives(self):
        from redb.extractors.decompiler.apk.method_extractor import _parse_dalvik_params
        result = _parse_dalvik_params("IZJ")
        assert result == ["I", "Z", "J"]

    def test_parse_dalvik_params_object(self):
        from redb.extractors.decompiler.apk.method_extractor import _parse_dalvik_params
        result = _parse_dalvik_params("Ljava/lang/String;")
        assert result == ["Ljava/lang/String;"]

    def test_parse_dalvik_params_mixed(self):
        from redb.extractors.decompiler.apk.method_extractor import _parse_dalvik_params
        result = _parse_dalvik_params("ILjava/lang/String;Z")
        assert result == ["I", "Ljava/lang/String;", "Z"]

    def test_parse_dalvik_params_array(self):
        from redb.extractors.decompiler.apk.method_extractor import _parse_dalvik_params
        result = _parse_dalvik_params("[I[Ljava/lang/String;")
        assert result == ["[I", "[Ljava/lang/String;"]


# ============================================================================
# APKCodeAnalyzer Tests (mocked tools)
# ============================================================================

class TestAPKCodeAnalyzer:
    """Tests for the APKCodeAnalyzer orchestration."""

    def test_init_defaults(self):
        from redb.extractors.decompiler.apk.analyzer import APKCodeAnalyzer
        analyzer = APKCodeAnalyzer("/fake/path.apk")
        assert analyzer.filepath == "/fake/path.apk"
        assert analyzer.timeout == 600
        assert analyzer.min_instructions == 5

    def test_init_custom_timeout(self):
        from redb.extractors.decompiler.apk.analyzer import APKCodeAnalyzer
        analyzer = APKCodeAnalyzer("/fake/path.apk", timeout=300)
        assert analyzer.timeout == 300

    def test_init_min_instructions_env(self):
        from redb.extractors.decompiler.apk.analyzer import APKCodeAnalyzer
        with patch.dict(os.environ, {"APK_MIN_METHOD_INSTRUCTIONS": "10"}):
            analyzer = APKCodeAnalyzer("/fake/path.apk")
            assert analyzer.min_instructions == 10

    @patch("redb.extractors.decompiler.apk.analyzer.APKCodeAnalyzer._run_androguard")
    @patch.object(
        __import__("redb.extractors.decompiler.apk.apktool_wrapper", fromlist=["ApktoolDisassembler"]).ApktoolDisassembler,
        "disassemble",
        return_value=False,
    )
    @patch.object(
        __import__("redb.extractors.decompiler.apk.jadx_wrapper", fromlist=["JADXDecompiler"]).JADXDecompiler,
        "decompile",
        return_value=False,
    )
    def test_extract_returns_dict_keys(self, mock_jadx, mock_apktool, mock_androguard):
        from redb.extractors.decompiler.apk.analyzer import APKCodeAnalyzer
        mock_analysis = MagicMock()
        mock_analysis.get_methods.return_value = []
        mock_androguard.return_value = (MagicMock(), [], mock_analysis)

        analyzer = APKCodeAnalyzer("/fake/path.apk", log=MagicMock())
        results = analyzer.extract()

        assert "decompiled_content" in results
        assert "decompiled_refs" in results
        assert "smali_content" in results
        assert "smali_refs" in results
        assert "similarity_metrics" in results
        assert "strings" in results
        assert "analysis_errors" in results
        analyzer.cleanup()

    @patch("redb.extractors.decompiler.apk.analyzer.APKCodeAnalyzer._run_androguard")
    def test_extract_androguard_failure(self, mock_androguard):
        from redb.extractors.decompiler.apk.analyzer import APKCodeAnalyzer
        mock_androguard.side_effect = Exception("Analysis failed")

        analyzer = APKCodeAnalyzer("/fake/path.apk", log=MagicMock())
        results = analyzer.extract()

        assert len(results["analysis_errors"]) > 0
        analyzer.cleanup()

    def test_cleanup_removes_temp_dirs(self):
        from redb.extractors.decompiler.apk.analyzer import APKCodeAnalyzer
        analyzer = APKCodeAnalyzer("/fake/path.apk")
        tmpdir = tempfile.mkdtemp()
        analyzer._temp_dirs.append(tmpdir)
        assert os.path.isdir(tmpdir)
        analyzer.cleanup()
        assert not os.path.isdir(tmpdir)

    def test_normalize_java(self):
        from redb.extractors.decompiler.apk.analyzer import _normalize_java
        source = "  public void foo() {\n    int x = 1;\n  }\n"
        normalized = _normalize_java(source)
        assert normalized == "public void foo() {\nint x = 1;\n}"


class TestNormalizeAndroguardOperands:
    """Tests for androguard-to-apktool output normalization."""

    def test_invoke_adds_braces(self):
        from redb.extractors.decompiler.apk.analyzer import _normalize_androguard_operands
        result = _normalize_androguard_operands(
            "invoke-direct",
            "v1, Ljava/lang/Object;-><init>()V"
        )
        assert result == "{v1}, Ljava/lang/Object;-><init>()V"

    def test_invoke_virtual_multiple_regs(self):
        from redb.extractors.decompiler.apk.analyzer import _normalize_androguard_operands
        result = _normalize_androguard_operands(
            "invoke-virtual",
            "v0, v1, Ljava/lang/String;->equals(Ljava/lang/Object;)Z"
        )
        assert result == "{v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z"

    def test_invoke_static_no_regs(self):
        from redb.extractors.decompiler.apk.analyzer import _normalize_androguard_operands
        # invoke-static with no register args (rare but possible)
        result = _normalize_androguard_operands(
            "invoke-static",
            "Ljava/lang/System;->gc()V"
        )
        # First part starts with L, so no registers to wrap
        assert result == "Ljava/lang/System;->gc()V"

    def test_invoke_range(self):
        from redb.extractors.decompiler.apk.analyzer import _normalize_androguard_operands
        result = _normalize_androguard_operands(
            "invoke-virtual/range",
            "v3, v4, v5, Landroid/util/Log;->d(Ljava/lang/String; Ljava/lang/String;)I"
        )
        assert result == "{v3, v4, v5}, Landroid/util/Log;->d(Ljava/lang/String; Ljava/lang/String;)I"

    def test_iget_field_colon(self):
        from redb.extractors.decompiler.apk.analyzer import _normalize_androguard_operands
        result = _normalize_androguard_operands(
            "iget-object",
            "v0, v4, LF;->a Ljava/lang/String;"
        )
        assert result == "v0, v4, LF;->a:Ljava/lang/String;"

    def test_iput_field_colon(self):
        from redb.extractors.decompiler.apk.analyzer import _normalize_androguard_operands
        result = _normalize_androguard_operands(
            "iput-object",
            "v0, v1, LA;->b Ljava/lang/String;"
        )
        assert result == "v0, v1, LA;->b:Ljava/lang/String;"

    def test_sget_primitive_field(self):
        from redb.extractors.decompiler.apk.analyzer import _normalize_androguard_operands
        result = _normalize_androguard_operands(
            "sget",
            "v0, Lcom/Foo;->count I"
        )
        assert result == "v0, Lcom/Foo;->count:I"

    def test_sput_array_field(self):
        from redb.extractors.decompiler.apk.analyzer import _normalize_androguard_operands
        result = _normalize_androguard_operands(
            "sput-object",
            "v0, Lcom/Foo;->data [B"
        )
        assert result == "v0, Lcom/Foo;->data:[B"

    def test_non_invoke_non_field_passthrough(self):
        from redb.extractors.decompiler.apk.analyzer import _normalize_androguard_operands
        result = _normalize_androguard_operands(
            "const-string",
            'v0, "hello world"'
        )
        assert result == 'v0, "hello world"'

    def test_move_passthrough(self):
        from redb.extractors.decompiler.apk.analyzer import _normalize_androguard_operands
        result = _normalize_androguard_operands("move", "v0, v4")
        assert result == "v0, v4"

    def test_goto_passthrough(self):
        from redb.extractors.decompiler.apk.analyzer import _normalize_androguard_operands
        result = _normalize_androguard_operands("goto", "+005h")
        assert result == "+005h"


class TestSmaliCFG:
    """Tests for smali CFG construction and metrics computation."""

    def test_empty_body(self):
        from redb.extractors.decompiler.apk.smali_cfg import compute_cfg_metrics
        m = compute_cfg_metrics("")
        assert m.block_count == 0
        assert m.cyclomatic_complexity == 1

    def test_linear_method_apktool(self):
        """Single basic block — no branches."""
        from redb.extractors.decompiler.apk.smali_cfg import compute_cfg_metrics
        smali = textwrap.dedent("""\
            .registers 2
            invoke-direct {v1}, Ljava/lang/Object;-><init>()V
            const-string v0, "hello"
            iput-object v0, v1, LA;->a:Ljava/lang/String;
            return-void
        """)
        m = compute_cfg_metrics(smali)
        assert m.block_count == 1
        assert m.edge_count == 0
        assert m.cyclomatic_complexity == 1
        assert m.loop_count == 0
        assert m.max_depth == 0
        assert m.max_fan_out == 0

    def test_linear_method_androguard(self):
        """Single basic block — androguard format (no labels/directives)."""
        from redb.extractors.decompiler.apk.smali_cfg import compute_cfg_metrics
        smali = (
            "    invoke-direct {v1}, Ljava/lang/Object;-><init>()V\n"
            "    const-string v0, \"hello\"\n"
            "    iput-object v0, v1, LA;->a:Ljava/lang/String;\n"
            "    return-void"
        )
        m = compute_cfg_metrics(smali)
        assert m.block_count == 1
        assert m.edge_count == 0
        assert m.cyclomatic_complexity == 1

    def test_if_branch_apktool(self):
        """Diamond pattern: if-else with two paths merging."""
        from redb.extractors.decompiler.apk.smali_cfg import compute_cfg_metrics
        smali = textwrap.dedent("""\
            .registers 2
            const/4 v0, 0x0
            if-eqz v0, :cond_0
            const/4 v0, 0x1
            goto :goto_0
            :cond_0
            const/4 v0, 0x2
            :goto_0
            return v0
        """)
        m = compute_cfg_metrics(smali)
        # Blocks: [const,if] [const,goto] [const] [return]
        assert m.block_count == 4
        assert m.cyclomatic_complexity == 2  # E - N + 2
        assert m.loop_count == 0
        assert m.max_fan_out == 2  # if-branch has 2 successors

    def test_loop_apktool(self):
        """Simple loop: goto back to earlier label."""
        from redb.extractors.decompiler.apk.smali_cfg import compute_cfg_metrics
        smali = textwrap.dedent("""\
            .registers 2
            const/4 v0, 0x0
            :loop_start
            add-int/lit8 v0, v0, 0x1
            const/16 v1, 0xa
            if-lt v0, v1, :loop_start
            return v0
        """)
        m = compute_cfg_metrics(smali)
        assert m.loop_count == 1  # back edge from if-lt to loop_start
        assert m.cyclomatic_complexity >= 2

    def test_if_branch_androguard(self):
        """If-else in androguard offset format."""
        from redb.extractors.decompiler.apk.smali_cfg import compute_cfg_metrics
        smali = (
            "    const/4 v0, 0x0\n"
            "    if-eqz v0, +2h\n"        # skip 2 instructions ahead
            "    const/4 v0, 0x1\n"
            "    goto +1h\n"               # skip 1 ahead
            "    const/4 v0, 0x2\n"
            "    return v0"
        )
        m = compute_cfg_metrics(smali)
        assert m.block_count >= 3
        assert m.cyclomatic_complexity >= 2

    def test_multiple_if_branches(self):
        """Multiple if-branches increase cyclomatic complexity."""
        from redb.extractors.decompiler.apk.smali_cfg import compute_cfg_metrics
        smali = textwrap.dedent("""\
            .registers 3
            if-eqz v0, :cond_0
            if-eqz v1, :cond_1
            const/4 v2, 0x0
            goto :end
            :cond_0
            const/4 v2, 0x1
            goto :end
            :cond_1
            const/4 v2, 0x2
            :end
            return v2
        """)
        m = compute_cfg_metrics(smali)
        # Two if-branches: CC should be >= 3
        assert m.cyclomatic_complexity >= 3

    def test_return_terminates_block(self):
        """Return instruction terminates block with no successor."""
        from redb.extractors.decompiler.apk.smali_cfg import compute_cfg_metrics
        smali = textwrap.dedent("""\
            .registers 1
            const/4 v0, 0x0
            return v0
        """)
        m = compute_cfg_metrics(smali)
        assert m.block_count == 1
        assert m.edge_count == 0

    def test_throw_terminates_block(self):
        """Throw instruction terminates block with no successor."""
        from redb.extractors.decompiler.apk.smali_cfg import compute_cfg_metrics
        smali = textwrap.dedent("""\
            .registers 1
            new-instance v0, Ljava/lang/RuntimeException;
            invoke-direct {v0}, Ljava/lang/RuntimeException;-><init>()V
            throw v0
        """)
        m = compute_cfg_metrics(smali)
        assert m.block_count == 1
        assert m.edge_count == 0

    def test_max_depth_linear_chain(self):
        """Chain of blocks gives increasing depth."""
        from redb.extractors.decompiler.apk.smali_cfg import compute_cfg_metrics
        # 3 blocks chained: entry -> if -> fall-through -> return
        smali = textwrap.dedent("""\
            .registers 2
            const/4 v0, 0x0
            if-nez v0, :cond_0
            const/4 v0, 0x1
            :cond_0
            return v0
        """)
        m = compute_cfg_metrics(smali)
        assert m.max_depth >= 1

    def test_nested_loop_apktool(self):
        """Nested loop: two back edges."""
        from redb.extractors.decompiler.apk.smali_cfg import compute_cfg_metrics
        smali = textwrap.dedent("""\
            .registers 3
            const/4 v0, 0x0
            :outer
            const/4 v1, 0x0
            :inner
            add-int/lit8 v1, v1, 0x1
            if-lt v1, v2, :inner
            add-int/lit8 v0, v0, 0x1
            if-lt v0, v2, :outer
            return-void
        """)
        m = compute_cfg_metrics(smali)
        assert m.loop_count == 2

    def test_consistency_formula(self):
        """Verify E - N + 2 formula matches cyclomatic_complexity."""
        from redb.extractors.decompiler.apk.smali_cfg import compute_cfg_metrics
        smali = textwrap.dedent("""\
            .registers 2
            const/4 v0, 0x0
            if-eqz v0, :cond_0
            const/4 v0, 0x1
            goto :goto_0
            :cond_0
            const/4 v0, 0x2
            :goto_0
            return v0
        """)
        m = compute_cfg_metrics(smali)
        expected_cc = m.edge_count - m.block_count + 2
        assert m.cyclomatic_complexity == max(1, expected_cc)