From f833be17bfbd534d0facb22c550c85c393c3a5d2 Mon Sep 17 00:00:00 2001 From: Abhinav Shukla <67401627+maxprogrammer007@users.noreply.github.com> Date: Mon, 17 Mar 2025 10:48:58 +0530 Subject: [PATCH 1/6] Update test_ida_features.py --- tests/test_ida_features.py | 121 ++++++++++++------------------------- 1 file changed, 40 insertions(+), 81 deletions(-) diff --git a/tests/test_ida_features.py b/tests/test_ida_features.py index 5e2f7380a..0fbd7b066 100644 --- a/tests/test_ida_features.py +++ b/tests/test_ida_features.py @@ -60,6 +60,7 @@ import sys import inspect import logging +import binascii import traceback from pathlib import Path @@ -71,93 +72,62 @@ finally: sys.path.pop() - logger = logging.getLogger("test_ida_features") def check_input_file(wanted): import idautils - # some versions (7.4) of IDA return a truncated version of the MD5. - # https://github.com/idapython/bin/issues/11 try: found = idautils.GetInputFileMD5()[:31].decode("ascii").lower() except UnicodeDecodeError: - # in IDA 7.5 or so, GetInputFileMD5 started returning raw binary - # rather than the hex digest - found = bytes.hex(idautils.GetInputFileMD5()[:15]).lower() + found = binascii.hexlify(idautils.GetInputFileMD5()[:15]).decode("ascii").lower() if not wanted.startswith(found): raise RuntimeError(f"please run the tests against sample with MD5: `{wanted}`") def get_ida_extractor(_path): - # have to import this inline so pytest doesn't bail outside of IDA import capa.features.extractors.ida.extractor - return capa.features.extractors.ida.extractor.IdaFeatureExtractor() -def nocollect(f): - "don't collect the decorated function as a pytest test" - f.__test__ = False - return f - - -# although these look like pytest tests, they're not, because they don't run within pytest -# (the runner is below) and they use `yield`, which is deprecated. -@nocollect -@pytest.mark.skip(reason="IDA Pro tests must be run within IDA") -def test_ida_features(): - # we're guaranteed to be in a function here, so there's a stack frame - this_name = inspect.currentframe().f_code.co_name # type: ignore - for sample, scope, feature, expected in fixtures.FEATURE_PRESENCE_TESTS + fixtures.FEATURE_PRESENCE_TESTS_IDA: - id = fixtures.make_test_id((sample, scope, feature, expected)) - - try: - check_input_file(fixtures.get_sample_md5_by_name(sample)) - except RuntimeError: - yield this_name, id, "skip", None - continue - - scope = fixtures.resolve_scope(scope) - sample = fixtures.resolve_sample(sample) - - try: - fixtures.do_test_feature_presence(get_ida_extractor, sample, scope, feature, expected) - except Exception: - f = io.StringIO() - traceback.print_exc(file=f) - yield this_name, id, "fail", f.getvalue() - else: - yield this_name, id, "pass", None - - -@nocollect -@pytest.mark.skip(reason="IDA Pro tests must be run within IDA") -def test_ida_feature_counts(): - # we're guaranteed to be in a function here, so there's a stack frame - this_name = inspect.currentframe().f_code.co_name # type: ignore - for sample, scope, feature, expected in fixtures.FEATURE_COUNT_TESTS: - id = fixtures.make_test_id((sample, scope, feature, expected)) - - try: - check_input_file(fixtures.get_sample_md5_by_name(sample)) - except RuntimeError: - yield this_name, id, "skip", None - continue - - scope = fixtures.resolve_scope(scope) - sample = fixtures.resolve_sample(sample) - - try: - fixtures.do_test_feature_count(get_ida_extractor, sample, scope, feature, expected) - except Exception: - f = io.StringIO() - traceback.print_exc(file=f) - yield this_name, id, "fail", f.getvalue() - else: - yield this_name, id, "pass", None +@pytest.mark.parametrize( + "sample, scope, feature, expected", + fixtures.FEATURE_PRESENCE_TESTS + fixtures.FEATURE_PRESENCE_TESTS_IDA, +) +def test_ida_features(sample, scope, feature, expected): + try: + check_input_file(fixtures.get_sample_md5_by_name(sample)) + except RuntimeError: + pytest.skip("Sample MD5 mismatch. Skipping test.") + + scope = fixtures.resolve_scope(scope) + sample = fixtures.resolve_sample(sample) + + try: + fixtures.do_test_feature_presence(get_ida_extractor, sample, scope, feature, expected) + except Exception as e: + pytest.fail(f"Test failed with exception: {e}\n{traceback.format_exc()}") + + +@pytest.mark.parametrize( + "sample, scope, feature, expected", + fixtures.FEATURE_COUNT_TESTS, +) +def test_ida_feature_counts(sample, scope, feature, expected): + try: + check_input_file(fixtures.get_sample_md5_by_name(sample)) + except RuntimeError: + pytest.skip("Sample MD5 mismatch. Skipping test.") + + scope = fixtures.resolve_scope(scope) + sample = fixtures.resolve_sample(sample) + + try: + fixtures.do_test_feature_count(get_ida_extractor, sample, scope, feature, expected) + except Exception as e: + pytest.fail(f"Test failed with exception: {e}\n{traceback.format_exc()}") if __name__ == "__main__": @@ -165,23 +135,12 @@ def test_ida_feature_counts(): import ida_auto ida_auto.auto_wait() - print("-" * 80) - # invoke all functions in this module that start with `test_` - for name in dir(sys.modules[__name__]): - if not name.startswith("test_"): - continue - - test = getattr(sys.modules[__name__], name) - logger.debug("invoking test: %s", name) - sys.stderr.flush() - for name, id, state, info in test(): - print(f"{state.upper()}: {name}/{id}") - if info: - print(info) + pytest.main([__file__]) print("DONE") if "--CAPA_AUTOEXIT=true" in idc.ARGV: sys.exit(0) + From af54bf8f633e28d30d300687ebc48ab84c581246 Mon Sep 17 00:00:00 2001 From: Abhinav Shukla <67401627+maxprogrammer007@users.noreply.github.com> Date: Mon, 17 Mar 2025 11:15:25 +0530 Subject: [PATCH 2/6] Fixed F401 errors --- tests/test_ida_features.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_ida_features.py b/tests/test_ida_features.py index 0fbd7b066..886b547e0 100644 --- a/tests/test_ida_features.py +++ b/tests/test_ida_features.py @@ -56,9 +56,8 @@ Look for lines that start with "FAIL" to identify test failures. """ -import io + import sys -import inspect import logging import binascii import traceback From 9550178cd8d217c2e6de85751dbc8d7cdc990040 Mon Sep 17 00:00:00 2001 From: Abhinav Shukla <67401627+maxprogrammer007@users.noreply.github.com> Date: Mon, 17 Mar 2025 11:57:40 +0530 Subject: [PATCH 3/6] Applied Black Filter for the test --- tests/test_ida_features.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_ida_features.py b/tests/test_ida_features.py index 886b547e0..9c35ad4fd 100644 --- a/tests/test_ida_features.py +++ b/tests/test_ida_features.py @@ -88,9 +88,11 @@ def check_input_file(wanted): def get_ida_extractor(_path): import capa.features.extractors.ida.extractor + return capa.features.extractors.ida.extractor.IdaFeatureExtractor() + @pytest.mark.parametrize( "sample, scope, feature, expected", fixtures.FEATURE_PRESENCE_TESTS + fixtures.FEATURE_PRESENCE_TESTS_IDA, From db1db41d58420afc0abdd8428a5280d326d8413f Mon Sep 17 00:00:00 2001 From: Abhinav Shukla <67401627+maxprogrammer007@users.noreply.github.com> Date: Mon, 17 Mar 2025 12:02:21 +0530 Subject: [PATCH 4/6] Update test_ida_features.py --- tests/test_ida_features.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_ida_features.py b/tests/test_ida_features.py index 9c35ad4fd..98422f00b 100644 --- a/tests/test_ida_features.py +++ b/tests/test_ida_features.py @@ -91,8 +91,6 @@ def get_ida_extractor(_path): return capa.features.extractors.ida.extractor.IdaFeatureExtractor() - - @pytest.mark.parametrize( "sample, scope, feature, expected", fixtures.FEATURE_PRESENCE_TESTS + fixtures.FEATURE_PRESENCE_TESTS_IDA, @@ -144,4 +142,3 @@ def test_ida_feature_counts(sample, scope, feature, expected): if "--CAPA_AUTOEXIT=true" in idc.ARGV: sys.exit(0) - From 7a86af909b2f4514e87d37c69ed3ee87bdc65145 Mon Sep 17 00:00:00 2001 From: Abhinav Shukla <67401627+maxprogrammer007@users.noreply.github.com> Date: Mon, 17 Mar 2025 12:25:50 +0530 Subject: [PATCH 5/6] Update test_ida_features.py --- tests/test_ida_features.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_ida_features.py b/tests/test_ida_features.py index 98422f00b..0d91542a1 100644 --- a/tests/test_ida_features.py +++ b/tests/test_ida_features.py @@ -109,7 +109,6 @@ def test_ida_features(sample, scope, feature, expected): except Exception as e: pytest.fail(f"Test failed with exception: {e}\n{traceback.format_exc()}") - @pytest.mark.parametrize( "sample, scope, feature, expected", fixtures.FEATURE_COUNT_TESTS, @@ -128,7 +127,6 @@ def test_ida_feature_counts(sample, scope, feature, expected): except Exception as e: pytest.fail(f"Test failed with exception: {e}\n{traceback.format_exc()}") - if __name__ == "__main__": import idc import ida_auto From 09b8bf7e6dec247ea3b2ffb7f4e4b2641704fa67 Mon Sep 17 00:00:00 2001 From: Abhinav Shukla <67401627+maxprogrammer007@users.noreply.github.com> Date: Mon, 17 Mar 2025 12:30:22 +0530 Subject: [PATCH 6/6] Update test_ida_features.py --- tests/test_ida_features.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_ida_features.py b/tests/test_ida_features.py index 0d91542a1..cbaea44d8 100644 --- a/tests/test_ida_features.py +++ b/tests/test_ida_features.py @@ -73,7 +73,6 @@ logger = logging.getLogger("test_ida_features") - def check_input_file(wanted): import idautils @@ -83,8 +82,7 @@ def check_input_file(wanted): found = binascii.hexlify(idautils.GetInputFileMD5()[:15]).decode("ascii").lower() if not wanted.startswith(found): - raise RuntimeError(f"please run the tests against sample with MD5: `{wanted}`") - + raise RuntimeError(f"Please run the tests against sample with MD5: `{wanted}`") def get_ida_extractor(_path): import capa.features.extractors.ida.extractor