-
Notifications
You must be signed in to change notification settings - Fork 0
feat: skill_id lifecycle filter + eof_count for End2EndTest #110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,6 +49,22 @@ def handle_async(self, message: Message): | |
| self.bus.emit(Message("ovos.utterance.handled", context=message.context)) | ||
|
|
||
|
|
||
| class TwoLifecycleSkill(OVOSSkill): | ||
| """Emits two lifecycles tagged with distinct context skill_ids, to exercise | ||
| the End2EndTest ``skill_id`` filter. Each lifecycle ends on the shared | ||
| ``ovos.utterance.handled`` topic (so eof_count=2 spans both).""" | ||
|
|
||
| def initialize(self): | ||
| self.add_event("unittest.two_lifecycles", self.handle_two) | ||
|
|
||
| def handle_two(self, message: Message): | ||
| for sid in ("life.a", "life.b"): | ||
| ctx = dict(message.context) | ||
| ctx["skill_id"] = sid | ||
| self.bus.emit(Message(f"{sid}.step", context=ctx)) | ||
| self.bus.emit(Message("ovos.utterance.handled", context=ctx)) | ||
|
|
||
|
|
||
| def _session(sid="ext-test", pipeline=None): | ||
| s = Session(sid) | ||
| s.lang = "en-US" | ||
|
|
@@ -983,5 +999,77 @@ def test_count_mismatch_prints_first_differing(self): | |
| test.execute(timeout=10) | ||
|
|
||
|
|
||
| class TestSkillIdFilter(unittest.TestCase): | ||
| """The skill_id filter isolates one dispatch lifecycle from concurrent ones.""" | ||
|
|
||
| def setUp(self): | ||
| LOG.set_level("ERROR") | ||
| self.mc = get_minicroft([SKILL_ID], | ||
| extra_skills={SKILL_ID: TwoLifecycleSkill}) | ||
|
|
||
| def tearDown(self): | ||
| self.mc.stop() | ||
| LOG.set_level("CRITICAL") | ||
|
|
||
| def _common_kwargs(self): | ||
| return dict( | ||
| minicroft=self.mc, | ||
| skill_ids=[SKILL_ID], | ||
| eof_msgs=["ovos.utterance.handled"], | ||
| eof_count=2, # both lifecycles terminate on ovos.utterance.handled | ||
| test_routing=False, | ||
| test_active_skills=False, | ||
| test_final_session=False, | ||
| ignore_messages=DEFAULT_IGNORED + HANDLER_LIFECYCLE, | ||
| verbose=False, | ||
| ) | ||
|
Comment on lines
+1014
to
+1025
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Keep routing enabled in the filtered cases. The new contract is that Suggested change def _common_kwargs(self):
return dict(
minicroft=self.mc,
skill_ids=[SKILL_ID],
eof_msgs=["ovos.utterance.handled"],
eof_count=2, # both lifecycles terminate on ovos.utterance.handled
- test_routing=False,
test_active_skills=False,
test_final_session=False,
ignore_messages=DEFAULT_IGNORED + HANDLER_LIFECYCLE,
verbose=False,
)
@@
test = End2EndTest(
source_message=src,
expected_messages=[src],
test_message_number=False,
test_msg_type=False,
test_msg_data=False,
test_msg_context=False,
+ test_routing=False,
**self._common_kwargs(),
)🤖 Prompt for AI Agents |
||
|
|
||
| def test_filter_isolates_one_lifecycle(self): | ||
| """Only messages whose context skill_id matches are asserted.""" | ||
| src = _make_custom("unittest.two_lifecycles") | ||
| test = End2EndTest( | ||
| source_message=src, | ||
| skill_id="life.a", | ||
| expected_messages=[ | ||
| Message("life.a.step", {}, {"skill_id": "life.a"}), | ||
| Message("ovos.utterance.handled", {}, {"skill_id": "life.a"}), | ||
| ], | ||
| **self._common_kwargs(), | ||
| ) | ||
| # passes only if life.b.* and the source (no skill_id) are filtered out | ||
| test.execute(timeout=10) | ||
|
|
||
| def test_filter_the_other_lifecycle(self): | ||
| """The same scenario, filtered to the other skill_id.""" | ||
| src = _make_custom("unittest.two_lifecycles") | ||
| test = End2EndTest( | ||
| source_message=src, | ||
| skill_id="life.b", | ||
| expected_messages=[ | ||
| Message("life.b.step", {}, {"skill_id": "life.b"}), | ||
| Message("ovos.utterance.handled", {}, {"skill_id": "life.b"}), | ||
| ], | ||
| **self._common_kwargs(), | ||
| ) | ||
| test.execute(timeout=10) | ||
|
|
||
| def test_unfiltered_sees_both_lifecycles(self): | ||
| """Without the filter, eof_count=2 captures both lifecycles' messages.""" | ||
| src = _make_custom("unittest.two_lifecycles") | ||
| test = End2EndTest( | ||
| source_message=src, | ||
| expected_messages=[src], | ||
| test_message_number=False, | ||
| test_msg_type=False, | ||
| test_msg_data=False, | ||
| test_msg_context=False, | ||
| **self._common_kwargs(), | ||
| ) | ||
| result = test.execute(timeout=10) | ||
| types = [m.msg_type for m in result] | ||
| self.assertIn("life.a.step", types) | ||
| self.assertIn("life.b.step", types) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Reuse the same
CaptureSessionfor the second capture.This only proves that a new instance starts clean. If
CaptureSession.capture()stopped resetting_eof_seen, this test would still pass becausecs2gets a fresh counter and fresh handlers.Suggested change
def test_eof_count_resets_between_captures(self): """The eof counter resets per capture() call so eof_count applies fresh.""" cs = CaptureSession(self.mc, eof_msgs=["test.eof"], eof_count=2, ignore_messages=[]) self._emit_after(0.05, Message("test.eof")) self._emit_after(0.10, Message("test.eof")) cs.capture(Message("test.trigger1"), timeout=3) - cs.finish() - # a second capture must again require 2 eofs, not be already-done - cs2 = CaptureSession(self.mc, eof_msgs=["test.eof"], eof_count=2, - ignore_messages=[]) + first_len = len(cs.responses) + # a second capture on the same session must again require 2 eofs self._emit_after(0.05, Message("test.eof")) self._emit_after(0.10, Message("test.mid")) self._emit_after(0.15, Message("test.eof")) - cs2.capture(Message("test.trigger2"), timeout=3) - types = [m.msg_type for m in cs2.finish()] + cs.capture(Message("test.trigger2"), timeout=3) + types = [m.msg_type for m in cs.finish()[first_len:]] self.assertIn("test.mid", types)📝 Committable suggestion
🤖 Prompt for AI Agents