from __future__ import unicode_literals # So that we can format strings with Unicode characters import random import sys from StringIO import StringIO from robot_interface.utils.microphone import ( choose_mic_default, choose_mic_interactive, choose_mic_arguments, choose_mic, 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): """ Tests that the default microphone selection function returns a valid microphone dictionary containing all necessary keys with correct types and values. The result must contain at least "index", as this is used to identify the microphone, and "name" for logging. It must have one or more channels (`maxInputChannels`), and a default sample rate of at least 16000 Hz. :param pyaudio_instance: A mocked or real PyAudio instance used to query microphone information. :type pyaudio_instance: PyAudio """ 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): """ Tests the robustness of the interactive selection when the user first enters a non-integer value, ensuring the system prompts again without error and accepts a valid integer on the second attempt. :param pyaudio_instance: A mocked or real PyAudio instance. :type pyaudio_instance: PyAudio :param mocker: The fixture used for mocking built-in functions and system objects. :type mocker: pytest_mock.plugin.MockerFixture """ microphones = get_microphones(pyaudio_instance) target_microphone = next(microphones) 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"] == target_microphone["index"] 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): """ Tests that the interactive selection method prevents the user from entering a negative integer as a microphone index. :param pyaudio_instance: A mocked or real PyAudio instance. :type pyaudio_instance: PyAudio :param mocker: The fixture used for mocking built-in functions and system objects. :type mocker: pytest_mock.plugin.MockerFixture """ microphones = get_microphones(pyaudio_instance) target_microphone = next(microphones) 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"] == target_microphone["index"] 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): """ Tests that the interactive selection method prevents the user from entering an index that exceeds the total number of available microphones. :param pyaudio_instance: A mocked or real PyAudio instance. :type pyaudio_instance: PyAudio :param mocker: The fixture used for mocking built-in functions and system objects. :type mocker: pytest_mock.plugin.MockerFixture """ 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): """ Tests the core interactive functionality by simulating the selection of a random valid microphone index and verifying that the correct microphone information is returned. :param pyaudio_instance: A mocked or real PyAudio instance. :type pyaudio_instance: PyAudio :param mocker: The fixture used for mocking built-in functions and system objects. :type mocker: pytest_mock.plugin.MockerFixture """ 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"] def test_choose_mic_no_arguments(self, pyaudio_instance, mocker): mocker.patch.object(sys, "argv", []) result = choose_mic_arguments(pyaudio_instance) assert result is None def test_choose_mic_arguments(self, pyaudio_instance, mocker): """ Tests `choose_mic_arguments` when the microphone name is passed as a separate argument. :param pyaudio_instance: A mocked or real PyAudio instance. :type pyaudio_instance: PyAudio :param mocker: The fixture used for mocking built-in functions and system objects. :type mocker: pytest_mock.plugin.MockerFixture """ for mic in get_microphones(pyaudio_instance): mocker.patch.object(sys, "argv", ["--microphone", mic["name"]]) result = choose_mic_arguments(pyaudio_instance) assert result is not None assert result == mic def test_choose_mic_arguments_eq(self, pyaudio_instance, mocker): """ Tests `choose_mic_arguments` when the microphone name is passed using an equals sign (`--microphone=NAME`). :param pyaudio_instance: A mocked or real PyAudio instance. :type pyaudio_instance: PyAudio :param mocker: The fixture used for mocking built-in functions and system objects. :type mocker: pytest_mock.plugin.MockerFixture """ for mic in get_microphones(pyaudio_instance): mocker.patch.object(sys, "argv", ["--microphone={}".format(mic["name"])]) result = choose_mic_arguments(pyaudio_instance) assert result is not None assert result == mic def test_choose_mic_arguments_not_exist(self, pyaudio_instance, mocker): """ Tests `choose_mic_arguments` when a non-existent microphone name is passed via command-line arguments, expecting the function to return None. :param pyaudio_instance: A mocked or real PyAudio instance. :type pyaudio_instance: PyAudio :param mocker: The fixture used for mocking built-in functions and system objects. :type mocker: pytest_mock.plugin.MockerFixture """ mocker.patch.object(sys, "argv", ["--microphone", "Surely this microphone doesn't exist"]) result = choose_mic_arguments(pyaudio_instance) assert result is None def test_choose_mic_with_argument(self, pyaudio_instance, mocker): """ Tests `choose_mic` function when a valid microphone is specified via command-line arguments. :param pyaudio_instance: A mocked or real PyAudio instance. :type pyaudio_instance: PyAudio :param mocker: The fixture used for mocking built-in functions and system objects. :type mocker: pytest_mock.plugin.MockerFixture """ mic = next(get_microphones(pyaudio_instance)) mocker.patch.object(sys, "argv", ["--microphone", mic["name"]]) result = choose_mic(pyaudio_instance) assert result is not None assert result == mic def test_choose_mic_no_argument(self, pyaudio_instance, mocker): """ Tests `choose_mic` function when no command-line arguments are provided, verifying that the function falls back correctly to the system's default microphone selection. :param pyaudio_instance: A mocked or real PyAudio instance. :type pyaudio_instance: PyAudio :param mocker: The fixture used for mocking built-in functions and system objects. :type mocker: pytest_mock.plugin.MockerFixture """ default_mic = choose_mic_default(pyaudio_instance) mocker.patch.object(sys, "argv", []) result = choose_mic(pyaudio_instance) assert result is not None assert result == default_mic