ovoscope 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.1
2
+ Name: ovoscope
3
+ Version: 0.0.1
4
+ Summary: End-to-end test framework for OpenVoiceOS skills
5
+ Home-page: https://github.com/TigreGotico/ovoscope
6
+ Author: JarbasAI
7
+ Author-email: jarbasai@mailfence.com
8
+ License: Apache-2.0
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Testing
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+
17
+ # OvoScope
18
+
19
+ **OvoScope** is an end-to-end testing framework for [OVOS](https://openvoiceos.org) skills.
20
+
21
+ It contains the full core runtime environment using a lightweight in-process `ovos-core`, allowing skill developers to test the full skill message flow, from utterance to intent handling to final bus responses — without launching a full assistant stack.
22
+
23
+ > Like a microscope for your OVOS skills.
24
+
25
+ ---
26
+
27
+ ## Features
28
+
29
+ - Simulates OVOS Core messagebus interactions
30
+ - Sends test `Message` objects and captures responses
31
+ - Verifies message types, data, routing, session handling, and language
32
+ - Automatically flips message direction when configured
33
+ - Designed to integrate cleanly into `unittest` or `pytest` workflows
34
+
35
+ ---
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ pip install ovoscope
41
+ ````
42
+
43
+ ---
44
+
45
+ ## Usage Example
46
+
47
+ Testing scenario of complete intent failure (no skills installed)
48
+
49
+ ```python
50
+ from ovoscope import End2EndTest
51
+ from ovos_bus_client.message import Message
52
+ from ovos_bus_client.session import Session
53
+
54
+ session = Session("test123")
55
+ message = Message("recognizer_loop:utterance",
56
+ {"utterances": ["hello world"]},
57
+ {"session": session.serialize(), "source": "A", "destination": "B"})
58
+
59
+ test = End2EndTest(
60
+ skill_ids=[],
61
+ source_message=message,
62
+ expected_messages=[
63
+ Message("recognizer_loop:utterance", {"utterances": ["hello world"]}, {"session": session.serialize()}),
64
+ Message("complete_intent_failure", {}, {"session": session.serialize()}),
65
+ Message("ovos.utterance.handled", {}, {"session": session.serialize()}),
66
+ ]
67
+ )
68
+
69
+ test.execute()
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Why OvoScope?
75
+
76
+ * Lightweight: No need to launch a full messagebus or audio stack
77
+ * Isolated: Use `FakeBus` and `MiniCroft` for fast, reliable test environments
78
+ * Flexible: Works with any skill that conforms to OVOS skill loading
79
+
80
+ ---
81
+
82
+ ## License
83
+
84
+ [Apache 2.0](LICENSE)
85
+
86
+ ---
87
+
88
+ ## Contributing
89
+
90
+ PRs are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
91
+
92
+
@@ -0,0 +1,76 @@
1
+ # OvoScope
2
+
3
+ **OvoScope** is an end-to-end testing framework for [OVOS](https://openvoiceos.org) skills.
4
+
5
+ It contains the full core runtime environment using a lightweight in-process `ovos-core`, allowing skill developers to test the full skill message flow, from utterance to intent handling to final bus responses — without launching a full assistant stack.
6
+
7
+ > Like a microscope for your OVOS skills.
8
+
9
+ ---
10
+
11
+ ## Features
12
+
13
+ - Simulates OVOS Core messagebus interactions
14
+ - Sends test `Message` objects and captures responses
15
+ - Verifies message types, data, routing, session handling, and language
16
+ - Automatically flips message direction when configured
17
+ - Designed to integrate cleanly into `unittest` or `pytest` workflows
18
+
19
+ ---
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pip install ovoscope
25
+ ````
26
+
27
+ ---
28
+
29
+ ## Usage Example
30
+
31
+ Testing scenario of complete intent failure (no skills installed)
32
+
33
+ ```python
34
+ from ovoscope import End2EndTest
35
+ from ovos_bus_client.message import Message
36
+ from ovos_bus_client.session import Session
37
+
38
+ session = Session("test123")
39
+ message = Message("recognizer_loop:utterance",
40
+ {"utterances": ["hello world"]},
41
+ {"session": session.serialize(), "source": "A", "destination": "B"})
42
+
43
+ test = End2EndTest(
44
+ skill_ids=[],
45
+ source_message=message,
46
+ expected_messages=[
47
+ Message("recognizer_loop:utterance", {"utterances": ["hello world"]}, {"session": session.serialize()}),
48
+ Message("complete_intent_failure", {}, {"session": session.serialize()}),
49
+ Message("ovos.utterance.handled", {}, {"session": session.serialize()}),
50
+ ]
51
+ )
52
+
53
+ test.execute()
54
+ ```
55
+
56
+ ---
57
+
58
+ ## Why OvoScope?
59
+
60
+ * Lightweight: No need to launch a full messagebus or audio stack
61
+ * Isolated: Use `FakeBus` and `MiniCroft` for fast, reliable test environments
62
+ * Flexible: Works with any skill that conforms to OVOS skill loading
63
+
64
+ ---
65
+
66
+ ## License
67
+
68
+ [Apache 2.0](LICENSE)
69
+
70
+ ---
71
+
72
+ ## Contributing
73
+
74
+ PRs are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
75
+
76
+
@@ -0,0 +1,171 @@
1
+ import dataclasses
2
+ import threading
3
+ from time import sleep
4
+ from typing import Union, List
5
+
6
+ from ovos_bus_client.message import Message
7
+ from ovos_bus_client.session import SessionManager, Session
8
+ from ovos_bus_client.util.scheduler import EventScheduler
9
+ from ovos_core.intent_services import IntentService
10
+ from ovos_core.skill_manager import SkillManager
11
+ from ovos_plugin_manager.skills import find_skill_plugins
12
+ from ovos_utils.fakebus import FakeBus
13
+ from ovos_utils.log import LOG
14
+ from ovos_utils.process_utils import ProcessState
15
+
16
+
17
+
18
+ class MiniCroft(SkillManager):
19
+ def __init__(self, skill_ids, *args, **kwargs):
20
+ bus = FakeBus()
21
+ super().__init__(bus, *args, **kwargs)
22
+ self.skill_ids = skill_ids
23
+ self.intent_service = IntentService(self.bus)
24
+ self.scheduler = EventScheduler(bus, schedule_file="/tmp/schetest.json")
25
+
26
+ def load_metadata_transformers(self, cfg):
27
+ self.intent_service.metadata_plugins.config = cfg
28
+ self.intent_service.metadata_plugins.load_plugins()
29
+
30
+ def load_plugin_skills(self):
31
+ LOG.info("loading skill plugins")
32
+ plugins = find_skill_plugins()
33
+ for skill_id, plug in plugins.items():
34
+ if skill_id not in self.skill_ids:
35
+ continue
36
+ if skill_id not in self.plugin_skills:
37
+ self._load_plugin_skill(skill_id, plug)
38
+
39
+ def run(self):
40
+ """Load skills and mark core as ready to start tests"""
41
+ self.status.set_alive()
42
+ self.load_plugin_skills()
43
+ self.status.set_ready()
44
+ LOG.info("Skills all loaded!")
45
+
46
+ def stop(self):
47
+ super().stop()
48
+ self.scheduler.shutdown()
49
+ SessionManager.bus = None
50
+ SessionManager.sessions = {}
51
+ SessionManager.default_session = SessionManager.sessions["default"] = Session("default")
52
+
53
+
54
+ def get_minicroft(skill_ids: Union[List[str], str]):
55
+ if isinstance(skill_ids, str):
56
+ skill_ids = [skill_ids]
57
+ assert isinstance(skill_ids, list)
58
+ croft1 = MiniCroft(skill_ids)
59
+ croft1.start()
60
+ while croft1.status.state != ProcessState.READY:
61
+ sleep(0.2)
62
+ return croft1
63
+
64
+
65
+ @dataclasses.dataclass()
66
+ class End2EndTest:
67
+ skill_ids: List[str] # skill_ids to load during the test
68
+ source_message: Message # starts the test
69
+ expected_messages: List[Message] # tests are performed against message list
70
+
71
+ # if received, end message capture
72
+ eof_msgs: List[str] = dataclasses.field(default_factory=lambda: ["ovos.utterance.handled"])
73
+
74
+ # messages after which source and destination flip in the message.context
75
+ flip_points: List[str] = dataclasses.field(default_factory=lambda: ["recognizer_loop:utterance"])
76
+
77
+ # test assertions to run
78
+ test_session_lang: bool = True
79
+ test_session_pipeline: bool = True
80
+ test_msg_type: bool = True
81
+ test_msg_data: bool = True
82
+ test_msg_context: bool = True
83
+ test_routing: bool = True
84
+
85
+ def capture_messages(self, timeout=20) -> List[Message]:
86
+
87
+ test_message = self.source_message
88
+
89
+ responses = []
90
+ done = threading.Event()
91
+
92
+ def handle_message(msg: str):
93
+ nonlocal responses
94
+ if done.is_set():
95
+ return
96
+ msg = Message.deserialize(msg)
97
+ responses.append(msg)
98
+
99
+ def handle_end_of_test(msg: Message):
100
+ done.set()
101
+
102
+ minicroft = get_minicroft(self.skill_ids)
103
+
104
+ minicroft.bus.on("message", handle_message)
105
+ for m in self.eof_msgs:
106
+ minicroft.bus.on(m, handle_end_of_test)
107
+
108
+ minicroft.bus.emit(test_message)
109
+ done.wait(timeout)
110
+
111
+ minicroft.stop()
112
+
113
+ return responses
114
+
115
+ def execute(self, timeout=30):
116
+ e_src = self.source_message.context.get("source")
117
+ e_dst = self.source_message.context.get("destination")
118
+ messages = self.capture_messages(timeout)
119
+ for expected, received in zip(self.expected_messages, messages):
120
+ sess_e = SessionManager.get(expected)
121
+ sess_r = SessionManager.get(received)
122
+ if self.test_msg_type:
123
+ assert expected.msg_type == received.msg_type
124
+ if self.test_msg_data:
125
+ for k, v in expected.data.items():
126
+ assert received.data[k] == v
127
+ if self.test_msg_context:
128
+ for k, v in expected.context.items():
129
+ assert received.context[k] == v
130
+ if self.test_routing:
131
+ r_src = received.context.get("source")
132
+ r_dst = received.context.get("destination")
133
+ assert e_src == r_src
134
+ assert e_dst == r_dst
135
+ if expected.msg_type in self.flip_points:
136
+ e_src, e_dst = e_dst, e_src
137
+
138
+ if self.test_session_lang:
139
+ assert sess_e.lang == sess_r.lang
140
+ if self.test_session_pipeline:
141
+ assert sess_e.pipeline == sess_e.pipeline
142
+
143
+ if __name__ == "__main__":
144
+ LOG.set_level("CRITICAL")
145
+
146
+ session = Session("123") # change lang, pipeline, whatever as needed
147
+ message = Message("recognizer_loop:utterance",
148
+ {"utterances": ["hello world"]},
149
+ {"session": session.serialize(),
150
+ "source": "A", "destination": "B"})
151
+
152
+ test = End2EndTest(
153
+ skill_ids=["skill-ovos-hello-world.openvoiceos"],
154
+ source_message=message,
155
+ expected_messages=[
156
+ Message("recognizer_loop:utterance",
157
+ {"utterances": ["hello world"]},
158
+ {"session": session.serialize()}),
159
+ Message("mycroft.audio.play_sound",
160
+ {"uri":"snd/error.mp3"},
161
+ {"session": session.serialize()}),
162
+ Message("complete_intent_failure",
163
+ {},
164
+ {"session": session.serialize()}),
165
+ Message("ovos.utterance.handled",
166
+ {},
167
+ {"session": session.serialize()}),
168
+ ]
169
+ )
170
+
171
+ test.execute()
@@ -0,0 +1,6 @@
1
+ # START_VERSION_BLOCK
2
+ VERSION_MAJOR = 0
3
+ VERSION_MINOR = 0
4
+ VERSION_BUILD = 1
5
+ VERSION_ALPHA = 0
6
+ # END_VERSION_BLOCK
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.1
2
+ Name: ovoscope
3
+ Version: 0.0.1
4
+ Summary: End-to-end test framework for OpenVoiceOS skills
5
+ Home-page: https://github.com/TigreGotico/ovoscope
6
+ Author: JarbasAI
7
+ Author-email: jarbasai@mailfence.com
8
+ License: Apache-2.0
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Testing
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+
17
+ # OvoScope
18
+
19
+ **OvoScope** is an end-to-end testing framework for [OVOS](https://openvoiceos.org) skills.
20
+
21
+ It contains the full core runtime environment using a lightweight in-process `ovos-core`, allowing skill developers to test the full skill message flow, from utterance to intent handling to final bus responses — without launching a full assistant stack.
22
+
23
+ > Like a microscope for your OVOS skills.
24
+
25
+ ---
26
+
27
+ ## Features
28
+
29
+ - Simulates OVOS Core messagebus interactions
30
+ - Sends test `Message` objects and captures responses
31
+ - Verifies message types, data, routing, session handling, and language
32
+ - Automatically flips message direction when configured
33
+ - Designed to integrate cleanly into `unittest` or `pytest` workflows
34
+
35
+ ---
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ pip install ovoscope
41
+ ````
42
+
43
+ ---
44
+
45
+ ## Usage Example
46
+
47
+ Testing scenario of complete intent failure (no skills installed)
48
+
49
+ ```python
50
+ from ovoscope import End2EndTest
51
+ from ovos_bus_client.message import Message
52
+ from ovos_bus_client.session import Session
53
+
54
+ session = Session("test123")
55
+ message = Message("recognizer_loop:utterance",
56
+ {"utterances": ["hello world"]},
57
+ {"session": session.serialize(), "source": "A", "destination": "B"})
58
+
59
+ test = End2EndTest(
60
+ skill_ids=[],
61
+ source_message=message,
62
+ expected_messages=[
63
+ Message("recognizer_loop:utterance", {"utterances": ["hello world"]}, {"session": session.serialize()}),
64
+ Message("complete_intent_failure", {}, {"session": session.serialize()}),
65
+ Message("ovos.utterance.handled", {}, {"session": session.serialize()}),
66
+ ]
67
+ )
68
+
69
+ test.execute()
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Why OvoScope?
75
+
76
+ * Lightweight: No need to launch a full messagebus or audio stack
77
+ * Isolated: Use `FakeBus` and `MiniCroft` for fast, reliable test environments
78
+ * Flexible: Works with any skill that conforms to OVOS skill loading
79
+
80
+ ---
81
+
82
+ ## License
83
+
84
+ [Apache 2.0](LICENSE)
85
+
86
+ ---
87
+
88
+ ## Contributing
89
+
90
+ PRs are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
91
+
92
+
@@ -0,0 +1,10 @@
1
+ README.md
2
+ setup.py
3
+ ovoscope/__init__.py
4
+ ovoscope/version.py
5
+ ovoscope.egg-info/PKG-INFO
6
+ ovoscope.egg-info/SOURCES.txt
7
+ ovoscope.egg-info/dependency_links.txt
8
+ ovoscope.egg-info/not-zip-safe
9
+ ovoscope.egg-info/requires.txt
10
+ ovoscope.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ ovos-core>=1.1.0
@@ -0,0 +1 @@
1
+ ovoscope
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,53 @@
1
+ from setuptools import setup, find_packages
2
+ import os
3
+
4
+ BASEDIR = os.path.abspath(os.path.dirname(__file__))
5
+
6
+ def get_version():
7
+ """ Find the version of the package"""
8
+ version_file = os.path.join(BASEDIR, 'ovoscope', 'version.py')
9
+ major, minor, build, alpha = (None, None, None, None)
10
+ with open(version_file) as f:
11
+ for line in f:
12
+ if 'VERSION_MAJOR' in line:
13
+ major = line.split('=')[1].strip()
14
+ elif 'VERSION_MINOR' in line:
15
+ minor = line.split('=')[1].strip()
16
+ elif 'VERSION_BUILD' in line:
17
+ build = line.split('=')[1].strip()
18
+ elif 'VERSION_ALPHA' in line:
19
+ alpha = line.split('=')[1].strip()
20
+
21
+ if ((major and minor and build and alpha) or
22
+ '# END_VERSION_BLOCK' in line):
23
+ break
24
+ version = f"{major}.{minor}.{build}"
25
+ if alpha and int(alpha) > 0:
26
+ version += f"a{alpha}"
27
+ return version
28
+
29
+ setup(
30
+ name="ovoscope",
31
+ version=get_version(),
32
+ description="End-to-end test framework for OpenVoiceOS skills",
33
+ long_description=open(f"{BASEDIR}/README.md").read(),
34
+ long_description_content_type="text/markdown",
35
+ author="JarbasAI",
36
+ author_email="jarbasai@mailfence.com",
37
+ url="https://github.com/TigreGotico/ovoscope",
38
+ license="Apache-2.0",
39
+ packages=find_packages(),
40
+ install_requires=[
41
+ "ovos-core>=1.1.0"
42
+ ],
43
+ python_requires='>=3.10',
44
+ classifiers=[
45
+ "Programming Language :: Python :: 3",
46
+ "License :: OSI Approved :: Apache Software License",
47
+ "Operating System :: OS Independent",
48
+ "Intended Audience :: Developers",
49
+ "Topic :: Software Development :: Testing",
50
+ ],
51
+ include_package_data=True,
52
+ zip_safe=False,
53
+ )