jarvis / tests /test_integration.py
Jonathan Haas
Clean up test lint debt and dead test variables
4fe6f2d
Raw
History Blame Contribute Delete
4.21 kB
"""Integration tests — verify subsystems work together.
These test the wiring between components using mocks for external
dependencies (hardware, APIs) but real internal logic.
"""
import time
import numpy as np
from unittest.mock import MagicMock, patch
from jarvis.presence import PresenceLoop, State
from jarvis.robot.controller import RobotController
class TestPresenceWithFaceTracker:
"""Test that face tracker feeds into presence loop correctly."""
@patch("jarvis.vision.face_tracker.YOLO")
def test_face_tracker_drives_presence(self, mock_yolo_cls):
import torch
robot = RobotController(sim=True)
robot.connect()
presence = PresenceLoop(robot)
# Setup YOLO to detect a face on the left
mock_model = MagicMock()
mock_yolo_cls.return_value = mock_model
mock_box = MagicMock()
mock_box.xyxy = [torch.tensor([50.0, 200.0, 150.0, 300.0])] # left side
mock_box.conf = [torch.tensor(0.9)]
mock_result = MagicMock()
mock_result.boxes = [mock_box]
mock_model.return_value = [mock_result]
frame = np.zeros((480, 640, 3), dtype=np.uint8)
from jarvis.vision.face_tracker import FaceTracker
tracker = FaceTracker(
presence=presence,
get_frame=lambda: frame,
fps=50,
)
presence.signals.state = State.LISTENING
presence.start()
tracker.start()
time.sleep(0.3)
tracker.stop()
presence.stop()
# Face was on the left -> tracker should have produced recent face signals.
assert presence.signals.face_last_seen is not None
class TestPresenceStateTransitions:
"""Test that presence loop handles rapid state changes."""
def test_rapid_state_cycling(self, mock_robot):
presence = PresenceLoop(mock_robot)
presence.start()
# Rapidly cycle through states
for _ in range(5):
for state in State:
presence.signals.state = state
time.sleep(0.02)
presence.stop()
# Should not crash or deadlock
class TestEmbodyIntegration:
"""Test that embody tool properly affects presence loop behavior."""
def test_nod_affects_speaking_state(self, mock_robot):
from jarvis.tools.robot import bind
presence = PresenceLoop(mock_robot)
bind(mock_robot, presence)
# Set speaking state with nod
presence.signals.state = State.SPEAKING
presence.signals.intent_nod = 0.8
# Run a few frames
for i in range(50):
presence._do_speaking(float(i) * 0.033, presence.signals)
# Pitch should be oscillating (nod effect)
# Just verify it's not stuck at 0
assert presence._pitch != 0.0 or presence._roll != 0.0
class TestAudioPipelineFlow:
"""Test audio pipeline data flow with mocked components."""
@patch("jarvis.audio.vad.load_silero_vad")
@patch("jarvis.audio.vad.VADIterator")
@patch("jarvis.audio.stt.WhisperModel")
def test_vad_to_stt_flow(self, mock_whisper_cls, mock_vad_iter, mock_load_vad):
import torch
# Setup VAD to detect speech
mock_model = MagicMock()
mock_model.return_value = torch.tensor(0.9)
mock_load_vad.return_value = mock_model
# Setup STT
mock_whisper = MagicMock()
seg = MagicMock()
seg.text = "hello jarvis"
mock_whisper.transcribe.return_value = (iter([seg]), MagicMock())
mock_whisper_cls.return_value = mock_whisper
from jarvis.audio.vad import VoiceActivityDetector
from jarvis.audio.stt import SpeechToText
vad = VoiceActivityDetector()
stt = SpeechToText()
# Simulate: VAD detects speech in 10 chunks, then silence
chunks = []
for _ in range(10):
chunk = np.random.randn(512).astype(np.float32) * 0.1
if vad.is_speech(chunk):
chunks.append(chunk)
# Concatenate and transcribe
if chunks:
audio = np.concatenate(chunks)
text = stt.transcribe(audio)
assert text == "hello jarvis"