-
Notifications
You must be signed in to change notification settings - Fork 333
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #346 from cclauss/patch-1
Add willwade's test_pyttsx3.py
- Loading branch information
Showing
3 changed files
with
245 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# This workflows will upload a Python Package using Twine when a release is created. | ||
# https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries | ||
name: CI | ||
|
||
on: | ||
push: | ||
branches: [master] | ||
pull_request: | ||
branches: [master] | ||
release: | ||
types: [created] # Only publish on tagged releases | ||
|
||
jobs: | ||
test: | ||
strategy: | ||
fail-fast: false | ||
matrix: | ||
os: [ubuntu-latest, windows-latest, macos-latest] | ||
python-version: ['3.9', '3.11', '3.13'] | ||
max-parallel: 9 | ||
runs-on: ${{ matrix.os }} | ||
timeout-minutes: 10 # Save resources while our pytests are hanging | ||
steps: | ||
- if: runner.os == 'Linux' | ||
run: | | ||
sudo apt-get update -q -q | ||
sudo apt-get install --yes espeak espeak-ng ffmpeg libespeak1 | ||
espeak --version | ||
espeak-ng --version | ||
ffmpeg --version || true | ||
- uses: actions/checkout@v4 | ||
- name: Set up Python | ||
uses: actions/setup-python@v5 | ||
with: | ||
python-version: ${{ matrix.python-version }} | ||
|
||
- name: Install dependencies | ||
run: | | ||
pip install --upgrade pip | ||
pip install pytest pytest-timeout | ||
pip install --editable . | ||
- name: Run tests | ||
run: pytest | ||
|
||
build: | ||
runs-on: ubuntu-latest | ||
needs: [test] # This ensures tests pass before build | ||
permissions: | ||
id-token: write | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
- name: Set up Python | ||
uses: actions/setup-python@v5 | ||
with: | ||
python-version: '3.x' | ||
|
||
- name: Install dependencies | ||
run: | | ||
pip install --upgrade pip | ||
pip install build twine | ||
- name: Clean previous builds | ||
run: | | ||
rm -rf dist | ||
- name: Build package | ||
run: | | ||
python -m build | ||
python -m twine check --strict dist/* | ||
deploy: | ||
needs: [build] | ||
runs-on: ubuntu-latest | ||
if: github.event_name == 'release' && github.event.action == 'created' # Only on release creation | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
- name: Set up Python | ||
uses: actions/setup-python@v5 | ||
with: | ||
python-version: '3.x' | ||
|
||
- name: Publish package distributions to PyPI | ||
uses: pypa/gh-action-pypi-publish@release/v1 | ||
with: | ||
password: ${{ secrets.PYPI_PASSWORD }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
[metadata] | ||
description-file = README.md | ||
long_description = file: README.md | ||
long_description_content_type = text/markdown |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import os | ||
import sys | ||
from unittest import mock | ||
|
||
import pytest | ||
import wave | ||
|
||
import pyttsx3 | ||
|
||
|
||
@pytest.fixture | ||
def engine(): | ||
"""Fixture for initializing pyttsx3 engine.""" | ||
engine = pyttsx3.init() | ||
yield engine | ||
engine.stop() # Ensure the engine stops after tests | ||
|
||
|
||
# Test for speaking text | ||
# @pytest.mark.timeout(10) # Set timeout to 10 seconds | ||
@pytest.mark.skipif(sys.platform == "win32", reason="TODO: Fix this test to pass on Windows") | ||
def test_speaking_text(engine): | ||
engine.say("Sally sells seashells by the seashore.") | ||
engine.say("The quick brown fox jumped over the lazy dog.") | ||
engine.runAndWait() | ||
|
||
|
||
# Test for saving voice to a file with additional validation | ||
# @pytest.mark.timeout(10) # Set timeout to 10 seconds | ||
# @pytest.mark.skipif(sys.platform in ("linux", "win32"), reason="TODO: Fix this test to pass on Linux and Windows") | ||
@pytest.mark.xfail(sys.platform == "darwin", reason="TODO: Fix this test to pass on macOS") | ||
def test_saving_to_file(engine, tmp_path): | ||
test_file = tmp_path / "test.wav" # Using .wav for easier validation | ||
|
||
# Save the speech to a file | ||
engine.save_to_file("Hello World", str(test_file)) | ||
engine.runAndWait() | ||
|
||
# Check if the file was created | ||
assert test_file.exists(), "The audio file was not created" | ||
|
||
# Check if the file is not empty | ||
assert test_file.stat().st_size > 0, "The audio file is empty" | ||
|
||
# Check if the file is a valid .wav file using the wave module | ||
with wave.open(str(test_file), "rb") as wf: | ||
assert wf.getnchannels() == 1, "The audio file should have 1 channel (mono)" | ||
assert wf.getsampwidth() == 2, "The audio file sample width should be 2 bytes" | ||
assert wf.getframerate() == 22050, "The audio file framerate should be 22050 Hz" | ||
|
||
|
||
# Test for listening for events | ||
# @pytest.mark.timeout(10) # Set timeout to 10 seconds | ||
@pytest.mark.skipif(sys.platform in ("linux", "win32"), reason="TODO: Fix this test to pass on Linux and Windows") | ||
def test_listening_for_events(engine): | ||
onStart = mock.Mock() | ||
onWord = mock.Mock() | ||
onEnd = mock.Mock() | ||
|
||
engine.connect("started-utterance", onStart) | ||
engine.connect("started-word", onWord) | ||
engine.connect("finished-utterance", onEnd) | ||
|
||
engine.say("The quick brown fox jumped over the lazy dog.") | ||
engine.runAndWait() | ||
|
||
# Ensure the event handlers were called | ||
assert onStart.called | ||
assert onWord.called | ||
assert onEnd.called | ||
|
||
|
||
# Test for interrupting an utterance | ||
# @pytest.mark.timeout(10) # Set timeout to 10 seconds | ||
@pytest.mark.skipif(sys.platform in ("linux", "win32"), reason="TODO: Fix this test to pass on Linux and Windows") | ||
def test_interrupting_utterance(engine): | ||
def onWord(name, location, length): | ||
if location > 10: | ||
engine.stop() | ||
|
||
onWord_mock = mock.Mock(side_effect=onWord) | ||
engine.connect("started-word", onWord_mock) | ||
engine.say("The quick brown fox jumped over the lazy dog.") | ||
engine.runAndWait() | ||
|
||
# Check that stop was called | ||
assert onWord_mock.called | ||
|
||
|
||
# Test for changing voices | ||
# @pytest.mark.timeout(10) # Set timeout to 10 seconds | ||
@pytest.mark.skipif(sys.platform in ("linux", "win32"), reason="TODO: Fix this test to pass on Linux and Windows") | ||
def test_changing_voices(engine): | ||
voices = engine.getProperty("voices") | ||
for voice in voices: | ||
engine.setProperty("voice", voice.id) | ||
engine.say("The quick brown fox jumped over the lazy dog.") | ||
engine.runAndWait() | ||
|
||
|
||
# Test for changing speech rate | ||
# @pytest.mark.timeout(10) # Set timeout to 10 seconds | ||
@pytest.mark.skipif(sys.platform in ("linux", "win32"), reason="TODO: Fix this test to pass on Linux and Windows") | ||
def test_changing_speech_rate(engine): | ||
rate = engine.getProperty("rate") | ||
engine.setProperty("rate", rate + 50) | ||
engine.say("The quick brown fox jumped over the lazy dog.") | ||
engine.runAndWait() | ||
|
||
|
||
# Test for changing volume | ||
# @pytest.mark.timeout(10) # Set timeout to 10 seconds | ||
@pytest.mark.skipif(sys.platform in ("linux", "win32"), reason="TODO: Fix this test to pass on Linux and Windows") | ||
def test_changing_volume(engine): | ||
volume = engine.getProperty("volume") | ||
engine.setProperty("volume", volume - 0.25) | ||
engine.say("The quick brown fox jumped over the lazy dog.") | ||
engine.runAndWait() | ||
|
||
|
||
# Test for running a driver event loop | ||
# @pytest.mark.timeout(10) # Set timeout to 10 seconds | ||
@pytest.mark.skipif(sys.platform in ("linux", "win32"), reason="TODO: Fix this test to pass on Linux and Windows") | ||
def test_running_driver_event_loop(engine): | ||
def onStart(name): | ||
print("starting", name) | ||
|
||
def onWord(name, location, length): | ||
print("word", name, location, length) | ||
|
||
def onEnd(name, completed): | ||
if name == "fox": | ||
engine.say("What a lazy dog!", "dog") | ||
elif name == "dog": | ||
engine.endLoop() | ||
|
||
engine.connect("started-utterance", onStart) | ||
engine.connect("started-word", onWord) | ||
engine.connect("finished-utterance", onEnd) | ||
engine.say("The quick brown fox jumped over the lazy dog.", "fox") | ||
engine.startLoop() | ||
|
||
|
||
# Test for using an external event loop | ||
# @pytest.mark.timeout(10) # Set timeout to 10 seconds | ||
@pytest.mark.skipif(sys.platform in ("linux", "win32"), reason="TODO: Fix this test to pass on Linux and Windows") | ||
def test_external_event_loop(engine): | ||
def externalLoop(): | ||
for _ in range(5): | ||
engine.iterate() | ||
|
||
engine.say("The quick brown fox jumped over the lazy dog.", "fox") | ||
engine.startLoop(False) | ||
externalLoop() | ||
engine.endLoop() |