Writing Analyzers
To write an OFRAK Analyzer, a contributor needs to:
- Create a class that inherits from
ofrak.component.component_analyzer.Analyzer
with a definedofrak.model.component_model.CC
andofrak.component.component_analyzer.AnalyzerReturnType
; - Implement the
targets
andoutputs
to indicate what resource tags the analyzer targets and what attributes it returns (the idiomatic OFRAK way to do this is to use a field); - Implement the
analyze
method such that it performs analysis and returns the definedAnalyzerReturnType
.
The following is an example of a fully-implemented OFRAK Analyzer.
from dataclasses import dataclass
from ofrak.component.analyzer import Analyzer
from ofrak.model.resource_model import ResourceAttributes
from ofrak.resource import Resource
from ofrak.resource_view import ResourceView
@dataclass
class Foo(ResourceView):
pass
@dataclass(**ResourceAttributes.DATACLASS_PARAMS)
class FooBar(ResourceAttributes):
foobar: str
class FooAnalyzer(Analyzer[None, FooBar]):
targets = (Foo,)
outputs = FooBar
async def analyze(self, resource: Resource, config=None) -> FooBar:
return FooBar("foobar!")
Handling External Dependencies
If the Analyzer makes use of tools that are not packaged in modules installable via pip
from
PyPI (commonly command-line tools), these dependencies must be explicitly declared as part of the
analyzer's class declaration. See the Components Using External Tools doc for
information on how to do that.
Testing Analyzers
test_ofrak.unit.analyzer.analyzer_test_case.AnalyzerTests
provides a suite of test cases that can be used to test implemented OFRAK Analyzers.
To use this test suite, a contributor should create a file, e.g. test_foo_analyzer.py
, which should include the following:
- An implementation of a subclass of
AnalyzerTestCase
which contains assets needed for the test: in addition to the analyzer type and expected analyzer return type, this often includes the bytes needed to create the resource. For example,FooAnalyzerTestCase
. - A subclass of
PopulatedAnalyzerTestCase
for the given analyzer. For example,PopulatedFooAnalyzerTestCase
. - A parametrized fixture titled
test_case
which returns the populated test cases. - A subclass of
AnalyzerTests
, i.e.TestFooAnalyzer
.
See the following for an example:
from dataclasses import dataclass
import pytest
from ofrak import OFRAKContext
from ofrak.resource import Resource
from test_ofrak.unit.analyzer.analyzer_test_case import (
AnalyzerTestCase,
PopulatedAnalyzerTestCase,
AnalyzerTests,
)
@dataclass
class FooAnalyzerTestCase(AnalyzerTestCase):
resource_contents: bytes
@dataclass
class PopulatedFooAnalyzerTestCase(PopulatedAnalyzerTestCase, FooAnalyzerTestCase):
ofrak_context: OFRAKContext
resource: Resource
def get_analyzer(self):
return self.ofrak_context.component_locator.get_by_type(self.analyzer_type)
@pytest.fixture(
params=[
FooAnalyzerTestCase(FooAnalyzer, (FooBar("foobar!"),), b"Hello world\n")
]
)
async def test_case(
request, ofrak_context: OFRAKContext, test_id: str
) -> PopulatedFooAnalyzerTestCase:
test_case: FooAnalyzerTestCase = request.param
resource = await ofrak_context.create_root_resource(test_id, test_case.resource_contents)
return PopulatedFooAnalyzerTestCase(
test_case.analyzer_type,
test_case.expected_result,
test_case.resource_contents,
ofrak_context,
resource,
)
class TestFooAnalyzer(AnalyzerTests):
pass
When pytest runs this file, it will run all of the tests in AnalyzerTests
with the input provided from the parameterized test_case
fixture in this file.
See test_ofrak.unit.analyzer.test_magic_analyzer
for another example.