Add more local sourced directories for scripts and sounds meaning each user can have own scripts and sound themes when using -x. Hopefully fixed the remainder of the random freeze bug.

This commit is contained in:
Storm Dragon
2026-05-12 17:23:50 -04:00
parent 4022fc4006
commit b599a25945
15 changed files with 516 additions and 879 deletions
+122
View File
@@ -0,0 +1,122 @@
from unittest.mock import Mock
import pytest
from fenrirscreenreader.core.commandManager import CommandManager
from fenrirscreenreader.core.settingsManager import SettingsManager
def write_script(path):
path.write_text("#!/bin/sh\n", encoding="utf-8")
path.chmod(0o755)
def build_command_environment(local_path, system_path):
settings_manager = Mock()
settings_manager.get_user_script_path.return_value = str(local_path)
settings_manager.get_setting.return_value = str(system_path)
valid_keys = {
"KEY_A",
"KEY_B",
"KEY_C",
"KEY_D",
"KEY_E",
"KEY_SCRIPT",
}
return {
"commands": {"commands": {}},
"commandsIgnore": {"commands": {}},
"bindings": {
str([1, sorted(["KEY_C", "KEY_SCRIPT"])]): "EXISTING"
},
"rawBindings": {},
"general": {"curr_user": "Username"},
"runtime": {
"SettingsManager": settings_manager,
"DebugManager": Mock(write_debug_out=Mock()),
"InputManager": Mock(
is_valid_key=Mock(side_effect=lambda key: key in valid_keys)
),
},
}
@pytest.mark.unit
def test_local_scripts_load_before_non_conflicting_system_scripts(tmp_path):
local_path = tmp_path / "local"
system_path = tmp_path / "system"
local_path.mkdir()
system_path.mkdir()
write_script(local_path / "local_only__-__KEY_A")
write_script(local_path / "same_name__-__KEY_B")
write_script(system_path / "same_name__-__KEY_D")
write_script(system_path / "system_only__-__KEY_E")
env = build_command_environment(local_path, system_path)
command_manager = CommandManager()
command_manager.initialize = lambda environment: None
command_manager.env = env
command_manager.load_script_commands()
commands = env["commands"]["commands"]
assert "LOCAL_ONLY__-__KEY_A" in commands
assert "SYSTEM_ONLY__-__KEY_E" in commands
assert commands["SAME_NAME__-__KEY_B"].script_path == str(
local_path / "same_name__-__KEY_B"
)
assert "SAME_NAME__-__KEY_D" not in commands
@pytest.mark.unit
def test_system_scripts_skip_existing_shortcut_bindings(tmp_path):
local_path = tmp_path / "local"
system_path = tmp_path / "system"
local_path.mkdir()
system_path.mkdir()
write_script(system_path / "conflicting_key__-__KEY_C")
write_script(system_path / "system_only__-__KEY_E")
env = build_command_environment(local_path, system_path)
command_manager = CommandManager()
command_manager.env = env
command_manager.load_script_commands()
commands = env["commands"]["commands"]
assert "CONFLICTING_KEY__-__KEY_C" not in commands
assert "SYSTEM_ONLY__-__KEY_E" in commands
@pytest.mark.unit
def test_sound_theme_resolution_prefers_local_soundpacks(tmp_path):
manager = SettingsManager()
local_root = tmp_path / "local" / "fenrir"
system_root = tmp_path / "system" / "sounds" / "fenrir"
local_theme = local_root / "sounds" / "default"
system_theme = system_root / "default"
local_theme.mkdir(parents=True)
system_theme.mkdir(parents=True)
(local_theme / "soundicons.conf").write_text(
"Accept=Accept.wav\n", encoding="utf-8"
)
(system_theme / "soundicons.conf").write_text(
"Accept=SystemAccept.wav\n", encoding="utf-8"
)
manager.user_resource_root = str(local_root)
manager.system_sound_roots = [str(system_root)]
assert manager.resolve_sound_theme_path("default") == str(local_theme)
@pytest.mark.unit
def test_sound_theme_resolution_accepts_absolute_theme_path(tmp_path):
manager = SettingsManager()
theme_path = tmp_path / "custom"
theme_path.mkdir()
(theme_path / "soundicons.conf").write_text(
"Accept=Accept.wav\n", encoding="utf-8"
)
assert manager.resolve_sound_theme_path(str(theme_path)) == str(theme_path)
+48
View File
@@ -0,0 +1,48 @@
from unittest.mock import Mock
import pytest
from fenrirscreenreader.core.outputManager import OutputManager
def build_output_manager():
settings_manager = Mock()
settings_manager.get_setting_as_bool.return_value = True
settings_manager.get_setting_as_float.return_value = 1.0
sound_driver = Mock()
output_manager = OutputManager()
output_manager.env = {
"soundIcons": {
"ACCEPT": "/tmp/Accept.wav",
"ERRORSCREEN": "/tmp/ErrorScreen.wav",
},
"runtime": {
"SettingsManager": settings_manager,
"SoundDriver": sound_driver,
"SpeechDriver": Mock(),
"DebugManager": Mock(write_debug_out=Mock()),
},
}
return output_manager, sound_driver
@pytest.mark.unit
def test_present_text_allows_sound_only_feedback():
output_manager, sound_driver = build_output_manager()
output_manager.present_text("", sound_icon="Accept", interrupt=False)
sound_driver.play_sound_file.assert_called_once_with(
"/tmp/Accept.wav", False
)
@pytest.mark.unit
def test_play_sound_supports_error_alias():
output_manager, sound_driver = build_output_manager()
assert output_manager.play_sound("Error") is True
sound_driver.play_sound_file.assert_called_once_with(
"/tmp/ErrorScreen.wav", True
)
+42 -1
View File
@@ -1,6 +1,9 @@
import pytest
import threading
import time
from unittest.mock import Mock
import pytest
from fenrirscreenreader.core.eventData import FenrirEventType
from fenrirscreenreader.screenDriver.ptyDriver import PTYConstants
from fenrirscreenreader.screenDriver.ptyDriver import Terminal
@@ -71,10 +74,48 @@ def test_pty_stdin_input_interrupts_output_when_all_keys_interrupt_enabled():
}
pty_driver.interrupt_output_on_stdin_input(b"a")
pty_driver.stdin_interrupt_thread.join(timeout=1.0)
output_manager.interrupt_output.assert_called_once_with()
@pytest.mark.unit
def test_pty_stdin_input_interrupt_does_not_block_input_injection():
pty_driver = PtyDriver()
settings_manager = Mock()
settings_manager.get_setting_as_bool.return_value = True
settings_manager.get_setting.return_value = ""
interrupt_started = threading.Event()
release_interrupt = threading.Event()
def slow_interrupt():
interrupt_started.set()
release_interrupt.wait(timeout=1.0)
output_manager = Mock(interrupt_output=Mock(side_effect=slow_interrupt))
pty_driver.env = {
"input": {"curr_input": []},
"runtime": {
"SettingsManager": settings_manager,
"OutputManager": output_manager,
"DebugManager": Mock(write_debug_out=Mock()),
},
}
pty_driver.inject_text_to_screen = Mock()
start_time = time.monotonic()
pty_driver.handle_stdin_input(b"a", Mock())
elapsed = time.monotonic() - start_time
try:
assert interrupt_started.wait(timeout=0.2)
assert elapsed < 0.2
pty_driver.inject_text_to_screen.assert_called_once_with(b"a")
finally:
release_interrupt.set()
pty_driver.stdin_interrupt_thread.join(timeout=1.0)
@pytest.mark.unit
def test_pty_stdin_input_honors_interrupt_disabled():
pty_driver = PtyDriver()
+29
View File
@@ -0,0 +1,29 @@
from unittest.mock import Mock
import pytest
from fenrirscreenreader.core.soundDriver import sound_driver
from fenrirscreenreader.soundDriver import gstreamerDriver
@pytest.mark.unit
def test_base_sound_driver_shutdown_clears_initialized_flag():
driver = sound_driver()
driver.initialize({})
driver.shutdown()
assert driver._initialized is False
@pytest.mark.unit
def test_gstreamer_driver_unavailable_logs_without_crashing(monkeypatch):
monkeypatch.setattr(gstreamerDriver, "_gstreamerAvailable", False)
monkeypatch.setattr(gstreamerDriver, "_availableError", "missing", raising=False)
debug_manager = Mock(write_debug_out=Mock())
driver = gstreamerDriver.driver()
driver.initialize({"runtime": {"DebugManager": debug_manager}})
debug_manager.write_debug_out.assert_called()
assert driver._initialized is False
+33
View File
@@ -0,0 +1,33 @@
from unittest.mock import Mock
import pytest
from fenrirscreenreader.commands.commands import subprocess as subprocess_command
@pytest.mark.unit
def test_script_command_executes_without_shell(monkeypatch):
process = Mock()
process.communicate.return_value = (b"done", b"")
popen = Mock(return_value=process)
monkeypatch.setattr(subprocess_command, "Popen", popen)
output_manager = Mock()
command = subprocess_command.command()
command.initialize(
{
"general": {"curr_user": "Username"},
"runtime": {"OutputManager": output_manager},
},
"/tmp/script with spaces",
)
command._thread_run()
popen.assert_called_once_with(
["/tmp/script with spaces", "Username"],
stdout=subprocess_command.PIPE,
stderr=subprocess_command.PIPE,
)
output_manager.present_text.assert_called_once_with(
"done", sound_icon="", interrupt=False
)