test_extensions.py 8.41 KB
Newer Older
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
1
from __future__ import print_function
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
2
import os
3
from copy import deepcopy
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
4
import logging
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
5

6 7 8 9 10
try:
    from unittest import mock
except ImportError:
    import mock

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
11
from functools import partial
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
12
from senpy.extensions import Senpy
13
from senpy import plugins
14
from senpy.models import Error, Results, Entry, EmotionSet, Emotion, Plugin
15
from senpy import api
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
16
from flask import Flask
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
17
from unittest import TestCase
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
18 19


20 21 22 23 24
def analyse(instance, **kwargs):
    request = api.parse_call(kwargs)
    return instance.analyse(request)


J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
25
class ExtensionsTest(TestCase):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
26
    def setUp(self):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
27
        self.app = Flask('test_extensions')
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
28 29
        self.examples_dir = os.path.join(os.path.dirname(__file__), '..', 'example-plugins')
        self.senpy = Senpy(plugin_folder=self.examples_dir,
30 31
                           app=self.app,
                           default_plugins=False)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
32
        self.senpy.deactivate_all()
33
        self.senpy.activate_plugin("Dummy", sync=True)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
34 35 36 37 38

    def test_init(self):
        """ Initialising the app with the extension.  """
        assert hasattr(self.app, "senpy")
        tapp = Flask("temp app")
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
39
        self.senpy.init_app(tapp)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
40 41 42 43
        assert hasattr(tapp, "senpy")

    def test_discovery(self):
        """ Discovery of plugins in given folders.  """
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
44
        # noinspection PyProtectedMember
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
45 46 47 48 49
        print(self.senpy.plugins())
        assert self.senpy.get_plugin("dummy")

    def test_add_delete(self):
        '''Should be able to add and delete new plugins. '''
50
        new = plugins.Analysis(name='new', description='new', version=0)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
51 52 53 54 55 56 57 58 59 60 61 62 63 64
        self.senpy.add_plugin(new)
        assert new in self.senpy.plugins()
        self.senpy.delete_plugin(new)
        assert new not in self.senpy.plugins()

    def test_adding_folder(self):
        """ It should be possible for senpy to look for plugins in more folders. """
        senpy = Senpy(plugin_folder=None,
                      app=self.app,
                      default_plugins=False)
        assert not senpy.analysis_plugins
        senpy.add_folder(self.examples_dir)
        assert senpy.analysis_plugins
        self.assertRaises(AttributeError, senpy.add_folder, 'DOES NOT EXIST')
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
65

66 67
    def test_installing(self):
        """ Installing a plugin """
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
68 69
        info = {
            'name': 'TestPip',
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
70
            'module': 'mynoop',
71
            'description': None,
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
72 73
            'requirements': ['noop'],
            'version': 0
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
74
        }
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
75
        module = plugins.from_info(info, root=self.examples_dir, install=True)
76
        assert module.name == 'TestPip'
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
77 78
        assert module
        import noop
79
        dir(noop)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
80

81
    def test_enabling(self):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
82
        """ Enabling a plugin """
83
        self.senpy.activate_all(sync=True)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
84 85
        assert len(self.senpy.plugins()) >= 3
        assert self.senpy.get_plugin("Sleep").is_activated
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
86

87 88 89 90 91 92 93 94 95 96
    def test_installing_nonexistent(self):
        """ Fail if the dependencies cannot be met """
        info = {
            'name': 'TestPipFail',
            'module': 'dummy',
            'description': None,
            'requirements': ['IAmMakingThisPackageNameUpToFail'],
            'version': 0
        }
        with self.assertRaises(Error):
97
            plugins.install_deps(info)
98

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
99 100
    def test_disabling(self):
        """ Disabling a plugin """
101
        self.senpy.deactivate_all(sync=True)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
102 103
        assert not self.senpy.get_plugin("dummy").is_activated
        assert not self.senpy.get_plugin("sleep").is_activated
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
104 105 106 107

    def test_default(self):
        """ Default plugin should be set """
        assert self.senpy.default_plugin
108 109 110 111 112 113 114 115
        assert self.senpy.default_plugin.name == "Dummy"
        self.senpy.deactivate_all(sync=True)
        logging.debug("Default: {}".format(self.senpy.default_plugin))
        assert self.senpy.default_plugin is None

    def test_noplugin(self):
        """ Don't analyse if there isn't any plugin installed """
        self.senpy.deactivate_all(sync=True)
116
        self.assertRaises(Error, partial(analyse, self.senpy, input="tupni"))
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
117 118 119

    def test_analyse(self):
        """ Using a plugin """
120 121
        # I was using mock until plugin started inheriting
        # Leaf (defaultdict with  __setattr__ and __getattr__.
122 123
        r1 = analyse(self.senpy, algorithm="Dummy", input="tupni", output="tuptuo")
        r2 = analyse(self.senpy, input="tupni", output="tuptuo")
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
124 125
        assert r1.analysis[0] == "endpoint:plugins/Dummy_0.1"
        assert r2.analysis[0] == "endpoint:plugins/Dummy_0.1"
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
126
        assert r1.entries[0]['nif:isString'] == 'input'
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
127

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
128 129 130 131 132 133 134 135 136 137 138
    def test_analyse_empty(self):
        """ Trying to analyse when no plugins are installed should raise an error."""
        senpy = Senpy(plugin_folder=None,
                      app=self.app,
                      default_plugins=False)
        self.assertRaises(Error, senpy.analyse, Results())

    def test_analyse_wrong(self):
        """ Trying to analyse with a non-existent plugin should raise an error."""
        self.assertRaises(Error, analyse, self.senpy, algorithm='DOES NOT EXIST', input='test')

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
139 140 141 142 143 144 145
    def test_analyse_jsonld(self):
        """ Using a plugin with JSON-LD input"""
        js_input = '''{
        "@id": "prueba",
        "@type": "results",
        "entries": [
          {"@id": "entry1",
146
           "nif:isString": "tupni",
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
147 148 149 150
           "@type": "entry"
          }
        ]
        }'''
151 152 153 154 155 156 157 158
        r1 = analyse(self.senpy,
                     algorithm="Dummy",
                     input=js_input,
                     informat="json-ld",
                     output="tuptuo")
        r2 = analyse(self.senpy,
                     input="tupni",
                     output="tuptuo")
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
159 160
        assert r1.analysis[0] == "endpoint:plugins/Dummy_0.1"
        assert r2.analysis[0] == "endpoint:plugins/Dummy_0.1"
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
161
        assert r1.entries[0]['nif:isString'] == 'input'
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
162

163 164
    def test_analyse_error(self):
        mm = mock.MagicMock()
165
        mm.id = 'magic_mock'
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
166
        mm.name = 'mock'
167
        mm.is_activated = True
168
        mm.analyse_entries.side_effect = Error('error in analysis', status=500)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
169
        self.senpy.add_plugin(mm)
170
        try:
171
            analyse(self.senpy, input='nothing', algorithm='MOCK')
172 173
            assert False
        except Error as ex:
174
            assert 'error in analysis' in ex['message']
175 176
            assert ex['status'] == 500

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
177 178 179
        ex = Exception('generic exception on analysis')
        mm.analyse.side_effect = ex
        mm.analyse_entries.side_effect = ex
180 181

        try:
182
            analyse(self.senpy, input='nothing', algorithm='MOCK')
183
            assert False
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
184
        except Exception as ex:
185
            assert 'generic exception on analysis' in str(ex)
186

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
187 188
    def test_filtering(self):
        """ Filtering plugins """
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
189 190 191
        assert len(self.senpy.plugins(name="Dummy")) > 0
        assert not len(self.senpy.plugins(name="NotDummy"))
        assert self.senpy.plugins(name="Dummy", is_activated=True)
192
        self.senpy.deactivate_plugin("Dummy", sync=True)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
193 194
        assert not len(self.senpy.plugins(name="Dummy",
                                          is_activated=True))
195 196

    def test_load_default_plugins(self):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
197
        senpy = Senpy(plugin_folder=self.examples_dir, default_plugins=True)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
198
        assert len(senpy.plugins()) > 1
199 200

    def test_convert_emotions(self):
201
        self.senpy.activate_all(sync=True)
202
        plugin = Plugin({
203 204
            'id': 'imaginary',
            'onyx:usesEmotionModel': 'emoml:fsre-dimensions'
205
        })
206
        eSet1 = EmotionSet()
207
        eSet1.prov__wasGeneratedBy = plugin['@id']
208 209 210 211 212 213
        eSet1['onyx:hasEmotion'].append(Emotion({
            'emoml:arousal': 1,
            'emoml:potency': 0,
            'emoml:valence': 0
        }))
        response = Results({
214
            'analysis': [{'plugin': plugin}],
215
            'entries': [Entry({
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
216
                'nif:isString': 'much ado about nothing',
217 218 219 220 221 222
                'emotions': [eSet1]
            })]
        })
        params = {'emotionModel': 'emoml:big6',
                  'conversion': 'full'}
        r1 = deepcopy(response)
223 224
        r1.parameters = params
        self.senpy.convert_emotions(r1)
225 226 227
        assert len(r1.entries[0].emotions) == 2
        params['conversion'] = 'nested'
        r2 = deepcopy(response)
228 229
        r2.parameters = params
        self.senpy.convert_emotions(r2)
230 231 232 233
        assert len(r2.entries[0].emotions) == 1
        assert r2.entries[0].emotions[0]['prov:wasDerivedFrom'] == eSet1
        params['conversion'] = 'filtered'
        r3 = deepcopy(response)
234 235
        r3.parameters = params
        self.senpy.convert_emotions(r3)
236
        assert len(r3.entries[0].emotions) == 1
237
        r3.jsonld()