You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
f37ca70a89
API: add LightsManager for multicolor LEDs
244 lines
7.5 KiB
Python
244 lines
7.5 KiB
Python
# Unit tests for AudioFlinger service
|
|
import unittest
|
|
import sys
|
|
|
|
|
|
# Mock hardware before importing
|
|
class MockPWM:
|
|
def __init__(self, pin, freq=0, duty=0):
|
|
self.pin = pin
|
|
self.last_freq = freq
|
|
self.last_duty = duty
|
|
|
|
def freq(self, value=None):
|
|
if value is not None:
|
|
self.last_freq = value
|
|
return self.last_freq
|
|
|
|
def duty_u16(self, value=None):
|
|
if value is not None:
|
|
self.last_duty = value
|
|
return self.last_duty
|
|
|
|
|
|
class MockPin:
|
|
IN = 0
|
|
OUT = 1
|
|
|
|
def __init__(self, pin_number, mode=None):
|
|
self.pin_number = pin_number
|
|
self.mode = mode
|
|
|
|
|
|
# Inject mocks
|
|
class MockMachine:
|
|
PWM = MockPWM
|
|
Pin = MockPin
|
|
sys.modules['machine'] = MockMachine()
|
|
|
|
class MockLock:
|
|
def acquire(self):
|
|
pass
|
|
def release(self):
|
|
pass
|
|
|
|
class MockThread:
|
|
@staticmethod
|
|
def allocate_lock():
|
|
return MockLock()
|
|
@staticmethod
|
|
def start_new_thread(func, args, **kwargs):
|
|
pass # No-op for testing
|
|
@staticmethod
|
|
def stack_size(size=None):
|
|
return 16384 if size is None else None
|
|
|
|
sys.modules['_thread'] = MockThread()
|
|
|
|
class MockMposApps:
|
|
@staticmethod
|
|
def good_stack_size():
|
|
return 16384
|
|
|
|
sys.modules['mpos.apps'] = MockMposApps()
|
|
|
|
|
|
# Now import the module to test
|
|
import mpos.audio.audioflinger as AudioFlinger
|
|
|
|
|
|
class TestAudioFlinger(unittest.TestCase):
|
|
"""Test cases for AudioFlinger service."""
|
|
|
|
def setUp(self):
|
|
"""Initialize AudioFlinger before each test."""
|
|
self.buzzer = MockPWM(MockPin(46))
|
|
self.i2s_pins = {'sck': 2, 'ws': 47, 'sd': 16}
|
|
|
|
# Reset volume to default before each test
|
|
AudioFlinger.set_volume(70)
|
|
|
|
AudioFlinger.init(
|
|
device_type=AudioFlinger.DEVICE_BOTH,
|
|
i2s_pins=self.i2s_pins,
|
|
buzzer_instance=self.buzzer
|
|
)
|
|
|
|
def tearDown(self):
|
|
"""Clean up after each test."""
|
|
AudioFlinger.stop()
|
|
|
|
def test_initialization(self):
|
|
"""Test that AudioFlinger initializes correctly."""
|
|
self.assertEqual(AudioFlinger.get_device_type(), AudioFlinger.DEVICE_BOTH)
|
|
self.assertEqual(AudioFlinger._i2s_pins, self.i2s_pins)
|
|
self.assertEqual(AudioFlinger._buzzer_instance, self.buzzer)
|
|
|
|
def test_device_types(self):
|
|
"""Test device type constants."""
|
|
self.assertEqual(AudioFlinger.DEVICE_NULL, 0)
|
|
self.assertEqual(AudioFlinger.DEVICE_I2S, 1)
|
|
self.assertEqual(AudioFlinger.DEVICE_BUZZER, 2)
|
|
self.assertEqual(AudioFlinger.DEVICE_BOTH, 3)
|
|
|
|
def test_stream_types(self):
|
|
"""Test stream type constants and priority order."""
|
|
self.assertEqual(AudioFlinger.STREAM_MUSIC, 0)
|
|
self.assertEqual(AudioFlinger.STREAM_NOTIFICATION, 1)
|
|
self.assertEqual(AudioFlinger.STREAM_ALARM, 2)
|
|
|
|
# Higher number = higher priority
|
|
self.assertTrue(AudioFlinger.STREAM_MUSIC < AudioFlinger.STREAM_NOTIFICATION)
|
|
self.assertTrue(AudioFlinger.STREAM_NOTIFICATION < AudioFlinger.STREAM_ALARM)
|
|
|
|
def test_volume_control(self):
|
|
"""Test volume get/set operations."""
|
|
# Set volume
|
|
AudioFlinger.set_volume(50)
|
|
self.assertEqual(AudioFlinger.get_volume(), 50)
|
|
|
|
# Test clamping to 0-100 range
|
|
AudioFlinger.set_volume(150)
|
|
self.assertEqual(AudioFlinger.get_volume(), 100)
|
|
|
|
AudioFlinger.set_volume(-10)
|
|
self.assertEqual(AudioFlinger.get_volume(), 0)
|
|
|
|
def test_device_null_rejects_playback(self):
|
|
"""Test that DEVICE_NULL rejects all playback requests."""
|
|
# Re-initialize with no device
|
|
AudioFlinger.init(
|
|
device_type=AudioFlinger.DEVICE_NULL,
|
|
i2s_pins=None,
|
|
buzzer_instance=None
|
|
)
|
|
|
|
# WAV should be rejected
|
|
result = AudioFlinger.play_wav("test.wav")
|
|
self.assertFalse(result)
|
|
|
|
# RTTTL should be rejected
|
|
result = AudioFlinger.play_rtttl("Test:d=4,o=5,b=120:c")
|
|
self.assertFalse(result)
|
|
|
|
def test_device_i2s_only_rejects_rtttl(self):
|
|
"""Test that DEVICE_I2S rejects buzzer playback."""
|
|
# Re-initialize with I2S only
|
|
AudioFlinger.init(
|
|
device_type=AudioFlinger.DEVICE_I2S,
|
|
i2s_pins=self.i2s_pins,
|
|
buzzer_instance=None
|
|
)
|
|
|
|
# RTTTL should be rejected (no buzzer)
|
|
result = AudioFlinger.play_rtttl("Test:d=4,o=5,b=120:c")
|
|
self.assertFalse(result)
|
|
|
|
def test_device_buzzer_only_rejects_wav(self):
|
|
"""Test that DEVICE_BUZZER rejects I2S playback."""
|
|
# Re-initialize with buzzer only
|
|
AudioFlinger.init(
|
|
device_type=AudioFlinger.DEVICE_BUZZER,
|
|
i2s_pins=None,
|
|
buzzer_instance=self.buzzer
|
|
)
|
|
|
|
# WAV should be rejected (no I2S)
|
|
result = AudioFlinger.play_wav("test.wav")
|
|
self.assertFalse(result)
|
|
|
|
def test_missing_i2s_pins_rejects_wav(self):
|
|
"""Test that missing I2S pins rejects WAV playback."""
|
|
# Re-initialize with I2S device but no pins
|
|
AudioFlinger.init(
|
|
device_type=AudioFlinger.DEVICE_I2S,
|
|
i2s_pins=None,
|
|
buzzer_instance=None
|
|
)
|
|
|
|
result = AudioFlinger.play_wav("test.wav")
|
|
self.assertFalse(result)
|
|
|
|
def test_is_playing_initially_false(self):
|
|
"""Test that is_playing() returns False initially."""
|
|
self.assertFalse(AudioFlinger.is_playing())
|
|
|
|
def test_stop_with_no_playback(self):
|
|
"""Test that stop() can be called when nothing is playing."""
|
|
# Should not raise exception
|
|
AudioFlinger.stop()
|
|
self.assertFalse(AudioFlinger.is_playing())
|
|
|
|
def test_get_device_type(self):
|
|
"""Test that get_device_type() returns correct value."""
|
|
# Test DEVICE_BOTH
|
|
AudioFlinger.init(
|
|
device_type=AudioFlinger.DEVICE_BOTH,
|
|
i2s_pins=self.i2s_pins,
|
|
buzzer_instance=self.buzzer
|
|
)
|
|
self.assertEqual(AudioFlinger.get_device_type(), AudioFlinger.DEVICE_BOTH)
|
|
|
|
# Test DEVICE_I2S
|
|
AudioFlinger.init(
|
|
device_type=AudioFlinger.DEVICE_I2S,
|
|
i2s_pins=self.i2s_pins,
|
|
buzzer_instance=None
|
|
)
|
|
self.assertEqual(AudioFlinger.get_device_type(), AudioFlinger.DEVICE_I2S)
|
|
|
|
# Test DEVICE_BUZZER
|
|
AudioFlinger.init(
|
|
device_type=AudioFlinger.DEVICE_BUZZER,
|
|
i2s_pins=None,
|
|
buzzer_instance=self.buzzer
|
|
)
|
|
self.assertEqual(AudioFlinger.get_device_type(), AudioFlinger.DEVICE_BUZZER)
|
|
|
|
# Test DEVICE_NULL
|
|
AudioFlinger.init(
|
|
device_type=AudioFlinger.DEVICE_NULL,
|
|
i2s_pins=None,
|
|
buzzer_instance=None
|
|
)
|
|
self.assertEqual(AudioFlinger.get_device_type(), AudioFlinger.DEVICE_NULL)
|
|
|
|
def test_audio_focus_check_no_current_stream(self):
|
|
"""Test audio focus allows playback when no stream is active."""
|
|
result = AudioFlinger._check_audio_focus(AudioFlinger.STREAM_MUSIC)
|
|
self.assertTrue(result)
|
|
|
|
def test_init_creates_lock(self):
|
|
"""Test that initialization creates a stream lock."""
|
|
self.assertIsNotNone(AudioFlinger._stream_lock)
|
|
|
|
def test_volume_default_value(self):
|
|
"""Test that default volume is reasonable."""
|
|
# After init, volume should be at default (70)
|
|
AudioFlinger.init(
|
|
device_type=AudioFlinger.DEVICE_NULL,
|
|
i2s_pins=None,
|
|
buzzer_instance=None
|
|
)
|
|
self.assertEqual(AudioFlinger.get_volume(), 70)
|