Coverage for test/unittests/test_skill_manager.py: 98%
107 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-17 13:44 +0000
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-17 13:44 +0000
1# Copyright 2019 Mycroft AI Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14#
15import tempfile
16from copy import deepcopy
17from pathlib import Path
18from shutil import rmtree
19from unittest import TestCase
20from unittest.mock import Mock, patch
22from ovos_bus_client.message import Message
23from ovos_config import Configuration
24from ovos_config import LocalConf, DEFAULT_CONFIG
25from ovos_core.skill_manager import SkillManager
26from ovos_workshop.skill_launcher import SkillLoader
29class MessageBusMock:
30 """Replaces actual message bus calls in unit tests.
32 The message bus should not be running during unit tests so mock it
33 out in a way that makes it easy to test code that calls it.
34 """
36 def __init__(self):
37 self.message_types = []
38 self.message_data = []
39 self.event_handlers = []
41 def emit(self, message):
42 self.message_types.append(message.msg_type)
43 self.message_data.append(message.data)
45 def on(self, event, _):
46 self.event_handlers.append(event)
48 def once(self, event, _):
49 self.event_handlers.append(event)
51 def wait_for_response(self, message):
52 self.emit(message)
55def mock_config():
56 """Supply a reliable return value for the Configuration.get() method."""
57 config = deepcopy(LocalConf(DEFAULT_CONFIG))
58 config['skills']['priority_skills'] = ['foobar']
59 config['data_dir'] = str(tempfile.mkdtemp())
60 config['enclosure'] = {}
61 return config
64@patch.dict(Configuration._Configuration__patch, mock_config())
65class TestSkillManager(TestCase):
66 mock_package = 'ovos_core.skill_manager.'
68 def setUp(self):
69 temp_dir = tempfile.mkdtemp()
70 self.temp_dir = Path(temp_dir)
71 self.message_bus_mock = MessageBusMock()
72 self._mock_log()
73 self.skill_manager = SkillManager(self.message_bus_mock)
74 self._mock_skill_loader_instance()
76 def _mock_log(self):
77 log_patch = patch(self.mock_package + 'LOG')
78 self.addCleanup(log_patch.stop)
79 self.log_mock = log_patch.start()
81 def tearDown(self):
82 rmtree(str(self.temp_dir))
84 def _mock_skill_loader_instance(self):
85 self.skill_dir = self.temp_dir.joinpath('test_skill')
86 self.skill_loader_mock = Mock(spec=SkillLoader)
87 self.skill_loader_mock.instance = Mock()
88 self.skill_loader_mock.instance.default_shutdown = Mock()
89 self.skill_loader_mock.instance.converse = Mock()
90 self.skill_loader_mock.instance.converse.return_value = True
91 self.skill_loader_mock.skill_id = 'test_skill'
92 self.skill_manager.plugin_skills = {
93 str(self.skill_dir): self.skill_loader_mock
94 }
96 def test_instantiate(self):
97 expected_result = [
98 'skillmanager.list',
99 'skillmanager.deactivate',
100 'skillmanager.keep',
101 'skillmanager.activate',
102 #'mycroft.skills.initialized',
103 'mycroft.network.connected',
104 'mycroft.internet.connected',
105 'mycroft.gui.available',
106 'mycroft.network.disconnected',
107 'mycroft.internet.disconnected',
108 'mycroft.gui.unavailable',
109 'mycroft.skills.is_alive',
110 'mycroft.skills.is_ready',
111 'mycroft.skills.all_loaded'
112 ]
114 self.assertListEqual(expected_result,
115 self.message_bus_mock.event_handlers)
118 def test_send_skill_list(self):
119 self.skill_loader_mock.active = True
120 self.skill_loader_mock.loaded = True
121 self.skill_manager.send_skill_list(None)
123 self.assertListEqual(
124 ['mycroft.skills.list'],
125 self.message_bus_mock.message_types
126 )
127 message_data = self.message_bus_mock.message_data[-1]
128 self.assertIn('test_skill', message_data.keys())
129 skill_data = message_data['test_skill']
130 self.assertDictEqual(dict(active=True, id='test_skill'), skill_data)
132 def test_stop(self):
133 self.skill_manager.stop()
135 self.assertTrue(self.skill_manager._stop_event.is_set())
136 instance = self.skill_loader_mock.instance
137 instance.default_shutdown.assert_called_once_with()
139 def test_deactivate_skill(self):
140 message = Message("test.message", {'skill': 'test_skill'})
141 message.response = Mock()
142 self.skill_manager.deactivate_skill(message)
143 self.skill_loader_mock.deactivate.assert_called_once()
144 message.response.assert_called_once()
146 def test_deactivate_except(self):
147 message = Message("test.message", {'skill': 'test_skill'})
148 message.response = Mock()
149 self.skill_loader_mock.active = True
150 foo_skill_loader = Mock(spec=SkillLoader)
151 foo_skill_loader.skill_id = 'foo'
152 foo2_skill_loader = Mock(spec=SkillLoader)
153 foo2_skill_loader.skill_id = 'foo2'
154 test_skill_loader = Mock(spec=SkillLoader)
155 test_skill_loader.skill_id = 'test_skill'
156 self.skill_manager.plugin_skills['foo'] = foo_skill_loader
157 self.skill_manager.plugin_skills['foo2'] = foo2_skill_loader
158 self.skill_manager.plugin_skills['test_skill'] = test_skill_loader
160 self.skill_manager.deactivate_except(message)
161 foo_skill_loader.deactivate.assert_called_once()
162 foo2_skill_loader.deactivate.assert_called_once()
163 self.assertFalse(test_skill_loader.deactivate.called)
165 def test_activate_skill(self):
166 message = Message("test.message", {'skill': 'test_skill'})
167 message.response = Mock()
168 test_skill_loader = Mock(spec=SkillLoader)
169 test_skill_loader.skill_id = 'test_skill'
170 test_skill_loader.active = False
172 self.skill_manager.plugin_skills = {}
173 self.skill_manager.plugin_skills['test_skill'] = test_skill_loader
175 self.skill_manager.activate_skill(message)
176 test_skill_loader.activate.assert_called_once()
177 message.response.assert_called_once()