Skip to content

Commit

Permalink
tests: Introduce AnalysisPluginTestConfig
Browse files Browse the repository at this point in the history
This replaces multiple markers with just a single marker.
  • Loading branch information
maringuu committed Jan 24, 2023
1 parent e7b6de6 commit cf4f92a
Show file tree
Hide file tree
Showing 32 changed files with 80 additions and 63 deletions.
4 changes: 1 addition & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,7 @@ testpaths = [
"src/plugins/**/test",
]
markers = [
"AnalysisPluginClass: Set the Class of the analysis_plugin fixture",
"plugin_init_kwargs: Keyword arguments to pass to AnalysisPluginClass",
"plugin_start_worker: Whether or not to start actual workers",
"AnalysisPluginTestConfig: Configure the analysis_plugin fixture",
"cfg_defaults: Overwrite defaults for the testing config",
"WebInterfaceUnitTestConfig: Configure the web_interface fixture",
]
57 changes: 33 additions & 24 deletions src/conftest.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from __future__ import annotations

import dataclasses
import grp
import logging
import os
from configparser import ConfigParser
from tempfile import TemporaryDirectory
from typing import Type

import pytest
from pydantic.dataclasses import dataclass

import config
from analysis.PluginBase import AnalysisBasePlugin
Expand Down Expand Up @@ -160,17 +163,27 @@ def patch_cfg(cfg_tuple):
mpatch.undo()


@dataclass(config=dict(arbitrary_types_allowed=True))
class AnalysisPluginTestConfig:
"""A class configuring the :py:func:`analysis_plugin` fixture."""

#: The class of the plugin to be tested. It will most probably be called ``AnalysisPlugin``.
plugin_class: Type[AnalysisBasePlugin] = AnalysisBasePlugin
#: Whether or not to start the workers (see ``AnalysisPlugin.start_worker``)
start_processes: bool = False
#: Keyword arguments to be given to the ``plugin_class`` constructor.
init_kwargs: dict = dataclasses.field(default_factory=dict)


@pytest.fixture
def analysis_plugin(request, monkeypatch, patch_cfg):
"""Returns an instance of an AnalysisPlugin.
The following pytest markers affect this fixture:
This fixture can be configured by the supplying an instance of ``AnalysisPluginTestConfig`` as marker of the same
name.
* AnalysisPluginClass: The plugin class type. Must be a class derived from ``AnalysisBasePlugin``.
The marker has to be set with ``@pytest.mark.with_args`` to work around pytest
`link weirdness <https://docs.pytest.org/en/7.1.x/example/markers.html#passing-a-callable-to-custom-markers>`.
* plugin_start_worker: If set the AnalysisPluginClass.start_worker method will NOT be overwritten.
If not set the method is overwritten by a stub that does nothing.
* plugin_init_kwargs: Additional keyword arguments that shall be passed to the ``AnalysisPluginClass`` constructor
.. seealso::
The documentation of :py:class:`AnalysisPluginTestConfig`
If this fixture does not fit your needs (which normally should not be necessary) you can define a fixture like this:
Expand All @@ -187,13 +200,18 @@ def my_fancy_plugin(analysis_plugin)
.. Note::
If you use the ``plugin_start_worker`` marker and want to modify plugin configuration like for example TIMEOUT
you have to put the following in your test:
If you want to set ``AnalysisPluginTestConfig.start_processes = True`` and want to modify plugin configuration
like for example TIMEOUT you have to put the following in your test:
.. code-block::
@pytest.mark.AnalysisPluginClass.with_args(MyFancyPlugin)
# Don't use `plugin_start_worker`
@pytest.mark.AnalysisPluginTestConfig(
AnalysisPluginTestConfig(
plugin_class=MyFancyPlugin,
# Actually don't start the processes in the fixture
start_processes = False,
),
)
def my_fancy_test(analysis_plugin, monkeypatch):
# Undo the patching of MyFancyPlugin.start_worker
monkeypatch.undo()
Expand All @@ -208,25 +226,16 @@ def my_fancy_test(analysis_plugin, monkeypatch):
# create a new instance.
#
# See also: The note in the doc comment.
test_config = merge_markers(request, 'AnalysisPluginTestConfig', AnalysisPluginTestConfig)

plugin_class_marker = request.node.get_closest_marker('AnalysisPluginClass')
assert plugin_class_marker, '@pytest.mark.AnalysisPluginClass has to be defined'
PluginClass = plugin_class_marker.args[0]
assert issubclass(
PluginClass, AnalysisBasePlugin
), f'{PluginClass.__name__} is not a subclass of {AnalysisBasePlugin.__name__}'
PluginClass = test_config.plugin_class

# We don't want to actually start workers when testing, except for some special cases
plugin_start_worker_marker = request.node.get_closest_marker('plugin_start_worker')
if not plugin_start_worker_marker:
if not test_config.start_processes:
monkeypatch.setattr(PluginClass, 'start_worker', lambda _: None)

plugin_init_kwargs_marker = request.node.get_closest_marker('plugin_init_kwargs')
kwargs = plugin_init_kwargs_marker.kwargs if plugin_init_kwargs_marker else {}

plugin_instance = PluginClass(
view_updater=CommonDatabaseMock(),
**kwargs,
**test_config.init_kwargs,
)
yield plugin_instance

Expand Down
2 changes: 1 addition & 1 deletion src/plugins/analysis/binwalk/test/test_plugin_binwalk.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
'''


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestPluginBinwalk:
def test_signature_analysis(self, analysis_plugin):
test_file = FileObject(file_path=f'{get_test_data_dir()}/container/test.zip')
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/analysis/checksec/test/test_plugin_checksec.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
FILE_PATH_EXE_STRIPPED = PLUGIN_DIR / 'test/data/Hallo_stripped'


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
def test_check_mitigations(analysis_plugin):
test_file = FileObject(file_path=str(FILE_PATH_EXE))
test_file.processed_analysis['file_type'] = {'full': 'ELF 64-bit LSB shared object, x86-64, dynamically linked'}
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/analysis/crypto_hints/test/test_crypto_hints.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
TEST_DATA_DIR = Path(__file__).parent / 'data'


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
def test_additional_rules(analysis_plugin):
test_file = FileObject(file_path=str(TEST_DATA_DIR / 'additional_rules_test_file'))
processed_file = analysis_plugin.process_object(test_file)
Expand All @@ -25,7 +25,7 @@ def test_additional_rules(analysis_plugin):
assert rule in result


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
def test_basic_scan_feature(analysis_plugin):
test_file = FileObject(file_path=str(TEST_DATA_DIR / 'CRC32_table'))
processed_file = analysis_plugin.process_object(test_file)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def _rule_match(analysis_plugin, filename, expected_rule_name, expected_number_o
), f'Expected rule {expected_rule_name} missing'


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestCryptoMaterial:
# pylint:disable=no-self-use
def test_gnupg(self, analysis_plugin):
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/analysis/cve_lookup/test/test_cve_lookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def test_search_cve_summary(monkeypatch):
assert MATCHED_SUMMARY == actual_match


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestCveLookup:
def test_process_object(self, analysis_plugin):
TEST_FW.processed_analysis['software_components'] = SOFTWARE_COMPONENTS_ANALYSIS_RESULT
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/analysis/cwe_checker/test/test_cwe_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from ..code.cwe_checker import AnalysisPlugin


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestCweCheckerFunctions:
def test_parse_cwe_checker_output(self, analysis_plugin):
test_data = """[
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/analysis/device_tree/test/test_device_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
TEST_BROKEN = TEST_DATA / 'broken.dtb'


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
def test_process_object(analysis_plugin):
test_object = FileObject()
test_object.processed_analysis['file_type'] = {'mime': 'linux/device-tree'}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def stub_object():
return test_object


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestElfAnalysis:
@pytest.mark.parametrize(
'tag, tag_color',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def file_system_metadata_plugin(analysis_plugin):
yield analysis_plugin


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestFileSystemMetadata:
test_file_tar = TEST_DATA_DIR / 'test.tar'
test_file_fs = TEST_DATA_DIR / 'squashfs.img'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from ..code.file_type import AnalysisPlugin


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
def test_detect_type_of_file(analysis_plugin):
test_file = FileObject(file_path=f'{get_test_data_dir()}/container/test.zip')
test_file = analysis_plugin.process_object(test_file)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
TEST_DATA = Path(get_test_data_dir())


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestHardwareAnalysis:
def test_cpu_architecture_found(self, analysis_plugin):
test_object = FileObject()
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/analysis/hash/test/test_plugin_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
}
},
)
@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestAnalysisPluginHash:
def test_all_hashes(self, analysis_plugin):
result = analysis_plugin.process_object(MockFileObject()).processed_analysis[analysis_plugin.NAME]
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/analysis/hashlookup/test/test_hashlookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def file_object(monkeypatch):
return test_file


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestHashlookup:
def test_process_object_unknown_hash(self, analysis_plugin, file_object):
file_object.processed_analysis['file_hashes'] = {'sha256': file_object.sha256}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
TEST_DATA_DIR = Path(__file__).parent / 'data'


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestAnalysisPluginInformationLeaks:
def test_find_path(self, analysis_plugin):
fo = MockFileObject()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def _get_fo(path):
return fo


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestAnalysisPluginInit:
test_file_not_text = FileObject(file_path=f'{_test_init_dir}etc/systemd/system/foobar')
test_file_not_text.processed_analysis['file_type'] = {'mime': 'application/zip'}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
TEST_FILE_DIR = Path(__file__).parent / 'data'


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestAnalysisPluginInputVectors:
def test_process_object_inputs(self, analysis_plugin):
result = self.assert_process_object(analysis_plugin, 'test_fgets.elf')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def test_white_ip_and_uris(input_list, whitelist, expected_output):
assert sorted(AnalysisPlugin.whitelist_ip_and_uris(whitelist, input_list)) == expected_output


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestAnalysisPluginInterestingUris:
def test_process_object(self, analysis_plugin):
fo = create_test_file_object()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def ip_and_uri_finder_plugin(analysis_plugin):
yield analysis_plugin


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestAnalysisPluginIpAndUriFinder:
def test_process_object_ips(self, ip_and_uri_finder_plugin):
with tempfile.NamedTemporaryFile() as tmp:
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/analysis/ipc/test/test_ipc_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
}


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
@pytest.mark.parametrize(
'test_file, expected_result, expected_summary',
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
TEST_DATA_DIR = Path(__file__).parent / 'data'


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class ExtractIKConfigTest:
def test_probably_kernel_config_true(self, analysis_plugin):
test_file = FileObject(file_path=str(TEST_DATA_DIR / 'configs/CONFIG'))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
TEST_DATA_DIR = os.path.join(get_dir_of_file(__file__), 'data')


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestAnalysisPluginsKnownVulnerabilities:
_software_components_result = json.loads((Path(TEST_DATA_DIR) / 'sc.json').read_text())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def test_object():
return create_test_file_object()


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestSourceCodeAnalysis:
def test_process_object_not_supported(self, analysis_plugin, test_object, monkeypatch):
monkeypatch.setattr(
Expand Down
9 changes: 7 additions & 2 deletions src/plugins/analysis/qemu_exec/test/test_plugin_qemu_exec.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from requests.exceptions import ConnectionError as RequestConnectionError
from requests.exceptions import ReadTimeout

from conftest import AnalysisPluginTestConfig
from test.common_helper import TEST_FW, create_test_firmware, get_test_data_dir
from test.mock import mock_patch

Expand Down Expand Up @@ -83,8 +84,12 @@ def execute_docker_error(monkeypatch):
monkeypatch.setattr('docker.client.from_env', DockerClientMock)


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.plugin_init_kwargs(unpacker=MockUnpacker())
@pytest.mark.AnalysisPluginTestConfig(
AnalysisPluginTestConfig(
plugin_class=AnalysisPlugin,
init_kwargs={'unpacker': MockUnpacker()},
),
)
class TestPluginQemuExec:
def test_has_relevant_type(self, analysis_plugin):
assert analysis_plugin._has_relevant_type(None) is False
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
TEST_DATA_DIR = os.path.join(get_dir_of_file(__file__), 'data')


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestAnalysisPluginsSoftwareComponents:
def test_process_object(self, analysis_plugin):
test_file = FileObject(file_path=os.path.join(TEST_DATA_DIR, 'yara_test_file'))
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/analysis/string_evaluation/test/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from ..code.string_eval import AnalysisPlugin


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
def test_find_strings(analysis_plugin):
fo = create_test_file_object()
fo.processed_analysis['printable_strings'] = dict(
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/analysis/strings/test/test_plugin_strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
}
},
)
@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestAnalysisPlugInPrintableStrings:
strings = ['first string', 'second<>_$tring!', 'third:?-+012345/\\string']
offsets = [(3, strings[0]), (21, strings[1]), (61, strings[2])]
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/analysis/tlsh/test/test_plugin_tlsh.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def tlsh_plugin(analysis_plugin, monkeypatch):
yield analysis_plugin


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestTlsh:
def test_one_matching_file(self, tlsh_plugin, test_object):

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
TEST_DATA_DIR = Path(__file__).parent / 'data'


@pytest.mark.AnalysisPluginClass.with_args(AnalysisPlugin)
@pytest.mark.AnalysisPluginTestConfig(dict(plugin_class=AnalysisPlugin))
class TestAnalysisPluginPasswordFileAnalyzer:
def test_process_object_shadow_file(self, analysis_plugin):
test_file = FileObject(file_path=str(TEST_DATA_DIR / 'passwd_test'))
Expand Down
Loading

0 comments on commit cf4f92a

Please sign in to comment.