test: unit test mock PyAudio, integration test use real
Make unit tests use a mock version of PyAudio, while making integration tests using the real version. If no real microphone is available, these integration tests are skipped. ref: N25B-119
This commit is contained in:
97
test/common/microphone_utils.py
Normal file
97
test/common/microphone_utils.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import random
|
||||||
|
import sys
|
||||||
|
from StringIO import StringIO
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from robot_interface.utils.microphone import choose_mic_default, choose_mic_interactive, get_microphones
|
||||||
|
|
||||||
|
|
||||||
|
class MicrophoneUtils(object):
|
||||||
|
"""Shared tests for any PyAudio-like implementation, e.g. mock and real."""
|
||||||
|
|
||||||
|
def test_choose_mic_default(self, pyaudio_instance):
|
||||||
|
"""
|
||||||
|
The result must contain at least "index", as this is used to identify the microphone.
|
||||||
|
The "name" is used for logging, so it should also exist.
|
||||||
|
It must have one or more channels.
|
||||||
|
Lastly it must be capable of sending at least 16000 samples per second.
|
||||||
|
"""
|
||||||
|
result = choose_mic_default(pyaudio_instance)
|
||||||
|
assert "index" in result
|
||||||
|
assert isinstance(result["index"], (int, long))
|
||||||
|
|
||||||
|
assert "name" in result
|
||||||
|
assert isinstance(result["name"], (str, unicode))
|
||||||
|
|
||||||
|
assert "maxInputChannels" in result
|
||||||
|
assert isinstance(result["maxInputChannels"], (int, long))
|
||||||
|
assert result["maxInputChannels"] > 0
|
||||||
|
|
||||||
|
assert "defaultSampleRate" in result
|
||||||
|
assert isinstance(result["defaultSampleRate"], float)
|
||||||
|
assert result["defaultSampleRate"] >= 16000
|
||||||
|
|
||||||
|
def test_choose_mic_interactive_input_not_int(self, pyaudio_instance, mocker):
|
||||||
|
"""
|
||||||
|
First mock an input that's not an integer, then a valid integer. There should be no errors.
|
||||||
|
"""
|
||||||
|
mock_input = mocker.patch("__builtin__.raw_input", side_effect=["not an integer", "0"])
|
||||||
|
fake_out = StringIO()
|
||||||
|
mocker.patch.object(sys, "stdout", fake_out)
|
||||||
|
|
||||||
|
result = choose_mic_interactive(pyaudio_instance)
|
||||||
|
assert "index" in result
|
||||||
|
assert isinstance(result["index"], (int, long))
|
||||||
|
assert result["index"] == 0
|
||||||
|
|
||||||
|
assert mock_input.called
|
||||||
|
|
||||||
|
assert any(p.startswith("Please enter a number") for p in fake_out.getvalue().splitlines())
|
||||||
|
|
||||||
|
def test_choose_mic_interactive_negative_index(self, pyaudio_instance, mocker):
|
||||||
|
"""
|
||||||
|
Make sure that the interactive method does not allow negative integers as input.
|
||||||
|
"""
|
||||||
|
mock_input = mocker.patch("__builtin__.raw_input", side_effect=["-1", "0"])
|
||||||
|
fake_out = StringIO()
|
||||||
|
mocker.patch.object(sys, "stdout", fake_out)
|
||||||
|
|
||||||
|
result = choose_mic_interactive(pyaudio_instance)
|
||||||
|
assert "index" in result
|
||||||
|
assert isinstance(result["index"], (int, long))
|
||||||
|
assert result["index"] == 0
|
||||||
|
|
||||||
|
assert mock_input.called
|
||||||
|
|
||||||
|
assert any(p.startswith("Please enter a number") for p in fake_out.getvalue().splitlines())
|
||||||
|
|
||||||
|
def test_choose_mic_interactive_index_too_high(self, pyaudio_instance, mocker):
|
||||||
|
"""
|
||||||
|
Make sure that the interactive method does not allow indices higher than the highest mic index.
|
||||||
|
"""
|
||||||
|
real_count = len(list(get_microphones(pyaudio_instance)))
|
||||||
|
mock_input = mocker.patch("__builtin__.raw_input", side_effect=[str(real_count), "0"])
|
||||||
|
fake_out = StringIO()
|
||||||
|
mocker.patch.object(sys, "stdout", fake_out)
|
||||||
|
|
||||||
|
result = choose_mic_interactive(pyaudio_instance)
|
||||||
|
assert "index" in result
|
||||||
|
assert isinstance(result["index"], (int, long))
|
||||||
|
|
||||||
|
assert mock_input.called
|
||||||
|
|
||||||
|
assert any(p.startswith("Please enter a number") for p in fake_out.getvalue().splitlines())
|
||||||
|
|
||||||
|
def test_choose_mic_interactive_random_index(self, pyaudio_instance, mocker):
|
||||||
|
"""
|
||||||
|
Get a random index from the list of available mics, make sure it's correct.
|
||||||
|
"""
|
||||||
|
microphones = list(get_microphones(pyaudio_instance))
|
||||||
|
random_index = random.randrange(len(microphones))
|
||||||
|
mocker.patch("__builtin__.raw_input", side_effect=[str(random_index)])
|
||||||
|
|
||||||
|
result = choose_mic_interactive(pyaudio_instance)
|
||||||
|
assert "index" in result
|
||||||
|
assert isinstance(result["index"], (int, long))
|
||||||
|
assert result["index"] == microphones[random_index]["index"]
|
||||||
20
test/integration/test_microphone_utils.py
Normal file
20
test/integration/test_microphone_utils.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import pyaudio
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from common.microphone_utils import MicrophoneUtils
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def pyaudio_instance():
|
||||||
|
audio = pyaudio.PyAudio()
|
||||||
|
try:
|
||||||
|
audio.get_default_input_device_info()
|
||||||
|
return audio
|
||||||
|
except IOError:
|
||||||
|
pytest.skip("No microphone available to test with.")
|
||||||
|
|
||||||
|
|
||||||
|
class TestAudioIntegration(MicrophoneUtils):
|
||||||
|
"""Run shared audio behavior tests with the mock implementation."""
|
||||||
|
pass
|
||||||
@@ -1,107 +1,85 @@
|
|||||||
import functools
|
# coding=utf-8
|
||||||
import random
|
import mock
|
||||||
from StringIO import StringIO
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import pyaudio
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from common.microphone_utils import MicrophoneUtils
|
||||||
from robot_interface.utils.microphone import choose_mic_default, choose_mic_interactive
|
from robot_interface.utils.microphone import choose_mic_default, choose_mic_interactive
|
||||||
|
|
||||||
|
|
||||||
|
class MockPyAudio:
|
||||||
|
def __init__(self):
|
||||||
|
# You can predefine fake device info here
|
||||||
|
self.devices = [
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"name": u"Someone’s Microphone", # Using a Unicode ’ character
|
||||||
|
"maxInputChannels": 2,
|
||||||
|
"maxOutputChannels": 0,
|
||||||
|
"defaultSampleRate": 44100.0,
|
||||||
|
"defaultLowInputLatency": 0.01,
|
||||||
|
"defaultLowOutputLatency": 0.01,
|
||||||
|
"defaultHighInputLatency": 0.1,
|
||||||
|
"defaultHighOutputLatency": 0.1,
|
||||||
|
"hostApi": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 1,
|
||||||
|
"name": u"Mock Speaker 1",
|
||||||
|
"maxInputChannels": 0,
|
||||||
|
"maxOutputChannels": 2,
|
||||||
|
"defaultSampleRate": 48000.0,
|
||||||
|
"defaultLowInputLatency": 0.01,
|
||||||
|
"defaultLowOutputLatency": 0.01,
|
||||||
|
"defaultHighInputLatency": 0.1,
|
||||||
|
"defaultHighOutputLatency": 0.1,
|
||||||
|
"hostApi": 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_device_count(self):
|
||||||
|
"""Return the number of available mock devices."""
|
||||||
|
return len(self.devices)
|
||||||
|
|
||||||
|
def get_device_info_by_index(self, index):
|
||||||
|
"""Return information for a given mock device index."""
|
||||||
|
if 0 <= index < len(self.devices):
|
||||||
|
return self.devices[index]
|
||||||
|
else:
|
||||||
|
raise IOError("Invalid device index: {}".format(index))
|
||||||
|
|
||||||
|
def get_default_input_device_info(self):
|
||||||
|
"""Return info for a default mock input device."""
|
||||||
|
for device in self.devices:
|
||||||
|
if device.get("maxInputChannels", 0) > 0:
|
||||||
|
return device
|
||||||
|
raise IOError("No default input device found")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def pyaudio_instance():
|
def pyaudio_instance():
|
||||||
audio = pyaudio.PyAudio()
|
return MockPyAudio()
|
||||||
yield audio
|
|
||||||
|
|
||||||
|
|
||||||
def test_choose_mic_default(pyaudio_instance):
|
def _raise_io_error():
|
||||||
"""
|
raise IOError()
|
||||||
The result must contain at least "index", as this is used to identify the microphone.
|
|
||||||
The "name" is used for logging, so it should also exist.
|
|
||||||
It must have one or more channels.
|
|
||||||
Lastly it must be capable of sending at least 16000 samples per second.
|
|
||||||
"""
|
|
||||||
result = choose_mic_default(pyaudio_instance)
|
|
||||||
assert "index" in result
|
|
||||||
assert isinstance(result["index"], long)
|
|
||||||
|
|
||||||
assert "name" in result
|
|
||||||
assert isinstance(result["name"], (str, unicode))
|
|
||||||
|
|
||||||
assert "maxInputChannels" in result
|
|
||||||
assert isinstance(result["maxInputChannels"], long)
|
|
||||||
assert result["maxInputChannels"] > 0
|
|
||||||
|
|
||||||
assert "defaultSampleRate" in result
|
|
||||||
assert isinstance(result["defaultSampleRate"], float)
|
|
||||||
assert result["defaultSampleRate"] >= 16000
|
|
||||||
|
|
||||||
|
|
||||||
def test_choose_mic_interactive_input_not_int(pyaudio_instance, mocker):
|
class TestAudioUnit(MicrophoneUtils):
|
||||||
"""
|
"""Run shared audio behavior tests with the mock implementation."""
|
||||||
First mock an input that's not an integer, then a valid integer. There should be no errors.
|
def test_choose_mic_default_no_mic(self):
|
||||||
"""
|
mock_pyaudio = mock.Mock()
|
||||||
mock_input = mocker.patch("__builtin__.raw_input", side_effect=["not an integer", "0"])
|
mock_pyaudio.get_device_count = mock.Mock(return_value=0L)
|
||||||
fake_out = StringIO()
|
mock_pyaudio.get_default_input_device_info = _raise_io_error
|
||||||
mocker.patch.object(sys, "stdout", fake_out)
|
|
||||||
|
|
||||||
result = choose_mic_interactive(pyaudio_instance)
|
result = choose_mic_default(mock_pyaudio)
|
||||||
assert "index" in result
|
|
||||||
assert isinstance(result["index"], (int, long))
|
|
||||||
assert result["index"] == 0
|
|
||||||
|
|
||||||
assert mock_input.called
|
assert result is None
|
||||||
|
|
||||||
assert any(p.startswith("Please enter a number") for p in fake_out.getvalue().splitlines())
|
def test_choose_mic_interactive_no_mic(self):
|
||||||
|
mock_pyaudio = mock.Mock()
|
||||||
|
mock_pyaudio.get_device_count = mock.Mock(return_value=0L)
|
||||||
|
mock_pyaudio.get_default_input_device_info = _raise_io_error
|
||||||
|
|
||||||
|
result = choose_mic_interactive(mock_pyaudio)
|
||||||
|
|
||||||
def test_choose_mic_interactive_negative_index(pyaudio_instance, mocker):
|
assert result is None
|
||||||
"""
|
|
||||||
Make sure that the interactive method does not allow negative integers as input.
|
|
||||||
"""
|
|
||||||
mock_input = mocker.patch("__builtin__.raw_input", side_effect=["-1", "0"])
|
|
||||||
fake_out = StringIO()
|
|
||||||
mocker.patch.object(sys, "stdout", fake_out)
|
|
||||||
|
|
||||||
result = choose_mic_interactive(pyaudio_instance)
|
|
||||||
assert "index" in result
|
|
||||||
assert isinstance(result["index"], (int, long))
|
|
||||||
assert result["index"] == 0
|
|
||||||
|
|
||||||
assert mock_input.called
|
|
||||||
|
|
||||||
assert any(p.startswith("Please enter a number") for p in fake_out.getvalue().splitlines())
|
|
||||||
|
|
||||||
|
|
||||||
def test_choose_mic_interactive_index_too_high(pyaudio_instance, mocker):
|
|
||||||
"""
|
|
||||||
Make sure that the interactive method does not allow indices higher than the highest mic index.
|
|
||||||
"""
|
|
||||||
real_count = pyaudio_instance.get_device_count()
|
|
||||||
mock_input = mocker.patch("__builtin__.raw_input", side_effect=[str(real_count), "0"])
|
|
||||||
fake_out = StringIO()
|
|
||||||
mocker.patch.object(sys, "stdout", fake_out)
|
|
||||||
|
|
||||||
result = choose_mic_interactive(pyaudio_instance)
|
|
||||||
assert "index" in result
|
|
||||||
assert isinstance(result["index"], (int, long))
|
|
||||||
assert result["index"] == 0
|
|
||||||
|
|
||||||
assert mock_input.called
|
|
||||||
|
|
||||||
assert any(p.startswith("Please enter a number") for p in fake_out.getvalue().splitlines())
|
|
||||||
|
|
||||||
|
|
||||||
def test_choose_mic_interactive_random_index(pyaudio_instance, mocker):
|
|
||||||
"""
|
|
||||||
Get a random index from the list of available mics, make sure it's correct.
|
|
||||||
"""
|
|
||||||
real_count = pyaudio_instance.get_device_count()
|
|
||||||
random_index = random.randrange(real_count)
|
|
||||||
mocker.patch("__builtin__.raw_input", side_effect=[str(random_index)])
|
|
||||||
|
|
||||||
result = choose_mic_interactive(pyaudio_instance)
|
|
||||||
assert "index" in result
|
|
||||||
assert isinstance(result["index"], (int, long))
|
|
||||||
assert result["index"] == random_index
|
|
||||||
|
|||||||
Reference in New Issue
Block a user