From 0db0d2387e8ec294fbe4f19079b1d44190a72c30 Mon Sep 17 00:00:00 2001 From: Evana Gizzi Date: Mon, 9 Oct 2023 17:02:07 -0400 Subject: [PATCH] Started building out the interface for custom complex reasoning plug ins. Closes #55, this depends on PR #53 --- .../reasoning_plugin_abstract/__init__.py | 0 .../reasoning_plugin_abstract/core.py | 41 ++++++ .../{test_core.py => test_AI_plugin_core.py} | 0 .../test_reasoning_plugin_core.py | 121 ++++++++++++++++++ 4 files changed, 162 insertions(+) create mode 100644 onair/src/reasoning/reasoning_plugin_abstract/__init__.py create mode 100644 onair/src/reasoning/reasoning_plugin_abstract/core.py rename test/onair/src/ai_components/ai_plugin_abstract/{test_core.py => test_AI_plugin_core.py} (100%) create mode 100644 test/onair/src/reasoning/reasoning_plugin_abstract/test_reasoning_plugin_core.py diff --git a/onair/src/reasoning/reasoning_plugin_abstract/__init__.py b/onair/src/reasoning/reasoning_plugin_abstract/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/onair/src/reasoning/reasoning_plugin_abstract/core.py b/onair/src/reasoning/reasoning_plugin_abstract/core.py new file mode 100644 index 00000000..45cd372f --- /dev/null +++ b/onair/src/reasoning/reasoning_plugin_abstract/core.py @@ -0,0 +1,41 @@ +# GSC-19165-1, "The On-Board Artificial Intelligence Research (OnAIR) Platform" +# +# Copyright © 2023 United States Government as represented by the Administrator of +# the National Aeronautics and Space Administration. No copyright is claimed in the +# United States under Title 17, U.S. Code. All Other Rights Reserved. +# +# Licensed under the NASA Open Source Agreement version 1.3 +# See "NOSA GSC-19165-1 OnAIR.pdf" + +from abc import ABC, abstractmethod +"""This object serves as a proxy for all complex plug-ins. + Therefore, the ReasoningPlugIn object is meant to induce + standards and structures of compliance for user-created + and/or imported plug-ins/libraries +""" +class ReasoningPlugIn(ABC): + def __init__(self, _name, _headers): + """ + Superclass for custom/complex reasoning plugins that users can write. + Appropriate for instances where intelligence may leverage many forms of + AI plugins in a non-trivial way. Also, allows for easier modularity. + """ + assert(len(_headers)>0) + self.component_name = _name + self.headers = _headers + + @abstractmethod + def update(self, frame=[], high_level_info=[]): + """ + Given streamed data point and other sources of high-level info, + system should update internally + """ + raise NotImplementedError + + @abstractmethod + def render_reasoning(self): + """ + System should return its diagnosis + """ + raise NotImplementedError + diff --git a/test/onair/src/ai_components/ai_plugin_abstract/test_core.py b/test/onair/src/ai_components/ai_plugin_abstract/test_AI_plugin_core.py similarity index 100% rename from test/onair/src/ai_components/ai_plugin_abstract/test_core.py rename to test/onair/src/ai_components/ai_plugin_abstract/test_AI_plugin_core.py diff --git a/test/onair/src/reasoning/reasoning_plugin_abstract/test_reasoning_plugin_core.py b/test/onair/src/reasoning/reasoning_plugin_abstract/test_reasoning_plugin_core.py new file mode 100644 index 00000000..245fd2b7 --- /dev/null +++ b/test/onair/src/reasoning/reasoning_plugin_abstract/test_reasoning_plugin_core.py @@ -0,0 +1,121 @@ +# GSC-19165-1, "The On-Board Artificial Intelligence Research (OnAIR) Platform" +# +# Copyright © 2023 United States Government as represented by the Administrator of +# the National Aeronautics and Space Administration. No copyright is claimed in the +# United States under Title 17, U.S. Code. All Other Rights Reserved. +# +# Licensed under the NASA Open Source Agreement version 1.3 +# See "NOSA GSC-19165-1 OnAIR.pdf" + +""" Test Generic Component Core (abstract class) Functionality """ +import pytest +from mock import MagicMock + +import onair.src.reasoning.reasoning_plugin_abstract.core as core +from onair.src.reasoning.reasoning_plugin_abstract.core import ReasoningPlugIn + +class FakeReasoningPlugIn(ReasoningPlugIn): + def __init__(self, _name, _headers): + return super().__init__(_name, _headers) + + def update(self): + return None + + def render_reasoning(self): + return dict() + +class IncompleteFakeReasoningPlugIn(ReasoningPlugIn): + def __init__(self, _name, _headers): + return super().__init__(_name, _headers) + +class BadFakeReasoningPlugIn(ReasoningPlugIn): + def __init__(self, _name, _headers): + return super().__init__(_name, _headers) + + def update(self): + return super().update() + + def render_reasoning(self): + return super().render_reasoning() + +# abstract methods tests +def test_ReasoningPlugIn_raises_error_because_of_unimplemented_abstract_methods(): + # Arrange - None + # Act + with pytest.raises(TypeError) as e_info: + cut = ReasoningPlugIn.__new__(ReasoningPlugIn) + + # Assert + assert "Can't instantiate abstract class ReasoningPlugIn with" in e_info.__str__() + assert "update" in e_info.__str__() + assert "render_reasoning" in e_info.__str__() + +# Incomplete plugin call tests +def test_ReasoningPlugIn_raises_error_when_an_inherited_class_is_instantiated_because_abstract_methods_are_not_implemented_by_that_class(): + # Arrange - None + # Act + with pytest.raises(TypeError) as e_info: + cut = IncompleteFakeReasoningPlugIn.__new__(IncompleteFakeReasoningPlugIn) + + # Assert + assert "Can't instantiate abstract class IncompleteFakeReasoningPlugIn with" in e_info.__str__() + assert "update" in e_info.__str__() + assert "render_reasoning" in e_info.__str__() + +def test_ReasoningPlugIn_raises_error_when_an_inherited_class_calls_abstract_methods_in_parent(): + # Act + cut = BadFakeReasoningPlugIn.__new__(BadFakeReasoningPlugIn) + + # populate list with the functions that should raise exceptions when called. + not_implemented_functions = [cut.update, cut.render_reasoning] + for fnc in not_implemented_functions: + with pytest.raises(NotImplementedError) as e_info: + fnc() + assert "NotImplementedError" in e_info.__str__() + +# Complete plugin call tests +def test_ReasoningPlugIn_does_not_raise_error_when_an_inherited_class_is_instantiated_because_abstract_methods_are_implemented_by_that_class(): + # Arrange + exception_raised = False + try: + fake_ic = FakeReasoningPlugIn.__new__(FakeReasoningPlugIn) + except: + exception_raised = True + + # Assert + assert exception_raised == False + +# Complete plugin call tests + +# __init__ tests +def test_ReasoningPlugIn__init__raises_assertion_error_when_given__headers_len_is_not_greater_than_0(): + # Arrange + arg__name = MagicMock() + arg__headers = [] + + cut = FakeReasoningPlugIn.__new__(FakeReasoningPlugIn) + + # Act + with pytest.raises(AssertionError) as e_info: + cut.__init__(arg__name, arg__headers) + + # Assert + assert e_info.match('') + +def test_ReasoningPlugIn__init__sets_instance_values_to_given_args_when_given__headers_len_is_greater_than_0(mocker): + # Arrange + arg__name = MagicMock() + arg__headers = MagicMock() + + cut = FakeReasoningPlugIn.__new__(FakeReasoningPlugIn) + + mocker.patch(core.__name__ + '.len', return_value=pytest.gen.randint(1, 200)) # arbitrary, from 1 to 200 (but > 0) + + # Act + cut.__init__(arg__name, arg__headers) + + # Assert + assert core.len.call_count == 1 + assert core.len.call_args_list[0].args == (arg__headers,) + assert cut.component_name == arg__name + assert cut.headers == arg__headers