feat(identity): rename user-visible identity to voculinux with one-time legacy import
The fork takes its own name everywhere a user can see it: binaries (voculinux, voculinux-gui), desktop entry, autostart, tray, D-Bus namespace (com.voculinux.Voculinux, no alias for the old name), config/data/cache dirs, single-instance lock, IBus engine identity, tray icons, resource prefixes, VOCULINUX_* environment names, and the update-checker user agent. The internal Python module stays vocalinux and the distribution metadata keeps the upstream URLs as provenance. On first run a previous Vocalinux install's config.json, custom dictionary, and portal restore token are copied once into the new locations (new config always wins, no merge, no write-back); models re-resolve through the pinned-checksum download path. An offline naming-sweep test proves no old-name strings remain on user-visible surfaces outside the allowlist. Refs #18
This commit is contained in:
+7
-7
@@ -19,11 +19,11 @@ import pytest
|
||||
os.environ["PYTEST_RUNNING"] = "1"
|
||||
|
||||
# Isolate the whole test process from the developer's real desktop session
|
||||
# and Vocalinux state, mirroring a fresh CI runner:
|
||||
# and Voculinux state, mirroring a fresh CI runner:
|
||||
# * XDG base dirs point at a throwaway directory, so locally downloaded
|
||||
# models (~/.local/share/vocalinux/models) and the real config file
|
||||
# models (~/.local/share/voculinux/models) and the real config file
|
||||
# never leak into tests. This must happen at conftest import time:
|
||||
# vocalinux modules freeze paths like MODELS_DIR at first import during
|
||||
# voculinux modules freeze paths like MODELS_DIR at first import during
|
||||
# collection, before any fixture could run. The directory is shared for
|
||||
# the whole session on purpose — some tests rely on directories that
|
||||
# earlier tests created, exactly as they do on CI with the runner's HOME.
|
||||
@@ -31,7 +31,7 @@ os.environ["PYTEST_RUNNING"] = "1"
|
||||
# session flips IBus/keyboard code paths (issue #478) that CI never sees.
|
||||
# Tests that need specific values set them via patch.dict/monkeypatch, which
|
||||
# layer on top of this.
|
||||
_XDG_ISOLATION_DIR = tempfile.mkdtemp(prefix="vocalinux-pytest-xdg-")
|
||||
_XDG_ISOLATION_DIR = tempfile.mkdtemp(prefix="voculinux-pytest-xdg-")
|
||||
os.environ["XDG_DATA_HOME"] = os.path.join(_XDG_ISOLATION_DIR, "data")
|
||||
os.environ["XDG_CONFIG_HOME"] = os.path.join(_XDG_ISOLATION_DIR, "config")
|
||||
os.environ["XDG_CACHE_HOME"] = os.path.join(_XDG_ISOLATION_DIR, "cache")
|
||||
@@ -53,7 +53,7 @@ try:
|
||||
except ImportError:
|
||||
_real_numpy = None
|
||||
else:
|
||||
sys._vocalinux_real_numpy = _real_numpy
|
||||
sys._voculinux_real_numpy = _real_numpy
|
||||
|
||||
# And the same for zipfile: test_recognition_manager.py and
|
||||
# test_recognition_manager_download_buffer.py assign a MagicMock to
|
||||
@@ -62,7 +62,7 @@ else:
|
||||
# reachable for the ones that need it.
|
||||
import zipfile as _real_zipfile # noqa: E402
|
||||
|
||||
sys._vocalinux_real_zipfile = _real_zipfile
|
||||
sys._voculinux_real_zipfile = _real_zipfile
|
||||
|
||||
# Prevent specific known-blocking daemon threads from starting during tests.
|
||||
# Source code in ibus_engine.py and evdev_backend.py spawns daemon threads
|
||||
@@ -105,7 +105,7 @@ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")
|
||||
|
||||
# Always mock GI/GTK modules before any test files are collected.
|
||||
# On CI, real gi (PyGObject) is installed via "pip install PyGObject" and
|
||||
# module-level imports in vocalinux source files (ibus_engine, tray_indicator,
|
||||
# module-level imports in voculinux source files (ibus_engine, tray_indicator,
|
||||
# settings_dialog, etc.) will trigger GTK initialization or IBus daemon
|
||||
# connections that hang indefinitely in headless environments.
|
||||
# pytest-timeout cannot interrupt these C-level blocking calls.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Distribution Testing Checklist
|
||||
|
||||
Use this checklist when testing Vocalinux on a new Linux distribution.
|
||||
Use this checklist when testing Voculinux on a new Linux distribution.
|
||||
|
||||
## Pre-Installation
|
||||
|
||||
@@ -51,22 +51,22 @@ Use this checklist when testing Vocalinux on a new Linux distribution.
|
||||
- [ ] Check for errors during installation
|
||||
- [ ] Verify virtual environment was created
|
||||
```bash
|
||||
ls -la ~/.local/share/vocalinux/venv/
|
||||
ls -la ~/.local/share/voculinux/venv/
|
||||
```
|
||||
|
||||
- [ ] Check wrapper scripts exist
|
||||
```bash
|
||||
ls -la ~/.local/bin/vocalinux*
|
||||
ls -la ~/.local/bin/voculinux*
|
||||
```
|
||||
|
||||
- [ ] Check desktop entry was created
|
||||
```bash
|
||||
ls -la ~/.local/share/applications/vocalinux.desktop
|
||||
ls -la ~/.local/share/applications/voculinux.desktop
|
||||
```
|
||||
|
||||
- [ ] Check icons were installed
|
||||
```bash
|
||||
ls -la ~/.local/share/icons/hicolor/scalable/apps/vocalinux*
|
||||
ls -la ~/.local/share/icons/hicolor/scalable/apps/voculinux*
|
||||
```
|
||||
|
||||
## Post-Installation
|
||||
@@ -74,7 +74,7 @@ Use this checklist when testing Vocalinux on a new Linux distribution.
|
||||
### Launch from Terminal
|
||||
- [ ] Launch from terminal
|
||||
```bash
|
||||
~/.local/bin/vocalinux-gui
|
||||
~/.local/bin/voculinux-gui
|
||||
```
|
||||
|
||||
- [ ] Check for startup errors
|
||||
@@ -154,7 +154,7 @@ Use this checklist when testing Vocalinux on a new Linux distribution.
|
||||
- [ ] Settings persist after restart
|
||||
- [ ] Config file is created correctly
|
||||
```bash
|
||||
cat ~/.config/vocalinux/config.json
|
||||
cat ~/.config/voculinux/config.json
|
||||
```
|
||||
|
||||
### Model Download (if using Whisper)
|
||||
@@ -162,7 +162,7 @@ Use this checklist when testing Vocalinux on a new Linux distribution.
|
||||
- [ ] Download progress is shown
|
||||
- [ ] Model is saved to correct location
|
||||
```bash
|
||||
ls -la ~/.local/share/vocalinux/models/
|
||||
ls -la ~/.local/share/voculinux/models/
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
@@ -179,7 +179,7 @@ Use this checklist when testing Vocalinux on a new Linux distribution.
|
||||
- [ ] Memory usage is reasonable
|
||||
```bash
|
||||
# Check memory usage
|
||||
ps aux | grep vocalinux
|
||||
ps aux | grep voculinux
|
||||
```
|
||||
|
||||
- [ ] CPU usage is reasonable during idle
|
||||
@@ -190,17 +190,17 @@ Use this checklist when testing Vocalinux on a new Linux distribution.
|
||||
- [ ] Application can be quit cleanly
|
||||
- [ ] No processes left running
|
||||
```bash
|
||||
ps aux | grep vocalinux
|
||||
ps aux | grep voculinux
|
||||
```
|
||||
|
||||
- [ ] Uninstallation works
|
||||
```bash
|
||||
bash ~/.local/share/vocalinux/uninstall.sh # if exists
|
||||
bash ~/.local/share/voculinux/uninstall.sh # if exists
|
||||
# or manual cleanup
|
||||
rm -rf ~/.local/share/vocalinux
|
||||
rm -rf ~/.config/vocalinux
|
||||
rm ~/.local/share/applications/vocalinux.desktop
|
||||
rm ~/.local/bin/vocalinux*
|
||||
rm -rf ~/.local/share/voculinux
|
||||
rm -rf ~/.config/voculinux
|
||||
rm ~/.local/share/applications/voculinux.desktop
|
||||
rm ~/.local/bin/voculinux*
|
||||
```
|
||||
|
||||
## Known Issues
|
||||
@@ -262,7 +262,7 @@ After completing the checklist:
|
||||
3. **Attach logs** if there were errors:
|
||||
- Installer output
|
||||
- Application output from terminal
|
||||
- Journal logs: `journalctl --user -u vocalinux -b`
|
||||
- Journal logs: `journalctl --user -u voculinux -b`
|
||||
|
||||
4. **Mention any workarounds** you found
|
||||
|
||||
@@ -293,13 +293,13 @@ pactl list sources short # PulseAudio
|
||||
arecord -l # ALSA
|
||||
|
||||
# Check for running processes
|
||||
ps aux | grep vocalinux
|
||||
ps aux | grep voculinux
|
||||
|
||||
# Check memory usage
|
||||
ps aux | grep vocalinux | awk '{print $6}'
|
||||
ps aux | grep voculinux | awk '{print $6}'
|
||||
|
||||
# Check logs
|
||||
journalctl --user -b | grep vocalinux
|
||||
journalctl --user -b | grep voculinux
|
||||
|
||||
# Run dependency checker
|
||||
bash scripts/check-system-deps.sh
|
||||
@@ -308,7 +308,7 @@ bash scripts/check-system-deps.sh
|
||||
bash -n install.sh
|
||||
|
||||
# Verify installation
|
||||
ls -la ~/.local/bin/vocalinux*
|
||||
ls -la ~/.local/share/vocalinux/
|
||||
ls -la ~/.config/vocalinux/
|
||||
ls -la ~/.local/bin/voculinux*
|
||||
ls -la ~/.local/share/voculinux/
|
||||
ls -la ~/.config/voculinux/
|
||||
```
|
||||
|
||||
+41
-26
@@ -7,7 +7,7 @@ Seams under test (per spec Testing Decisions):
|
||||
the rename ticket must flip (or deliberately keep) without network access.
|
||||
|
||||
No user-visible behaviour, name, path, or string changes: all asserted values
|
||||
are the pre-rename Vocalinux/vocalinux literals.
|
||||
are the pre-rename Voculinux/voculinux literals.
|
||||
"""
|
||||
|
||||
import ast
|
||||
@@ -26,27 +26,27 @@ def _load_identity():
|
||||
def test_central_values_are_current_names():
|
||||
"""Shared definition still carries the pre-rename literals."""
|
||||
identity = _load_identity()
|
||||
assert identity.APP_NAME == "Vocalinux"
|
||||
assert identity.APP_ID == "vocalinux"
|
||||
assert identity.APP_DIR_NAME == "vocalinux"
|
||||
assert identity.CLI_PROG == "vocalinux"
|
||||
assert identity.BUS_NAME == "com.vocalinux.Vocalinux"
|
||||
assert identity.OBJECT_PATH == "/com/vocalinux/Vocalinux"
|
||||
assert identity.INTERFACE_NAME == "com.vocalinux.Vocalinux"
|
||||
assert identity.DESKTOP_FILENAME == "vocalinux.desktop"
|
||||
assert identity.DESKTOP_ICON_NAME == "vocalinux"
|
||||
assert identity.ENGINE_NAME == "vocalinux"
|
||||
assert identity.ENGINE_LONGNAME == "Vocalinux"
|
||||
assert identity.COMPONENT_NAME == "org.freedesktop.IBus.Vocalinux"
|
||||
assert identity.IBUS_DIR_NAME == "vocalinux-ibus"
|
||||
assert identity.UPDATE_CHECKER_USER_AGENT == "Vocalinux-UpdateChecker"
|
||||
assert identity.RESOURCE_DIR_NAME == "vocalinux"
|
||||
assert identity.VERIFICATION_STAMP_NAME == ".vocalinux_verified"
|
||||
assert identity.LOG_FILE_PREFIX == "vocalinux_logs_"
|
||||
assert identity.PORTAL_TOKEN_PREFIX == "vocalinux_"
|
||||
assert identity.ENV_FORCE_BACKEND == "VOCALINUX_FORCE_BACKEND"
|
||||
assert identity.ENV_INJECT_MODIFIER_WAIT == "VOCALINUX_INJECT_MODIFIER_WAIT"
|
||||
assert identity.ENV_YDOTOOL_KEY_DELAY == "VOCALINUX_YDOTOOL_KEY_DELAY"
|
||||
assert identity.APP_NAME == "Voculinux"
|
||||
assert identity.APP_ID == "voculinux"
|
||||
assert identity.APP_DIR_NAME == "voculinux"
|
||||
assert identity.CLI_PROG == "voculinux"
|
||||
assert identity.BUS_NAME == "com.voculinux.Voculinux"
|
||||
assert identity.OBJECT_PATH == "/com/voculinux/Voculinux"
|
||||
assert identity.INTERFACE_NAME == "com.voculinux.Voculinux"
|
||||
assert identity.DESKTOP_FILENAME == "voculinux.desktop"
|
||||
assert identity.DESKTOP_ICON_NAME == "voculinux"
|
||||
assert identity.ENGINE_NAME == "voculinux"
|
||||
assert identity.ENGINE_LONGNAME == "Voculinux"
|
||||
assert identity.COMPONENT_NAME == "org.freedesktop.IBus.Voculinux"
|
||||
assert identity.IBUS_DIR_NAME == "voculinux-ibus"
|
||||
assert identity.UPDATE_CHECKER_USER_AGENT == "Voculinux-UpdateChecker"
|
||||
assert identity.RESOURCE_DIR_NAME == "voculinux"
|
||||
assert identity.VERIFICATION_STAMP_NAME == ".voculinux_verified"
|
||||
assert identity.LOG_FILE_PREFIX == "voculinux_logs_"
|
||||
assert identity.PORTAL_TOKEN_PREFIX == "voculinux_"
|
||||
assert identity.ENV_FORCE_BACKEND == "VOCULINUX_FORCE_BACKEND"
|
||||
assert identity.ENV_INJECT_MODIFIER_WAIT == "VOCULINUX_INJECT_MODIFIER_WAIT"
|
||||
assert identity.ENV_YDOTOOL_KEY_DELAY == "VOCULINUX_YDOTOOL_KEY_DELAY"
|
||||
|
||||
|
||||
def _imports_app_identity(path: Path) -> bool:
|
||||
@@ -108,13 +108,13 @@ def test_source_pinned_literals_reserved_for_rename():
|
||||
"""
|
||||
about = (SRC_ROOT / "ui" / "settings_about.py").read_text(encoding="utf-8")
|
||||
for literal in (
|
||||
'VOCALINUX_SITE_URL = "https://vocalinux.com"',
|
||||
'title="Vocalinux"',
|
||||
'get_icon_path("vocalinux")',
|
||||
'VOCULINUX_SITE_URL = "https://voculinux.com"',
|
||||
'title="Voculinux"',
|
||||
'get_icon_path("voculinux")',
|
||||
):
|
||||
assert literal in about, literal
|
||||
pad = (SRC_ROOT / "ui" / "dictation_pad.py").read_text(encoding="utf-8")
|
||||
assert 'window.set_title("Vocalinux Dictation Pad")' in pad
|
||||
assert 'window.set_title("Voculinux Dictation Pad")' in pad
|
||||
|
||||
|
||||
def test_rename_inventory_files_exist():
|
||||
@@ -134,3 +134,18 @@ def test_retained_inventory_records_internal_names():
|
||||
import vocalinux
|
||||
|
||||
assert vocalinux.__name__ == "vocalinux"
|
||||
|
||||
|
||||
def test_legacy_names_recorded_and_unclaimed():
|
||||
"""The old install identity is recorded for the one-time import only.
|
||||
|
||||
No compatibility alias: the legacy D-Bus name differs from the claimed
|
||||
one, and no source claims it.
|
||||
"""
|
||||
identity = _load_identity()
|
||||
assert identity.LEGACY_APP_DIR_NAME == "vocalinux"
|
||||
assert identity.LEGACY_APP_DIR_NAME != identity.APP_DIR_NAME
|
||||
assert identity.LEGACY_BUS_NAME == "com.vocalinux.Vocalinux"
|
||||
assert identity.LEGACY_BUS_NAME != identity.BUS_NAME
|
||||
service = (SRC_ROOT / "dbus_service.py").read_text(encoding="utf-8")
|
||||
assert identity.LEGACY_BUS_NAME not in service
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""The AppImage reuses its pywhispercpp Vulkan build only when the pins match.
|
||||
|
||||
The compile does not depend on the Vocalinux commit. The cache id is the base
|
||||
The compile does not depend on the Voculinux commit. The cache id is the base
|
||||
image, tool_checksums.txt, the pinned cmake and ninja, the Vulkan flags, and
|
||||
the pywhispercpp pin. It is not the git SHA and not the Actions run id. A tree
|
||||
without libggml-vulkan.so is a CPU wheel and is not cached.
|
||||
@@ -39,8 +39,8 @@ def _run(args: list[str], env: dict[str, str] | None = None) -> subprocess.Compl
|
||||
|
||||
def _cache_id(pins: Path, install: Path, extra: dict[str, str] | None = None) -> str:
|
||||
env = os.environ.copy()
|
||||
env["VOCALINUX_PINS"] = str(pins)
|
||||
env["VOCALINUX_INSTALL_SH"] = str(install)
|
||||
env["VOCULINUX_PINS"] = str(pins)
|
||||
env["VOCULINUX_INSTALL_SH"] = str(install)
|
||||
if extra:
|
||||
env.update(extra)
|
||||
result = _run(["bash", str(ID_SCRIPT)], env=env)
|
||||
@@ -103,7 +103,7 @@ def test_native_cache_id_tracks_build_tools_and_compile_flags(tmp_path: Path) ->
|
||||
tools = tmp_path / "appimage-tools.txt"
|
||||
tools_text = TOOLS.read_text(encoding="utf-8")
|
||||
tools.write_text(tools_text.replace("cmake", "cmake-other", 1), encoding="utf-8")
|
||||
changed_tools = _cache_id(PINS, INSTALL_SH, extra={"VOCALINUX_APPIMAGE_TOOLS": str(tools)})
|
||||
changed_tools = _cache_id(PINS, INSTALL_SH, extra={"VOCULINUX_APPIMAGE_TOOLS": str(tools)})
|
||||
assert changed_tools != baseline
|
||||
|
||||
commented = tmp_path / "build-comment.sh"
|
||||
@@ -111,7 +111,7 @@ def test_native_cache_id_tracks_build_tools_and_compile_flags(tmp_path: Path) ->
|
||||
commented.write_text(
|
||||
build_text.replace("# GPU:", "# GPU: unchanged flags\n# GPU:", 1), encoding="utf-8"
|
||||
)
|
||||
assert _cache_id(PINS, INSTALL_SH, extra={"VOCALINUX_BUILD_SH": str(commented)}) == baseline
|
||||
assert _cache_id(PINS, INSTALL_SH, extra={"VOCULINUX_BUILD_SH": str(commented)}) == baseline
|
||||
|
||||
flagged = tmp_path / "build-flags.sh"
|
||||
flagged.write_text(
|
||||
@@ -122,7 +122,7 @@ def test_native_cache_id_tracks_build_tools_and_compile_flags(tmp_path: Path) ->
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
assert _cache_id(PINS, INSTALL_SH, extra={"VOCALINUX_BUILD_SH": str(flagged)}) != baseline
|
||||
assert _cache_id(PINS, INSTALL_SH, extra={"VOCULINUX_BUILD_SH": str(flagged)}) != baseline
|
||||
|
||||
|
||||
def test_native_cache_id_does_not_read_the_commit_or_the_run() -> None:
|
||||
@@ -307,11 +307,11 @@ def test_a_failed_restore_runs_the_compile_command(tmp_path: Path) -> None:
|
||||
|
||||
def test_native_cache_dir_binds_the_arch_to_that_id(tmp_path: Path) -> None:
|
||||
env = os.environ.copy()
|
||||
env["VOCALINUX_APPIMAGE_CACHE"] = str(tmp_path / "cache")
|
||||
env.pop("VOCALINUX_PINS", None)
|
||||
env.pop("VOCALINUX_INSTALL_SH", None)
|
||||
env.pop("VOCALINUX_APPIMAGE_TOOLS", None)
|
||||
env.pop("VOCALINUX_BUILD_SH", None)
|
||||
env["VOCULINUX_APPIMAGE_CACHE"] = str(tmp_path / "cache")
|
||||
env.pop("VOCULINUX_PINS", None)
|
||||
env.pop("VOCULINUX_INSTALL_SH", None)
|
||||
env.pop("VOCULINUX_APPIMAGE_TOOLS", None)
|
||||
env.pop("VOCULINUX_BUILD_SH", None)
|
||||
result = _source("pywhispercpp_native_cache_dir x86_64", env=env)
|
||||
assert result.returncode == 0, result.stderr
|
||||
expected = _cache_id(PINS, INSTALL_SH)
|
||||
@@ -331,7 +331,7 @@ def test_build_reuses_the_cached_vulkan_tree_before_compiling() -> None:
|
||||
match = re.search(r"\nrebuild_pywhispercpp_vulkan\(\) \{(.+?)\n\}\n", text, re.S)
|
||||
assert match, "rebuild_pywhispercpp_vulkan disappeared"
|
||||
body = match.group(1)
|
||||
assert body.index("VOCALINUX_APPIMAGE_SKIP_VULKAN") < body.index("pywhispercpp_restore_or_run")
|
||||
assert body.index("VOCULINUX_APPIMAGE_SKIP_VULKAN") < body.index("pywhispercpp_restore_or_run")
|
||||
assert "GGML_VULKAN=1" not in body
|
||||
assert "_compile_pywhispercpp_vulkan" in body
|
||||
compile_fn = re.search(r"\n_compile_pywhispercpp_vulkan\(\) \{(.+?)\n\}\n", text, re.S)
|
||||
@@ -349,7 +349,7 @@ def test_appimage_workflows_share_one_native_cache_key() -> None:
|
||||
keys = []
|
||||
for name in APPIMAGE_WORKFLOWS:
|
||||
text = (WORKFLOWS / name).read_text(encoding="utf-8")
|
||||
found = re.findall(r"path: ~/.cache/vocalinux-appimage\n\s+key: (.+)\n", text)
|
||||
found = re.findall(r"path: ~/.cache/voculinux-appimage\n\s+key: (.+)\n", text)
|
||||
assert found, name
|
||||
assert all(key == NATIVE_KEY for key in found), found
|
||||
for token in ("github.sha", "github.run_id", "github.run_number", "github.run_attempt"):
|
||||
@@ -366,7 +366,7 @@ def test_appimage_workflows_share_one_native_cache_key() -> None:
|
||||
for token in ("github.sha", "github.run_id", "github.run_number", "github.run_attempt"):
|
||||
assert token not in step, step
|
||||
assert "packaging/appimage/docker-build.sh" in text
|
||||
assert 'VOCALINUX_APPIMAGE_REQUIRE_VULKAN: "1"' in text
|
||||
assert 'VOCULINUX_APPIMAGE_REQUIRE_VULKAN: "1"' in text
|
||||
assert "aarch64" in text
|
||||
keys.extend(found)
|
||||
assert len(keys) == 5
|
||||
|
||||
@@ -113,7 +113,7 @@ def test_running_from_appimage_follows_appdir(monkeypatch):
|
||||
|
||||
monkeypatch.delenv("APPDIR", raising=False)
|
||||
assert not running_from_appimage()
|
||||
monkeypatch.setenv("APPDIR", "/tmp/.mount_vocalinux")
|
||||
monkeypatch.setenv("APPDIR", "/tmp/.mount_voculinux")
|
||||
assert running_from_appimage()
|
||||
|
||||
|
||||
@@ -185,8 +185,8 @@ def test_appimage_build_rebuilds_pywhispercpp_with_vulkan():
|
||||
text = BUILD_SH.read_text()
|
||||
assert "GGML_VULKAN=1" in text
|
||||
assert "libggml-vulkan" in text
|
||||
assert "VOCALINUX_APPIMAGE_REQUIRE_VULKAN" in text
|
||||
assert "VOCALINUX_APPIMAGE_SKIP_VULKAN" in text
|
||||
assert "VOCULINUX_APPIMAGE_REQUIRE_VULKAN" in text
|
||||
assert "VOCULINUX_APPIMAGE_SKIP_VULKAN" in text
|
||||
assert "rebuild_pywhispercpp_vulkan" in text
|
||||
assert 'CC="${CC:-gcc}"' in text
|
||||
assert 'CXX="${CXX:-g++}"' in text
|
||||
@@ -345,7 +345,7 @@ def test_vulkan_shaders_get_the_compiler_the_base_image_lacks():
|
||||
for workflow in APPIMAGE_WORKFLOWS:
|
||||
content = (WORKFLOWS / workflow).read_text(encoding="utf-8")
|
||||
assert (
|
||||
"vocalinux-appimage" in content
|
||||
"voculinux-appimage" in content
|
||||
), f"{workflow} does not cache the built glslc; every run rebuilds it"
|
||||
|
||||
|
||||
|
||||
+12
-12
@@ -44,9 +44,9 @@ def test_local_gate_isolates_outputs_and_retains_failed_diagnostics(
|
||||
checkout = tmp_path / "checkout"
|
||||
scripts = checkout / "scripts"
|
||||
scripts.mkdir(parents=True)
|
||||
template = checkout / "packaging/aur/vocalinux"
|
||||
template = checkout / "packaging/aur/voculinux"
|
||||
template.mkdir(parents=True)
|
||||
shutil.copy2(ROOT / "packaging/aur/vocalinux/PKGBUILD", template / "PKGBUILD")
|
||||
shutil.copy2(ROOT / "packaging/aur/voculinux/PKGBUILD", template / "PKGBUILD")
|
||||
shutil.copy2(ROOT / "scripts/prepare_aur_source.py", scripts / "prepare_aur_source.py")
|
||||
|
||||
# Keep the real cleanup policy: shared dist/ would reject these outputs.
|
||||
@@ -78,22 +78,22 @@ def test_local_gate_isolates_outputs_and_retains_failed_diagnostics(
|
||||
if os.environ['AUR_GATE_TEST_FAILURE'] == 'build':
|
||||
sys.exit(31)
|
||||
args.output.mkdir(parents=True, exist_ok=True)
|
||||
with tarfile.open(args.output / 'vocalinux-0.18.4.tar.gz', 'w:gz') as archive:
|
||||
metadata = b'Name: vocalinux\\nVersion: 0.18.4\\n'
|
||||
member = tarfile.TarInfo('vocalinux-0.18.4/PKG-INFO')
|
||||
with tarfile.open(args.output / 'voculinux-0.18.4.tar.gz', 'w:gz') as archive:
|
||||
metadata = b'Name: voculinux\\nVersion: 0.18.4\\n'
|
||||
member = tarfile.TarInfo('voculinux-0.18.4/PKG-INFO')
|
||||
member.size = len(metadata)
|
||||
archive.addfile(member, io.BytesIO(metadata))
|
||||
(args.output / 'vocalinux-0.18.4-py3-none-any.whl').write_text('new wheel')
|
||||
(args.output / 'voculinux-0.18.4-py3-none-any.whl').write_text('new wheel')
|
||||
"""))
|
||||
|
||||
# Existing packaging outputs, wheel diagnostics and dev tooling are not ours.
|
||||
protected = {}
|
||||
for name in (
|
||||
"dist/vocalinux-0.18.4-py3-none-any.whl",
|
||||
"dist/vocalinux-0.18.4.tar.gz",
|
||||
"dist/Vocalinux-0.18.4-x86_64.AppImage",
|
||||
"dist/vocalinux_0.18.4_amd64.deb",
|
||||
"dist/vocalinux-0.18.4.x86_64.rpm",
|
||||
"dist/voculinux-0.18.4-py3-none-any.whl",
|
||||
"dist/voculinux-0.18.4.tar.gz",
|
||||
"dist/Voculinux-0.18.4-x86_64.AppImage",
|
||||
"dist/voculinux_0.18.4_amd64.deb",
|
||||
"dist/voculinux-0.18.4.x86_64.rpm",
|
||||
"build/reproducible-wheel/canonical.log",
|
||||
".venv/keep.txt",
|
||||
):
|
||||
@@ -133,7 +133,7 @@ def test_local_gate_isolates_outputs_and_retains_failed_diagnostics(
|
||||
source = next(arg.removesuffix(':/aur-source:ro') for arg in sys.argv
|
||||
if arg.endswith(':/aur-source:ro'))
|
||||
assert (Path(source) / 'PKGBUILD').is_file()
|
||||
assert (Path(source) / 'vocalinux-0.18.4.tar.gz').is_file()
|
||||
assert (Path(source) / 'voculinux-0.18.4.tar.gz').is_file()
|
||||
assert 'AUR_SOURCE=/aur-source' in sys.argv
|
||||
with open(os.environ['AUR_GATE_TEST_LOG'], 'a') as log:
|
||||
log.write(json.dumps(['arch', source]) + '\\n')
|
||||
|
||||
+22
-22
@@ -11,8 +11,8 @@ from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
AUR = REPO_ROOT / "packaging" / "aur"
|
||||
PKGBUILD = AUR / "vocalinux" / "PKGBUILD"
|
||||
BIN_DIR = AUR / "vocalinux-bin"
|
||||
PKGBUILD = AUR / "voculinux" / "PKGBUILD"
|
||||
BIN_DIR = AUR / "voculinux-bin"
|
||||
BIN_PKGBUILD = BIN_DIR / "PKGBUILD"
|
||||
GATE_SH = AUR / "build-test.sh"
|
||||
WORKFLOWS = REPO_ROOT / ".github" / "workflows"
|
||||
@@ -190,7 +190,7 @@ def test_the_smoke_walks_the_pinned_deps():
|
||||
|
||||
assert "requirements/runtime.txt" in script
|
||||
assert "--require-hashes" in script
|
||||
assert "pacman -U" in script and "vocalinux --version" in script
|
||||
assert "pacman -U" in script and "voculinux --version" in script
|
||||
# Arch name -> PyPI name for the export the gate installs from.
|
||||
pypi_names = {"python-pywhispercpp": "pywhispercpp", "python-pynput": "pynput"}
|
||||
for arch_name, pypi_name in pypi_names.items():
|
||||
@@ -369,7 +369,7 @@ def test_bin_package_ships_the_release_appimage_per_arch() -> None:
|
||||
text = _bin_pkgbuild()
|
||||
assert "arch=('x86_64' 'aarch64')" in text
|
||||
for arch in ("x86_64", "aarch64"):
|
||||
asset = f"Vocalinux-${{_tag}}-{arch}.AppImage"
|
||||
asset = f"Voculinux-${{_tag}}-{arch}.AppImage"
|
||||
assert (
|
||||
f'"{asset}::https://github.com/VocaHQ/vocalinux/releases/download/'
|
||||
f"v${{_tag}}/{asset}" in text
|
||||
@@ -383,19 +383,19 @@ def test_bin_package_installs_the_appimage_intact_and_runnable() -> None:
|
||||
text = _bin_pkgbuild()
|
||||
assert "options=('!strip')" in text
|
||||
assert "'fuse2'" in text
|
||||
assert 'install -Dm755 "Vocalinux-${_tag}-${CARCH}.AppImage"' in text
|
||||
assert 'install -Dm755 "Voculinux-${_tag}-${CARCH}.AppImage"' in text
|
||||
assert '"/opt/${pkgname}/' in text
|
||||
assert "ln -s" in text and '"${pkgdir}/usr/bin/vocalinux"' in text
|
||||
assert "ln -s" in text and '"${pkgdir}/usr/bin/voculinux"' in text
|
||||
|
||||
|
||||
def test_bin_package_conflicts_with_the_source_and_git_packages() -> None:
|
||||
"""All three packages ship /usr/bin/vocalinux; provides= lets a depends on
|
||||
'vocalinux' resolve to the -bin install."""
|
||||
"""All three packages ship /usr/bin/voculinux; provides= lets a depends on
|
||||
'voculinux' resolve to the -bin install."""
|
||||
text = _bin_pkgbuild()
|
||||
assert "provides=('vocalinux')" in text
|
||||
assert "provides=('voculinux')" in text
|
||||
conflicts = re.search(r"conflicts=\(([^)]*)\)", text)
|
||||
assert conflicts, "vocalinux-bin has no conflicts=()"
|
||||
for name in ("'vocalinux'", "'vocalinux-git'"):
|
||||
assert conflicts, "voculinux-bin has no conflicts=()"
|
||||
for name in ("'voculinux'", "'voculinux-git'"):
|
||||
assert name in conflicts.group(1)
|
||||
|
||||
|
||||
@@ -405,8 +405,8 @@ def test_bin_pkgbuild_bundled_files_match_the_repo_copies() -> None:
|
||||
outside the bundle). Guarded byte-for-byte so a fix to a canonical file
|
||||
cannot leave the -bin package shipping the stale one."""
|
||||
for name, canonical in (
|
||||
("vocalinux.desktop", REPO_ROOT / "vocalinux.desktop"),
|
||||
("vocalinux.svg", REPO_ROOT / "resources" / "icons" / "scalable" / "vocalinux.svg"),
|
||||
("voculinux.desktop", REPO_ROOT / "voculinux.desktop"),
|
||||
("voculinux.svg", REPO_ROOT / "resources" / "icons" / "scalable" / "voculinux.svg"),
|
||||
("LICENSE", REPO_ROOT / "LICENSE"),
|
||||
):
|
||||
bundled = BIN_DIR / name
|
||||
@@ -415,10 +415,10 @@ def test_bin_pkgbuild_bundled_files_match_the_repo_copies() -> None:
|
||||
f"{name} drifted from {canonical.relative_to(REPO_ROOT)};" " sync the bundled copy"
|
||||
)
|
||||
assert f"'{name}'" in _bin_pkgbuild(), f"{name} not in source=()"
|
||||
desktop = (BIN_DIR / "vocalinux.desktop").read_text(encoding="utf-8")
|
||||
desktop = (BIN_DIR / "voculinux.desktop").read_text(encoding="utf-8")
|
||||
# The names package() links into /usr/bin and installs under hicolor.
|
||||
assert re.search(r"^Exec=vocalinux$", desktop, re.M)
|
||||
assert re.search(r"^Icon=vocalinux$", desktop, re.M)
|
||||
assert re.search(r"^Exec=voculinux$", desktop, re.M)
|
||||
assert re.search(r"^Icon=voculinux$", desktop, re.M)
|
||||
|
||||
|
||||
def test_bin_package_asks_the_host_only_for_what_the_bundle_lacks() -> None:
|
||||
@@ -436,7 +436,7 @@ def test_bin_package_asks_the_host_only_for_what_the_bundle_lacks() -> None:
|
||||
assert "'wtype'" in depends.group(1)
|
||||
lines = text.splitlines()
|
||||
start = next((i for i, line in enumerate(lines) if line.startswith("optdepends=(")), None)
|
||||
assert start is not None, "vocalinux-bin has no optdepends=()"
|
||||
assert start is not None, "voculinux-bin has no optdepends=()"
|
||||
end = next(i for i in range(start + 1, len(lines)) if lines[i].strip() == ")")
|
||||
optdepends = "\n".join(lines[start + 1 : end])
|
||||
for tool in ("ibus", "xclip", "wl-clipboard", "ydotool"):
|
||||
@@ -445,7 +445,7 @@ def test_bin_package_asks_the_host_only_for_what_the_bundle_lacks() -> None:
|
||||
|
||||
|
||||
def test_bin_pkgbuild_tracks_the_release_tag_like_the_source_one() -> None:
|
||||
"""Same pkgver/_tag split as packaging/aur/vocalinux, real digests for
|
||||
"""Same pkgver/_tag split as packaging/aur/voculinux, real digests for
|
||||
the published AppImages, and the release workflow must bump and publish
|
||||
both — a -bin left on the last tag would serve the previous release's
|
||||
AppImage."""
|
||||
@@ -455,14 +455,14 @@ def test_bin_pkgbuild_tracks_the_release_tag_like_the_source_one() -> None:
|
||||
# Local builds must verify payloads against real sums, not SKIP.
|
||||
assert "'SKIP'" not in text
|
||||
release = RELEASE.read_text(encoding="utf-8")
|
||||
assert "packaging/aur/vocalinux-bin/PKGBUILD" in release, (
|
||||
assert "packaging/aur/voculinux-bin/PKGBUILD" in release, (
|
||||
"release.yml bumps the source PKGBUILD only; the -bin package would"
|
||||
" keep installing the previous release's AppImage"
|
||||
)
|
||||
# The publish step's own updpkgsums only refreshes the x86_64 array, so
|
||||
# release.yml pins both per-arch digests itself right before publishing.
|
||||
assert re.search(r"for arch in x86_64 aarch64[\s\S]*sha256sums_\$\{arch\}", release), (
|
||||
"release.yml must pin per-arch AppImage digests for vocalinux-bin — "
|
||||
"release.yml must pin per-arch AppImage digests for voculinux-bin — "
|
||||
"the publish action's updpkgsums only covers the runner's arch"
|
||||
)
|
||||
# The aarch64 AppImage lands in a later job; the -bin publish must wait
|
||||
@@ -472,12 +472,12 @@ def test_bin_pkgbuild_tracks_the_release_tag_like_the_source_one() -> None:
|
||||
), "publish-aur-bin must need build-appimage-arm64 so the aarch64 asset exists"
|
||||
# The -bin PKGBUILD names local sources; a lone-PKGBUILD publish leaves
|
||||
# users unable to build. asset_dir mirrors the whole directory to AUR.
|
||||
assert "asset_dir: packaging/aur/vocalinux-bin" in release
|
||||
assert "asset_dir: packaging/aur/voculinux-bin" in release
|
||||
|
||||
|
||||
def test_the_gate_builds_the_bin_package_too() -> None:
|
||||
"""The -bin package needs build validation as much as the source one:
|
||||
the gate stubs the release AppImage and runs makepkg on it."""
|
||||
script = GATE_SH.read_text(encoding="utf-8")
|
||||
assert "vocalinux-bin" in script
|
||||
assert "voculinux-bin" in script
|
||||
assert "--skipchecksums" in script
|
||||
|
||||
@@ -678,7 +678,7 @@ class TestAutoPauseConfigDefaults(unittest.TestCase):
|
||||
from vocalinux.ui.config_manager import ConfigManager
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
config_dir = os.path.join(tmp, "vocalinux")
|
||||
config_dir = os.path.join(tmp, "voculinux")
|
||||
config_file = os.path.join(config_dir, "config.json")
|
||||
os.makedirs(config_dir, exist_ok=True)
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@ class TestAutostartManager(unittest.TestCase):
|
||||
def test_get_exec_command_prefers_installed_command(self):
|
||||
with patch(
|
||||
"vocalinux.ui.autostart_manager.shutil.which",
|
||||
return_value="/usr/bin/vocalinux",
|
||||
return_value="/usr/bin/voculinux",
|
||||
):
|
||||
command = autostart_manager.get_exec_command()
|
||||
self.assertEqual(command, "/usr/bin/vocalinux --start-minimized")
|
||||
self.assertEqual(command, "/usr/bin/voculinux --start-minimized")
|
||||
|
||||
def test_get_exec_command_falls_back_to_python_module(self):
|
||||
with (
|
||||
@@ -31,22 +31,22 @@ class TestAutostartManager(unittest.TestCase):
|
||||
|
||||
def test_get_exec_command_flatpak_uses_flatpak_run(self):
|
||||
# Inside a Flatpak the host session manager must use the flatpak launcher,
|
||||
# which is always on PATH, rather than /app/bin/vocalinux.
|
||||
with patch.dict("os.environ", {"FLATPAK_ID": "com.vocalinux.Vocalinux"}, clear=False):
|
||||
# which is always on PATH, rather than /app/bin/voculinux.
|
||||
with patch.dict("os.environ", {"FLATPAK_ID": "com.voculinux.Voculinux"}, clear=False):
|
||||
command = autostart_manager.get_exec_command()
|
||||
self.assertEqual(command, "flatpak run com.vocalinux.Vocalinux --start-minimized")
|
||||
self.assertEqual(command, "flatpak run com.voculinux.Voculinux --start-minimized")
|
||||
|
||||
def test_enable_autostart_flatpak_writes_flatpak_run_exec(self):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
with patch.dict(
|
||||
"os.environ",
|
||||
{"XDG_CONFIG_HOME": tmp_dir, "FLATPAK_ID": "com.vocalinux.Vocalinux"},
|
||||
{"XDG_CONFIG_HOME": tmp_dir, "FLATPAK_ID": "com.voculinux.Voculinux"},
|
||||
clear=False,
|
||||
):
|
||||
self.assertTrue(autostart_manager.enable_autostart())
|
||||
desktop_file = Path(tmp_dir) / "autostart" / "vocalinux.desktop"
|
||||
desktop_file = Path(tmp_dir) / "autostart" / "voculinux.desktop"
|
||||
content = desktop_file.read_text(encoding="utf-8")
|
||||
self.assertIn("Exec=flatpak run com.vocalinux.Vocalinux --start-minimized", content)
|
||||
self.assertIn("Exec=flatpak run com.voculinux.Voculinux --start-minimized", content)
|
||||
|
||||
def test_enable_disable_autostart_creates_and_removes_entry(self):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
@@ -54,16 +54,16 @@ class TestAutostartManager(unittest.TestCase):
|
||||
patch.dict("os.environ", {"XDG_CONFIG_HOME": tmp_dir}, clear=False),
|
||||
patch(
|
||||
"vocalinux.ui.autostart_manager.shutil.which",
|
||||
return_value="/usr/bin/vocalinux",
|
||||
return_value="/usr/bin/voculinux",
|
||||
),
|
||||
):
|
||||
enabled = autostart_manager.enable_autostart()
|
||||
self.assertTrue(enabled)
|
||||
|
||||
desktop_file = Path(tmp_dir) / "autostart" / "vocalinux.desktop"
|
||||
desktop_file = Path(tmp_dir) / "autostart" / "voculinux.desktop"
|
||||
self.assertTrue(desktop_file.exists())
|
||||
content = desktop_file.read_text(encoding="utf-8")
|
||||
self.assertIn("Exec=/usr/bin/vocalinux --start-minimized", content)
|
||||
self.assertIn("Exec=/usr/bin/voculinux --start-minimized", content)
|
||||
|
||||
disabled = autostart_manager.disable_autostart()
|
||||
self.assertTrue(disabled)
|
||||
|
||||
@@ -34,8 +34,8 @@ class TestAutostartManagerExtra:
|
||||
from vocalinux.ui import autostart_manager
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
autostart_file = Path(tmp_dir) / "vocalinux.desktop"
|
||||
autostart_file.write_text("[Desktop Entry]\nName=Vocalinux")
|
||||
autostart_file = Path(tmp_dir) / "voculinux.desktop"
|
||||
autostart_file.write_text("[Desktop Entry]\nName=Voculinux")
|
||||
|
||||
with patch.object(autostart_manager, "get_autostart_file", return_value=autostart_file):
|
||||
assert autostart_manager.is_autostart_enabled() is True
|
||||
@@ -45,7 +45,7 @@ class TestAutostartManagerExtra:
|
||||
from vocalinux.ui import autostart_manager
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
autostart_file = Path(tmp_dir) / "vocalinux.desktop"
|
||||
autostart_file = Path(tmp_dir) / "voculinux.desktop"
|
||||
|
||||
with patch.object(autostart_manager, "get_autostart_file", return_value=autostart_file):
|
||||
assert autostart_manager.is_autostart_enabled() is False
|
||||
@@ -59,7 +59,7 @@ class TestAutostartManagerExtra:
|
||||
readonly_dir = Path(tmp_dir) / "readonly"
|
||||
readonly_dir.mkdir()
|
||||
|
||||
autostart_file = readonly_dir / "vocalinux.desktop"
|
||||
autostart_file = readonly_dir / "voculinux.desktop"
|
||||
readonly_dir.chmod(0o444) # Remove write permission
|
||||
|
||||
try:
|
||||
@@ -72,7 +72,7 @@ class TestAutostartManagerExtra:
|
||||
with patch.object(
|
||||
autostart_manager,
|
||||
"get_exec_command",
|
||||
return_value="vocalinux --start-minimized",
|
||||
return_value="voculinux --start-minimized",
|
||||
):
|
||||
result = autostart_manager.enable_autostart()
|
||||
assert result is False
|
||||
@@ -87,23 +87,23 @@ class TestAutostartManagerExtra:
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
with patch.dict(os.environ, {"XDG_CONFIG_HOME": tmp_dir}, clear=False):
|
||||
with patch(
|
||||
"vocalinux.ui.autostart_manager.shutil.which", return_value="/usr/bin/vocalinux"
|
||||
"vocalinux.ui.autostart_manager.shutil.which", return_value="/usr/bin/voculinux"
|
||||
):
|
||||
result = autostart_manager.enable_autostart()
|
||||
assert result is True
|
||||
|
||||
autostart_file = Path(tmp_dir) / "autostart" / "vocalinux.desktop"
|
||||
autostart_file = Path(tmp_dir) / "autostart" / "voculinux.desktop"
|
||||
assert autostart_file.exists()
|
||||
content = autostart_file.read_text(encoding="utf-8")
|
||||
assert "[Desktop Entry]" in content
|
||||
assert "vocalinux" in content
|
||||
assert "voculinux" in content
|
||||
|
||||
def test_disable_autostart_file_not_found(self):
|
||||
"""Test disable_autostart succeeds even if file doesn't exist."""
|
||||
from vocalinux.ui import autostart_manager
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
autostart_file = Path(tmp_dir) / "vocalinux.desktop"
|
||||
autostart_file = Path(tmp_dir) / "voculinux.desktop"
|
||||
|
||||
with patch.object(autostart_manager, "get_autostart_file", return_value=autostart_file):
|
||||
result = autostart_manager.disable_autostart()
|
||||
@@ -114,8 +114,8 @@ class TestAutostartManagerExtra:
|
||||
from vocalinux.ui import autostart_manager
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
autostart_file = Path(tmp_dir) / "vocalinux.desktop"
|
||||
autostart_file.write_text("[Desktop Entry]\nName=Vocalinux")
|
||||
autostart_file = Path(tmp_dir) / "voculinux.desktop"
|
||||
autostart_file.write_text("[Desktop Entry]\nName=Voculinux")
|
||||
|
||||
# Create a mock that will raise a permission error on unlink()
|
||||
mock_file = MagicMock()
|
||||
@@ -133,12 +133,12 @@ class TestAutostartManagerExtra:
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
with patch.dict(os.environ, {"XDG_CONFIG_HOME": tmp_dir}, clear=False):
|
||||
with patch(
|
||||
"vocalinux.ui.autostart_manager.shutil.which", return_value="/usr/bin/vocalinux"
|
||||
"vocalinux.ui.autostart_manager.shutil.which", return_value="/usr/bin/voculinux"
|
||||
):
|
||||
result = autostart_manager.set_autostart(True)
|
||||
assert result is True
|
||||
|
||||
autostart_file = Path(tmp_dir) / "autostart" / "vocalinux.desktop"
|
||||
autostart_file = Path(tmp_dir) / "autostart" / "voculinux.desktop"
|
||||
assert autostart_file.exists()
|
||||
|
||||
def test_set_autostart_enabled_false(self):
|
||||
@@ -149,8 +149,8 @@ class TestAutostartManagerExtra:
|
||||
# First create the file
|
||||
autostart_dir = Path(tmp_dir) / "autostart"
|
||||
autostart_dir.mkdir(parents=True)
|
||||
autostart_file = autostart_dir / "vocalinux.desktop"
|
||||
autostart_file.write_text("[Desktop Entry]\nName=Vocalinux")
|
||||
autostart_file = autostart_dir / "voculinux.desktop"
|
||||
autostart_file.write_text("[Desktop Entry]\nName=Voculinux")
|
||||
|
||||
with patch.dict(os.environ, {"XDG_CONFIG_HOME": tmp_dir}, clear=False):
|
||||
result = autostart_manager.set_autostart(False)
|
||||
@@ -164,10 +164,10 @@ class TestAutostartManagerExtra:
|
||||
with patch("vocalinux.ui.autostart_manager.shutil.which", return_value=None):
|
||||
with patch("vocalinux.ui.autostart_manager.sys.frozen", True, create=True):
|
||||
with patch(
|
||||
"vocalinux.ui.autostart_manager.sys.executable", "/usr/local/bin/vocalinux-bin"
|
||||
"vocalinux.ui.autostart_manager.sys.executable", "/usr/local/bin/voculinux-bin"
|
||||
):
|
||||
command = autostart_manager.get_exec_command()
|
||||
assert "/usr/local/bin/vocalinux-bin --start-minimized" in command
|
||||
assert "/usr/local/bin/voculinux-bin --start-minimized" in command
|
||||
|
||||
def test_get_autostart_dir_without_xdg_config_home(self):
|
||||
"""Test get_autostart_dir uses default location when XDG_CONFIG_HOME is not set."""
|
||||
@@ -186,5 +186,5 @@ class TestAutostartManagerExtra:
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
with patch.dict(os.environ, {"XDG_CONFIG_HOME": tmp_dir}, clear=False):
|
||||
autostart_file = autostart_manager.get_autostart_file()
|
||||
expected = Path(tmp_dir) / "autostart" / "vocalinux.desktop"
|
||||
expected = Path(tmp_dir) / "autostart" / "voculinux.desktop"
|
||||
assert autostart_file == expected
|
||||
|
||||
@@ -116,7 +116,7 @@ def test_overflow_does_not_reset_deadline(stream: MagicMock, clock: SimpleNamesp
|
||||
def test_portaudio_chunk_preserves_normalization(
|
||||
stream: MagicMock, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
monkeypatch.setitem(sys.modules, "numpy", sys._vocalinux_real_numpy)
|
||||
monkeypatch.setitem(sys.modules, "numpy", sys._voculinux_real_numpy)
|
||||
stream.read.return_value = b"\x64\x00\x64\x00" * 1024
|
||||
source = capture.PortAudioCaptureSource()
|
||||
source.stream = stream
|
||||
@@ -133,7 +133,7 @@ def audio_test(monkeypatch: pytest.MonkeyPatch, stream: MagicMock) -> tuple[Magi
|
||||
"""Supply one negotiated stream without touching PortAudio or the desktop."""
|
||||
audio = MagicMock()
|
||||
audio.get_default_input_device_info.return_value = {"name": "Test Mic", "index": 3}
|
||||
monkeypatch.setitem(sys.modules, "numpy", sys._vocalinux_real_numpy)
|
||||
monkeypatch.setitem(sys.modules, "numpy", sys._voculinux_real_numpy)
|
||||
monkeypatch.setitem(sys.modules, "pyaudio", MagicMock(PyAudio=lambda: audio, paInt16=8))
|
||||
monkeypatch.setattr(capture, "_open_capture_stream", lambda *_args: (1, 16000, stream))
|
||||
return audio, stream
|
||||
@@ -246,7 +246,7 @@ def test_pipewire_select_is_sliced_and_cancellable(monkeypatch: pytest.MonkeyPat
|
||||
def test_pipewire_test_partial_failure_cleans_up(
|
||||
monkeypatch: pytest.MonkeyPatch, failure: str
|
||||
) -> None:
|
||||
monkeypatch.setitem(sys.modules, "numpy", sys._vocalinux_real_numpy)
|
||||
monkeypatch.setitem(sys.modules, "numpy", sys._voculinux_real_numpy)
|
||||
source = MagicMock(sample_rate=16000, _CHUNK_FRAMES=1024)
|
||||
source.read_chunk.side_effect = [
|
||||
b"\x01\x00" * 1024,
|
||||
|
||||
@@ -17,7 +17,7 @@ from vocalinux.speech_recognition import recognition_manager as rm
|
||||
def manager(monkeypatch: pytest.MonkeyPatch) -> Generator[rm.SpeechRecognitionManager, None, None]:
|
||||
monkeypatch.setattr(rm.SpeechRecognitionManager, "_init_vosk", lambda self: None)
|
||||
monkeypatch.setattr(rm, "load_silero_vad", lambda: None)
|
||||
monkeypatch.setitem(sys.modules, "numpy", sys._vocalinux_real_numpy)
|
||||
monkeypatch.setitem(sys.modules, "numpy", sys._voculinux_real_numpy)
|
||||
for name in ("play_start_sound", "play_stop_sound", "play_error_sound"):
|
||||
monkeypatch.setattr(rm, name, Mock())
|
||||
# Close this patch before the autouse notification patch exits.
|
||||
|
||||
@@ -26,7 +26,7 @@ def manager(monkeypatch: pytest.MonkeyPatch) -> Generator[rm.SpeechRecognitionMa
|
||||
monkeypatch.setattr(rm.SpeechRecognitionManager, "_init_vosk", lambda self: None)
|
||||
monkeypatch.setattr(rm, "load_silero_vad", lambda: None)
|
||||
monkeypatch.setattr(rm.SpeechRecognitionManager, "_download_selected_model_if_missing", Mock())
|
||||
monkeypatch.setitem(sys.modules, "numpy", sys._vocalinux_real_numpy)
|
||||
monkeypatch.setitem(sys.modules, "numpy", sys._voculinux_real_numpy)
|
||||
for name in ("play_start_sound", "play_stop_sound", "play_error_sound"):
|
||||
monkeypatch.setattr(rm, name, Mock())
|
||||
monkeypatch.setattr(rm, "_CAPTURE_JOIN_SECONDS", 0.02)
|
||||
|
||||
@@ -32,7 +32,7 @@ class TestConfigManager(unittest.TestCase):
|
||||
"""Set up test environment."""
|
||||
# Create a temporary directory for configuration
|
||||
self.temp_dir = tempfile.TemporaryDirectory()
|
||||
self.temp_config_dir = os.path.join(self.temp_dir.name, ".config/vocalinux")
|
||||
self.temp_config_dir = os.path.join(self.temp_dir.name, ".config/voculinux")
|
||||
_ensure_test_config_dir(self.temp_config_dir)
|
||||
self.temp_config_file = os.path.join(self.temp_config_dir, "config.json")
|
||||
|
||||
@@ -891,7 +891,7 @@ class TestTypedAccessors(unittest.TestCase):
|
||||
def test_whispercpp_advanced_persistence(self):
|
||||
# This class skips ConfigManager.__init__, so unlike TestConfigManager
|
||||
# nothing has redirected CONFIG_FILE: without the patches below this
|
||||
# save_config() overwrites the developer's real ~/.config/vocalinux/
|
||||
# save_config() overwrites the developer's real ~/.config/voculinux/
|
||||
# config.json with the synthetic fixture from setUp.
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_file = os.path.join(temp_dir, "config.json")
|
||||
|
||||
@@ -21,9 +21,11 @@ def get_repo_root() -> Path:
|
||||
# Check for common repo indicators
|
||||
if (parent / ".git").exists() or (parent / "install.sh").exists():
|
||||
return parent
|
||||
# Also check if we're in a 'vocalinux' subdirectory (CI structure)
|
||||
if parent.name == "vocalinux" and (parent.parent / "vocalinux" / "install.sh").exists():
|
||||
return parent.parent / "vocalinux"
|
||||
# Also check if we're in a checkout-named subdirectory (CI structure)
|
||||
if parent.name in {"vocalinux", "voculinux"}:
|
||||
for sibling in ("voculinux", "vocalinux"):
|
||||
if (parent.parent / sibling / "install.sh").exists():
|
||||
return parent.parent / sibling
|
||||
# Fallback: use parent of tests directory
|
||||
return current.parent.parent.parent
|
||||
|
||||
@@ -98,10 +100,10 @@ class TestCrossDistroCompatibility:
|
||||
if desktop_start == -1:
|
||||
pytest.skip("install_desktop_entry function not found")
|
||||
desktop_section = install_sh_content[desktop_start : desktop_start + 1000]
|
||||
if "sed -i" in desktop_section and "vocalinux.desktop" in desktop_section:
|
||||
if "sed -i" in desktop_section and "voculinux.desktop" in desktop_section:
|
||||
assert (
|
||||
"/usr/lib/girepository-1.0"
|
||||
not in desktop_section.split("sed -i")[1].split("vocalinux.desktop")[0]
|
||||
not in desktop_section.split("sed -i")[1].split("voculinux.desktop")[0]
|
||||
), "Desktop entry should not hardcode /usr/lib/girepository-1.0 path"
|
||||
|
||||
def test_detect_typelib_path_with_pkgconfig(self, install_sh_content):
|
||||
|
||||
@@ -91,10 +91,10 @@ def test_terms_are_live_reloaded_and_scanner_friendly(tmp_path: Path, monkeypatc
|
||||
"""The UTF-8 line file accepts comments and applies an external replacement."""
|
||||
manager = manager_at(tmp_path, monkeypatch, FakeConfig({"dictionary": {"enabled": True}}))
|
||||
terms_file = tmp_path / TERMS_FILENAME
|
||||
terms_file.write_text("# scanner comment\nVocaLinux\nvocalinux\nSupabase\n", encoding="utf-8")
|
||||
terms_file.write_text("# scanner comment\nVoculinux\nvoculinux\nSupabase\n", encoding="utf-8")
|
||||
|
||||
assert manager.get_terms() == ["VocaLinux", "Supabase"]
|
||||
assert manager.build_initial_prompt() == "VocaLinux Supabase"
|
||||
assert manager.get_terms() == ["Voculinux", "Supabase"]
|
||||
assert manager.build_initial_prompt() == "Voculinux Supabase"
|
||||
|
||||
terms_file.write_text("PyGObject\n", encoding="utf-8")
|
||||
assert manager.build_initial_prompt() == "PyGObject"
|
||||
@@ -105,8 +105,8 @@ def test_terms_save_is_atomic_shape_and_transient_override_is_not_written(
|
||||
) -> None:
|
||||
"""UI saves standard terms safely while CLI terms remain session-only."""
|
||||
manager = manager_at(tmp_path, monkeypatch)
|
||||
assert manager.save_terms(["VocaLinux", "vocalinux", "# ignored", " PyGObject "])
|
||||
assert (tmp_path / TERMS_FILENAME).read_text(encoding="utf-8") == "VocaLinux\nPyGObject\n"
|
||||
assert manager.save_terms(["Voculinux", "voculinux", "# ignored", " PyGObject "])
|
||||
assert (tmp_path / TERMS_FILENAME).read_text(encoding="utf-8") == "Voculinux\nPyGObject\n"
|
||||
|
||||
override = tmp_path / "override.txt"
|
||||
override.write_text("Override\n", encoding="utf-8")
|
||||
@@ -122,15 +122,15 @@ def test_incremental_term_edits_preserve_comments_and_blank_lines(
|
||||
"""GTK add/remove operations retain unrelated scanner-managed source lines."""
|
||||
manager = manager_at(tmp_path, monkeypatch)
|
||||
terms_file = tmp_path / TERMS_FILENAME
|
||||
terms_file.write_text("# scanner note\n\nVocaLinux\n# retain me\nPyGObject\n", encoding="utf-8")
|
||||
terms_file.write_text("# scanner note\n\nVoculinux\n# retain me\nPyGObject\n", encoding="utf-8")
|
||||
|
||||
assert manager.add_term("Supabase")
|
||||
assert terms_file.read_text(encoding="utf-8") == (
|
||||
"# scanner note\n\nVocaLinux\n# retain me\nPyGObject\nSupabase\n"
|
||||
"# scanner note\n\nVoculinux\n# retain me\nPyGObject\nSupabase\n"
|
||||
)
|
||||
assert manager.remove_term("PyGObject")
|
||||
assert terms_file.read_text(encoding="utf-8") == (
|
||||
"# scanner note\n\nVocaLinux\n# retain me\nSupabase\n"
|
||||
"# scanner note\n\nVoculinux\n# retain me\nSupabase\n"
|
||||
)
|
||||
|
||||
|
||||
@@ -278,7 +278,7 @@ def test_pr_767_dictionary_configuration_keys_and_contract_are_preserved(
|
||||
) -> None:
|
||||
"""Existing enabled, file_path, and max_words settings remain effective."""
|
||||
configured_path = tmp_path / TERMS_FILENAME
|
||||
configured_path.write_text("VocaLinux\nPyGObject\n", encoding="utf-8")
|
||||
configured_path.write_text("Voculinux\nPyGObject\n", encoding="utf-8")
|
||||
config = FakeConfig(
|
||||
{
|
||||
"dictionary": {
|
||||
@@ -294,7 +294,7 @@ def test_pr_767_dictionary_configuration_keys_and_contract_are_preserved(
|
||||
assert Path(DEFAULT_TERMS_PATH).name == TERMS_FILENAME
|
||||
assert manager.terms_enabled()
|
||||
assert manager.terms_path() == configured_path
|
||||
assert manager.build_initial_prompt() == "VocaLinux"
|
||||
assert manager.build_initial_prompt() == "Voculinux"
|
||||
|
||||
|
||||
def test_default_terms_path_follows_xdg_config_home(tmp_path: Path, monkeypatch) -> None:
|
||||
@@ -306,10 +306,10 @@ def test_default_terms_path_follows_xdg_config_home(tmp_path: Path, monkeypatch)
|
||||
assert manager.corrections_path() == tmp_path / CORRECTIONS_FILENAME
|
||||
|
||||
|
||||
def test_legacy_vocalinux_terms_root_keeps_corrections_at_config_dir(
|
||||
def test_legacy_voculinux_terms_root_keeps_corrections_at_config_dir(
|
||||
tmp_path: Path, monkeypatch
|
||||
) -> None:
|
||||
"""Persisted ~/.config/vocalinux terms do not move the corrections file."""
|
||||
"""Legacy ~/.config/vocalinux terms do not move the corrections file."""
|
||||
monkeypatch.setattr("vocalinux.custom_dictionary.config_dir", lambda: str(tmp_path))
|
||||
legacy_corrections = Path.home() / ".config" / "vocalinux" / CORRECTIONS_FILENAME
|
||||
expanded_legacy = str(Path.home() / ".config" / "vocalinux" / TERMS_FILENAME)
|
||||
@@ -434,7 +434,7 @@ def test_corrections_fallback_survives_copy_failure(tmp_path: Path, monkeypatch)
|
||||
def test_corrections_stay_at_config_dir_with_arbitrary_terms_parent(
|
||||
tmp_path: Path, monkeypatch
|
||||
) -> None:
|
||||
"""Corrections ignore the terms file location outside a vocalinux directory."""
|
||||
"""Corrections ignore the terms file location outside a voculinux directory."""
|
||||
xdg_dir = tmp_path / "xdg-config"
|
||||
xdg_dir.mkdir()
|
||||
monkeypatch.setattr("vocalinux.custom_dictionary.config_dir", lambda: str(xdg_dir))
|
||||
@@ -568,7 +568,7 @@ def test_invalid_or_unreadable_configured_terms_path_is_not_persisted(
|
||||
config = FakeConfig({"dictionary": {"file_path": previous_path}})
|
||||
manager = manager_at(tmp_path, monkeypatch, config)
|
||||
|
||||
assert not manager.set_terms_path("~vocalinux-user-does-not-exist/dictionary.txt")
|
||||
assert not manager.set_terms_path("~voculinux-user-does-not-exist/dictionary.txt")
|
||||
assert config.get("dictionary", "file_path") == previous_path
|
||||
|
||||
assert not manager.set_terms_path(str(tmp_path))
|
||||
@@ -581,7 +581,7 @@ def test_invalid_saved_terms_path_is_safe_to_read_and_report(tmp_path: Path, mon
|
||||
{
|
||||
"dictionary": {
|
||||
"enabled": True,
|
||||
"file_path": "~vocalinux-user-does-not-exist/dictionary.txt",
|
||||
"file_path": "~voculinux-user-does-not-exist/dictionary.txt",
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -609,7 +609,7 @@ def test_terms_path_save_failure_preserves_the_previous_configured_path(
|
||||
def test_invalid_transient_terms_path_does_not_crash(tmp_path: Path, monkeypatch) -> None:
|
||||
"""An unresolved CLI tilde path disables only its session's terms safely."""
|
||||
manager_at(tmp_path, monkeypatch)
|
||||
manager = CustomDictionaryManager(FakeConfig(), "~vocalinux-user-does-not-exist/dictionary.txt")
|
||||
manager = CustomDictionaryManager(FakeConfig(), "~voculinux-user-does-not-exist/dictionary.txt")
|
||||
|
||||
assert manager.terms_enabled()
|
||||
assert manager.terms_path() is None
|
||||
@@ -638,7 +638,7 @@ def test_file_edits_preserve_existing_permissions(tmp_path: Path, monkeypatch) -
|
||||
manager = manager_at(tmp_path, monkeypatch)
|
||||
|
||||
terms_file = tmp_path / TERMS_FILENAME
|
||||
terms_file.write_text("VocaLinux\n", encoding="utf-8")
|
||||
terms_file.write_text("Voculinux\n", encoding="utf-8")
|
||||
terms_file.chmod(0o600)
|
||||
assert manager.save_terms(["Supabase"])
|
||||
assert stat.S_IMODE(terms_file.stat().st_mode) == 0o600
|
||||
@@ -652,7 +652,7 @@ def test_file_edits_preserve_existing_permissions(tmp_path: Path, monkeypatch) -
|
||||
|
||||
def test_dictionary_file_argument_is_available_for_a_session_override(monkeypatch) -> None:
|
||||
"""The retained CLI option accepts a terms file without changing config."""
|
||||
monkeypatch.setattr("sys.argv", ["vocalinux", "--dictionary-file", "/tmp/terms.txt"])
|
||||
monkeypatch.setattr("sys.argv", ["voculinux", "--dictionary-file", "/tmp/terms.txt"])
|
||||
|
||||
assert parse_arguments().dictionary_file == "/tmp/terms.txt"
|
||||
|
||||
@@ -961,7 +961,7 @@ def test_invalid_terms_file_is_ignored_and_explained(tmp_path: Path, monkeypatch
|
||||
def test_whisper_receives_the_live_custom_terms_prompt(tmp_path: Path, monkeypatch) -> None:
|
||||
"""Whisper gets the current file contents as initial_prompt at transcription time."""
|
||||
dictionary = manager_at(tmp_path, monkeypatch, FakeConfig({"dictionary": {"enabled": True}}))
|
||||
(tmp_path / TERMS_FILENAME).write_text("VocaLinux\n", encoding="utf-8")
|
||||
(tmp_path / TERMS_FILENAME).write_text("Voculinux\n", encoding="utf-8")
|
||||
manager = recognition_manager("whisper", dictionary)
|
||||
manager.model = MagicMock()
|
||||
manager.model.transcribe.return_value = {"text": "ok"}
|
||||
@@ -970,13 +970,13 @@ def test_whisper_receives_the_live_custom_terms_prompt(tmp_path: Path, monkeypat
|
||||
with patch.dict("sys.modules", {"numpy": mock_numpy(), "torch": MagicMock()}):
|
||||
assert manager._transcribe_with_whisper([b"\x00\x00"]) == "ok"
|
||||
|
||||
assert manager.model.transcribe.call_args.kwargs["initial_prompt"] == "VocaLinux"
|
||||
assert manager.model.transcribe.call_args.kwargs["initial_prompt"] == "Voculinux"
|
||||
|
||||
|
||||
def test_faster_whisper_receives_the_live_custom_terms_prompt(tmp_path: Path, monkeypatch) -> None:
|
||||
"""Faster Whisper receives the same live vocabulary bias as OpenAI Whisper."""
|
||||
dictionary = manager_at(tmp_path, monkeypatch, FakeConfig({"dictionary": {"enabled": True}}))
|
||||
(tmp_path / TERMS_FILENAME).write_text("VocaLinux\n", encoding="utf-8")
|
||||
(tmp_path / TERMS_FILENAME).write_text("Voculinux\n", encoding="utf-8")
|
||||
manager = object.__new__(SpeechRecognitionManager)
|
||||
manager.dictionary_manager = dictionary
|
||||
manager._faster_whisper_engine = MagicMock()
|
||||
@@ -985,7 +985,7 @@ def test_faster_whisper_receives_the_live_custom_terms_prompt(tmp_path: Path, mo
|
||||
|
||||
assert manager._transcribe_with_faster_whisper([b"\x00\x00"]) == "ok"
|
||||
manager._faster_whisper_engine.transcribe.assert_called_once_with(
|
||||
[b"\x00\x00"], language="auto", initial_prompt="VocaLinux"
|
||||
[b"\x00\x00"], language="auto", initial_prompt="Voculinux"
|
||||
)
|
||||
|
||||
|
||||
@@ -994,7 +994,7 @@ def test_whispercpp_composes_and_clears_live_terms_prompt(tmp_path: Path, monkey
|
||||
config = FakeConfig({"dictionary": {"enabled": True}})
|
||||
dictionary = manager_at(tmp_path, monkeypatch, config)
|
||||
terms_file = tmp_path / TERMS_FILENAME
|
||||
terms_file.write_text("VocaLinux\n", encoding="utf-8")
|
||||
terms_file.write_text("Voculinux\n", encoding="utf-8")
|
||||
manager = recognition_manager("whisper_cpp", dictionary, advanced_prompt="Explicit context")
|
||||
manager._model_lock = threading.Lock()
|
||||
manager.model = MagicMock()
|
||||
@@ -1008,7 +1008,7 @@ def test_whispercpp_composes_and_clears_live_terms_prompt(tmp_path: Path, monkey
|
||||
assert manager._transcribe_with_whispercpp([b"\x00\x00"]) == "ok"
|
||||
|
||||
prompts = [call.kwargs["initial_prompt"] for call in manager.model.transcribe.call_args_list]
|
||||
assert prompts == ["Explicit context VocaLinux", "Explicit context", ""]
|
||||
assert prompts == ["Explicit context Voculinux", "Explicit context", ""]
|
||||
|
||||
|
||||
def test_vosk_warns_once_for_bias_while_corrections_remain_available(
|
||||
@@ -1016,7 +1016,7 @@ def test_vosk_warns_once_for_bias_while_corrections_remain_available(
|
||||
) -> None:
|
||||
"""VOSK rejects vocabulary bias only; its transcript can still be corrected."""
|
||||
dictionary = manager_at(tmp_path, monkeypatch, FakeConfig({"dictionary": {"enabled": True}}))
|
||||
(tmp_path / TERMS_FILENAME).write_text("VocaLinux\n", encoding="utf-8")
|
||||
(tmp_path / TERMS_FILENAME).write_text("Voculinux\n", encoding="utf-8")
|
||||
manager = recognition_manager("vosk", dictionary)
|
||||
manager._get_vosk_model_path = MagicMock(return_value="/models/vosk")
|
||||
vosk = MagicMock()
|
||||
@@ -1041,11 +1041,11 @@ def test_whispercpp_prompt_composes_advanced_and_terms() -> None:
|
||||
recognition_manager = object.__new__(SpeechRecognitionManager)
|
||||
recognition_manager.whispercpp_initial_prompt = "Write product names accurately."
|
||||
recognition_manager.dictionary_manager = type(
|
||||
"Dictionary", (), {"build_initial_prompt": lambda self: "VocaLinux"}
|
||||
"Dictionary", (), {"build_initial_prompt": lambda self: "Voculinux"}
|
||||
)()
|
||||
|
||||
assert (
|
||||
recognition_manager._get_whispercpp_prompt() == "Write product names accurately. VocaLinux"
|
||||
recognition_manager._get_whispercpp_prompt() == "Write product names accurately. Voculinux"
|
||||
)
|
||||
recognition_manager.dictionary_manager = type(
|
||||
"Dictionary", (), {"build_initial_prompt": lambda self: None}
|
||||
|
||||
@@ -16,7 +16,7 @@ import pytest
|
||||
if TYPE_CHECKING:
|
||||
from vocalinux.dbus_service import VocalinuxDBusService
|
||||
|
||||
# gi is mocked globally by conftest. All vocalinux imports are done lazily
|
||||
# gi is mocked globally by conftest. All voculinux imports are done lazily
|
||||
# inside the individual tests. Importing them at module level would bind their
|
||||
# `gi.repository` references at collection time and clash with test_main.py,
|
||||
# which replaces sys.modules["gi"] / sys.modules["gi.repository"] wholesale --
|
||||
@@ -35,7 +35,7 @@ def test_cli_flag_sends_matching_command(flag, command):
|
||||
from vocalinux.main import main
|
||||
|
||||
with patch("vocalinux.dbus_service.send_command", return_value=True) as mock_send:
|
||||
with patch.object(sys, "argv", ["vocalinux", flag]):
|
||||
with patch.object(sys, "argv", ["voculinux", flag]):
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
main()
|
||||
assert exc_info.value.code == 0
|
||||
@@ -47,7 +47,7 @@ def test_cli_flag_exits_nonzero_when_no_instance():
|
||||
from vocalinux.main import main
|
||||
|
||||
with patch("vocalinux.dbus_service.send_command", return_value=False):
|
||||
with patch.object(sys, "argv", ["vocalinux", "--toggle"]):
|
||||
with patch.object(sys, "argv", ["voculinux", "--toggle"]):
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
main()
|
||||
assert exc_info.value.code == 1
|
||||
@@ -58,7 +58,7 @@ def test_no_trigger_flag_does_not_dispatch():
|
||||
from vocalinux.main import main
|
||||
|
||||
# Mock single_instance via sys.modules (not patch("...acquire_lock")) so we
|
||||
# don't import the real module and set it as a vocalinux package attribute,
|
||||
# don't import the real module and set it as a voculinux package attribute,
|
||||
# which would defeat patch.dict-based single-instance tests elsewhere.
|
||||
mock_single_instance = MagicMock()
|
||||
mock_single_instance.acquire_lock.return_value = True
|
||||
@@ -66,7 +66,7 @@ def test_no_trigger_flag_does_not_dispatch():
|
||||
# check_dependencies returns False so main() exits early after dispatch.
|
||||
with patch.dict(sys.modules, {"vocalinux.single_instance": mock_single_instance}):
|
||||
with patch("vocalinux.main.check_dependencies", return_value=False):
|
||||
with patch.object(sys, "argv", ["vocalinux"]):
|
||||
with patch.object(sys, "argv", ["voculinux"]):
|
||||
with pytest.raises(SystemExit):
|
||||
main()
|
||||
mock_send.assert_not_called()
|
||||
@@ -269,7 +269,7 @@ def test_on_bus_acquired_handles_register_failure():
|
||||
# Patch GLib.Error to a real class so both the raise and the `except` clause
|
||||
# reference the same type and the GLib.Error branch is exercised.
|
||||
with patch.object(dbus_service.GLib, "Error", RuntimeError):
|
||||
service._on_bus_acquired(connection, "com.vocalinux.Vocalinux")
|
||||
service._on_bus_acquired(connection, "com.voculinux.Voculinux")
|
||||
|
||||
assert service._registration_id == 0
|
||||
|
||||
@@ -278,8 +278,8 @@ def test_name_acquired_and_lost_are_safe_to_call():
|
||||
"""The name-acquired/lost callbacks only log and never raise."""
|
||||
service = _make_service()
|
||||
connection = MagicMock()
|
||||
service._on_name_acquired(connection, "com.vocalinux.Vocalinux")
|
||||
service._on_name_lost(connection, "com.vocalinux.Vocalinux")
|
||||
service._on_name_acquired(connection, "com.voculinux.Voculinux")
|
||||
service._on_name_lost(connection, "com.voculinux.Voculinux")
|
||||
|
||||
|
||||
def test_invoke_swallows_callback_errors():
|
||||
|
||||
@@ -136,7 +136,7 @@ class TestOverlayConfig(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.temp_dir = tempfile.TemporaryDirectory()
|
||||
self.temp_config_dir = os.path.join(self.temp_dir.name, ".config/vocalinux")
|
||||
self.temp_config_dir = os.path.join(self.temp_dir.name, ".config/voculinux")
|
||||
_ensure_test_config_dir(self.temp_config_dir)
|
||||
self.temp_config_file = os.path.join(self.temp_config_dir, "config.json")
|
||||
|
||||
@@ -1199,7 +1199,7 @@ class TestOverlayConfigMissingUiSection(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.temp_dir = tempfile.TemporaryDirectory()
|
||||
self.temp_config_dir = os.path.join(self.temp_dir.name, ".config/vocalinux")
|
||||
self.temp_config_dir = os.path.join(self.temp_dir.name, ".config/voculinux")
|
||||
_ensure_test_config_dir(self.temp_config_dir)
|
||||
self.temp_config_file = os.path.join(self.temp_config_dir, "config.json")
|
||||
self.config_dir_patcher = patch(
|
||||
|
||||
@@ -190,7 +190,7 @@ class TestDictationPadConfig(unittest.TestCase):
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.temp_dir = tempfile.TemporaryDirectory()
|
||||
self.temp_config_dir = os.path.join(self.temp_dir.name, ".config/vocalinux")
|
||||
self.temp_config_dir = os.path.join(self.temp_dir.name, ".config/voculinux")
|
||||
_ensure_test_config_dir(self.temp_config_dir)
|
||||
self.temp_config_file = os.path.join(self.temp_config_dir, "config.json")
|
||||
|
||||
|
||||
@@ -790,7 +790,7 @@ class TestEvdevKeyboardBackendPermissionHint:
|
||||
result = backend.get_permission_hint()
|
||||
assert result is None
|
||||
|
||||
@patch.dict("os.environ", {"SNAP": "/snap/vocalinux/x1"}, clear=False)
|
||||
@patch.dict("os.environ", {"SNAP": "/snap/voculinux/x1"}, clear=False)
|
||||
@patch("vocalinux.ui.keyboard_backends.evdev_backend.EVDEV_AVAILABLE", True)
|
||||
@patch("vocalinux.ui.keyboard_backends.evdev_backend.find_keyboard_devices")
|
||||
def test_permission_hint_snap_no_devices(self, mock_find_devices):
|
||||
@@ -802,7 +802,7 @@ class TestEvdevKeyboardBackendPermissionHint:
|
||||
assert "raw-input" in result
|
||||
assert "hardware-observe" in result
|
||||
|
||||
@patch.dict("os.environ", {"SNAP": "/snap/vocalinux/x1"}, clear=False)
|
||||
@patch.dict("os.environ", {"SNAP": "/snap/voculinux/x1"}, clear=False)
|
||||
@patch("vocalinux.ui.keyboard_backends.evdev_backend.EVDEV_AVAILABLE", True)
|
||||
@patch("vocalinux.ui.keyboard_backends.evdev_backend.find_keyboard_devices")
|
||||
@patch("vocalinux.ui.keyboard_backends.evdev_backend.InputDevice")
|
||||
@@ -1804,7 +1804,7 @@ class TestEvdevGrabAndForwarding:
|
||||
device = MagicMock()
|
||||
device.fileno.return_value = 10
|
||||
device.info.bustype = 0x06 # BUS_VIRTUAL
|
||||
device.name = "AT Translated Set 2 keyboard (vocalinux)"
|
||||
device.name = "AT Translated Set 2 keyboard (voculinux)"
|
||||
mock_input_device.return_value = device
|
||||
|
||||
backend = EvdevKeyboardBackend()
|
||||
@@ -3192,7 +3192,7 @@ class TestWriteOnlyUInput:
|
||||
patch.object(eb._WriteOnlyUInput, "_find_device", return_value=device),
|
||||
patch.object(eb._uinput, "write") as write,
|
||||
):
|
||||
uinput = eb._WriteOnlyUInput(events={1: [30]}, name="kbd (vocalinux)", bustype=0x06)
|
||||
uinput = eb._WriteOnlyUInput(events={1: [30]}, name="kbd (voculinux)", bustype=0x06)
|
||||
yield uinput, mock_open, set_phys, enable, setup, create, write, device
|
||||
|
||||
def test_opens_wronly_and_runs_setup(self) -> None:
|
||||
@@ -3243,7 +3243,7 @@ class TestWriteOnlyUInput:
|
||||
),
|
||||
):
|
||||
with pytest.raises(TypeError):
|
||||
eb._WriteOnlyUInput(events=None, name="kbd (vocalinux)", bustype=0x06)
|
||||
eb._WriteOnlyUInput(events=None, name="kbd (voculinux)", bustype=0x06)
|
||||
|
||||
mock_close.assert_called_once_with(42)
|
||||
|
||||
@@ -3254,7 +3254,7 @@ class TestWriteOnlyUInput:
|
||||
|
||||
name = _clone_device_name(device)
|
||||
|
||||
assert name.endswith(" (vocalinux)")
|
||||
assert name.endswith(" (voculinux)")
|
||||
assert len(name.encode("utf-8")) <= 79
|
||||
|
||||
def test_clone_device_name_keeps_short_names(self) -> None:
|
||||
@@ -3262,7 +3262,7 @@ class TestWriteOnlyUInput:
|
||||
device = MagicMock()
|
||||
device.name = "AT Translated Set 2 keyboard"
|
||||
|
||||
assert _clone_device_name(device) == "AT Translated Set 2 keyboard (vocalinux)"
|
||||
assert _clone_device_name(device) == "AT Translated Set 2 keyboard (voculinux)"
|
||||
|
||||
|
||||
class TestSharedEvdevDeviceLayer:
|
||||
|
||||
@@ -326,17 +326,17 @@ class TestFasterWhisperEngine:
|
||||
|
||||
def test_transcribe_passes_initial_prompt(self) -> None:
|
||||
"""Test that vocabulary bias is forwarded to faster-whisper."""
|
||||
whisper_mock = self._mock_whisper_model([MagicMock(text="VocaLinux")])
|
||||
whisper_mock = self._mock_whisper_model([MagicMock(text="Voculinux")])
|
||||
with patch.dict(sys.modules, {"faster_whisper": whisper_mock}):
|
||||
engine = FasterWhisperEngine(model_size="tiny", device="cpu")
|
||||
engine.init()
|
||||
|
||||
audio = np.array([0, 1000, -1000, 0], dtype=np.int16)
|
||||
engine.transcribe([audio.tobytes()], initial_prompt="VocaLinux")
|
||||
engine.transcribe([audio.tobytes()], initial_prompt="Voculinux")
|
||||
|
||||
assert (
|
||||
whisper_mock.WhisperModel.return_value.transcribe.call_args.kwargs["initial_prompt"]
|
||||
== "VocaLinux"
|
||||
== "Voculinux"
|
||||
)
|
||||
|
||||
def test_transcribe_empty_audio(self):
|
||||
@@ -429,7 +429,7 @@ class TestFasterWhisperEngine:
|
||||
assert text == ""
|
||||
|
||||
def test_language_mapping(self):
|
||||
"""Test that Vocalinux language codes are mapped correctly."""
|
||||
"""Test that Voculinux language codes are mapped correctly."""
|
||||
engine = FasterWhisperEngine(language="en-us")
|
||||
assert engine._normalize_language() == "en"
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class _FakeGtkDialog:
|
||||
pass
|
||||
|
||||
|
||||
_ISOLATED_MODULE_NAME = "vocalinux_test_first_run_dialog_isolated"
|
||||
_ISOLATED_MODULE_NAME = "voculinux_test_first_run_dialog_isolated"
|
||||
|
||||
|
||||
def _load_first_run_dialog_with_fake_gtk():
|
||||
|
||||
@@ -47,7 +47,7 @@ PYPROJECT = REPO_ROOT / "pyproject.toml"
|
||||
EXPORT = REPO_ROOT / "requirements" / "vad.txt"
|
||||
FLATPAK_DIR = REPO_ROOT / "packaging" / "flatpak"
|
||||
DEPS_YAML = FLATPAK_DIR / "python3-dependencies.yaml"
|
||||
MANIFEST = FLATPAK_DIR / "com.vocalinux.Vocalinux.yml"
|
||||
MANIFEST = FLATPAK_DIR / "com.voculinux.Voculinux.yml"
|
||||
WORKFLOW = REPO_ROOT / ".github" / "workflows" / "flatpak.yml"
|
||||
|
||||
#: Kept in step with scripts/sync_flatpak_deps.py, which resolves the same
|
||||
|
||||
@@ -266,7 +266,7 @@ class TestPairingDecode(unittest.TestCase):
|
||||
|
||||
class TestSandbox(unittest.TestCase):
|
||||
def test_flatpak_fails_closed(self):
|
||||
state = detect_sandbox({"FLATPAK_ID": "com.vocahq.Vocalinux"})
|
||||
state = detect_sandbox({"FLATPAK_ID": "com.vocahq.Voculinux"})
|
||||
self.assertTrue(state.blocked)
|
||||
self.assertEqual(state.kind, "flatpak")
|
||||
self.assertIn("Flatpak", state.hint)
|
||||
@@ -704,7 +704,7 @@ class TestOrphanComposeDetect(unittest.TestCase):
|
||||
manager.stop_async()
|
||||
fake_thread.assert_called_once()
|
||||
kwargs = fake_thread.call_args.kwargs
|
||||
self.assertEqual(kwargs.get("name"), "vocalinux-gateway-stop")
|
||||
self.assertEqual(kwargs.get("name"), "voculinux-gateway-stop")
|
||||
self.assertEqual(kwargs.get("target").__func__, manager._stop_worker.__func__)
|
||||
started.start.assert_called_once()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Guards for the environment Vocalinux hands to host binaries.
|
||||
"""Guards for the environment Voculinux hands to host binaries.
|
||||
|
||||
An AppImage exports its own `LD_LIBRARY_PATH`, `GI_TYPELIB_PATH` and
|
||||
`PYTHONHOME`, and every child process inherits them. A host `ibus` linked
|
||||
|
||||
@@ -29,7 +29,7 @@ sys.modules["gi"] = _mock_gi
|
||||
sys.modules["gi.repository"] = _mock_gi_repo
|
||||
|
||||
for _key in list(sys.modules.keys()):
|
||||
if "vocalinux" in _key and "ibus_engine" in _key:
|
||||
if "voculinux" in _key and "ibus_engine" in _key:
|
||||
del sys.modules[_key]
|
||||
|
||||
import vocalinux.text_injection.ibus_engine as ibus_engine # noqa: E402
|
||||
|
||||
+20
-20
@@ -94,7 +94,7 @@ class TestIBusTextInjectorSetupFailures(unittest.TestCase):
|
||||
with self.assertRaises(IBusSetupError) as context:
|
||||
IBusTextInjector(auto_activate=True)
|
||||
|
||||
self.assertIn("Failed to activate Vocalinux IBus engine", str(context.exception))
|
||||
self.assertIn("Failed to activate Voculinux IBus engine", str(context.exception))
|
||||
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBUS_AVAILABLE", True)
|
||||
@patch("vocalinux.text_injection.ibus_engine.ensure_ibus_dir")
|
||||
@@ -137,7 +137,7 @@ class TestIBusEngineHelpers(unittest.TestCase):
|
||||
|
||||
def test_ensure_ibus_dir_creates_directory(self):
|
||||
"""Test that ensure_ibus_dir creates the directory."""
|
||||
with patch("vocalinux.text_injection.ibus_engine.VOCALINUX_IBUS_DIR") as mock_dir:
|
||||
with patch("vocalinux.text_injection.ibus_engine.VOCULINUX_IBUS_DIR") as mock_dir:
|
||||
mock_dir.mkdir = MagicMock()
|
||||
from vocalinux.text_injection.ibus_engine import ensure_ibus_dir
|
||||
|
||||
@@ -157,7 +157,7 @@ class TestIsEngineActive(unittest.TestCase):
|
||||
@patch("subprocess.run")
|
||||
def test_engine_active(self, mock_run):
|
||||
"""Test detection when engine is active."""
|
||||
mock_run.return_value = MagicMock(stdout="vocalinux", returncode=0)
|
||||
mock_run.return_value = MagicMock(stdout="voculinux", returncode=0)
|
||||
|
||||
from vocalinux.text_injection.ibus_engine import is_engine_active
|
||||
|
||||
@@ -589,12 +589,12 @@ class TestSwitchEngine(unittest.TestCase):
|
||||
# First call: ibus engine <name> (switch)
|
||||
# Second call: ibus engine (get current) - for verification
|
||||
switch_result = MagicMock(returncode=0)
|
||||
verify_result = MagicMock(returncode=0, stdout="vocalinux\n")
|
||||
verify_result = MagicMock(returncode=0, stdout="voculinux\n")
|
||||
mock_run.side_effect = [switch_result, verify_result]
|
||||
|
||||
from vocalinux.text_injection.ibus_engine import switch_engine
|
||||
|
||||
result = switch_engine("vocalinux")
|
||||
result = switch_engine("voculinux")
|
||||
self.assertTrue(result)
|
||||
self.assertEqual(mock_run.call_count, 2)
|
||||
|
||||
@@ -621,7 +621,7 @@ class TestSwitchEngine(unittest.TestCase):
|
||||
|
||||
from vocalinux.text_injection.ibus_engine import switch_engine
|
||||
|
||||
result = switch_engine("vocalinux")
|
||||
result = switch_engine("voculinux")
|
||||
self.assertFalse(result)
|
||||
|
||||
|
||||
@@ -674,8 +674,8 @@ class TestIBusTextInjector(unittest.TestCase):
|
||||
self.assertGreaterEqual(mock_ensure_dir.call_count, 1)
|
||||
# Engine should be started
|
||||
mock_start_engine.assert_called_once()
|
||||
# Should switch to vocalinux engine
|
||||
mock_switch.assert_called_once_with("vocalinux")
|
||||
# Should switch to voculinux engine
|
||||
mock_switch.assert_called_once_with("voculinux")
|
||||
mock_wait_ready.assert_called_once_with()
|
||||
self.assertEqual(injector._previous_engine, "xkb:us::eng")
|
||||
|
||||
@@ -1003,20 +1003,20 @@ class TestIBusTextInjector(unittest.TestCase):
|
||||
|
||||
@patch(
|
||||
"vocalinux.text_injection.ibus_engine._query_active_engine",
|
||||
return_value="vocalinux",
|
||||
return_value="voculinux",
|
||||
)
|
||||
@patch("vocalinux.text_injection.ibus_engine.stop_engine_process")
|
||||
@patch(
|
||||
"vocalinux.text_injection.ibus_engine.get_current_engine",
|
||||
return_value="vocalinux",
|
||||
return_value="voculinux",
|
||||
)
|
||||
@patch("vocalinux.text_injection.ibus_engine.switch_engine", return_value=True)
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBUS_AVAILABLE", True)
|
||||
@patch("vocalinux.text_injection.ibus_engine.ensure_ibus_dir")
|
||||
def test_stop_uses_cached_engine_when_stuck_on_vocalinux(
|
||||
def test_stop_uses_cached_engine_when_stuck_on_voculinux(
|
||||
self, mock_ensure_dir, mock_switch, mock_get_current, mock_stop_proc, mock_live
|
||||
):
|
||||
"""If quit finds vocalinux selected, fall back to the warmup-captured engine (#558)."""
|
||||
"""If quit finds voculinux selected, fall back to the warmup-captured engine (#558)."""
|
||||
from vocalinux.text_injection.ibus_engine import IBusTextInjector
|
||||
|
||||
injector = IBusTextInjector(auto_activate=False)
|
||||
@@ -1031,15 +1031,15 @@ class TestIBusTextInjector(unittest.TestCase):
|
||||
@patch("vocalinux.text_injection.ibus_engine.stop_engine_process")
|
||||
@patch(
|
||||
"vocalinux.text_injection.ibus_engine.get_current_engine",
|
||||
return_value="vocalinux",
|
||||
return_value="voculinux",
|
||||
)
|
||||
@patch("vocalinux.text_injection.ibus_engine.switch_engine")
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBUS_AVAILABLE", True)
|
||||
@patch("vocalinux.text_injection.ibus_engine.ensure_ibus_dir")
|
||||
def test_stop_skips_restore_when_only_vocalinux_active(
|
||||
def test_stop_skips_restore_when_only_voculinux_active(
|
||||
self, mock_ensure_dir, mock_switch, mock_get_current, mock_stop_proc
|
||||
):
|
||||
"""Do not try to reselect vocalinux after its process has been torn down."""
|
||||
"""Do not try to reselect voculinux after its process has been torn down."""
|
||||
from vocalinux.text_injection.ibus_engine import IBusTextInjector
|
||||
|
||||
injector = IBusTextInjector(auto_activate=False)
|
||||
@@ -1091,7 +1091,7 @@ class TestIBusTextInjector(unittest.TestCase):
|
||||
@patch("vocalinux.text_injection.ibus_engine.start_engine_process", return_value=True)
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBusTextInjector._wait_for_engine_ready")
|
||||
@patch("vocalinux.text_injection.ibus_engine.switch_engine")
|
||||
def test_prepare_engine_does_not_activate_vocalinux_engine(
|
||||
def test_prepare_engine_does_not_activate_voculinux_engine(
|
||||
self,
|
||||
mock_switch,
|
||||
mock_wait_ready,
|
||||
@@ -1124,7 +1124,7 @@ class TestIBusTextInjector(unittest.TestCase):
|
||||
mock_is_active,
|
||||
mock_get_current,
|
||||
):
|
||||
"""Warmup caches the live engine so quit can restore after a stuck vocalinux (#558)."""
|
||||
"""Warmup caches the live engine so quit can restore after a stuck voculinux (#558)."""
|
||||
from vocalinux.text_injection.ibus_engine import IBusTextInjector
|
||||
|
||||
injector = IBusTextInjector(auto_activate=False)
|
||||
@@ -1194,7 +1194,7 @@ class TestIBusTextInjector(unittest.TestCase):
|
||||
mock_get_xkb,
|
||||
mock_restore_xkb,
|
||||
):
|
||||
"""Scoped injection must restore XKB after the Vocalinux engine switch (#664)."""
|
||||
"""Scoped injection must restore XKB after the Voculinux engine switch (#664)."""
|
||||
from vocalinux.text_injection.ibus_engine import ENGINE_NAME, IBusTextInjector
|
||||
|
||||
mock_socket_path.exists.return_value = True
|
||||
@@ -1615,14 +1615,14 @@ class TestIBusTextInjector(unittest.TestCase):
|
||||
mock_is_active,
|
||||
mock_get_current,
|
||||
):
|
||||
"""Test inject_text returns False when switch to Vocalinux engine fails."""
|
||||
"""Test inject_text returns False when switch to Voculinux engine fails."""
|
||||
from vocalinux.text_injection.ibus_engine import IBusTextInjector
|
||||
|
||||
injector = IBusTextInjector(auto_activate=False)
|
||||
result = injector.inject_text("Hello")
|
||||
|
||||
self.assertFalse(result)
|
||||
mock_switch.assert_called_once_with("vocalinux")
|
||||
mock_switch.assert_called_once_with("voculinux")
|
||||
|
||||
@patch("vocalinux.text_injection.ibus_engine.get_current_engine", return_value=None)
|
||||
@patch("vocalinux.text_injection.ibus_engine.is_engine_active", return_value=False)
|
||||
|
||||
@@ -194,7 +194,7 @@ class TestIBusEngineModuleFunctions(unittest.TestCase):
|
||||
@patch("subprocess.run")
|
||||
def test_is_engine_active_success(self, mock_run):
|
||||
"""Test when engine is active."""
|
||||
mock_run.return_value = MagicMock(returncode=0, stdout="vocalinux")
|
||||
mock_run.return_value = MagicMock(returncode=0, stdout="voculinux")
|
||||
from vocalinux.text_injection.ibus_engine import is_engine_active
|
||||
|
||||
result = is_engine_active()
|
||||
@@ -231,10 +231,10 @@ class TestIBusEngineModuleFunctions(unittest.TestCase):
|
||||
@patch("vocalinux.text_injection.ibus_engine.get_current_engine")
|
||||
def test_switch_engine_success(self, mock_get_current, mock_run):
|
||||
"""Test switching engine successfully."""
|
||||
mock_get_current.return_value = "vocalinux"
|
||||
mock_get_current.return_value = "voculinux"
|
||||
from vocalinux.text_injection.ibus_engine import switch_engine
|
||||
|
||||
result = switch_engine("vocalinux")
|
||||
result = switch_engine("voculinux")
|
||||
self.assertTrue(result)
|
||||
|
||||
@patch("subprocess.run")
|
||||
@@ -244,7 +244,7 @@ class TestIBusEngineModuleFunctions(unittest.TestCase):
|
||||
mock_get_current.return_value = "other-engine"
|
||||
from vocalinux.text_injection.ibus_engine import switch_engine
|
||||
|
||||
result = switch_engine("vocalinux")
|
||||
result = switch_engine("voculinux")
|
||||
self.assertFalse(result)
|
||||
|
||||
@patch("vocalinux.text_injection.ibus_engine.PID_FILE")
|
||||
@@ -380,7 +380,7 @@ class TestVocalinuxEngine(unittest.TestCase):
|
||||
VocalinuxEngine._server_socket = None
|
||||
VocalinuxEngine._server_running = False
|
||||
|
||||
def test_vocalinux_engine_init(self):
|
||||
def test_voculinux_engine_init(self):
|
||||
"""Test VocalinuxEngine initialization."""
|
||||
from vocalinux.text_injection.ibus_engine import VocalinuxEngine
|
||||
|
||||
@@ -394,7 +394,7 @@ class TestVocalinuxEngine(unittest.TestCase):
|
||||
self.assertIsNone(VocalinuxEngine._server_socket)
|
||||
self.assertFalse(VocalinuxEngine._server_running)
|
||||
|
||||
def test_vocalinux_engine_do_enable(self):
|
||||
def test_voculinux_engine_do_enable(self):
|
||||
"""Test engine enable signal handler."""
|
||||
from vocalinux.text_injection.ibus_engine import VocalinuxEngine
|
||||
|
||||
@@ -404,7 +404,7 @@ class TestVocalinuxEngine(unittest.TestCase):
|
||||
# Verify active instance is set
|
||||
self.assertEqual(VocalinuxEngine._active_instance, engine)
|
||||
|
||||
def test_vocalinux_engine_do_disable(self):
|
||||
def test_voculinux_engine_do_disable(self):
|
||||
"""Test engine disable signal handler."""
|
||||
from vocalinux.text_injection.ibus_engine import VocalinuxEngine
|
||||
|
||||
@@ -414,7 +414,7 @@ class TestVocalinuxEngine(unittest.TestCase):
|
||||
# Instance should still be active (not cleared on disable)
|
||||
self.assertEqual(VocalinuxEngine._active_instance, engine)
|
||||
|
||||
def test_vocalinux_engine_do_focus_in(self):
|
||||
def test_voculinux_engine_do_focus_in(self):
|
||||
"""Test engine focus in signal handler."""
|
||||
from vocalinux.text_injection.ibus_engine import VocalinuxEngine
|
||||
|
||||
@@ -423,7 +423,7 @@ class TestVocalinuxEngine(unittest.TestCase):
|
||||
self.assertEqual(VocalinuxEngine._active_instance, engine)
|
||||
self.assertTrue(VocalinuxEngine._focus_event.is_set())
|
||||
|
||||
def test_vocalinux_engine_do_focus_out(self):
|
||||
def test_voculinux_engine_do_focus_out(self):
|
||||
"""Test engine focus out signal handler."""
|
||||
from vocalinux.text_injection.ibus_engine import VocalinuxEngine
|
||||
|
||||
@@ -508,7 +508,7 @@ class TestVocalinuxEngine(unittest.TestCase):
|
||||
self.assertIsNone(VocalinuxEngine._active_instance)
|
||||
self.assertFalse(VocalinuxEngine._focus_event.is_set())
|
||||
|
||||
def test_vocalinux_engine_do_process_key_event(self):
|
||||
def test_voculinux_engine_do_process_key_event(self):
|
||||
"""Test engine key event processing."""
|
||||
from vocalinux.text_injection.ibus_engine import VocalinuxEngine
|
||||
|
||||
@@ -517,7 +517,7 @@ class TestVocalinuxEngine(unittest.TestCase):
|
||||
# Should return False to pass through all keys
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_vocalinux_engine_inject_text_empty(self):
|
||||
def test_voculinux_engine_inject_text_empty(self):
|
||||
"""Test text injection with empty text."""
|
||||
from vocalinux.text_injection.ibus_engine import VocalinuxEngine
|
||||
|
||||
@@ -526,7 +526,7 @@ class TestVocalinuxEngine(unittest.TestCase):
|
||||
# Empty text should return True (no-op)
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_vocalinux_engine_inject_text_success(self):
|
||||
def test_voculinux_engine_inject_text_success(self):
|
||||
"""Test successful text injection."""
|
||||
from vocalinux.text_injection.ibus_engine import VocalinuxEngine
|
||||
|
||||
@@ -540,7 +540,7 @@ class TestVocalinuxEngine(unittest.TestCase):
|
||||
self.assertTrue(result)
|
||||
engine.commit_text.assert_called_once()
|
||||
|
||||
def test_vocalinux_engine_inject_text_failure(self):
|
||||
def test_voculinux_engine_inject_text_failure(self):
|
||||
"""Test text injection error handling."""
|
||||
from vocalinux.text_injection.ibus_engine import VocalinuxEngine
|
||||
|
||||
@@ -733,7 +733,7 @@ class TestVocalinuxEngineApplication(unittest.TestCase):
|
||||
pass
|
||||
|
||||
@patch("vocalinux.text_injection.ibus_engine.VocalinuxEngine._start_socket_server")
|
||||
def test_vocalinux_engine_application_init(self, mock_start_server):
|
||||
def test_voculinux_engine_application_init(self, mock_start_server):
|
||||
"""Test VocalinuxEngineApplication initialization."""
|
||||
from vocalinux.text_injection.ibus_engine import VocalinuxEngineApplication
|
||||
|
||||
@@ -748,7 +748,7 @@ class TestVocalinuxEngineApplication(unittest.TestCase):
|
||||
self.assertEqual(type(app.mainloop).__name__, "MagicMock")
|
||||
|
||||
@patch("vocalinux.text_injection.ibus_engine.VocalinuxEngine._start_socket_server")
|
||||
def test_vocalinux_engine_application_on_disconnected(self, mock_start_server):
|
||||
def test_voculinux_engine_application_on_disconnected(self, mock_start_server):
|
||||
"""Test IBus disconnection handler."""
|
||||
from vocalinux.text_injection.ibus_engine import VocalinuxEngineApplication
|
||||
|
||||
@@ -760,7 +760,7 @@ class TestVocalinuxEngineApplication(unittest.TestCase):
|
||||
app.mainloop.quit.assert_called_once()
|
||||
|
||||
@patch("vocalinux.text_injection.ibus_engine.VocalinuxEngine._start_socket_server")
|
||||
def test_vocalinux_engine_application_run(self, mock_start_server):
|
||||
def test_voculinux_engine_application_run(self, mock_start_server):
|
||||
"""Test engine application main loop."""
|
||||
from vocalinux.text_injection.ibus_engine import VocalinuxEngineApplication
|
||||
|
||||
@@ -774,7 +774,7 @@ class TestVocalinuxEngineApplication(unittest.TestCase):
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBus.Component")
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBus.Factory.new")
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBus.Bus")
|
||||
def test_vocalinux_engine_application_standalone_registers_component(
|
||||
def test_voculinux_engine_application_standalone_registers_component(
|
||||
self,
|
||||
mock_bus_cls,
|
||||
mock_factory_new,
|
||||
@@ -810,7 +810,7 @@ class TestVocalinuxEngineApplication(unittest.TestCase):
|
||||
@patch("vocalinux.text_injection.ibus_engine.VocalinuxEngine._start_socket_server")
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBus.Factory.new")
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBus.Bus")
|
||||
def test_vocalinux_engine_application_ibus_exec_requests_name(
|
||||
def test_voculinux_engine_application_ibus_exec_requests_name(
|
||||
self,
|
||||
mock_bus_cls,
|
||||
mock_factory_new,
|
||||
@@ -835,7 +835,7 @@ class TestVocalinuxEngineApplication(unittest.TestCase):
|
||||
@patch("vocalinux.text_injection.ibus_engine.VocalinuxEngine._start_socket_server")
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBus.Factory.new")
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBus.Bus")
|
||||
def test_vocalinux_engine_application_request_name_failure_raises(
|
||||
def test_voculinux_engine_application_request_name_failure_raises(
|
||||
self,
|
||||
mock_bus_cls,
|
||||
mock_factory_new,
|
||||
@@ -858,7 +858,7 @@ class TestVocalinuxEngineApplication(unittest.TestCase):
|
||||
@patch("vocalinux.text_injection.ibus_engine.VocalinuxEngine._start_socket_server")
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBus.Factory.new")
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBus.Bus")
|
||||
def test_vocalinux_engine_application_register_component_failure_raises(
|
||||
def test_voculinux_engine_application_register_component_failure_raises(
|
||||
self,
|
||||
mock_bus_cls,
|
||||
mock_factory_new,
|
||||
@@ -880,7 +880,7 @@ class TestVocalinuxEngineApplication(unittest.TestCase):
|
||||
|
||||
@patch("vocalinux.text_injection.ibus_engine.VocalinuxEngine._start_socket_server")
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBus.Bus")
|
||||
def test_vocalinux_engine_application_disconnected_bus_raises(
|
||||
def test_voculinux_engine_application_disconnected_bus_raises(
|
||||
self,
|
||||
mock_bus_cls,
|
||||
mock_start_server,
|
||||
@@ -898,7 +898,7 @@ class TestVocalinuxEngineApplication(unittest.TestCase):
|
||||
@patch("vocalinux.text_injection.ibus_engine.VocalinuxEngine._start_socket_server")
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBus.Factory.new")
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBus.Bus")
|
||||
def test_vocalinux_engine_application_none_connection_raises(
|
||||
def test_voculinux_engine_application_none_connection_raises(
|
||||
self,
|
||||
mock_bus_cls,
|
||||
mock_factory_new,
|
||||
@@ -962,7 +962,7 @@ class TestIBusEngineMainEntrypoint(unittest.TestCase):
|
||||
msg=f"stdout={result.stdout!r} stderr={result.stderr!r}",
|
||||
)
|
||||
self.assertIn("<engines>", result.stdout)
|
||||
self.assertIn("vocalinux", result.stdout)
|
||||
self.assertIn("voculinux", result.stdout)
|
||||
self.assertNotIn("attempted relative import", result.stderr)
|
||||
|
||||
def test_main_passes_ibus_flag_to_application(self):
|
||||
@@ -1008,7 +1008,7 @@ class TestStartEngineProcess(unittest.TestCase):
|
||||
patch.object(ibus_engine, "is_engine_process_running", side_effect=running_states),
|
||||
patch.object(ibus_engine, "ensure_ibus_dir"),
|
||||
patch.object(ibus_engine, "PID_FILE") as mock_pid,
|
||||
patch.object(ibus_engine, "VOCALINUX_IBUS_DIR", Path(tempfile.mkdtemp())),
|
||||
patch.object(ibus_engine, "VOCULINUX_IBUS_DIR", Path(tempfile.mkdtemp())),
|
||||
patch.object(ibus_engine.subprocess, "Popen", return_value=mock_proc) as mock_popen,
|
||||
patch.object(ibus_engine.time, "sleep"),
|
||||
patch.object(ibus_engine.sys, "prefix", "/venv"),
|
||||
@@ -1040,7 +1040,7 @@ class TestStartEngineProcess(unittest.TestCase):
|
||||
patch.object(ibus_engine, "is_engine_process_running", side_effect=iter([False, True])),
|
||||
patch.object(ibus_engine, "ensure_ibus_dir"),
|
||||
patch.object(ibus_engine, "PID_FILE"),
|
||||
patch.object(ibus_engine, "VOCALINUX_IBUS_DIR", Path(tempfile.mkdtemp())),
|
||||
patch.object(ibus_engine, "VOCULINUX_IBUS_DIR", Path(tempfile.mkdtemp())),
|
||||
patch.object(ibus_engine.subprocess, "Popen", return_value=mock_proc) as mock_popen,
|
||||
patch.object(ibus_engine.time, "sleep"),
|
||||
):
|
||||
@@ -1077,7 +1077,7 @@ class TestStartEngineProcess(unittest.TestCase):
|
||||
patch.object(ibus_engine, "is_engine_process_running", return_value=False),
|
||||
patch.object(ibus_engine, "ensure_ibus_dir"),
|
||||
patch.object(ibus_engine, "PID_FILE") as mock_pid,
|
||||
patch.object(ibus_engine, "VOCALINUX_IBUS_DIR", tmp),
|
||||
patch.object(ibus_engine, "VOCULINUX_IBUS_DIR", tmp),
|
||||
patch.object(ibus_engine.subprocess, "Popen", side_effect=popen_side_effect),
|
||||
patch.object(ibus_engine.time, "sleep"),
|
||||
patch.object(ibus_engine.logger, "error") as mock_error,
|
||||
@@ -1110,7 +1110,7 @@ class TestStartEngineProcess(unittest.TestCase):
|
||||
patch.object(ibus_engine, "is_engine_process_running", return_value=False),
|
||||
patch.object(ibus_engine, "ensure_ibus_dir"),
|
||||
patch.object(ibus_engine, "PID_FILE") as mock_pid,
|
||||
patch.object(ibus_engine, "VOCALINUX_IBUS_DIR", tmp),
|
||||
patch.object(ibus_engine, "VOCULINUX_IBUS_DIR", tmp),
|
||||
patch.object(ibus_engine.subprocess, "Popen", return_value=mock_proc),
|
||||
patch.object(ibus_engine.time, "sleep"),
|
||||
patch.object(ibus_engine.logger, "error") as mock_error,
|
||||
@@ -1137,7 +1137,7 @@ class TestStartEngineProcess(unittest.TestCase):
|
||||
patch.object(ibus_engine, "is_engine_process_running", return_value=False),
|
||||
patch.object(ibus_engine, "ensure_ibus_dir"),
|
||||
patch.object(ibus_engine, "PID_FILE") as mock_pid,
|
||||
patch.object(ibus_engine, "VOCALINUX_IBUS_DIR", tmp),
|
||||
patch.object(ibus_engine, "VOCULINUX_IBUS_DIR", tmp),
|
||||
patch.object(ibus_engine.subprocess, "Popen", return_value=mock_proc),
|
||||
patch.object(ibus_engine.time, "sleep"),
|
||||
patch.object(ibus_engine.logger, "error") as mock_error,
|
||||
@@ -1173,7 +1173,7 @@ class TestStartEngineProcess(unittest.TestCase):
|
||||
patch.object(ibus_engine, "is_engine_process_running", return_value=False),
|
||||
patch.object(ibus_engine, "ensure_ibus_dir"),
|
||||
patch.object(ibus_engine, "PID_FILE") as mock_pid,
|
||||
patch.object(ibus_engine, "VOCALINUX_IBUS_DIR", tmp),
|
||||
patch.object(ibus_engine, "VOCULINUX_IBUS_DIR", tmp),
|
||||
patch.object(ibus_engine.subprocess, "Popen", return_value=mock_proc),
|
||||
patch.object(ibus_engine.time, "sleep"),
|
||||
):
|
||||
@@ -1198,7 +1198,7 @@ class TestStartEngineProcess(unittest.TestCase):
|
||||
patch.object(ibus_engine, "is_engine_process_running", return_value=False),
|
||||
patch.object(ibus_engine, "ensure_ibus_dir"),
|
||||
patch.object(ibus_engine, "PID_FILE") as mock_pid,
|
||||
patch.object(ibus_engine, "VOCALINUX_IBUS_DIR", tmp),
|
||||
patch.object(ibus_engine, "VOCULINUX_IBUS_DIR", tmp),
|
||||
patch.object(ibus_engine.subprocess, "Popen", return_value=mock_proc),
|
||||
patch.object(ibus_engine.time, "sleep"),
|
||||
):
|
||||
@@ -1230,7 +1230,7 @@ class TestStartEngineProcess(unittest.TestCase):
|
||||
patch.object(ibus_engine, "is_engine_process_running", return_value=False),
|
||||
patch.object(ibus_engine, "ensure_ibus_dir"),
|
||||
patch.object(ibus_engine, "PID_FILE") as mock_pid,
|
||||
patch.object(ibus_engine, "VOCALINUX_IBUS_DIR", tmp),
|
||||
patch.object(ibus_engine, "VOCULINUX_IBUS_DIR", tmp),
|
||||
patch.object(ibus_engine.subprocess, "Popen", return_value=mock_proc),
|
||||
patch.object(ibus_engine.time, "sleep"),
|
||||
patch.object(ibus_engine.logger, "error") as mock_error,
|
||||
@@ -1249,7 +1249,7 @@ class TestStartEngineProcess(unittest.TestCase):
|
||||
with (
|
||||
patch.object(ibus_engine, "is_engine_process_running", return_value=False),
|
||||
patch.object(ibus_engine, "ensure_ibus_dir"),
|
||||
patch.object(ibus_engine, "VOCALINUX_IBUS_DIR", Path(tempfile.mkdtemp())),
|
||||
patch.object(ibus_engine, "VOCULINUX_IBUS_DIR", Path(tempfile.mkdtemp())),
|
||||
patch.object(ibus_engine.subprocess, "Popen", side_effect=OSError("cannot exec")),
|
||||
patch.object(ibus_engine.logger, "error") as mock_error,
|
||||
):
|
||||
|
||||
@@ -10,7 +10,7 @@ import sys
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
# Mock GI imports before importing any vocalinux modules that use gi.
|
||||
# Mock GI imports before importing any voculinux modules that use gi.
|
||||
# On CI, real gi/IBus may be installed; importing ibus_engine without mocks
|
||||
# would set IBUS_AVAILABLE=True and VocalinuxEngine would inherit from real
|
||||
# IBus.Engine, causing hangs when super().__init__() tries to connect to IBus.
|
||||
@@ -31,7 +31,7 @@ sys.modules["gi.repository"] = _mock_gi_repo
|
||||
|
||||
# Force reload ibus_engine so it picks up mocks (in case it was already imported)
|
||||
for _key in list(sys.modules.keys()):
|
||||
if "vocalinux" in _key and "ibus_engine" in _key:
|
||||
if "voculinux" in _key and "ibus_engine" in _key:
|
||||
del sys.modules[_key]
|
||||
|
||||
|
||||
@@ -323,8 +323,8 @@ class TestSwitchEngine(unittest.TestCase):
|
||||
"""Test successfully switching engines."""
|
||||
from vocalinux.text_injection.ibus_engine import switch_engine
|
||||
|
||||
mock_get_engine.return_value = "vocalinux"
|
||||
result = switch_engine("vocalinux")
|
||||
mock_get_engine.return_value = "voculinux"
|
||||
result = switch_engine("voculinux")
|
||||
self.assertTrue(result)
|
||||
|
||||
@patch("vocalinux.text_injection.ibus_engine.get_current_engine")
|
||||
@@ -335,7 +335,7 @@ class TestSwitchEngine(unittest.TestCase):
|
||||
from vocalinux.text_injection.ibus_engine import switch_engine
|
||||
|
||||
mock_get_engine.return_value = "other"
|
||||
result = switch_engine("vocalinux")
|
||||
result = switch_engine("voculinux")
|
||||
self.assertFalse(result)
|
||||
|
||||
|
||||
@@ -381,7 +381,7 @@ class TestStopEngineProcess(unittest.TestCase):
|
||||
|
||||
cmdline = MagicMock()
|
||||
cmdline.exists.return_value = True
|
||||
cmdline.read_text.return_value = "python /x/vocalinux/text_injection/ibus_engine.py --ibus"
|
||||
cmdline.read_text.return_value = "python /x/voculinux/text_injection/ibus_engine.py --ibus"
|
||||
mock_path_cls.return_value = cmdline
|
||||
|
||||
# SIGTERM succeeds; first liveness check still alive; second gone.
|
||||
@@ -409,7 +409,7 @@ class TestStopEngineProcess(unittest.TestCase):
|
||||
|
||||
cmdline = MagicMock()
|
||||
cmdline.exists.return_value = True
|
||||
cmdline.read_text.return_value = "python /x/vocalinux/text_injection/ibus_engine.py --ibus"
|
||||
cmdline.read_text.return_value = "python /x/voculinux/text_injection/ibus_engine.py --ibus"
|
||||
mock_path_cls.return_value = cmdline
|
||||
|
||||
# SIGTERM, then keep reporting alive until wait timeout, then SIGKILL succeeds.
|
||||
|
||||
@@ -30,7 +30,7 @@ sys.modules["gi"] = _mock_gi
|
||||
sys.modules["gi.repository"] = _mock_gi_repo
|
||||
|
||||
for _key in list(sys.modules.keys()):
|
||||
if "vocalinux" in _key and "ibus_engine" in _key:
|
||||
if "voculinux" in _key and "ibus_engine" in _key:
|
||||
del sys.modules[_key]
|
||||
|
||||
from vocalinux.text_injection.ibus_engine import restore_xkb_layout # noqa: E402
|
||||
|
||||
@@ -84,14 +84,14 @@ class TestWaitForModifiersReleased:
|
||||
|
||||
def test_disabled_via_env(self, monkeypatch):
|
||||
inj = _bare_injector()
|
||||
monkeypatch.setenv("VOCALINUX_INJECT_MODIFIER_WAIT", "0")
|
||||
monkeypatch.setenv("VOCULINUX_INJECT_MODIFIER_WAIT", "0")
|
||||
with patch.object(inj, "_held_modifier_keycodes") as held:
|
||||
inj._wait_for_modifiers_released()
|
||||
held.assert_not_called()
|
||||
|
||||
def test_gives_up_after_timeout(self, monkeypatch):
|
||||
inj = _bare_injector()
|
||||
monkeypatch.setenv("VOCALINUX_INJECT_MODIFIER_WAIT", "0.05")
|
||||
monkeypatch.setenv("VOCULINUX_INJECT_MODIFIER_WAIT", "0.05")
|
||||
# Always held -> must still return (best-effort) rather than hang.
|
||||
with patch.object(inj, "_held_modifier_keycodes", return_value={56}), patch("time.sleep"):
|
||||
inj._wait_for_modifiers_released() # should return within the timeout
|
||||
@@ -227,30 +227,30 @@ class TestInjectionModifierWaitSeconds:
|
||||
@pytest.mark.parametrize("raw,expected", [("2.5", 2.5), ("0", 0.0), ("0.5", 0.5)])
|
||||
def test_valid_values(self, raw, expected, monkeypatch):
|
||||
inj = _bare_injector()
|
||||
monkeypatch.setenv("VOCALINUX_INJECT_MODIFIER_WAIT", raw)
|
||||
monkeypatch.setenv("VOCULINUX_INJECT_MODIFIER_WAIT", raw)
|
||||
assert inj._injection_modifier_wait_seconds() == expected
|
||||
|
||||
@pytest.mark.parametrize("raw", ["inf", "-inf", "nan", "Infinity", "NaN"])
|
||||
def test_non_finite_falls_back_to_default(self, raw, monkeypatch):
|
||||
inj = _bare_injector()
|
||||
monkeypatch.setenv("VOCALINUX_INJECT_MODIFIER_WAIT", raw)
|
||||
monkeypatch.setenv("VOCULINUX_INJECT_MODIFIER_WAIT", raw)
|
||||
assert inj._injection_modifier_wait_seconds() == 1.0
|
||||
|
||||
def test_non_numeric_falls_back_to_default(self, monkeypatch):
|
||||
inj = _bare_injector()
|
||||
monkeypatch.setenv("VOCALINUX_INJECT_MODIFIER_WAIT", "abc")
|
||||
monkeypatch.setenv("VOCULINUX_INJECT_MODIFIER_WAIT", "abc")
|
||||
assert inj._injection_modifier_wait_seconds() == 1.0
|
||||
|
||||
def test_unset_uses_default(self, monkeypatch):
|
||||
inj = _bare_injector()
|
||||
monkeypatch.delenv("VOCALINUX_INJECT_MODIFIER_WAIT", raising=False)
|
||||
monkeypatch.delenv("VOCULINUX_INJECT_MODIFIER_WAIT", raising=False)
|
||||
assert inj._injection_modifier_wait_seconds() == 1.0
|
||||
|
||||
def test_inf_does_not_block_forever(self, monkeypatch):
|
||||
# Regression: with 'inf', the wait must still terminate (bounded by the
|
||||
# finite default) instead of looping forever while a modifier is held.
|
||||
inj = _bare_injector()
|
||||
monkeypatch.setenv("VOCALINUX_INJECT_MODIFIER_WAIT", "inf")
|
||||
monkeypatch.setenv("VOCULINUX_INJECT_MODIFIER_WAIT", "inf")
|
||||
# Simulate the clock crossing the finite default deadline in a few steps.
|
||||
clock = iter([0.0, 0.5, 1.5])
|
||||
monkeypatch.setattr("time.monotonic", lambda: next(clock, 2.0))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Behavioral tests for the installer's speech-engine sanity check.
|
||||
|
||||
config.json survives reinstalls, so it can name an engine the current venv
|
||||
cannot import — vosk is an optional extra since #705. Vocalinux then dies at
|
||||
cannot import — vosk is an optional extra since #705. Voculinux then dies at
|
||||
startup with "No module named 'vosk'" instead of the installer saying so.
|
||||
"""
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ def test_the_gate_takes_this_commit_from_git_not_the_working_directory():
|
||||
), "the gate no longer extracts the tree from git"
|
||||
installer = INSTALLER.read_text(encoding="utf-8").splitlines()
|
||||
assert any(
|
||||
"grep" in line and "pyproject.toml" in line and "vocalinux" in line for line in installer
|
||||
"grep" in line and "pyproject.toml" in line and "voculinux" in line for line in installer
|
||||
), (
|
||||
"local mode is no longer detected from pyproject.toml in the working"
|
||||
" directory; the gate may now be testing a downloaded tag instead of"
|
||||
@@ -206,9 +206,9 @@ def test_the_smoke_checks_what_the_installer_claims_to_have_created():
|
||||
gate = _gate_text()
|
||||
for construct in (
|
||||
r'\[ -x "\$VENV/bin/python" \]',
|
||||
r'\[ -x "\$INSTALL_HOME/\.local/bin/vocalinux" \]',
|
||||
r'\[ -f "\$INSTALL_HOME/\.local/share/applications/vocalinux\.desktop" \]',
|
||||
r"'\$INSTALL_HOME/\.local/bin/vocalinux' --version",
|
||||
r'\[ -x "\$INSTALL_HOME/\.local/bin/voculinux" \]',
|
||||
r'\[ -f "\$INSTALL_HOME/\.local/share/applications/voculinux\.desktop" \]',
|
||||
r"'\$INSTALL_HOME/\.local/bin/voculinux' --version",
|
||||
):
|
||||
assert re.search(construct, gate), f"the smoke no longer asserts {construct}"
|
||||
|
||||
@@ -306,13 +306,13 @@ def test_the_remote_gate_bootstraps_outside_a_checkout():
|
||||
checkout exists. Overriding the clone source is what keeps the run on this
|
||||
commit instead of the public repo's last release."""
|
||||
gate = _without_comments(_remote_gate_text())
|
||||
assert "VOCALINUX_REPO_URL" in gate, (
|
||||
assert "VOCULINUX_REPO_URL" in gate, (
|
||||
"the remote gate no longer redirects the clone source; it would"
|
||||
" install the public repo's tag rather than this commit"
|
||||
)
|
||||
installer = _without_comments(INSTALLER.read_text(encoding="utf-8"))
|
||||
assert re.search(r"VOCALINUX_REPO_URL:-https://github\.com/VocaHQ/vocalinux\.git", installer), (
|
||||
"install.sh no longer honors VOCALINUX_REPO_URL; the gate's mirror"
|
||||
assert re.search(r"VOCULINUX_REPO_URL:-https://github\.com/VocaHQ/vocalinux\.git", installer), (
|
||||
"install.sh no longer honors VOCULINUX_REPO_URL; the gate's mirror"
|
||||
" cannot reach it and the clone falls back to the public repo"
|
||||
)
|
||||
|
||||
@@ -346,7 +346,7 @@ def test_the_remote_gate_checks_handoff_artifacts():
|
||||
gate = _without_comments(_remote_gate_text())
|
||||
for construct in (
|
||||
r'\[ -x "\$REMOTE_VENV/bin/python" \]',
|
||||
r'\[ -f "\$INSTALL_HOME/\.local/bin/activate-vocalinux\.sh" \]',
|
||||
r'\[ -f "\$INSTALL_HOME/\.local/bin/activate-voculinux\.sh" \]',
|
||||
):
|
||||
assert re.search(construct, gate), f"the remote gate no longer asserts {construct}"
|
||||
|
||||
@@ -355,9 +355,9 @@ def test_the_remote_gate_smoke_tests_the_installed_app():
|
||||
"""Same standard as the local gate: the app binary answers --version
|
||||
through the wrapper the user's PATH carries."""
|
||||
gate = _without_comments(_remote_gate_text())
|
||||
assert re.search(r"'\$INSTALL_HOME/\.local/bin/vocalinux' --version", gate)
|
||||
assert re.search(r"'\$INSTALL_HOME/\.local/bin/voculinux' --version", gate)
|
||||
assert re.search(
|
||||
r'\[ -x "\$INSTALL_HOME/\.local/bin/vocalinux" \]', gate
|
||||
r'\[ -x "\$INSTALL_HOME/\.local/bin/voculinux" \]', gate
|
||||
), "the remote gate no longer asserts the user-facing wrapper exists"
|
||||
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ print_info() {{ :; }}
|
||||
require_distro_gi() {{ :; }}
|
||||
VENV_DIR={shlex.quote(str(venv))}
|
||||
INSTALL_DIR={shlex.quote(str(ROOT))}
|
||||
VOCALINUX_TMP_DIR={shlex.quote(str(tmp_path))}
|
||||
VOCULINUX_TMP_DIR={shlex.quote(str(tmp_path))}
|
||||
{source}
|
||||
{command}
|
||||
"""
|
||||
@@ -188,15 +188,15 @@ def test_remote_handoff_preserves_tagged_behavior(tmp_path: Path, remote_aware:
|
||||
"""A main bootstrap must preserve remote behavior for every tag."""
|
||||
clone = tmp_path / "old tagged tree"
|
||||
clone.mkdir()
|
||||
marker = 'CLEANUP_ON_EXIT="${VOCALINUX_REMOTE_INSTALL:-no}"\n' if remote_aware else ""
|
||||
marker = 'CLEANUP_ON_EXIT="${VOCULINUX_REMOTE_INSTALL:-no}"\n' if remote_aware else ""
|
||||
(clone / "install.sh").write_text(
|
||||
"#!/bin/bash\n"
|
||||
+ marker
|
||||
+ 'printf "%s\\n" "${VOCALINUX_REMOTE_INSTALL:-no}" "$PWD" "$@"\n'
|
||||
+ "helper=activate-vocalinux.sh\n"
|
||||
+ 'if [ "${VOCALINUX_REMOTE_INSTALL:-no}" = yes ]; then\n'
|
||||
+ 'printf "%s\\n" "${VOCULINUX_REMOTE_INSTALL:-no}" "$PWD" "$@"\n'
|
||||
+ "helper=activate-voculinux.sh\n"
|
||||
+ 'if [ "${VOCULINUX_REMOTE_INSTALL:-no}" = yes ]; then\n'
|
||||
+ ' mkdir -p "$HOME/.local/bin"\n'
|
||||
+ ' helper="$HOME/.local/bin/activate-vocalinux.sh"\n'
|
||||
+ ' helper="$HOME/.local/bin/activate-voculinux.sh"\n'
|
||||
+ "fi\n"
|
||||
+ 'printf helper > "$helper"\n'
|
||||
)
|
||||
@@ -214,7 +214,7 @@ def test_remote_handoff_preserves_tagged_behavior(tmp_path: Path, remote_aware:
|
||||
"-c",
|
||||
f"set -eu\n{handoff}\ncd {shlex.quote(str(clone))}\n"
|
||||
f"INSTALL_DIR={shlex.quote(str(clone))}\n"
|
||||
f"VOCALINUX_TMP_DIR={shlex.quote(str(scratch))}\n"
|
||||
f"VOCULINUX_TMP_DIR={shlex.quote(str(scratch))}\n"
|
||||
"INSTALLER_ARGS=(--auto --tag=v0.17.0 --engine=remote_api)\n"
|
||||
'print_error() { echo "$*" >&2; }\n'
|
||||
"handoff_to_tagged_installer",
|
||||
@@ -231,12 +231,12 @@ def test_remote_handoff_preserves_tagged_behavior(tmp_path: Path, remote_aware:
|
||||
"--auto",
|
||||
"--tag=v0.17.0",
|
||||
"--engine=remote_api",
|
||||
f"--venv-dir={home}/.local/share/vocalinux/venv",
|
||||
f"--venv-dir={home}/.local/share/voculinux/venv",
|
||||
]
|
||||
assert scratch.exists() != remote_aware
|
||||
helper = home / ".local/bin/activate-vocalinux.sh"
|
||||
helper = home / ".local/bin/activate-voculinux.sh"
|
||||
assert helper.read_text() == "helper"
|
||||
assert not (clone / "activate-vocalinux.sh").exists()
|
||||
assert not (clone / "activate-voculinux.sh").exists()
|
||||
|
||||
|
||||
def test_every_pip_install_has_an_explicit_resolution_boundary() -> None:
|
||||
|
||||
@@ -43,16 +43,16 @@ def test_help_does_not_create_install_log_or_scratch_dir(tmp_path, monkeypatch)
|
||||
)
|
||||
assert result.returncode == 0
|
||||
assert not list((tmp_path / "state").rglob("install-*.log"))
|
||||
assert not list((tmp_path / "tmp").glob("vocalinux-install.*"))
|
||||
assert not list((tmp_path / "tmp").glob("voculinux-install.*"))
|
||||
|
||||
|
||||
def test_scratch_dir_owns_pip_logs_and_model_temps() -> None:
|
||||
source = _installer_source()
|
||||
assert 'PIP_LOG_DIR="$VOCALINUX_TMP_DIR/pip"' in source
|
||||
assert 'export TMPDIR="$VOCALINUX_TMP_DIR"' in source
|
||||
assert 'TEMP_FILE="$VOCALINUX_TMP_DIR/tiny.pt"' in source
|
||||
assert 'TEMP_FILE="$VOCALINUX_TMP_DIR/ggml-tiny.bin"' in source
|
||||
assert 'TEMP_ZIP="$VOCALINUX_TMP_DIR/$(basename $SMALL_MODEL_URL)"' in source
|
||||
assert 'PIP_LOG_DIR="$VOCULINUX_TMP_DIR/pip"' in source
|
||||
assert 'export TMPDIR="$VOCULINUX_TMP_DIR"' in source
|
||||
assert 'TEMP_FILE="$VOCULINUX_TMP_DIR/tiny.pt"' in source
|
||||
assert 'TEMP_FILE="$VOCULINUX_TMP_DIR/ggml-tiny.bin"' in source
|
||||
assert 'TEMP_ZIP="$VOCULINUX_TMP_DIR/$(basename $SMALL_MODEL_URL)"' in source
|
||||
|
||||
|
||||
def test_system_dep_and_venv_failures_use_structured_exit_codes() -> None:
|
||||
@@ -67,10 +67,10 @@ def test_system_dep_and_venv_failures_use_structured_exit_codes() -> None:
|
||||
bootstrap_failure = source.split("install_pinned_build_tools || {", 1)[1].split("}", 1)[0]
|
||||
assert 'exit "$EXIT_NETWORK"' in bootstrap_failure
|
||||
assert (
|
||||
'print_error "Failed to install Vocalinux package. Installation cannot continue."' in source
|
||||
'print_error "Failed to install Voculinux package. Installation cannot continue."' in source
|
||||
)
|
||||
assert 'exit "$EXIT_NETWORK"' in source
|
||||
assert "Could not terminate all Vocalinux processes" in source
|
||||
assert "Could not terminate all Voculinux processes" in source
|
||||
assert 'exit "$EXIT_USER_ABORT"' in source
|
||||
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ def test_piped_installer_reaches_past_the_stdin_probe(tmp_path: Path) -> None:
|
||||
check=False,
|
||||
)
|
||||
output = transcript.read_text(encoding="utf-8", errors="replace")
|
||||
assert "Vocalinux Installer" in output
|
||||
assert "Voculinux Installer" in output
|
||||
assert "Usage:" in output
|
||||
|
||||
|
||||
|
||||
@@ -359,7 +359,7 @@ run_interactive_install
|
||||
echo "U=$UPGRADE_MODE N=$NON_INTERACTIVE I=$INTERACTIVE_MODE"
|
||||
""")
|
||||
|
||||
assert "existing Vocalinux installation was detected" in result.stdout
|
||||
assert "existing Voculinux installation was detected" in result.stdout
|
||||
assert "REACHED_GUIDED_SETUP" not in result.stdout
|
||||
assert "U=yes N=yes I=no" in result.stdout
|
||||
|
||||
@@ -407,7 +407,7 @@ def test_piped_installer_defaults_to_upgrade_on_existing_install(tmp_path: Path)
|
||||
venv = tmp_path / "venv"
|
||||
(venv / "bin").mkdir(parents=True)
|
||||
(venv / "bin" / "activate").touch()
|
||||
config_dir = tmp_path / ".config" / "vocalinux"
|
||||
config_dir = tmp_path / ".config" / "voculinux"
|
||||
config_dir.mkdir(parents=True)
|
||||
(config_dir / "config.json").write_text(json.dumps({"speech_recognition": {"engine": "vosk"}}))
|
||||
|
||||
@@ -432,7 +432,7 @@ def test_piped_installer_defaults_to_upgrade_on_existing_install(tmp_path: Path)
|
||||
env = os.environ.copy()
|
||||
env["HOME"] = str(tmp_path)
|
||||
# conftest points XDG_CONFIG_HOME at an isolation dir; the installer reads
|
||||
# config.json from $XDG_CONFIG_HOME/vocalinux, not straight from $HOME.
|
||||
# config.json from $XDG_CONFIG_HOME/voculinux, not straight from $HOME.
|
||||
env["XDG_CONFIG_HOME"] = str(tmp_path / ".config")
|
||||
env["XDG_STATE_HOME"] = str(tmp_path / ".local" / "state")
|
||||
env["TMPDIR"] = str(tmp_path)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Branch coverage tests for Vocalinux.
|
||||
Branch coverage tests for Voculinux.
|
||||
|
||||
This module tests uncovered branches in:
|
||||
- recognition_manager.py
|
||||
@@ -533,7 +533,7 @@ class TestRecognitionManagerModelReady:
|
||||
class TestParseArguments:
|
||||
"""Test parse_arguments function."""
|
||||
|
||||
@patch("sys.argv", ["vocalinux", "--debug"])
|
||||
@patch("sys.argv", ["voculinux", "--debug"])
|
||||
def test_parse_arguments_debug(self):
|
||||
"""Test parse_arguments with --debug flag."""
|
||||
from vocalinux.main import parse_arguments
|
||||
@@ -541,7 +541,7 @@ class TestParseArguments:
|
||||
args = parse_arguments()
|
||||
assert args.debug is True
|
||||
|
||||
@patch("sys.argv", ["vocalinux", "--engine", "whisper"])
|
||||
@patch("sys.argv", ["voculinux", "--engine", "whisper"])
|
||||
def test_parse_arguments_engine(self):
|
||||
"""Test parse_arguments with --engine flag."""
|
||||
from vocalinux.main import parse_arguments
|
||||
@@ -549,7 +549,7 @@ class TestParseArguments:
|
||||
args = parse_arguments()
|
||||
assert args.engine == "whisper"
|
||||
|
||||
@patch("sys.argv", ["vocalinux", "--model", "large"])
|
||||
@patch("sys.argv", ["voculinux", "--model", "large"])
|
||||
def test_parse_arguments_model(self):
|
||||
"""Test parse_arguments with --model flag."""
|
||||
from vocalinux.main import parse_arguments
|
||||
@@ -557,7 +557,7 @@ class TestParseArguments:
|
||||
args = parse_arguments()
|
||||
assert args.model == "large"
|
||||
|
||||
@patch("sys.argv", ["vocalinux", "--language", "en-us"])
|
||||
@patch("sys.argv", ["voculinux", "--language", "en-us"])
|
||||
def test_parse_arguments_language(self):
|
||||
"""Test parse_arguments with --language flag."""
|
||||
from vocalinux.main import parse_arguments
|
||||
@@ -565,7 +565,7 @@ class TestParseArguments:
|
||||
args = parse_arguments()
|
||||
assert args.language == "en-us"
|
||||
|
||||
@patch("sys.argv", ["vocalinux", "--wayland"])
|
||||
@patch("sys.argv", ["voculinux", "--wayland"])
|
||||
def test_parse_arguments_wayland(self):
|
||||
"""Test parse_arguments with --wayland flag."""
|
||||
from vocalinux.main import parse_arguments
|
||||
@@ -573,7 +573,7 @@ class TestParseArguments:
|
||||
args = parse_arguments()
|
||||
assert args.wayland is True
|
||||
|
||||
@patch("sys.argv", ["vocalinux", "--start-minimized"])
|
||||
@patch("sys.argv", ["voculinux", "--start-minimized"])
|
||||
def test_parse_arguments_start_minimized(self):
|
||||
"""Test parse_arguments with --start-minimized flag."""
|
||||
from vocalinux.main import parse_arguments
|
||||
@@ -898,7 +898,7 @@ class TestRecognitionManagerUpdateState:
|
||||
class TestParseArgumentsCombinations:
|
||||
"""Test parse_arguments with various flag combinations."""
|
||||
|
||||
@patch("sys.argv", ["vocalinux", "--debug", "--engine", "vosk", "--wayland"])
|
||||
@patch("sys.argv", ["voculinux", "--debug", "--engine", "vosk", "--wayland"])
|
||||
def test_parse_arguments_multiple_flags(self):
|
||||
"""Test parse_arguments with multiple flags."""
|
||||
from vocalinux.main import parse_arguments
|
||||
@@ -908,7 +908,7 @@ class TestParseArgumentsCombinations:
|
||||
assert args.engine == "vosk"
|
||||
assert args.wayland is True
|
||||
|
||||
@patch("sys.argv", ["vocalinux"])
|
||||
@patch("sys.argv", ["voculinux"])
|
||||
def test_parse_arguments_no_flags(self):
|
||||
"""Test parse_arguments with no flags."""
|
||||
from vocalinux.main import parse_arguments
|
||||
@@ -1306,7 +1306,7 @@ class TestCallbackManagement:
|
||||
class TestParseArgumentsAllCombos:
|
||||
"""Test all argument combinations."""
|
||||
|
||||
@patch("sys.argv", ["vocalinux", "--model", "small"])
|
||||
@patch("sys.argv", ["voculinux", "--model", "small"])
|
||||
def test_parse_arguments_model_small(self):
|
||||
"""Test parse_arguments with small model."""
|
||||
from vocalinux.main import parse_arguments
|
||||
@@ -1314,7 +1314,7 @@ class TestParseArgumentsAllCombos:
|
||||
args = parse_arguments()
|
||||
assert args.model == "small"
|
||||
|
||||
@patch("sys.argv", ["vocalinux", "--model", "medium"])
|
||||
@patch("sys.argv", ["voculinux", "--model", "medium"])
|
||||
def test_parse_arguments_model_medium(self):
|
||||
"""Test parse_arguments with medium model."""
|
||||
from vocalinux.main import parse_arguments
|
||||
@@ -1322,7 +1322,7 @@ class TestParseArgumentsAllCombos:
|
||||
args = parse_arguments()
|
||||
assert args.model == "medium"
|
||||
|
||||
@patch("sys.argv", ["vocalinux", "--language", "hi"])
|
||||
@patch("sys.argv", ["voculinux", "--language", "hi"])
|
||||
def test_parse_arguments_language_hi(self):
|
||||
"""Test parse_arguments with Hindi language."""
|
||||
from vocalinux.main import parse_arguments
|
||||
@@ -1330,7 +1330,7 @@ class TestParseArgumentsAllCombos:
|
||||
args = parse_arguments()
|
||||
assert args.language == "hi"
|
||||
|
||||
@patch("sys.argv", ["vocalinux", "--language", "es"])
|
||||
@patch("sys.argv", ["voculinux", "--language", "es"])
|
||||
def test_parse_arguments_language_es(self):
|
||||
"""Test parse_arguments with Spanish language."""
|
||||
from vocalinux.main import parse_arguments
|
||||
@@ -1338,7 +1338,7 @@ class TestParseArgumentsAllCombos:
|
||||
args = parse_arguments()
|
||||
assert args.language == "es"
|
||||
|
||||
@patch("sys.argv", ["vocalinux", "--engine", "vosk"])
|
||||
@patch("sys.argv", ["voculinux", "--engine", "vosk"])
|
||||
def test_parse_arguments_engine_vosk(self):
|
||||
"""Test parse_arguments with vosk engine."""
|
||||
from vocalinux.main import parse_arguments
|
||||
@@ -1346,7 +1346,7 @@ class TestParseArgumentsAllCombos:
|
||||
args = parse_arguments()
|
||||
assert args.engine == "vosk"
|
||||
|
||||
@patch("sys.argv", ["vocalinux", "--engine", "whisper_cpp"])
|
||||
@patch("sys.argv", ["voculinux", "--engine", "whisper_cpp"])
|
||||
def test_parse_arguments_engine_whisper_cpp(self):
|
||||
"""Test parse_arguments with whisper_cpp engine."""
|
||||
from vocalinux.main import parse_arguments
|
||||
|
||||
@@ -56,7 +56,7 @@ class TestDesktopEnvironmentDetect:
|
||||
is set — pynput over XWayland is not truly global.
|
||||
"""
|
||||
env = {
|
||||
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
||||
"FLATPAK_ID": "com.voculinux.Voculinux",
|
||||
"XDG_SESSION_TYPE": "wayland",
|
||||
"DISPLAY": ":0",
|
||||
}
|
||||
@@ -66,7 +66,7 @@ class TestDesktopEnvironmentDetect:
|
||||
def test_detect_flatpak_wayland_with_socket_stays_wayland(self):
|
||||
"""With a Wayland socket exposed, detection still reports Wayland."""
|
||||
env = {
|
||||
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
||||
"FLATPAK_ID": "com.voculinux.Voculinux",
|
||||
"XDG_SESSION_TYPE": "wayland",
|
||||
"WAYLAND_DISPLAY": "wayland-0",
|
||||
}
|
||||
@@ -81,7 +81,7 @@ class TestDesktopEnvironmentDetect:
|
||||
fake.is_available.return_value = True
|
||||
|
||||
env = {
|
||||
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
||||
"FLATPAK_ID": "com.voculinux.Voculinux",
|
||||
"XDG_SESSION_TYPE": "wayland",
|
||||
"DISPLAY": ":0",
|
||||
}
|
||||
@@ -119,7 +119,7 @@ class TestDesktopEnvironmentDetect:
|
||||
def test_create_backend_flatpak_native_x11_prefers_pynput(self) -> None:
|
||||
"""Flatpak on native X11 uses pynput outside GNOME."""
|
||||
env = {
|
||||
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
||||
"FLATPAK_ID": "com.voculinux.Voculinux",
|
||||
"XDG_SESSION_TYPE": "x11",
|
||||
"XDG_CURRENT_DESKTOP": "X-Cinnamon",
|
||||
"DISPLAY": ":0",
|
||||
@@ -140,7 +140,7 @@ class TestDesktopEnvironmentDetect:
|
||||
def test_create_backend_flatpak_gnome_native_x11_prefers_evdev(self) -> None:
|
||||
"""Flatpak on GNOME X11 prefers evdev."""
|
||||
env = {
|
||||
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
||||
"FLATPAK_ID": "com.voculinux.Voculinux",
|
||||
"XDG_SESSION_TYPE": "x11",
|
||||
"XDG_CURRENT_DESKTOP": "GNOME",
|
||||
"DISPLAY": ":0",
|
||||
@@ -162,7 +162,7 @@ class TestDesktopEnvironmentDetect:
|
||||
def test_create_backend_flatpak_gnome_native_x11_falls_back_to_pynput(self) -> None:
|
||||
"""Flatpak on GNOME X11 falls back when evdev cannot read /dev/input."""
|
||||
env = {
|
||||
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
||||
"FLATPAK_ID": "com.voculinux.Voculinux",
|
||||
"XDG_SESSION_TYPE": "x11",
|
||||
"XDG_CURRENT_DESKTOP": "GNOME",
|
||||
"DISPLAY": ":0",
|
||||
@@ -187,7 +187,7 @@ class TestDesktopEnvironmentDetect:
|
||||
def test_create_backend_flatpak_x11_with_wayland_display_prefers_evdev(self) -> None:
|
||||
"""A Wayland socket keeps Flatpak on the evdev path."""
|
||||
env = {
|
||||
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
||||
"FLATPAK_ID": "com.voculinux.Voculinux",
|
||||
"XDG_SESSION_TYPE": "x11",
|
||||
"WAYLAND_DISPLAY": "wayland-0",
|
||||
"DISPLAY": ":0",
|
||||
@@ -209,7 +209,7 @@ class TestDesktopEnvironmentDetect:
|
||||
def test_create_backend_flatpak_xwayland_without_evdev_returns_none(self) -> None:
|
||||
"""A Flatpak on a Wayland session without input access needs evdev."""
|
||||
env = {
|
||||
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
||||
"FLATPAK_ID": "com.voculinux.Voculinux",
|
||||
"XDG_SESSION_TYPE": "wayland",
|
||||
"DISPLAY": ":0",
|
||||
}
|
||||
@@ -231,7 +231,7 @@ class TestDesktopEnvironmentDetect:
|
||||
def test_create_backend_flatpak_unknown_session_prefers_evdev(self) -> None:
|
||||
"""Unknown Flatpak sessions conservatively prefer evdev."""
|
||||
env = {
|
||||
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
||||
"FLATPAK_ID": "com.voculinux.Voculinux",
|
||||
"DISPLAY": ":0",
|
||||
}
|
||||
fake = MagicMock()
|
||||
|
||||
@@ -375,7 +375,7 @@ class TestKeyboardShortcuts(unittest.TestCase):
|
||||
def test_unavailable_hints_snap_lists_both_connects(self):
|
||||
"""Snap log banner should list both raw-input and hardware-observe."""
|
||||
self.mock_create_backend.return_value = None
|
||||
with patch.dict("os.environ", {"SNAP": "/snap/vocalinux/x1"}, clear=False):
|
||||
with patch.dict("os.environ", {"SNAP": "/snap/voculinux/x1"}, clear=False):
|
||||
with patch("vocalinux.ui.keyboard_shortcuts.logger") as mock_logger:
|
||||
KeyboardShortcutManager()
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ def test_normalize_language_shortcuts_dedupes_by_shortcut() -> None:
|
||||
|
||||
|
||||
def test_language_shortcuts_roundtrip_through_config(tmp_path: Any) -> None:
|
||||
config_dir = tmp_path / "vocalinux"
|
||||
config_dir = tmp_path / "voculinux"
|
||||
config_dir.mkdir()
|
||||
config_file = config_dir / "config.json"
|
||||
with (
|
||||
@@ -137,7 +137,7 @@ def test_language_shortcuts_roundtrip_through_config(tmp_path: Any) -> None:
|
||||
def test_get_language_shortcuts_survives_a_broken_shortcuts_section() -> None:
|
||||
"""A hand-edited config replacing the section with a scalar reads as empty."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
config_dir = os.path.join(temp_dir, "vocalinux")
|
||||
config_dir = os.path.join(temp_dir, "voculinux")
|
||||
config_file = os.path.join(config_dir, "config.json")
|
||||
os.makedirs(config_dir, exist_ok=True)
|
||||
with open(config_file, "w") as handle:
|
||||
@@ -1062,7 +1062,7 @@ def test_slovenian_shortcut_row_persists_through_config(
|
||||
dialog_class: type[Any], tmp_path: Any
|
||||
) -> None:
|
||||
"""A row picked as Slovenian is saved and read back, not dropped (#970)."""
|
||||
config_dir = tmp_path / "vocalinux"
|
||||
config_dir = tmp_path / "voculinux"
|
||||
config_dir.mkdir()
|
||||
with (
|
||||
patch("vocalinux.ui.config_manager.CONFIG_DIR", str(config_dir)),
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
"""Ticket #18: one-time legacy Vocalinux import into the Voculinux locations.
|
||||
|
||||
Seams under test (all offline, no hardware):
|
||||
- A fresh run with a legacy install present imports ``config.json``, the
|
||||
custom dictionary, and the portal restore token exactly once and logs it;
|
||||
a second run imports nothing.
|
||||
- A fresh run with an existing new configuration never imports, merges, or
|
||||
writes to the legacy location.
|
||||
- A fresh run with no legacy install starts from defaults (nothing imported).
|
||||
- Models are not migrated.
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from vocalinux import legacy_import
|
||||
from vocalinux.legacy_import import import_legacy_once
|
||||
|
||||
|
||||
def _isolate(monkeypatch, tmp_path: Path) -> tuple[Path, Path]:
|
||||
"""Point XDG roots at scratch dirs; return (config_root, data_root)."""
|
||||
config_root = tmp_path / "config"
|
||||
data_root = tmp_path / "data"
|
||||
monkeypatch.setenv("XDG_CONFIG_HOME", str(config_root))
|
||||
monkeypatch.setenv("XDG_DATA_HOME", str(data_root))
|
||||
return config_root, data_root
|
||||
|
||||
|
||||
def _seed_legacy(config_root: Path, data_root: Path) -> dict[str, Path]:
|
||||
"""Create a legacy install's payload; return the created files."""
|
||||
legacy_config = config_root / "vocalinux"
|
||||
legacy_data = data_root / "vocalinux"
|
||||
legacy_config.mkdir(parents=True)
|
||||
legacy_data.mkdir(parents=True)
|
||||
created = {}
|
||||
for name, content in (
|
||||
("config.json", '{"general": {"first_run": false}}'),
|
||||
("dictionary.txt", "Supabase\n"),
|
||||
("custom-dictionary-corrections.json", json.dumps({"version": 1, "corrections": []})),
|
||||
):
|
||||
target = legacy_config / name
|
||||
target.write_text(content, encoding="utf-8")
|
||||
created[name] = target
|
||||
token = legacy_data / "remote_desktop_restore_token"
|
||||
token.write_text("legacy-token\n", encoding="utf-8")
|
||||
created["remote_desktop_restore_token"] = token
|
||||
return created
|
||||
|
||||
|
||||
def test_fresh_run_with_legacy_install_imports_once(monkeypatch, tmp_path, caplog) -> None:
|
||||
"""Legacy config, dictionary, and restore token copy over exactly once."""
|
||||
config_root, data_root = _isolate(monkeypatch, tmp_path)
|
||||
_seed_legacy(config_root, data_root)
|
||||
|
||||
with caplog.at_level("INFO", logger="vocalinux.legacy_import"):
|
||||
imported = import_legacy_once()
|
||||
|
||||
assert len(imported) == 4
|
||||
assert (
|
||||
config_root / "voculinux" / "config.json"
|
||||
).read_text() == '{"general": {"first_run": false}}'
|
||||
assert (config_root / "voculinux" / "dictionary.txt").read_text() == "Supabase\n"
|
||||
assert json.loads(
|
||||
(config_root / "voculinux" / "custom-dictionary-corrections.json").read_text()
|
||||
) == {
|
||||
"version": 1,
|
||||
"corrections": [],
|
||||
}
|
||||
assert (
|
||||
data_root / "voculinux" / "remote_desktop_restore_token"
|
||||
).read_text() == "legacy-token\n"
|
||||
assert any("Legacy Vocalinux import complete" in r.message for r in caplog.records)
|
||||
|
||||
# A second run imports nothing.
|
||||
assert import_legacy_once() == []
|
||||
|
||||
|
||||
def test_existing_new_config_never_imports_or_touches_legacy(monkeypatch, tmp_path) -> None:
|
||||
"""An existing new configuration always wins: no merge, no write-back."""
|
||||
config_root, data_root = _isolate(monkeypatch, tmp_path)
|
||||
seeded = _seed_legacy(config_root, data_root)
|
||||
before = {name: path.read_bytes() for name, path in seeded.items()}
|
||||
|
||||
new_config = config_root / "voculinux"
|
||||
new_config.mkdir(parents=True)
|
||||
(new_config / "config.json").write_text('{"general": {"first_run": true}}', encoding="utf-8")
|
||||
|
||||
assert import_legacy_once() == []
|
||||
assert (new_config / "config.json").read_text() == '{"general": {"first_run": true}}'
|
||||
assert not (new_config / "dictionary.txt").exists()
|
||||
assert not (data_root / "voculinux").exists()
|
||||
for name, path in seeded.items():
|
||||
assert path.read_bytes() == before[name], name
|
||||
|
||||
|
||||
def test_no_legacy_install_starts_from_defaults(monkeypatch, tmp_path) -> None:
|
||||
"""No legacy directories means nothing is imported."""
|
||||
_isolate(monkeypatch, tmp_path)
|
||||
assert import_legacy_once() == []
|
||||
|
||||
|
||||
def test_models_are_not_migrated(monkeypatch, tmp_path) -> None:
|
||||
"""A legacy models directory is left alone; rung files re-download."""
|
||||
config_root, data_root = _isolate(monkeypatch, tmp_path)
|
||||
_seed_legacy(config_root, data_root)
|
||||
legacy_models = data_root / "vocalinux" / "models"
|
||||
legacy_models.mkdir(parents=True)
|
||||
(legacy_models / "ggml-large-v3-turbo-q8_0.bin").write_bytes(b"fake-model")
|
||||
|
||||
imported = import_legacy_once()
|
||||
|
||||
assert not (data_root / "voculinux" / "models").exists()
|
||||
assert {Path(p).name for p in imported} == {
|
||||
"config.json",
|
||||
"dictionary.txt",
|
||||
"custom-dictionary-corrections.json",
|
||||
"remote_desktop_restore_token",
|
||||
}
|
||||
|
||||
|
||||
def test_partial_legacy_payload_imports_what_exists(monkeypatch, tmp_path) -> None:
|
||||
"""A legacy install with only a config still imports just that file."""
|
||||
config_root, _data_root = _isolate(monkeypatch, tmp_path)
|
||||
legacy_config = config_root / "vocalinux"
|
||||
legacy_config.mkdir(parents=True)
|
||||
(legacy_config / "config.json").write_text("{}", encoding="utf-8")
|
||||
|
||||
imported = import_legacy_once()
|
||||
|
||||
assert imported == [str(config_root / "voculinux" / "config.json")]
|
||||
assert import_legacy_once() == []
|
||||
|
||||
|
||||
def test_new_dictionary_never_overwritten_by_legacy(monkeypatch, tmp_path) -> None:
|
||||
"""A new dictionary file is never clobbered even when config is absent."""
|
||||
config_root, data_root = _isolate(monkeypatch, tmp_path)
|
||||
_seed_legacy(config_root, data_root)
|
||||
new_config = config_root / "voculinux"
|
||||
new_config.mkdir(parents=True)
|
||||
(new_config / "dictionary.txt").write_text("Mine\n", encoding="utf-8")
|
||||
|
||||
import_legacy_once()
|
||||
|
||||
# No new config.json exists, so the import ran — but the existing new
|
||||
# dictionary file must survive untouched.
|
||||
assert (new_config / "dictionary.txt").read_text() == "Mine\n"
|
||||
assert legacy_import.CONFIG_FILENAME == "config.json"
|
||||
@@ -129,7 +129,7 @@ class TestLoggingManager:
|
||||
|
||||
manager = lm.LoggingManager(max_records=10)
|
||||
try:
|
||||
assert manager.logs_dir == tmp_path / "vocalinux" / "logs"
|
||||
assert manager.logs_dir == tmp_path / "voculinux" / "logs"
|
||||
assert manager.logs_dir.is_dir()
|
||||
finally:
|
||||
root_logger.removeHandler(manager.handler)
|
||||
@@ -296,7 +296,7 @@ class TestLoggingManager:
|
||||
record2 = LogRecord(
|
||||
timestamp=datetime.now(),
|
||||
level="INFO",
|
||||
logger_name="vocalinux.speech.test",
|
||||
logger_name="voculinux.speech.test",
|
||||
message="Speech message",
|
||||
)
|
||||
logging_manager.add_log_record(record1)
|
||||
@@ -353,7 +353,7 @@ class TestLoggingManager:
|
||||
with open(filepath, "r") as f:
|
||||
content = f.read()
|
||||
|
||||
assert "VocaLinux Log Export" in content
|
||||
assert "Voculinux Log Export" in content
|
||||
assert "Export test message" in content
|
||||
finally:
|
||||
if os.path.exists(filepath):
|
||||
@@ -451,7 +451,7 @@ class TestLoggingManager:
|
||||
record2 = LogRecord(
|
||||
timestamp=timestamp2,
|
||||
level="ERROR",
|
||||
logger_name="vocalinux.speech.test",
|
||||
logger_name="voculinux.speech.test",
|
||||
message="Message 2",
|
||||
)
|
||||
logging_manager.add_log_record(record1)
|
||||
@@ -462,7 +462,7 @@ class TestLoggingManager:
|
||||
assert stats["total"] == 2
|
||||
assert stats["by_level"]["INFO"] == 1
|
||||
assert stats["by_level"]["ERROR"] == 1
|
||||
assert "vocalinux" in stats["by_module"]
|
||||
assert "voculinux" in stats["by_module"]
|
||||
assert stats["oldest"] == timestamp1
|
||||
assert stats["newest"] == timestamp2
|
||||
|
||||
|
||||
+18
-18
@@ -29,7 +29,7 @@ class TestMainModule(unittest.TestCase):
|
||||
def test_parse_arguments_defaults(self):
|
||||
"""Test argument parsing with defaults."""
|
||||
# Test with no arguments (model/engine/language will be None without defaults)
|
||||
with patch("sys.argv", ["vocalinux"]):
|
||||
with patch("sys.argv", ["voculinux"]):
|
||||
args = parse_arguments()
|
||||
self.assertFalse(args.debug)
|
||||
self.assertIsNone(args.model) # No default set, loaded from config instead
|
||||
@@ -45,7 +45,7 @@ class TestMainModule(unittest.TestCase):
|
||||
with patch(
|
||||
"sys.argv",
|
||||
[
|
||||
"vocalinux",
|
||||
"voculinux",
|
||||
"--debug",
|
||||
"--model",
|
||||
"large",
|
||||
@@ -67,33 +67,33 @@ class TestMainModule(unittest.TestCase):
|
||||
|
||||
def test_parse_arguments_model_values(self):
|
||||
"""Test model parsing for base and exact whisper.cpp model IDs."""
|
||||
with patch("sys.argv", ["vocalinux", "--model", "small"]):
|
||||
with patch("sys.argv", ["voculinux", "--model", "small"]):
|
||||
args = parse_arguments()
|
||||
self.assertEqual(args.model, "small")
|
||||
|
||||
with patch("sys.argv", ["vocalinux", "--model", "medium"]):
|
||||
with patch("sys.argv", ["voculinux", "--model", "medium"]):
|
||||
args = parse_arguments()
|
||||
self.assertEqual(args.model, "medium")
|
||||
|
||||
with patch("sys.argv", ["vocalinux", "--model", "large"]):
|
||||
with patch("sys.argv", ["voculinux", "--model", "large"]):
|
||||
args = parse_arguments()
|
||||
self.assertEqual(args.model, "large")
|
||||
|
||||
with patch("sys.argv", ["vocalinux", "--model", "medium.en-q5_0"]):
|
||||
with patch("sys.argv", ["voculinux", "--model", "medium.en-q5_0"]):
|
||||
args = parse_arguments()
|
||||
self.assertEqual(args.model, "medium.en-q5_0")
|
||||
|
||||
with patch("sys.argv", ["vocalinux", "--model", "large-v3-turbo"]):
|
||||
with patch("sys.argv", ["voculinux", "--model", "large-v3-turbo"]):
|
||||
args = parse_arguments()
|
||||
self.assertEqual(args.model, "large-v3-turbo")
|
||||
|
||||
def test_parse_arguments_engine_choices(self):
|
||||
"""Test that engine only accepts valid choices."""
|
||||
with patch("sys.argv", ["vocalinux", "--engine", "vosk"]):
|
||||
with patch("sys.argv", ["voculinux", "--engine", "vosk"]):
|
||||
args = parse_arguments()
|
||||
self.assertEqual(args.engine, "vosk")
|
||||
|
||||
with patch("sys.argv", ["vocalinux", "--engine", "whisper"]):
|
||||
with patch("sys.argv", ["voculinux", "--engine", "whisper"]):
|
||||
args = parse_arguments()
|
||||
self.assertEqual(args.engine, "whisper")
|
||||
|
||||
@@ -106,7 +106,7 @@ class TestMainModule(unittest.TestCase):
|
||||
# Spot-check newly exposed languages (issue #565 and catalog expansion).
|
||||
for lang in ["auto", "en-us", "hu", "ja", "ko", "ar", "nl", "pl", "sl", "uk"]:
|
||||
self.assertIn(lang, LANGUAGE_CHOICES)
|
||||
with patch("sys.argv", ["vocalinux", "--language", lang]):
|
||||
with patch("sys.argv", ["voculinux", "--language", lang]):
|
||||
args = parse_arguments()
|
||||
self.assertEqual(args.language, lang)
|
||||
|
||||
@@ -399,7 +399,7 @@ class TestMainModule(unittest.TestCase):
|
||||
mock_args.start_minimized = False
|
||||
mock_parse.return_value = mock_args
|
||||
|
||||
with patch("sys.argv", ["vocalinux", "--engine", engine]):
|
||||
with patch("sys.argv", ["voculinux", "--engine", engine]):
|
||||
main()
|
||||
|
||||
text_callback = mock_speech_instance.register_text_callback.call_args.args[0]
|
||||
@@ -923,7 +923,7 @@ class TestMainConfigPrecedence(unittest.TestCase):
|
||||
with patch(
|
||||
"sys.argv",
|
||||
[
|
||||
"vocalinux",
|
||||
"voculinux",
|
||||
"--engine",
|
||||
"whisper",
|
||||
"--model",
|
||||
@@ -989,7 +989,7 @@ class TestMainConfigPrecedence(unittest.TestCase):
|
||||
mock_action_handler.return_value = mock_action_instance
|
||||
|
||||
# No CLI args for engine/model/language
|
||||
with patch("sys.argv", ["vocalinux"]):
|
||||
with patch("sys.argv", ["voculinux"]):
|
||||
with patch("vocalinux.main.logger"):
|
||||
main()
|
||||
|
||||
@@ -1040,7 +1040,7 @@ class TestMainConfigPrecedence(unittest.TestCase):
|
||||
with patch(
|
||||
"sys.argv",
|
||||
[
|
||||
"vocalinux",
|
||||
"voculinux",
|
||||
"--engine",
|
||||
"parakeet",
|
||||
"--model",
|
||||
@@ -1094,7 +1094,7 @@ class TestMainConfigPrecedence(unittest.TestCase):
|
||||
mock_tray.return_value = MagicMock()
|
||||
mock_action_handler.return_value = MagicMock()
|
||||
|
||||
with patch("sys.argv", ["vocalinux"]):
|
||||
with patch("sys.argv", ["voculinux"]):
|
||||
with patch("vocalinux.main.logger"):
|
||||
main()
|
||||
|
||||
@@ -1151,7 +1151,7 @@ class TestMainConfigPrecedence(unittest.TestCase):
|
||||
mock_tray.return_value = MagicMock()
|
||||
mock_action_handler.return_value = MagicMock()
|
||||
|
||||
with patch("sys.argv", ["vocalinux"]):
|
||||
with patch("sys.argv", ["voculinux"]):
|
||||
with patch("vocalinux.main.logger"):
|
||||
main()
|
||||
|
||||
@@ -1368,7 +1368,7 @@ def _boot_main_callbacks(
|
||||
)
|
||||
)
|
||||
mock_parse = stack.enter_context(patch("vocalinux.main.parse_arguments"))
|
||||
stack.enter_context(patch("sys.argv", ["vocalinux"]))
|
||||
stack.enter_context(patch("sys.argv", ["voculinux"]))
|
||||
|
||||
mock_args = MagicMock()
|
||||
mock_args.debug = False
|
||||
@@ -2144,7 +2144,7 @@ class TestSessionHistoryRecording(unittest.TestCase):
|
||||
|
||||
stack.enter_context(patch("vocalinux.ui.logging_manager.initialize_logging"))
|
||||
mock_parse = stack.enter_context(patch("vocalinux.main.parse_arguments"))
|
||||
stack.enter_context(patch("sys.argv", ["vocalinux"]))
|
||||
stack.enter_context(patch("sys.argv", ["voculinux"]))
|
||||
|
||||
mock_args = MagicMock()
|
||||
mock_args.debug = False
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Additional coverage tests for main.py module.
|
||||
|
||||
Tests for parse_args(), main(), and VocalinuxApp class (if present).
|
||||
Tests for parse_args(), main(), and VoculinuxApp class (if present).
|
||||
Covers normal startup, debug mode, version flag, keyboard interrupt, and exception handling.
|
||||
"""
|
||||
|
||||
@@ -41,7 +41,7 @@ class TestParseArguments(unittest.TestCase):
|
||||
|
||||
def test_parse_args_defaults(self):
|
||||
"""Test parsing arguments with defaults."""
|
||||
with patch.object(sys, "argv", ["vocalinux"]):
|
||||
with patch.object(sys, "argv", ["voculinux"]):
|
||||
from vocalinux.main import parse_arguments
|
||||
|
||||
args = parse_arguments()
|
||||
@@ -55,7 +55,7 @@ class TestParseArguments(unittest.TestCase):
|
||||
|
||||
def test_parse_args_debug_flag(self):
|
||||
"""Test parsing with debug flag."""
|
||||
with patch.object(sys, "argv", ["vocalinux", "--debug"]):
|
||||
with patch.object(sys, "argv", ["voculinux", "--debug"]):
|
||||
from vocalinux.main import parse_arguments
|
||||
|
||||
args = parse_arguments()
|
||||
@@ -66,18 +66,18 @@ class TestParseArguments(unittest.TestCase):
|
||||
from vocalinux.version import __version__
|
||||
|
||||
output = StringIO()
|
||||
with patch.object(sys, "argv", ["vocalinux", "--version"]):
|
||||
with patch.object(sys, "argv", ["voculinux", "--version"]):
|
||||
from vocalinux.main import parse_arguments
|
||||
|
||||
with redirect_stdout(output), self.assertRaises(SystemExit) as exit_context:
|
||||
parse_arguments()
|
||||
|
||||
assert exit_context.exception.code == 0
|
||||
assert output.getvalue() == f"vocalinux {__version__}\n"
|
||||
assert output.getvalue() == f"voculinux {__version__}\n"
|
||||
|
||||
def test_parse_args_model_argument(self):
|
||||
"""Test parsing with model argument."""
|
||||
with patch.object(sys, "argv", ["vocalinux", "--model", "small"]):
|
||||
with patch.object(sys, "argv", ["voculinux", "--model", "small"]):
|
||||
from vocalinux.main import parse_arguments
|
||||
|
||||
args = parse_arguments()
|
||||
@@ -85,7 +85,7 @@ class TestParseArguments(unittest.TestCase):
|
||||
|
||||
def test_parse_args_language_argument(self):
|
||||
"""Test parsing with language argument."""
|
||||
with patch.object(sys, "argv", ["vocalinux", "--language", "en-us"]):
|
||||
with patch.object(sys, "argv", ["voculinux", "--language", "en-us"]):
|
||||
from vocalinux.main import parse_arguments
|
||||
|
||||
args = parse_arguments()
|
||||
@@ -93,7 +93,7 @@ class TestParseArguments(unittest.TestCase):
|
||||
|
||||
def test_parse_args_engine_argument(self):
|
||||
"""Test parsing with engine argument."""
|
||||
with patch.object(sys, "argv", ["vocalinux", "--engine", "whisper_cpp"]):
|
||||
with patch.object(sys, "argv", ["voculinux", "--engine", "whisper_cpp"]):
|
||||
from vocalinux.main import parse_arguments
|
||||
|
||||
args = parse_arguments()
|
||||
@@ -101,7 +101,7 @@ class TestParseArguments(unittest.TestCase):
|
||||
|
||||
def test_parse_args_wayland_flag(self):
|
||||
"""Test parsing with wayland flag."""
|
||||
with patch.object(sys, "argv", ["vocalinux", "--wayland"]):
|
||||
with patch.object(sys, "argv", ["voculinux", "--wayland"]):
|
||||
from vocalinux.main import parse_arguments
|
||||
|
||||
args = parse_arguments()
|
||||
@@ -109,7 +109,7 @@ class TestParseArguments(unittest.TestCase):
|
||||
|
||||
def test_parse_args_start_minimized_flag(self):
|
||||
"""Test parsing with start-minimized flag."""
|
||||
with patch.object(sys, "argv", ["vocalinux", "--start-minimized"]):
|
||||
with patch.object(sys, "argv", ["voculinux", "--start-minimized"]):
|
||||
from vocalinux.main import parse_arguments
|
||||
|
||||
args = parse_arguments()
|
||||
@@ -117,7 +117,7 @@ class TestParseArguments(unittest.TestCase):
|
||||
|
||||
def test_parse_args_transcribe_file(self):
|
||||
"""Test parsing with the headless --transcribe-file flag."""
|
||||
with patch.object(sys, "argv", ["vocalinux", "--transcribe-file", "/tmp/a.wav"]):
|
||||
with patch.object(sys, "argv", ["voculinux", "--transcribe-file", "/tmp/a.wav"]):
|
||||
from vocalinux.main import parse_arguments
|
||||
|
||||
args = parse_arguments()
|
||||
@@ -157,7 +157,7 @@ class TestParseArguments(unittest.TestCase):
|
||||
sys,
|
||||
"argv",
|
||||
[
|
||||
"vocalinux",
|
||||
"voculinux",
|
||||
"--debug",
|
||||
"--model",
|
||||
"large",
|
||||
@@ -353,13 +353,13 @@ class TestMainFunction(unittest.TestCase):
|
||||
|
||||
output = StringIO()
|
||||
with patch.dict(sys.modules, {"vocalinux.single_instance": mock_single_instance}):
|
||||
with patch.object(sys, "argv", ["vocalinux", "--version"]):
|
||||
with patch.object(sys, "argv", ["voculinux", "--version"]):
|
||||
with redirect_stdout(output):
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
main()
|
||||
|
||||
assert exc_info.value.code == 0
|
||||
assert output.getvalue() == f"vocalinux {__version__}\n"
|
||||
assert output.getvalue() == f"voculinux {__version__}\n"
|
||||
mock_single_instance.acquire_lock.assert_not_called()
|
||||
|
||||
@patch("vocalinux.main.check_display_available")
|
||||
|
||||
@@ -341,7 +341,7 @@ class TestMainFunctionExtra:
|
||||
with patch.object(
|
||||
sys,
|
||||
"argv",
|
||||
["vocalinux", "--engine", "vosk", "--model", "medium", "--language", "es"],
|
||||
["voculinux", "--engine", "vosk", "--model", "medium", "--language", "es"],
|
||||
):
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
main()
|
||||
|
||||
@@ -140,7 +140,7 @@ class TestManifestCoverage(unittest.TestCase):
|
||||
class TestVerifyFile(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = Path(self.enterContext(__import__("tempfile").TemporaryDirectory()))
|
||||
self.payload = b"vocalinux model payload"
|
||||
self.payload = b"voculinux model payload"
|
||||
self.path = self.tmp / "model.bin"
|
||||
self.path.write_bytes(self.payload)
|
||||
self.digest = hashlib.sha256(self.payload).hexdigest()
|
||||
@@ -416,7 +416,7 @@ class TestExistingModelsAreVerified(unittest.TestCase):
|
||||
for name, checker in (
|
||||
("install_whisper_model", "verify_openai_model_checksum"),
|
||||
("install_whispercpp_model", "verify_model_checksum"),
|
||||
("install_vosk_models", ".vocalinux_verified"),
|
||||
("install_vosk_models", ".voculinux_verified"),
|
||||
):
|
||||
guard = self._exists_guard(name)
|
||||
self.assertLess(
|
||||
@@ -484,9 +484,9 @@ print_error() { echo "ERROR: $*"; }
|
||||
print_success() { echo "SUCCESS: $*"; }
|
||||
command_exists() { command -v "$1" >/dev/null 2>&1; }
|
||||
DATA_DIR="$ROOT/data"
|
||||
VOCALINUX_TMP_DIR="$ROOT/tmp"
|
||||
VOCULINUX_TMP_DIR="$ROOT/tmp"
|
||||
MODEL_CHECKSUMS_FILE="$ROOT/model_checksums.txt"
|
||||
mkdir -p "$VOCALINUX_TMP_DIR"
|
||||
mkdir -p "$VOCULINUX_TMP_DIR"
|
||||
|
||||
# The installer only probes for these; the download itself is stubbed out.
|
||||
check_connectivity() { return 0; }
|
||||
@@ -663,8 +663,8 @@ download_model_file() { echo "DOWNLOAD ATTEMPTED"; return 1; }
|
||||
"""The 40MB fetch must not happen when nothing can verify it."""
|
||||
with TemporaryDirectory() as tmp:
|
||||
result = self._run(
|
||||
f'DATA_DIR="{tmp}/data"; VOCALINUX_TMP_DIR="{tmp}/tmp"; '
|
||||
f'mkdir -p "$DATA_DIR" "$VOCALINUX_TMP_DIR"; '
|
||||
f'DATA_DIR="{tmp}/data"; VOCULINUX_TMP_DIR="{tmp}/tmp"; '
|
||||
f'mkdir -p "$DATA_DIR" "$VOCULINUX_TMP_DIR"; '
|
||||
f'MODEL_CHECKSUMS_FILE="{tmp}/nope.txt"; '
|
||||
"install_vosk_models; echo RC=$?",
|
||||
functions=("pinned_digest_for", "install_vosk_models"),
|
||||
|
||||
@@ -26,7 +26,7 @@ RELEASE_YML = WORKFLOWS / "release.yml"
|
||||
PIPELINE_YML = WORKFLOWS / "unified-pipeline.yml"
|
||||
|
||||
#: Runtime dependencies no distro packages; build.sh vendors their wheels
|
||||
#: under /usr/lib/vocalinux/vendor instead of declaring distro deps.
|
||||
#: under /usr/lib/voculinux/vendor instead of declaring distro deps.
|
||||
VENDORED = ("pywhispercpp", "pynput")
|
||||
|
||||
#: pip name -> the Debian/Ubuntu package that Provides it. pywhispercpp's own
|
||||
@@ -267,9 +267,9 @@ def test_nfpm_contents_and_launcher_env_agree() -> None:
|
||||
text = BUILD_SH.read_text(encoding="utf-8")
|
||||
smoke = SMOKE_SH.read_text(encoding="utf-8")
|
||||
for where in (text, smoke):
|
||||
assert "/usr/lib/vocalinux/app" in where
|
||||
assert "/usr/lib/vocalinux/vendor" in where
|
||||
assert "/usr/bin/vocalinux" in text and "/usr/bin/vocalinux-gui" in text
|
||||
assert "/usr/lib/voculinux/app" in where
|
||||
assert "/usr/lib/voculinux/vendor" in where
|
||||
assert "/usr/bin/voculinux" in text and "/usr/bin/voculinux-gui" in text
|
||||
assert "pywhispercpp.libs" in text and "pywhispercpp.libs" in smoke
|
||||
# Arguments keep their boundaries: $* into an unquoted exec re-splits a
|
||||
# path like "Meeting notes.wav" before argparse sees it (PR #913).
|
||||
@@ -286,8 +286,8 @@ def test_smoke_gate_covers_both_formats() -> None:
|
||||
smoke = SMOKE_SH.read_text(encoding="utf-8")
|
||||
assert "debian|ubuntu)" in smoke, "smoke must install the .deb on Debian-family images"
|
||||
assert "fedora)" in smoke, "smoke must install the .rpm on Fedora"
|
||||
assert "vocalinux --version" in smoke
|
||||
assert "vocalinux-gui --version" in smoke
|
||||
assert "voculinux --version" in smoke
|
||||
assert "voculinux-gui --version" in smoke
|
||||
assert "pywhispercpp.model" in smoke, "the smoke must prove the vendored extension loads"
|
||||
assert (
|
||||
'find_spec("pynput")' in smoke
|
||||
|
||||
@@ -139,7 +139,7 @@ class TestYdotoolTerminalPasteCommand(unittest.TestCase):
|
||||
def test_flatpak_uses_shift_keycodes(self, mock_which, _mock_map):
|
||||
mock_which.return_value = "/app/bin/ydotool"
|
||||
injector = _make_injector()
|
||||
with patch.dict("os.environ", {"FLATPAK_ID": "com.vocalinux.Vocalinux"}):
|
||||
with patch.dict("os.environ", {"FLATPAK_ID": "com.voculinux.Voculinux"}):
|
||||
self.assertEqual(
|
||||
injector._ydotool_ctrl_v_command(terminal=True),
|
||||
["ydotool", "key", "29:1", "42:1", "47:1", "47:0", "42:0", "29:0"],
|
||||
|
||||
+8
-8
@@ -11,9 +11,9 @@ class TestXdgHelpers:
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
assert paths.xdg_config_home() == os.path.expanduser("~/.config")
|
||||
assert paths.xdg_data_home() == os.path.expanduser("~/.local/share")
|
||||
assert paths.config_dir() == os.path.expanduser("~/.config/vocalinux")
|
||||
assert paths.data_dir() == os.path.expanduser("~/.local/share/vocalinux")
|
||||
assert paths.models_dir() == os.path.expanduser("~/.local/share/vocalinux/models")
|
||||
assert paths.config_dir() == os.path.expanduser("~/.config/voculinux")
|
||||
assert paths.data_dir() == os.path.expanduser("~/.local/share/voculinux")
|
||||
assert paths.models_dir() == os.path.expanduser("~/.local/share/voculinux/models")
|
||||
|
||||
def test_honours_xdg_environment(self):
|
||||
env = {
|
||||
@@ -21,9 +21,9 @@ class TestXdgHelpers:
|
||||
"XDG_DATA_HOME": "/run/user/1000/data",
|
||||
}
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
assert paths.config_dir() == "/run/user/1000/cfg/vocalinux"
|
||||
assert paths.data_dir() == "/run/user/1000/data/vocalinux"
|
||||
assert paths.models_dir() == "/run/user/1000/data/vocalinux/models"
|
||||
assert paths.config_dir() == "/run/user/1000/cfg/voculinux"
|
||||
assert paths.data_dir() == "/run/user/1000/data/voculinux"
|
||||
assert paths.models_dir() == "/run/user/1000/data/voculinux/models"
|
||||
|
||||
def test_empty_env_falls_back_to_default(self):
|
||||
# The XDG spec says empty values must be treated as unset.
|
||||
@@ -33,9 +33,9 @@ class TestXdgHelpers:
|
||||
|
||||
def test_flatpak_style_data_home(self):
|
||||
# Flatpak points XDG_DATA_HOME at ~/.var/app/<app-id>/data.
|
||||
flatpak_data = "/home/user/.var/app/com.vocalinux.Vocalinux/data"
|
||||
flatpak_data = "/home/user/.var/app/com.voculinux.Voculinux/data"
|
||||
with patch.dict(os.environ, {"XDG_DATA_HOME": flatpak_data}, clear=True):
|
||||
assert paths.models_dir() == os.path.join(flatpak_data, "vocalinux", "models")
|
||||
assert paths.models_dir() == os.path.join(flatpak_data, "voculinux", "models")
|
||||
|
||||
def test_is_within_directory(self, tmp_path):
|
||||
nested = tmp_path / "models" / "ggml-tiny.bin"
|
||||
|
||||
@@ -21,9 +21,9 @@ SPEC.loader.exec_module(PROTOCOL)
|
||||
|
||||
def test_parse_vmrss_kb_reads_kilobytes() -> None:
|
||||
"""VmRSS lines parse to kilobytes; anything else yields None."""
|
||||
status = "Name:\tvocalinux\nVmRSS:\t 412336 kB\nVmSwap:\t0 kB\n"
|
||||
status = "Name:\tvoculinux\nVmRSS:\t 412336 kB\nVmSwap:\t0 kB\n"
|
||||
assert PROTOCOL.parse_vmrss_kb(status) == 412336
|
||||
assert PROTOCOL.parse_vmrss_kb("Name:\tvocalinux\n") is None
|
||||
assert PROTOCOL.parse_vmrss_kb("Name:\tvoculinux\n") is None
|
||||
|
||||
|
||||
def test_describe_reports_count_mean_min_max() -> None:
|
||||
|
||||
@@ -635,7 +635,7 @@ class TestManagerPipeWireIntegration(unittest.TestCase):
|
||||
if isinstance(sys.modules.get("numpy"), MagicMock):
|
||||
# Restore the stashed real module — re-importing it would
|
||||
# leave sys.modules["numpy.*"] bound to an orphan.
|
||||
real_numpy = getattr(sys, "_vocalinux_real_numpy", None)
|
||||
real_numpy = getattr(sys, "_voculinux_real_numpy", None)
|
||||
if real_numpy is not None:
|
||||
sys.modules["numpy"] = real_numpy
|
||||
else:
|
||||
|
||||
@@ -36,7 +36,7 @@ def _numpy_is_real_while_comparing() -> Iterator[None]:
|
||||
``isinstance(value, numpy.bool_)`` and raises TypeError.
|
||||
"""
|
||||
current = sys.modules.get("numpy")
|
||||
real = getattr(sys, "_vocalinux_real_numpy", None)
|
||||
real = getattr(sys, "_voculinux_real_numpy", None)
|
||||
if isinstance(current, MagicMock) and real is not None:
|
||||
sys.modules["numpy"] = real
|
||||
try:
|
||||
@@ -620,7 +620,7 @@ def test_pactl_restores_each_channel(tmp_path: Path) -> None:
|
||||
def test_playback_duck_percent_clamps_and_round_trips(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
config_dir = tmp_path / "vocalinux"
|
||||
config_dir = tmp_path / "voculinux"
|
||||
config_dir.mkdir()
|
||||
config_file = config_dir / "config.json"
|
||||
monkeypatch.setattr("vocalinux.ui.config_manager.CONFIG_DIR", str(config_dir))
|
||||
|
||||
@@ -35,10 +35,10 @@ def sdist(
|
||||
extra: tuple[str, ...] = (),
|
||||
) -> Path:
|
||||
"""Write a small real sdist with independently controllable metadata/members."""
|
||||
path = directory / f"vocalinux-{version}.tar.gz"
|
||||
info = metadata if metadata is not None else f"Name: vocalinux\nVersion: {version}\n"
|
||||
path = directory / f"voculinux-{version}.tar.gz"
|
||||
info = metadata if metadata is not None else f"Name: voculinux\nVersion: {version}\n"
|
||||
with tarfile.open(path, "w:gz") as archive:
|
||||
for name, text in [(f"vocalinux-{version}/PKG-INFO", info)] + [
|
||||
for name, text in [(f"voculinux-{version}/PKG-INFO", info)] + [
|
||||
(name, "payload") for name in extra
|
||||
]:
|
||||
member = tarfile.TarInfo(name)
|
||||
@@ -208,11 +208,11 @@ def test_release_verification_cannot_be_silently_skipped(
|
||||
"metadata,extra",
|
||||
[
|
||||
("Name: other\nVersion: 0.18.4\n", ()),
|
||||
("Name: vocalinux\nVersion: 0.18.3\n", ()),
|
||||
("Name: vocalinux\nVersion: 0.18.4\nVersion: 0.18.3\n", ()),
|
||||
("Name: voculinux\nVersion: 0.18.3\n", ()),
|
||||
("Name: voculinux\nVersion: 0.18.4\nVersion: 0.18.3\n", ()),
|
||||
(None, ("other-root/file",)),
|
||||
(None, ("vocalinux-0.18.4/../unsafe",)),
|
||||
(None, ("vocalinux-0.18.4/PKG-INFO",)),
|
||||
(None, ("voculinux-0.18.4/../unsafe",)),
|
||||
(None, ("voculinux-0.18.4/PKG-INFO",)),
|
||||
],
|
||||
)
|
||||
def test_wrong_or_ambiguous_archive_metadata_is_rejected(
|
||||
|
||||
@@ -38,7 +38,7 @@ from vocalinux.audio.pulse_sources import (
|
||||
from vocalinux.utils import whispercpp_model_info
|
||||
from vocalinux.utils.snap import SNAP_GPU_NOTE, running_in_snap
|
||||
|
||||
SNAP_ENV = {"SNAP": "/snap/vocalinux/42", "SNAP_NAME": "vocalinux"}
|
||||
SNAP_ENV = {"SNAP": "/snap/voculinux/42", "SNAP_NAME": "voculinux"}
|
||||
|
||||
PACTL_JSON = [
|
||||
{
|
||||
@@ -108,7 +108,7 @@ def _runner(
|
||||
|
||||
|
||||
def _have_pactl(name: str) -> Optional[str]:
|
||||
return "/snap/vocalinux/42/usr/bin/pactl" if name == "pactl" else None
|
||||
return "/snap/voculinux/42/usr/bin/pactl" if name == "pactl" else None
|
||||
|
||||
|
||||
class TestPactlParsing:
|
||||
@@ -503,7 +503,7 @@ class TestSnapGpuReporting:
|
||||
|
||||
def test_running_in_snap(self) -> None:
|
||||
assert running_in_snap(SNAP_ENV)
|
||||
assert running_in_snap({"SNAP": "/snap/vocalinux/42"})
|
||||
assert running_in_snap({"SNAP": "/snap/voculinux/42"})
|
||||
assert not running_in_snap({})
|
||||
|
||||
def test_snap_reports_cpu_without_probing_host_gpu_tools(
|
||||
|
||||
@@ -14,7 +14,7 @@ REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
PYPROJECT = REPO_ROOT / "pyproject.toml"
|
||||
INSTALL_SH = REPO_ROOT / "install.sh"
|
||||
PIPELINE = REPO_ROOT / ".github" / "workflows" / "unified-pipeline.yml"
|
||||
PKGBUILD = REPO_ROOT / "packaging" / "aur" / "vocalinux" / "PKGBUILD"
|
||||
PKGBUILD = REPO_ROOT / "packaging" / "aur" / "voculinux" / "PKGBUILD"
|
||||
|
||||
# Everything user-facing that could state the floor. Scanned as trees rather than
|
||||
# a fixed list: docs and the website drifted to "3.9+" for a full release cycle
|
||||
|
||||
@@ -1022,7 +1022,7 @@ class TestInitializationEdgeCases(unittest.TestCase):
|
||||
with patch("os.path.exists") as mock_exists:
|
||||
|
||||
def exists_check(path):
|
||||
if ".vocalinux_preinstalled" in path:
|
||||
if ".voculinux_preinstalled" in path:
|
||||
return True
|
||||
return True
|
||||
|
||||
|
||||
@@ -817,7 +817,7 @@ class TestDownmixToMono(unittest.TestCase):
|
||||
def setUpClass(cls):
|
||||
# Other test modules may have left sys.modules["numpy"] as MagicMock.
|
||||
if isinstance(sys.modules.get("numpy"), MagicMock):
|
||||
real = getattr(sys, "_vocalinux_real_numpy", None)
|
||||
real = getattr(sys, "_voculinux_real_numpy", None)
|
||||
if real is not None:
|
||||
sys.modules["numpy"] = real
|
||||
else:
|
||||
@@ -1092,10 +1092,10 @@ class TestVoskInitialization(unittest.TestCase):
|
||||
):
|
||||
with patch(
|
||||
"vocalinux.speech_recognition.recognition_manager.SYSTEM_MODELS_DIRS",
|
||||
["/usr/share/vocalinux"],
|
||||
["/usr/share/voculinux"],
|
||||
):
|
||||
with patch.object(
|
||||
manager, "_get_vosk_model_path", return_value="/usr/share/vocalinux/model"
|
||||
manager, "_get_vosk_model_path", return_value="/usr/share/voculinux/model"
|
||||
):
|
||||
with patch("os.path.exists", return_value=True):
|
||||
with patch.dict("sys.modules", {"vosk": mock_vosk}):
|
||||
|
||||
@@ -18,7 +18,7 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
# Mock GI imports before importing any vocalinux modules that use gi.
|
||||
# Mock GI imports before importing any voculinux modules that use gi.
|
||||
# On CI, real gi/IBus packages are installed; without mocks, importing
|
||||
# ibus_engine would connect to a real IBus daemon and hang.
|
||||
if "gi" not in sys.modules:
|
||||
@@ -463,7 +463,7 @@ class TestDownloadWhispercppModel:
|
||||
class TestDownloadWhisperModel:
|
||||
"""OpenAI Whisper downloads run through the shared streaming helper.
|
||||
|
||||
Vocalinux fetches the checkpoint only to give the UI progress and a working
|
||||
Voculinux fetches the checkpoint only to give the UI progress and a working
|
||||
cancel; whisper.load_model() is what verifies it. Both were briefly lost when
|
||||
the downloader was removed altogether, so they are pinned here.
|
||||
"""
|
||||
@@ -534,7 +534,7 @@ class TestDownloadVoskModel:
|
||||
|
||||
# Extraction has to really happen here — the stamp lands in the tree it
|
||||
# creates — and other modules leave a MagicMock in sys.modules["zipfile"].
|
||||
real_zipfile = getattr(sys, "_vocalinux_real_zipfile", None) or __import__("zipfile")
|
||||
real_zipfile = getattr(sys, "_voculinux_real_zipfile", None) or __import__("zipfile")
|
||||
|
||||
with patch.dict("sys.modules", {"requests": mock_requests, "zipfile": real_zipfile}):
|
||||
with patch(
|
||||
|
||||
@@ -167,8 +167,8 @@ class TestGetSystemModelPaths(unittest.TestCase):
|
||||
mock_open.return_value.read.return_value = mock_os_release
|
||||
paths = _get_system_model_paths()
|
||||
self.assertIsInstance(paths, list)
|
||||
self.assertNotIn("/usr/local/share/vocalinux/models", paths)
|
||||
self.assertIn("/usr/share/vocalinux/models", paths)
|
||||
self.assertNotIn("/usr/local/share/voculinux/models", paths)
|
||||
self.assertIn("/usr/share/voculinux/models", paths)
|
||||
|
||||
def test_arch_paths_custom_xdg_dirs(self):
|
||||
# Regression test: custom XDG_DATA_DIRS + Arch os-release used to
|
||||
|
||||
@@ -22,7 +22,7 @@ from unittest.mock import MagicMock, patch # noqa: E402
|
||||
# load and don't restore it. Reuse the real module cached by conftest instead
|
||||
# of unloading/re-importing NumPy's compiled extensions.
|
||||
if isinstance(sys.modules.get("numpy"), MagicMock):
|
||||
_real_numpy = getattr(sys, "_vocalinux_real_numpy", None)
|
||||
_real_numpy = getattr(sys, "_voculinux_real_numpy", None)
|
||||
if _real_numpy is not None:
|
||||
sys.modules["numpy"] = _real_numpy
|
||||
|
||||
|
||||
@@ -249,7 +249,7 @@ def test_flatpak_release_jobs_reuse_ci_builder_pins():
|
||||
"action's restore-keys prefix matches after it appends -${arch}"
|
||||
)
|
||||
assert f"runs-on: {runner}" in block
|
||||
assert f"Vocalinux-${{{{ steps.get_version.outputs.VERSION }}}}-{arch}.flatpak" in block
|
||||
assert f"Voculinux-${{{{ steps.get_version.outputs.VERSION }}}}-{arch}.flatpak" in block
|
||||
assert "actions/upload-artifact" in block, f"{name} does not upload a workflow artifact"
|
||||
assert "gh release upload" not in block, f"{name} cannot run gh in the builder image"
|
||||
assert "python -m build" not in _without_comments(block)
|
||||
@@ -273,7 +273,7 @@ def test_the_release_notes_document_flatpak_bundles():
|
||||
assert "org.gnome.Platform//50" in body
|
||||
assert "no auto-update" in body
|
||||
assert "not on Flathub" in body
|
||||
assert "Vocalinux-__VERSION__-x86_64.flatpak" in body
|
||||
assert "Voculinux-__VERSION__-x86_64.flatpak" in body
|
||||
|
||||
|
||||
def test_the_remote_install_guidance_only_runs_when_the_remote_publishes():
|
||||
|
||||
@@ -657,7 +657,7 @@ def _wayland_injector(
|
||||
}
|
||||
env.update(extra_env or {})
|
||||
if forced:
|
||||
env["VOCALINUX_FORCE_BACKEND"] = forced
|
||||
env["VOCULINUX_FORCE_BACKEND"] = forced
|
||||
for key, value in env.items():
|
||||
if value is None:
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
@@ -767,7 +767,7 @@ class TestPortalBackendSelection:
|
||||
portal=cls,
|
||||
extra_env={
|
||||
"WAYLAND_DISPLAY": None,
|
||||
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
||||
"FLATPAK_ID": "com.voculinux.Voculinux",
|
||||
"DISPLAY": ":99",
|
||||
},
|
||||
)
|
||||
@@ -872,7 +872,7 @@ class TestPortalInjectionPaths:
|
||||
with (
|
||||
patch.dict(
|
||||
os.environ,
|
||||
{"FLATPAK_ID": "com.vocalinux.Vocalinux"},
|
||||
{"FLATPAK_ID": "com.voculinux.Voculinux"},
|
||||
clear=True,
|
||||
),
|
||||
patch.object(injector, "_wait_for_modifiers_released"),
|
||||
@@ -899,7 +899,7 @@ class TestPortalInjectionPaths:
|
||||
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"FLATPAK_ID": "com.vocalinux.Vocalinux"},
|
||||
{"FLATPAK_ID": "com.voculinux.Voculinux"},
|
||||
clear=True,
|
||||
):
|
||||
assert injector._demote_portal_backend() is False
|
||||
@@ -921,7 +921,7 @@ class TestPortalInjectionPaths:
|
||||
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"FLATPAK_ID": "com.vocalinux.Vocalinux"},
|
||||
{"FLATPAK_ID": "com.voculinux.Voculinux"},
|
||||
clear=True,
|
||||
):
|
||||
assert injector._demote_portal_backend() is True
|
||||
@@ -947,7 +947,7 @@ class TestPortalInjectionPaths:
|
||||
with (
|
||||
patch.dict(
|
||||
os.environ,
|
||||
{"FLATPAK_ID": "com.vocalinux.Vocalinux"},
|
||||
{"FLATPAK_ID": "com.voculinux.Voculinux"},
|
||||
clear=True,
|
||||
),
|
||||
patch.object(injector, "_wait_for_modifiers_released"),
|
||||
@@ -970,7 +970,7 @@ class TestPortalInjectionPaths:
|
||||
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"FLATPAK_ID": "com.vocalinux.Vocalinux"},
|
||||
{"FLATPAK_ID": "com.voculinux.Voculinux"},
|
||||
clear=True,
|
||||
):
|
||||
assert injector.inject_text("hello world") is False
|
||||
@@ -989,7 +989,7 @@ class TestPortalInjectionPaths:
|
||||
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"FLATPAK_ID": "com.vocalinux.Vocalinux"},
|
||||
{"FLATPAK_ID": "com.voculinux.Voculinux"},
|
||||
clear=True,
|
||||
):
|
||||
assert injector.inject_text("hello world") is False
|
||||
@@ -1008,7 +1008,7 @@ class TestPortalInjectionPaths:
|
||||
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"FLATPAK_ID": "com.vocalinux.Vocalinux"},
|
||||
{"FLATPAK_ID": "com.voculinux.Voculinux"},
|
||||
clear=True,
|
||||
):
|
||||
assert injector.inject_text("hello world") is True
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
"""Ticket #18: offline naming-sweep — no old-name strings on user-visible surfaces.
|
||||
|
||||
The fork takes its own name everywhere a user can see it. Anything still
|
||||
spelling the old name must be on the allowlist below:
|
||||
|
||||
- upstream provenance (``VocaHQ/vocalinux`` URLs),
|
||||
- the retained internal Python module name (``src/vocalinux`` paths,
|
||||
``vocalinux.*`` imports/attributes, ``-m vocalinux.main``),
|
||||
- retained internal class names (``VocalinuxEngine*``, ``VocalinuxDBusService``),
|
||||
- one-time-legacy import sources (old config/dictionary/bus literals),
|
||||
- generated or historical files (``uv.lock``, ``requirements/*.txt``,
|
||||
``CHANGELOG.md``),
|
||||
- the rest of ``web/`` (owned by ticket #24; only ``page.tsx`` and
|
||||
``package.json`` belong to this ticket).
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
OLD_NAME = re.compile(r"vocalinux", re.IGNORECASE)
|
||||
|
||||
SKIP_DIRS = {".git", "__pycache__", ".venv", "venv", "node_modules", ".next", "build", "dist"}
|
||||
SKIP_SUFFIXES = (".egg-info",)
|
||||
SKIP_FILES = {"uv.lock", "CHANGELOG.md"}
|
||||
SKIP_PREFIXES = ("requirements/",)
|
||||
BINARY_EXTS = (
|
||||
".wav",
|
||||
".mp3",
|
||||
".ogg",
|
||||
".png",
|
||||
".svg",
|
||||
".ico",
|
||||
".icns",
|
||||
".bin",
|
||||
".onnx",
|
||||
".pt",
|
||||
".zip",
|
||||
".tar",
|
||||
".gz",
|
||||
".whl",
|
||||
".AppImage",
|
||||
".zsync",
|
||||
".pyc",
|
||||
)
|
||||
|
||||
#: web/ belongs to ticket #24 except the two rename-inventory files.
|
||||
WEB_OWNED = {"web/src/app/page.tsx", "web/package.json"}
|
||||
|
||||
|
||||
def _allowed(path: str, line: str) -> bool:
|
||||
"""Return True when an old-name match is an allowlisted surface."""
|
||||
low = line.lower()
|
||||
if "vocahq/" in low or "jatinkrmalik/vocalinux" in low:
|
||||
return True
|
||||
if "vocalinux" in low and "voculinux" in low:
|
||||
# Migration-compat shims that name both installs at once.
|
||||
return True
|
||||
if "/{name}/vocalinux" in line:
|
||||
# Upstream repo-name template (retained provenance).
|
||||
return True
|
||||
if path == "snap/local/check_runtime.py" and '"vocalinux"' in line:
|
||||
# Smoke imports resolve the retained import package.
|
||||
return True
|
||||
if re.search(r'vocalinux\.["a-z_]', line):
|
||||
return True
|
||||
if line.strip().rstrip(",").lstrip("/ ") == '"vocalinux"':
|
||||
# Retained module-dir path segment element in tests and tooling
|
||||
# (single-line and os.path.join element forms).
|
||||
return True
|
||||
if 'site / "vocalinux"' in line or '"platform/vocalinux"' in line:
|
||||
# Fixture module trees named after the retained import package.
|
||||
return True
|
||||
if '"vocalinux/' in line:
|
||||
# Fixture module file paths under the retained package name.
|
||||
return True
|
||||
if "/vocalinux/" in line or "src/vocalinux" in line:
|
||||
return True
|
||||
if "-m vocalinux" in line or "import vocalinux" in line:
|
||||
return True
|
||||
if "VocalinuxEngine" in line or "VocalinuxDBusService" in line:
|
||||
return True
|
||||
if "cmdline" in line:
|
||||
return True
|
||||
if " - vocalinux" in line:
|
||||
# Log logger names keep the retained module path.
|
||||
return True
|
||||
if path in (
|
||||
"src/vocalinux/legacy_import.py",
|
||||
"tests/test_legacy_import.py",
|
||||
):
|
||||
return True
|
||||
if "LEGACY" in line or "legacy" in line:
|
||||
return True
|
||||
if "~/.config/vocalinux" in line or '".config" / "vocalinux"' in line:
|
||||
# Documented one-time-import source (ticket #18).
|
||||
return True
|
||||
if "from vocalinux" in line:
|
||||
return True
|
||||
if '"src"' in line and '"vocalinux"' in line:
|
||||
# Retained module-dir path segments in tests and tooling.
|
||||
return True
|
||||
if path == "tests/test_rename_sweep.py":
|
||||
return True
|
||||
if path == "pyproject.toml" and re.search(r'"vocalinux[."\'\]]', line):
|
||||
return True
|
||||
if path == "pyproject.toml" and "vocalinux.version" in line:
|
||||
return True
|
||||
if 'PKG_NAME="vocalinux"' in line:
|
||||
# Installer probes the retained import package, not the distribution.
|
||||
return True
|
||||
if '== "vocalinux"' in line or '.startswith("vocalinux' in line:
|
||||
# Retained module identity comparisons in test isolation helpers.
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _iter_files():
|
||||
for root, dirs, files in os.walk(REPO_ROOT):
|
||||
dirs[:] = [d for d in sorted(dirs) if d not in SKIP_DIRS]
|
||||
for name in sorted(files):
|
||||
if name in SKIP_FILES or name.endswith(BINARY_EXTS):
|
||||
continue
|
||||
full = os.path.join(root, name)
|
||||
rel = os.path.relpath(full, REPO_ROOT)
|
||||
if rel.startswith(SKIP_PREFIXES):
|
||||
continue
|
||||
if rel.startswith("web/") and rel not in WEB_OWNED:
|
||||
continue
|
||||
yield rel, full
|
||||
|
||||
|
||||
def test_naming_sweep_no_old_name_outside_allowlist() -> None:
|
||||
"""Every old-name hit must be an allowlisted retained surface."""
|
||||
offenders: list[str] = []
|
||||
for rel, full in _iter_files():
|
||||
if any(part.endswith(SKIP_SUFFIXES) for part in rel.split("/")):
|
||||
continue
|
||||
try:
|
||||
with open(full, encoding="utf-8") as handle:
|
||||
text = handle.read()
|
||||
except (UnicodeDecodeError, ValueError):
|
||||
continue
|
||||
for number, line in enumerate(text.splitlines(), 1):
|
||||
if OLD_NAME.search(line) and not _allowed(rel, line):
|
||||
offenders.append(f"{rel}:{number}:{line.strip()[:160]}")
|
||||
assert (
|
||||
offenders == []
|
||||
), f"{len(offenders)} old-name string(s) outside the allowlist:\n" + "\n".join(offenders[:60])
|
||||
|
||||
|
||||
def test_owned_web_files_carry_new_name() -> None:
|
||||
"""The two web rename surfaces name the fork (URLs aside)."""
|
||||
for rel in sorted(WEB_OWNED):
|
||||
with open(os.path.join(REPO_ROOT, rel), encoding="utf-8") as handle:
|
||||
text = handle.read()
|
||||
assert "Voculinux" in text or "voculinux" in text, rel
|
||||
@@ -11,13 +11,13 @@ from types import ModuleType
|
||||
import pytest
|
||||
|
||||
# Other legacy tests replace zipfile in sys.modules during collection.
|
||||
ZIPFILE = getattr(sys, "_vocalinux_real_zipfile", __import__("zipfile"))
|
||||
ZIPFILE = getattr(sys, "_voculinux_real_zipfile", __import__("zipfile"))
|
||||
SCRIPT = Path(__file__).resolve().parents[1] / "scripts/build_reproducible_wheel.py"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gate(monkeypatch: pytest.MonkeyPatch) -> ModuleType:
|
||||
"""Load the standalone build tool without importing Vocalinux/GTK."""
|
||||
"""Load the standalone build tool without importing Voculinux/GTK."""
|
||||
spec = importlib.util.spec_from_file_location("reproducible_wheel", SCRIPT)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
@@ -29,7 +29,7 @@ def gate(monkeypatch: pytest.MonkeyPatch) -> ModuleType:
|
||||
def wheel(path: Path, content: str = "payload", year: int = 2020) -> Path:
|
||||
"""Make a tiny real archive, including independently controllable ZIP metadata."""
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
info = ZIPFILE.ZipInfo("vocalinux/__init__.py", date_time=(year, 1, 1, 0, 0, 0))
|
||||
info = ZIPFILE.ZipInfo("voculinux/__init__.py", date_time=(year, 1, 1, 0, 0, 0))
|
||||
with ZIPFILE.ZipFile(path, "w") as archive:
|
||||
archive.writestr(info, content)
|
||||
return path
|
||||
@@ -59,7 +59,7 @@ def test_mismatches_fail_without_normalizing_archives(
|
||||
gate.compare_wheels(first, second, report)
|
||||
assert "FAIL" in report.read_text()
|
||||
if difference == "payload":
|
||||
assert "member content differs: vocalinux/__init__.py" in report.read_text()
|
||||
assert "member content differs: voculinux/__init__.py" in report.read_text()
|
||||
|
||||
|
||||
def test_snapshot_uses_working_source_without_ignored_build_state(
|
||||
|
||||
@@ -162,10 +162,10 @@ class TestResourceManagerCandidateSelection:
|
||||
(full_candidate / "sounds").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for icon in [
|
||||
"vocalinux",
|
||||
"vocalinux-microphone",
|
||||
"vocalinux-microphone-off",
|
||||
"vocalinux-microphone-process",
|
||||
"voculinux",
|
||||
"voculinux-microphone",
|
||||
"voculinux-microphone-off",
|
||||
"voculinux-microphone-process",
|
||||
]:
|
||||
(full_candidate / "icons" / "scalable" / f"{icon}.svg").write_text("x")
|
||||
|
||||
@@ -183,7 +183,7 @@ class TestResourceManagerCandidateSelection:
|
||||
ResourceManager._resources_dir = None
|
||||
manager = ResourceManager()
|
||||
|
||||
missing = Path("/tmp/this-path-should-not-exist-vocalinux-resource-manager")
|
||||
missing = Path("/tmp/this-path-should-not-exist-voculinux-resource-manager")
|
||||
candidate_score = getattr(manager, "_candidate_score")
|
||||
score = candidate_score(missing)
|
||||
assert score == -1
|
||||
|
||||
@@ -1265,7 +1265,7 @@ class TestSettingsNavigation(unittest.TestCase):
|
||||
"""Finalization keeps the Cancel button usable while recognition stops."""
|
||||
finalize_body = self.source_code.split("def _finalize_test")[1].split("\n def ")[0]
|
||||
self.assertIn("def stop_test", finalize_body)
|
||||
self.assertIn('name="vocalinux-test-dictation-stop"', finalize_body)
|
||||
self.assertIn('name="voculinux-test-dictation-stop"', finalize_body)
|
||||
self.assertIn(
|
||||
"GLib.timeout_add(100, self._wait_for_idle_then_restore_callbacks)",
|
||||
finalize_body,
|
||||
@@ -1342,7 +1342,7 @@ class TestAboutPage(unittest.TestCase):
|
||||
cls.about = cls.about_source.split("def _build_about_section")[1].split("\n def ")[0]
|
||||
|
||||
def test_three_groups_keep_updater(self) -> None:
|
||||
self.assertIn('title="Vocalinux"', self.about)
|
||||
self.assertIn('title="Voculinux"', self.about)
|
||||
self.assertIn('title="Part of VocaHQ"', self.about)
|
||||
self.assertIn('title="Talk to us"', self.about)
|
||||
self.assertIn('title="Updates"', self.about)
|
||||
@@ -1369,7 +1369,7 @@ class TestAboutPage(unittest.TestCase):
|
||||
|
||||
def test_family_and_talk_urls(self):
|
||||
for url in (
|
||||
"https://vocalinux.com",
|
||||
"https://voculinux.com",
|
||||
"https://vocahq.com",
|
||||
"https://vocamac.com",
|
||||
"https://vocawin.com",
|
||||
@@ -1395,29 +1395,29 @@ class TestAboutPage(unittest.TestCase):
|
||||
self.assertIn("_set_accessible_name(button, tooltip)", helper)
|
||||
self.assertIn('if label == "Open"', helper)
|
||||
for name in (
|
||||
"Open vocalinux.com website",
|
||||
"Open voculinux.com website",
|
||||
"Open vocahq.com",
|
||||
"Open vocalinux.com",
|
||||
"Open voculinux.com",
|
||||
"Open vocamac.com",
|
||||
"Open vocawin.com",
|
||||
"Open vocaphone.vocahq.com",
|
||||
"Open vocagateway.vocahq.com",
|
||||
"Open the Vocalinux GitHub repository",
|
||||
"Open the Voculinux GitHub repository",
|
||||
"Open the latest release page",
|
||||
):
|
||||
self.assertIn(name, self.source)
|
||||
self.assertIn("_set_accessible_name", self.about)
|
||||
|
||||
def test_website_is_product_site_not_only_github(self):
|
||||
self.assertIn("VOCALINUX_SITE_URL", self.about)
|
||||
self.assertIn("Product site at vocalinux.com", self.about)
|
||||
self.assertNotIn("Open the Vocalinux GitHub page", self.about)
|
||||
self.assertIn("VOCULINUX_SITE_URL", self.about)
|
||||
self.assertIn("Product site at voculinux.com", self.about)
|
||||
self.assertNotIn("Open the Voculinux GitHub page", self.about)
|
||||
|
||||
def test_source_code_row_sits_beside_website(self):
|
||||
self.assertIn('label="Website"', self.about)
|
||||
self.assertIn('label="Source code"', self.about)
|
||||
self.assertIn("GITHUB_REPO_URL", self.about)
|
||||
self.assertIn("Open the Vocalinux GitHub repository", self.about)
|
||||
self.assertIn("Open the Voculinux GitHub repository", self.about)
|
||||
self.assertLess(self.about.find('label="Website"'), self.about.find('label="Source code"'))
|
||||
|
||||
def test_talk_to_us_sits_above_updates(self):
|
||||
@@ -1457,15 +1457,15 @@ class TestAboutPage(unittest.TestCase):
|
||||
VOCAHQ_MAILTO_URL,
|
||||
VOCAHQ_SITE_URL,
|
||||
VOCAHQ_X_URL,
|
||||
VOCALINUX_SITE_URL,
|
||||
VOCAMAC_SITE_URL,
|
||||
VOCAPHONE_SITE_URL,
|
||||
VOCAWIN_SITE_URL,
|
||||
VOCULINUX_SITE_URL,
|
||||
_can_open_url,
|
||||
)
|
||||
|
||||
for url in (
|
||||
VOCALINUX_SITE_URL,
|
||||
VOCULINUX_SITE_URL,
|
||||
VOCAHQ_SITE_URL,
|
||||
VOCAMAC_SITE_URL,
|
||||
VOCAWIN_SITE_URL,
|
||||
@@ -1542,22 +1542,22 @@ class TestAboutPage(unittest.TestCase):
|
||||
/ "resources"
|
||||
/ "icons"
|
||||
/ "scalable"
|
||||
/ "vocalinux.svg"
|
||||
/ "voculinux.svg"
|
||||
)
|
||||
shipped = (
|
||||
Path(__file__).resolve().parents[1]
|
||||
/ "resources"
|
||||
/ "icons"
|
||||
/ "scalable"
|
||||
/ "vocalinux.svg"
|
||||
/ "voculinux.svg"
|
||||
)
|
||||
packaged_svg = packaged.read_text(encoding="utf-8")
|
||||
shipped_svg = shipped.read_text(encoding="utf-8")
|
||||
self.assertEqual(packaged_svg, shipped_svg)
|
||||
self.assertIn('<circle cx="229" cy="231.5" r="221.5" fill="#0F6B57"/>', packaged_svg)
|
||||
self.assertIn('aria-label="Vocalinux"', packaged_svg)
|
||||
self.assertIn('aria-label="Voculinux"', packaged_svg)
|
||||
self.assertNotIn("<rect", packaged_svg)
|
||||
self.assertIn('get_icon_path("vocalinux")', self.about)
|
||||
self.assertIn('get_icon_path("voculinux")', self.about)
|
||||
|
||||
def test_platform_marks_are_currentcolor_svgs(self):
|
||||
from pathlib import Path
|
||||
|
||||
@@ -215,7 +215,7 @@ def test_test_dictation_cancel_runs_off_thread_and_restores_callbacks() -> None:
|
||||
dialog.test_button.set_label.assert_called_with("Cancelling…")
|
||||
thread_cls.assert_called_once()
|
||||
assert thread_cls.call_args.kwargs["daemon"] is True
|
||||
assert thread_cls.call_args.kwargs["name"] == "vocalinux-test-dictation-cancel"
|
||||
assert thread_cls.call_args.kwargs["name"] == "voculinux-test-dictation-cancel"
|
||||
|
||||
with patch.object(settings_dialog, "GLib") as glib:
|
||||
thread_cls.call_args.kwargs["target"]()
|
||||
@@ -443,7 +443,7 @@ def test_finalize_test_buffered_reload_waits_for_idle_before_restore() -> None:
|
||||
dialog.speech_engine.stop_recognition.assert_not_called()
|
||||
thread_cls.assert_called_once()
|
||||
assert thread_cls.call_args.kwargs["daemon"] is True
|
||||
assert thread_cls.call_args.kwargs["name"] == "vocalinux-test-dictation-stop"
|
||||
assert thread_cls.call_args.kwargs["name"] == "voculinux-test-dictation-stop"
|
||||
thread_cls.call_args.kwargs["target"]()
|
||||
dialog.speech_engine.stop_recognition.assert_called_once()
|
||||
# Must not restore live injector yet — worker still holds the utterance.
|
||||
@@ -518,7 +518,7 @@ def test_cancel_during_processing_restores_once_without_result_check() -> None:
|
||||
):
|
||||
SettingsDialog._finalize_test(dialog)
|
||||
|
||||
assert thread_cls.call_args.kwargs["name"] == "vocalinux-test-dictation-stop"
|
||||
assert thread_cls.call_args.kwargs["name"] == "voculinux-test-dictation-stop"
|
||||
dialog._test_cancelled = True
|
||||
assert SettingsDialog._finish_test_after_user_cancel(dialog) is False
|
||||
assert dialog.test_buffer._text == "(Test cancelled)"
|
||||
|
||||
@@ -15,7 +15,7 @@ from unittest.mock import MagicMock, patch # noqa: E402
|
||||
# load and don't restore it. Reuse the real module cached by conftest instead
|
||||
# of unloading/re-importing NumPy's compiled extensions.
|
||||
if isinstance(sys.modules.get("numpy"), MagicMock):
|
||||
_real_numpy = getattr(sys, "_vocalinux_real_numpy", None)
|
||||
_real_numpy = getattr(sys, "_voculinux_real_numpy", None)
|
||||
if _real_numpy is not None:
|
||||
sys.modules["numpy"] = _real_numpy
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ class TestLockFileConstants(unittest.TestCase):
|
||||
"""Tests for lock file constants."""
|
||||
|
||||
def test_lock_file_path_is_in_data_home(self):
|
||||
"""Test that lock file lives under $XDG_DATA_HOME/vocalinux/."""
|
||||
"""Test that lock file lives under $XDG_DATA_HOME/voculinux/."""
|
||||
expected_dir = Path(data_dir())
|
||||
self.assertEqual(single_instance_module.LOCK_FILE_DIR, expected_dir)
|
||||
self.assertEqual(single_instance_module.LOCK_FILE_PATH, expected_dir / "instance.lock")
|
||||
|
||||
+19
-19
@@ -116,10 +116,10 @@ def test_record_audits_payload_and_preserves_export_digests(tmp_path: Path) -> N
|
||||
(source / "src/vocalinux/version.py").write_text('__version__ = "0.18.5"\n')
|
||||
root = tmp_path / "install"
|
||||
distribution(root, "demo", "1.0")
|
||||
distribution(root, "vocalinux", "0.18.5")
|
||||
distribution(root, "voculinux", "0.18.5")
|
||||
CHECK.record(root, source)
|
||||
manifest = json.loads((root / CHECK.MANIFEST).read_text())
|
||||
assert manifest["packages"] == {"demo": "1.0", "vocalinux": "0.18.5"}
|
||||
assert manifest["packages"] == {"demo": "1.0", "voculinux": "0.18.5"}
|
||||
assert manifest["python"] == list(sys.version_info[:2])
|
||||
for name, digest in manifest["exports"].items():
|
||||
assert digest == hashlib.sha256((source / "requirements" / name).read_bytes()).hexdigest()
|
||||
@@ -154,12 +154,12 @@ def test_launchers_are_relocatable_and_keep_locked_packages_first(
|
||||
header = (
|
||||
f"#!/bin/sh\n'''exec' {python} \"$0\" \"$@\"\n' '''\n" if long_shebang else f"#!{python}\n"
|
||||
)
|
||||
for name in ("vocalinux", "vocalinux-gui", "pwcpp"):
|
||||
for name in ("voculinux", "voculinux-gui", "pwcpp"):
|
||||
path = binary / name
|
||||
path.write_text(header + "print('entry point')\n")
|
||||
path.chmod(0o755)
|
||||
(binary / "python3").symlink_to(sys.executable)
|
||||
site = distribution(install, "vocalinux", "0.18.5")
|
||||
site = distribution(install, "voculinux", "0.18.5")
|
||||
package = site / "vocalinux"
|
||||
package.mkdir()
|
||||
(package / "__init__.py").write_text("")
|
||||
@@ -169,13 +169,13 @@ def test_launchers_are_relocatable_and_keep_locked_packages_first(
|
||||
platform.mkdir(parents=True)
|
||||
(platform / "__init__.py").write_text("raise RuntimeError('wrong runtime')\n")
|
||||
BUILD.relocate_scripts(install, tooling)
|
||||
for name in ("vocalinux", "vocalinux-gui", "pwcpp"):
|
||||
for name in ("voculinux", "voculinux-gui", "pwcpp"):
|
||||
text = (binary / name).read_text()
|
||||
assert text.startswith("#!/usr/bin/env python3\n")
|
||||
assert str(tooling) not in text
|
||||
assert os.access(binary / name, os.X_OK)
|
||||
result = subprocess.run(
|
||||
[str(binary / "vocalinux")],
|
||||
[str(binary / "voculinux")],
|
||||
env={
|
||||
**os.environ,
|
||||
"PATH": f"{binary}{os.pathsep}{os.environ['PATH']}",
|
||||
@@ -211,7 +211,7 @@ def test_build_orchestration_uses_hashes_and_separates_tooling(
|
||||
def run(argv: list[str], cwd: Path, env: dict[str, str]) -> None:
|
||||
calls.append((argv, cwd, env.copy()))
|
||||
if str(source) == argv[-1]:
|
||||
for name in ("vocalinux", "vocalinux-gui"):
|
||||
for name in ("voculinux", "voculinux-gui"):
|
||||
(install / "bin" / name).write_text(f"#!{argv[0]}\n")
|
||||
|
||||
monkeypatch.setattr(BUILD.venv.EnvBuilder, "create", create)
|
||||
@@ -309,7 +309,7 @@ def test_runtime_pip_flags_reject_corrupt_artifact_offline(tmp_path: Path) -> No
|
||||
environment = tmp_path / "tooling"
|
||||
venv.EnvBuilder(with_pip=True).create(environment)
|
||||
wheel = tmp_path / "demo-1.0-py3-none-any.whl"
|
||||
real_zipfile = getattr(sys, "_vocalinux_real_zipfile", zipfile)
|
||||
real_zipfile = getattr(sys, "_voculinux_real_zipfile", zipfile)
|
||||
with real_zipfile.ZipFile(wheel, "w") as archive:
|
||||
archive.writestr("demo.py", "VALUE = 1\n")
|
||||
archive.writestr(
|
||||
@@ -369,7 +369,7 @@ def test_full_python_payload_build_is_offline_and_keeps_project_metadata(tmp_pat
|
||||
(source / "src/vocalinux/version.py").write_text('__version__ = "0.18.5"\n')
|
||||
wheelhouse = tmp_path / "wheels"
|
||||
wheelhouse.mkdir()
|
||||
real_zipfile = getattr(sys, "_vocalinux_real_zipfile", zipfile)
|
||||
real_zipfile = getattr(sys, "_voculinux_real_zipfile", zipfile)
|
||||
|
||||
def wheel(name: str, version: str, files: dict[str, str]) -> Path:
|
||||
path = wheelhouse / f"{name}-{version}-py3-none-any.whl"
|
||||
@@ -399,13 +399,13 @@ def test_full_python_payload_build_is_offline_and_keeps_project_metadata(tmp_pat
|
||||
)
|
||||
runtime = wheel("demo", "1.0", {"demo.py": "VALUE = 1\n"})
|
||||
app = wheel(
|
||||
"vocalinux",
|
||||
"voculinux",
|
||||
"0.18.5",
|
||||
{
|
||||
"vocalinux/__init__.py": "",
|
||||
"vocalinux/main.py": "def main():\n print('fixture app')\n",
|
||||
"vocalinux-0.18.5.dist-info/entry_points.txt": "[console_scripts]\nvocalinux = vocalinux.main:main\n"
|
||||
"[gui_scripts]\nvocalinux-gui = vocalinux.main:main\n",
|
||||
"voculinux-0.18.5.dist-info/entry_points.txt": "[console_scripts]\nvoculinux = vocalinux.main:main\n"
|
||||
"[gui_scripts]\nvoculinux-gui = vocalinux.main:main\n",
|
||||
},
|
||||
)
|
||||
for filename, name, version, artifact in (
|
||||
@@ -421,7 +421,7 @@ def test_full_python_payload_build_is_offline_and_keeps_project_metadata(tmp_pat
|
||||
metadata = source / "pyproject.toml"
|
||||
metadata.write_text(
|
||||
'[build-system]\nrequires = []\nbuild-backend = "backend"\nbackend-path = ["."]\n'
|
||||
'[project]\nname = "vocalinux"\nversion = "0.18.5"\ndependencies = ["PyGObject"]\n'
|
||||
'[project]\nname = "voculinux"\nversion = "0.18.5"\ndependencies = ["PyGObject"]\n'
|
||||
)
|
||||
(source / "backend.py").write_text(
|
||||
"import shutil\nfrom pathlib import Path\n"
|
||||
@@ -434,11 +434,11 @@ def test_full_python_payload_build_is_offline_and_keeps_project_metadata(tmp_pat
|
||||
install = tmp_path / "install"
|
||||
BUILD.build(source, install, tmp_path / "work")
|
||||
assert metadata.read_bytes() == before
|
||||
CHECK.audit(install, {"demo": "1.0", "vocalinux": "0.18.5"})
|
||||
CHECK.audit(install, {"demo": "1.0", "voculinux": "0.18.5"})
|
||||
assert not list((tmp_path / "work").iterdir())
|
||||
assert not (install / "bin/pip").exists()
|
||||
result = subprocess.run(
|
||||
[str(install / "bin/python3"), str(install / "bin/vocalinux")],
|
||||
[str(install / "bin/python3"), str(install / "bin/voculinux")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
@@ -464,7 +464,7 @@ def test_artifact_smoke_refuses_user_install_and_checks_the_supplied_file(
|
||||
"with open(os.environ['SNAP_TEST_LOG'], 'a') as stream:\n"
|
||||
" stream.write(json.dumps([Path(sys.argv[0]).name, sys.argv[1:], "
|
||||
"os.environ.get('PYNPUT_BACKEND')]) + '\\n')\n"
|
||||
"if sys.argv[1:3] == ['list', 'vocalinux']:\n"
|
||||
"if sys.argv[1:3] == ['list', 'voculinux']:\n"
|
||||
" sys.exit(0 if os.environ['SNAP_TEST_EXISTING'] == '1' else 1)\n"
|
||||
)
|
||||
path.chmod(0o755)
|
||||
@@ -490,8 +490,8 @@ def test_artifact_smoke_refuses_user_install_and_checks_the_supplied_file(
|
||||
else:
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert calls[1][:2] == ["sudo", ["snap", "install", "--dangerous", str(artifact)]]
|
||||
assert calls[2][:2] == ["snap", ["run", "vocalinux", "--version"]]
|
||||
assert calls[3][1][:4] == ["run", "--shell", "vocalinux", "-c"]
|
||||
assert calls[2][:2] == ["snap", ["run", "voculinux", "--version"]]
|
||||
assert calls[3][1][:4] == ["run", "--shell", "voculinux", "-c"]
|
||||
assert '"$SNAP/snap/command-chain/desktop-launch"' in calls[3][1][4]
|
||||
assert '"$SNAP/usr/lib/vocalinux/check_runtime.py"' in calls[3][1][4]
|
||||
assert '"$SNAP/usr/lib/voculinux/check_runtime.py"' in calls[3][1][4]
|
||||
assert calls[3][2] == "dummy"
|
||||
|
||||
@@ -6,35 +6,35 @@ import yaml
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
SNAPCRAFT_YAML = REPO_ROOT / "snap" / "snapcraft.yaml"
|
||||
DESKTOP_FILE = REPO_ROOT / "snap" / "gui" / "vocalinux.desktop"
|
||||
SNAP_PNG = REPO_ROOT / "snap" / "gui" / "vocalinux.png"
|
||||
DESKTOP_FILE = REPO_ROOT / "snap" / "gui" / "voculinux.desktop"
|
||||
SNAP_PNG = REPO_ROOT / "snap" / "gui" / "voculinux.png"
|
||||
|
||||
|
||||
def test_snapcraft_recipe_and_gui_assets() -> None:
|
||||
assert SNAPCRAFT_YAML.is_file()
|
||||
doc = yaml.safe_load(SNAPCRAFT_YAML.read_text(encoding="utf-8"))
|
||||
assert doc["name"] == "vocalinux"
|
||||
assert doc["name"] == "voculinux"
|
||||
assert doc["base"] == "core24"
|
||||
assert doc["summary"] == "Free offline voice dictation for Linux"
|
||||
assert doc["license"] == "AGPL-3.0-only"
|
||||
assert doc["icon"] == "snap/gui/vocalinux.png"
|
||||
assert doc["website"] == "https://vocalinux.com"
|
||||
assert doc["icon"] == "snap/gui/voculinux.png"
|
||||
assert doc["website"] == "https://voculinux.com"
|
||||
assert doc["confinement"] == "strict"
|
||||
assert doc["grade"] == "stable"
|
||||
|
||||
plugs = set((doc.get("apps") or {}).get("vocalinux", {}).get("plugs") or [])
|
||||
plugs = set((doc.get("apps") or {}).get("voculinux", {}).get("plugs") or [])
|
||||
assert "raw-input" in plugs
|
||||
assert "hardware-observe" in plugs
|
||||
assert "audio-record" in plugs
|
||||
# The Store denied the super-privileged uinput grant; the plug (and the
|
||||
# uinput-only ydotool) must stay out or every revision is held in review.
|
||||
assert "uinput" not in plugs
|
||||
stage = doc["parts"]["vocalinux"].get("stage-packages") or []
|
||||
stage = doc["parts"]["voculinux"].get("stage-packages") or []
|
||||
assert "ydotool" not in stage
|
||||
|
||||
recipe_text = SNAPCRAFT_YAML.read_text(encoding="utf-8")
|
||||
assert "sudo snap connect vocalinux:raw-input" in recipe_text
|
||||
assert "sudo snap connect vocalinux:hardware-observe" in recipe_text
|
||||
assert "sudo snap connect voculinux:raw-input" in recipe_text
|
||||
assert "sudo snap connect voculinux:hardware-observe" in recipe_text
|
||||
|
||||
assert DESKTOP_FILE.is_file()
|
||||
assert SNAP_PNG.is_file()
|
||||
@@ -50,7 +50,7 @@ def test_snap_puts_gnome_platform_first_on_ld_library_path() -> None:
|
||||
come first or About-page SVGs fail with "Image type svg is not supported".
|
||||
"""
|
||||
doc = yaml.safe_load(SNAPCRAFT_YAML.read_text(encoding="utf-8"))
|
||||
env = (doc.get("apps") or {}).get("vocalinux", {}).get("environment") or {}
|
||||
env = (doc.get("apps") or {}).get("voculinux", {}).get("environment") or {}
|
||||
ld_path = env.get("LD_LIBRARY_PATH")
|
||||
assert isinstance(ld_path, str)
|
||||
assert ld_path.startswith("$SNAP/gnome-platform/usr/lib/$CRAFT_ARCH_TRIPLET:")
|
||||
@@ -63,8 +63,8 @@ def test_snap_docs_tell_users_to_connect_hardware_observe() -> None:
|
||||
install = (REPO_ROOT / "docs" / "INSTALL.md").read_text(encoding="utf-8")
|
||||
snapcraft = SNAPCRAFT_YAML.read_text(encoding="utf-8")
|
||||
for text in (readme, install, snapcraft):
|
||||
assert "sudo snap connect vocalinux:raw-input" in text
|
||||
assert "sudo snap connect vocalinux:hardware-observe" in text
|
||||
assert "sudo snap connect voculinux:raw-input" in text
|
||||
assert "sudo snap connect voculinux:hardware-observe" in text
|
||||
|
||||
|
||||
def test_snap_docs_explain_typing_without_uinput_plug() -> None:
|
||||
@@ -88,13 +88,13 @@ def test_snap_preserves_metadata_and_uses_gnome_gi() -> None:
|
||||
"""GI comes from GNOME without rewriting project dependencies."""
|
||||
text = SNAPCRAFT_YAML.read_text(encoding="utf-8")
|
||||
doc = yaml.safe_load(text)
|
||||
part = doc["parts"]["vocalinux"]
|
||||
part = doc["parts"]["voculinux"]
|
||||
assert part["plugin"] == "nil"
|
||||
assert "python-packages" not in part
|
||||
assert "sed -i" not in part["override-pull"]
|
||||
assert "snap/local/build.py" in part["override-build"]
|
||||
assert "craftctl default" not in part["override-build"]
|
||||
stage = doc["parts"]["vocalinux"].get("stage-packages") or []
|
||||
stage = doc["parts"]["voculinux"].get("stage-packages") or []
|
||||
assert "python3-gi" not in stage
|
||||
assert "python3-gi-cairo" not in stage
|
||||
|
||||
@@ -143,7 +143,7 @@ def test_release_notes_document_snap_sideload() -> None:
|
||||
assert "snap install --dangerous" in body
|
||||
assert "RemoteDesktop portal" in body
|
||||
assert "no `uinput` plug" in body
|
||||
assert "vocalinux_${{ steps.get_version.outputs.VERSION }}_amd64.snap" in body
|
||||
assert "voculinux_${{ steps.get_version.outputs.VERSION }}_amd64.snap" in body
|
||||
|
||||
|
||||
def test_snap_backfill_workflow_attests_only_the_snap() -> None:
|
||||
@@ -179,11 +179,11 @@ def test_snap_promote_dispatch_gates_stable_on_candidate() -> None:
|
||||
"""stable is the gated option of #783: a manual dispatch releasing the candidate revision."""
|
||||
text = (REPO_ROOT / ".github" / "workflows" / "snap-promote.yml").read_text(encoding="utf-8")
|
||||
assert "workflow_dispatch:" in text
|
||||
assert 'snapcraft release vocalinux "$REVISION" latest/stable' in text
|
||||
assert 'snapcraft release voculinux "$REVISION" latest/stable' in text
|
||||
assert "SNAPCRAFT_STORE_CREDENTIALS is unset; cannot promote the snap" in text
|
||||
assert "environment:" in text
|
||||
# The promote must prove the candidate revision carries the tag's version.
|
||||
assert "snapcraft status vocalinux" in text
|
||||
assert "snapcraft status voculinux" in text
|
||||
assert 'if [ "$seen_version" != "$VERSION" ]' in text
|
||||
|
||||
|
||||
@@ -222,7 +222,7 @@ def test_snap_builds_pywhispercpp_at_the_portable_baseline() -> None:
|
||||
alone leaves them all on."""
|
||||
doc = yaml.safe_load(SNAPCRAFT_YAML.read_text(encoding="utf-8"))
|
||||
env: dict = {}
|
||||
for entry in doc["parts"]["vocalinux"].get("build-environment") or []:
|
||||
for entry in doc["parts"]["voculinux"].get("build-environment") or []:
|
||||
env.update(entry)
|
||||
build = (REPO_ROOT / "snap/local/build.py").read_text()
|
||||
assert '"--no-binary=pywhispercpp"' in build
|
||||
|
||||
@@ -32,7 +32,7 @@ def _numpy_is_real_while_capturing() -> Iterator[None]:
|
||||
stalled read this file depends on.
|
||||
"""
|
||||
current = sys.modules.get("numpy")
|
||||
real = getattr(sys, "_vocalinux_real_numpy", None)
|
||||
real = getattr(sys, "_voculinux_real_numpy", None)
|
||||
if isinstance(current, MagicMock) and real is not None:
|
||||
sys.modules["numpy"] = real
|
||||
try:
|
||||
|
||||
@@ -31,7 +31,7 @@ def audio(monkeypatch: pytest.MonkeyPatch) -> Mock:
|
||||
audio.open.return_value.get_read_available.return_value = 1024
|
||||
audio.open.return_value.read.return_value = b"\x00\x01" * 1024
|
||||
monkeypatch.setitem(sys.modules, "pyaudio", SimpleNamespace(PyAudio=lambda: audio, paInt16=8))
|
||||
monkeypatch.setitem(sys.modules, "numpy", sys._vocalinux_real_numpy)
|
||||
monkeypatch.setitem(sys.modules, "numpy", sys._voculinux_real_numpy)
|
||||
return audio
|
||||
|
||||
|
||||
|
||||
+18
-18
@@ -197,7 +197,7 @@ class TestTextInjector(unittest.TestCase):
|
||||
def test_wayland_wtype_fail_xdotool_when_uinput_blocked(self) -> None:
|
||||
"""ydotool in PATH without /dev/uinput still cannot rescue a wtype miss."""
|
||||
with patch.dict(
|
||||
"os.environ", {"XDG_SESSION_TYPE": "wayland", "SNAP": "/snap/vocalinux/x1"}
|
||||
"os.environ", {"XDG_SESSION_TYPE": "wayland", "SNAP": "/snap/voculinux/x1"}
|
||||
):
|
||||
self.mock_which.side_effect = lambda cmd: {
|
||||
"wtype": "/usr/bin/wtype",
|
||||
@@ -298,7 +298,7 @@ class TestTextInjector(unittest.TestCase):
|
||||
injector.inject_text("Hello world")
|
||||
|
||||
# Clipboard paste fails (no wl-copy/xclip/xsel in this mock); type fallback.
|
||||
# Default key-delay is 2 (overridable via VOCALINUX_YDOTOOL_KEY_DELAY).
|
||||
# Default key-delay is 2 (overridable via VOCULINUX_YDOTOOL_KEY_DELAY).
|
||||
self.mock_subprocess.assert_any_call(
|
||||
["ydotool", "type", "--key-delay", "2", "--", "Hello world"],
|
||||
check=True,
|
||||
@@ -891,7 +891,7 @@ class TestTextInjector(unittest.TestCase):
|
||||
"""Flatpak pins ydotool 1.0.4; always use keycode:value form."""
|
||||
mock_which.return_value = "/app/bin/ydotool"
|
||||
injector = TextInjector.__new__(TextInjector)
|
||||
with patch.dict("os.environ", {"FLATPAK_ID": "com.vocalinux.Vocalinux"}):
|
||||
with patch.dict("os.environ", {"FLATPAK_ID": "com.voculinux.Voculinux"}):
|
||||
cmd = injector._ydotool_ctrl_v_command()
|
||||
self.assertEqual(cmd, ["ydotool", "key", "29:1", "47:1", "47:0", "29:0"])
|
||||
|
||||
@@ -3516,7 +3516,7 @@ class TestBackendPreference(unittest.TestCase):
|
||||
def test_environment_overrides_config(self):
|
||||
"""A one-off experiment must not require editing the user's config."""
|
||||
with _fake_config({"text_injection": {"backend": "ibus"}}):
|
||||
with patch.dict("os.environ", {"VOCALINUX_FORCE_BACKEND": "wtype"}):
|
||||
with patch.dict("os.environ", {"VOCULINUX_FORCE_BACKEND": "wtype"}):
|
||||
self.assertEqual(TextInjector._backend_preference(), "wtype")
|
||||
|
||||
def test_config_used_when_environment_unset(self):
|
||||
@@ -3530,14 +3530,14 @@ class TestBackendPreference(unittest.TestCase):
|
||||
self.assertEqual(TextInjector._backend_preference(), "auto")
|
||||
|
||||
def test_explicit_auto_overrides_a_saved_pin(self):
|
||||
"""``VOCALINUX_FORCE_BACKEND=auto`` asks for autodetection *this run*.
|
||||
"""``VOCULINUX_FORCE_BACKEND=auto`` asks for autodetection *this run*.
|
||||
|
||||
It must not be read as "nothing was set" and fall through to the saved
|
||||
pin, or the variable cannot undo a pin for a single run -- which is the
|
||||
one-off A/B test it exists for.
|
||||
"""
|
||||
with _fake_config({"text_injection": {"backend": "wtype"}}):
|
||||
with patch.dict("os.environ", {"VOCALINUX_FORCE_BACKEND": "auto"}):
|
||||
with patch.dict("os.environ", {"VOCULINUX_FORCE_BACKEND": "auto"}):
|
||||
self.assertEqual(TextInjector._backend_preference(), "auto")
|
||||
|
||||
def test_unset_environment_uses_the_saved_pin(self):
|
||||
@@ -3554,7 +3554,7 @@ class TestBackendPreference(unittest.TestCase):
|
||||
``test_explicit_auto_overrides_a_saved_pin``, where they did ask.
|
||||
"""
|
||||
with _fake_config({"text_injection": {"backend": "wtype"}}):
|
||||
with patch.dict("os.environ", {"VOCALINUX_FORCE_BACKEND": "wtpye"}):
|
||||
with patch.dict("os.environ", {"VOCULINUX_FORCE_BACKEND": "wtpye"}):
|
||||
self.assertEqual(TextInjector._backend_preference(), "wtype")
|
||||
|
||||
|
||||
@@ -3562,7 +3562,7 @@ class TestSelectableBackends(unittest.TestCase):
|
||||
"""xdotool is a pinnable value, and both readers accept the same set."""
|
||||
|
||||
def test_xdotool_is_accepted_from_the_environment(self):
|
||||
with patch.dict("os.environ", {"VOCALINUX_FORCE_BACKEND": "xdotool"}):
|
||||
with patch.dict("os.environ", {"VOCULINUX_FORCE_BACKEND": "xdotool"}):
|
||||
self.assertEqual(TextInjector._forced_backend_setting(), "xdotool")
|
||||
|
||||
def test_xdotool_is_accepted_from_config(self):
|
||||
@@ -3572,7 +3572,7 @@ class TestSelectableBackends(unittest.TestCase):
|
||||
def test_both_readers_accept_the_same_values(self):
|
||||
"""A value valid in one source must be valid in the other."""
|
||||
for value in TextInjector._SELECTABLE_BACKENDS:
|
||||
with patch.dict("os.environ", {"VOCALINUX_FORCE_BACKEND": value}):
|
||||
with patch.dict("os.environ", {"VOCULINUX_FORCE_BACKEND": value}):
|
||||
self.assertEqual(TextInjector._forced_backend_setting(), value)
|
||||
with _fake_config({"text_injection": {"backend": value}}):
|
||||
self.assertEqual(TextInjector._configured_backend(), value)
|
||||
@@ -3586,7 +3586,7 @@ class TestSelectableBackends(unittest.TestCase):
|
||||
|
||||
|
||||
class TestForcedBackendSetting(unittest.TestCase):
|
||||
"""The raw three-state reading of VOCALINUX_FORCE_BACKEND.
|
||||
"""The raw three-state reading of VOCULINUX_FORCE_BACKEND.
|
||||
|
||||
``_forced_backend()`` collapses unset and explicit ``auto`` together, which
|
||||
is fine for its own callers but loses the distinction ``_backend_preference()``
|
||||
@@ -3599,40 +3599,40 @@ class TestForcedBackendSetting(unittest.TestCase):
|
||||
|
||||
def test_explicit_auto_is_the_string_not_none(self):
|
||||
for value in ("auto", " AUTO "):
|
||||
with patch.dict("os.environ", {"VOCALINUX_FORCE_BACKEND": value}):
|
||||
with patch.dict("os.environ", {"VOCULINUX_FORCE_BACKEND": value}):
|
||||
self.assertEqual(TextInjector._forced_backend_setting(), "auto")
|
||||
|
||||
def test_empty_value_counts_as_unset(self):
|
||||
with patch.dict("os.environ", {"VOCALINUX_FORCE_BACKEND": " "}):
|
||||
with patch.dict("os.environ", {"VOCULINUX_FORCE_BACKEND": " "}):
|
||||
self.assertIsNone(TextInjector._forced_backend_setting())
|
||||
|
||||
def test_recognised_backends_are_returned(self):
|
||||
for value in ("ibus", "wtype", "ydotool", "xdotool"):
|
||||
with patch.dict("os.environ", {"VOCALINUX_FORCE_BACKEND": value.upper()}):
|
||||
with patch.dict("os.environ", {"VOCULINUX_FORCE_BACKEND": value.upper()}):
|
||||
self.assertEqual(TextInjector._forced_backend_setting(), value)
|
||||
|
||||
def test_unknown_value_is_unset_rather_than_auto(self):
|
||||
"""See ``test_typo_in_environment_is_treated_as_unset_not_as_auto``."""
|
||||
with patch.dict("os.environ", {"VOCALINUX_FORCE_BACKEND": "ibsu"}):
|
||||
with patch.dict("os.environ", {"VOCULINUX_FORCE_BACKEND": "ibsu"}):
|
||||
self.assertIsNone(TextInjector._forced_backend_setting())
|
||||
|
||||
|
||||
class TestForcedBackend(unittest.TestCase):
|
||||
"""VOCALINUX_FORCE_BACKEND overrides backend autodetection."""
|
||||
"""VOCULINUX_FORCE_BACKEND overrides backend autodetection."""
|
||||
|
||||
def test_unset_or_auto_means_auto(self):
|
||||
for value in ("", "auto", " AUTO "):
|
||||
with patch.dict("os.environ", {"VOCALINUX_FORCE_BACKEND": value}):
|
||||
with patch.dict("os.environ", {"VOCULINUX_FORCE_BACKEND": value}):
|
||||
self.assertEqual(TextInjector._forced_backend(), "auto")
|
||||
|
||||
def test_recognised_backends(self):
|
||||
for value in ("ibus", "wtype", "ydotool", "xdotool"):
|
||||
with patch.dict("os.environ", {"VOCALINUX_FORCE_BACKEND": value.upper()}):
|
||||
with patch.dict("os.environ", {"VOCULINUX_FORCE_BACKEND": value.upper()}):
|
||||
self.assertEqual(TextInjector._forced_backend(), value)
|
||||
|
||||
def test_unknown_value_falls_back_to_auto(self):
|
||||
"""A typo must not silently pin the wrong backend."""
|
||||
with patch.dict("os.environ", {"VOCALINUX_FORCE_BACKEND": "ibsu"}):
|
||||
with patch.dict("os.environ", {"VOCULINUX_FORCE_BACKEND": "ibsu"}):
|
||||
self.assertEqual(TextInjector._forced_backend(), "auto")
|
||||
|
||||
def test_missing_variable_means_auto(self):
|
||||
|
||||
@@ -111,7 +111,7 @@ class TestDetectEnvironment(unittest.TestCase):
|
||||
|
||||
obj = _make_injector(DesktopEnvironment.X11)
|
||||
env = {
|
||||
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
||||
"FLATPAK_ID": "com.voculinux.Voculinux",
|
||||
"XDG_SESSION_TYPE": "wayland",
|
||||
"DISPLAY": ":0",
|
||||
}
|
||||
@@ -126,7 +126,7 @@ class TestDetectEnvironment(unittest.TestCase):
|
||||
|
||||
obj = _make_injector(DesktopEnvironment.X11)
|
||||
env = {
|
||||
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
||||
"FLATPAK_ID": "com.voculinux.Voculinux",
|
||||
"XDG_SESSION_TYPE": "wayland",
|
||||
"DISPLAY": ":0",
|
||||
}
|
||||
@@ -140,7 +140,7 @@ class TestDetectEnvironment(unittest.TestCase):
|
||||
|
||||
obj = _make_injector(DesktopEnvironment.WAYLAND)
|
||||
env = {
|
||||
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
||||
"FLATPAK_ID": "com.voculinux.Voculinux",
|
||||
"XDG_SESSION_TYPE": "wayland",
|
||||
"WAYLAND_DISPLAY": "wayland-0",
|
||||
"DISPLAY": ":0",
|
||||
@@ -346,7 +346,7 @@ class TestCheckDependencies(unittest.TestCase):
|
||||
mock_ibus_class.assert_called_once()
|
||||
|
||||
def test_force_backend_wtype_skips_ibus(self):
|
||||
"""VOCALINUX_FORCE_BACKEND=wtype pins wtype even where IBus would be chosen."""
|
||||
"""VOCULINUX_FORCE_BACKEND=wtype pins wtype even where IBus would be chosen."""
|
||||
from vocalinux.text_injection.text_injector import DesktopEnvironment
|
||||
|
||||
obj = _make_injector(DesktopEnvironment.WAYLAND)
|
||||
@@ -354,7 +354,7 @@ class TestCheckDependencies(unittest.TestCase):
|
||||
with (
|
||||
patch.dict(
|
||||
os.environ,
|
||||
{"XDG_SESSION_TYPE": "wayland", "VOCALINUX_FORCE_BACKEND": "wtype"},
|
||||
{"XDG_SESSION_TYPE": "wayland", "VOCULINUX_FORCE_BACKEND": "wtype"},
|
||||
clear=True,
|
||||
),
|
||||
patch("vocalinux.text_injection.text_injector.is_ibus_available", return_value=True),
|
||||
@@ -450,7 +450,7 @@ class TestCheckDependencies(unittest.TestCase):
|
||||
run(pinned).assert_not_called()
|
||||
|
||||
def test_explicit_auto_environment_ignores_the_saved_config_pin(self):
|
||||
"""VOCALINUX_FORCE_BACKEND=auto is how you get one run of plain autodetection.
|
||||
"""VOCULINUX_FORCE_BACKEND=auto is how you get one run of plain autodetection.
|
||||
|
||||
The resolver already knows an explicit auto from an unset variable; this
|
||||
drives the whole of _check_dependencies to show the saved pin really is
|
||||
@@ -467,7 +467,7 @@ class TestCheckDependencies(unittest.TestCase):
|
||||
with (
|
||||
patch.dict(
|
||||
os.environ,
|
||||
{"XDG_SESSION_TYPE": "wayland", "VOCALINUX_FORCE_BACKEND": "auto"},
|
||||
{"XDG_SESSION_TYPE": "wayland", "VOCULINUX_FORCE_BACKEND": "auto"},
|
||||
clear=True,
|
||||
),
|
||||
patch("vocalinux.text_injection.text_injector.config_dir", return_value="/fake/config"),
|
||||
@@ -512,7 +512,7 @@ class TestCheckDependencies(unittest.TestCase):
|
||||
"XDG_CURRENT_DESKTOP": desktop,
|
||||
}
|
||||
if not config_pin:
|
||||
environ["VOCALINUX_FORCE_BACKEND"] = "ibus"
|
||||
environ["VOCULINUX_FORCE_BACKEND"] = "ibus"
|
||||
|
||||
with (
|
||||
patch.dict(
|
||||
@@ -628,7 +628,7 @@ class TestCheckDependencies(unittest.TestCase):
|
||||
"XDG_CURRENT_DESKTOP": "Hyprland",
|
||||
}
|
||||
if pin:
|
||||
environ["VOCALINUX_FORCE_BACKEND"] = pin
|
||||
environ["VOCULINUX_FORCE_BACKEND"] = pin
|
||||
with (
|
||||
patch.dict(os.environ, environ, clear=True),
|
||||
patch(
|
||||
@@ -716,7 +716,7 @@ class TestCheckDependencies(unittest.TestCase):
|
||||
self.assertFalse(self._not_applied(output))
|
||||
|
||||
def test_pinned_log_line_names_the_config_source_not_the_variable(self):
|
||||
"""A config-sourced pin must not be reported as VOCALINUX_FORCE_BACKEND."""
|
||||
"""A config-sourced pin must not be reported as VOCULINUX_FORCE_BACKEND."""
|
||||
from vocalinux.text_injection.text_injector import DesktopEnvironment, TextInjector
|
||||
|
||||
obj = _make_injector(DesktopEnvironment.WAYLAND)
|
||||
@@ -736,7 +736,7 @@ class TestCheckDependencies(unittest.TestCase):
|
||||
pinned_lines = [line for line in logs.output if "wtype" in line and "=" in line]
|
||||
self.assertTrue(pinned_lines)
|
||||
self.assertFalse(
|
||||
[line for line in pinned_lines if "VOCALINUX_FORCE_BACKEND" in line],
|
||||
[line for line in pinned_lines if "VOCULINUX_FORCE_BACKEND" in line],
|
||||
f"config-sourced pin reported as the env var: {pinned_lines}",
|
||||
)
|
||||
|
||||
@@ -764,7 +764,7 @@ class TestCheckDependencies(unittest.TestCase):
|
||||
with (
|
||||
patch.dict(
|
||||
os.environ,
|
||||
{"XDG_SESSION_TYPE": "wayland", "VOCALINUX_FORCE_BACKEND": "ibus"},
|
||||
{"XDG_SESSION_TYPE": "wayland", "VOCULINUX_FORCE_BACKEND": "ibus"},
|
||||
clear=True,
|
||||
),
|
||||
patch("vocalinux.text_injection.text_injector.is_ibus_available", return_value=True),
|
||||
@@ -864,7 +864,7 @@ class TestCheckDependencies(unittest.TestCase):
|
||||
)
|
||||
|
||||
def test_force_backend_ydotool_skips_ibus_and_wtype(self):
|
||||
"""VOCALINUX_FORCE_BACKEND=ydotool pins ydotool even when wtype is available."""
|
||||
"""VOCULINUX_FORCE_BACKEND=ydotool pins ydotool even when wtype is available."""
|
||||
from vocalinux.text_injection.text_injector import DesktopEnvironment
|
||||
|
||||
obj = _make_injector(DesktopEnvironment.WAYLAND)
|
||||
@@ -872,7 +872,7 @@ class TestCheckDependencies(unittest.TestCase):
|
||||
with (
|
||||
patch.dict(
|
||||
os.environ,
|
||||
{"XDG_SESSION_TYPE": "wayland", "VOCALINUX_FORCE_BACKEND": "ydotool"},
|
||||
{"XDG_SESSION_TYPE": "wayland", "VOCULINUX_FORCE_BACKEND": "ydotool"},
|
||||
clear=True,
|
||||
),
|
||||
patch("vocalinux.text_injection.text_injector.is_ibus_available", return_value=True),
|
||||
@@ -890,7 +890,7 @@ class TestCheckDependencies(unittest.TestCase):
|
||||
mock_ibus_class.assert_not_called()
|
||||
|
||||
def test_force_backend_ibus_bypasses_reachability_guards(self):
|
||||
"""VOCALINUX_FORCE_BACKEND=ibus selects IBus even on an unbridged compositor."""
|
||||
"""VOCULINUX_FORCE_BACKEND=ibus selects IBus even on an unbridged compositor."""
|
||||
from vocalinux.text_injection.text_injector import DesktopEnvironment, TextInjector
|
||||
|
||||
obj = _make_injector(DesktopEnvironment.WAYLAND)
|
||||
@@ -901,7 +901,7 @@ class TestCheckDependencies(unittest.TestCase):
|
||||
{
|
||||
"XDG_SESSION_TYPE": "wayland",
|
||||
"XDG_CURRENT_DESKTOP": "Hyprland",
|
||||
"VOCALINUX_FORCE_BACKEND": "ibus",
|
||||
"VOCULINUX_FORCE_BACKEND": "ibus",
|
||||
},
|
||||
clear=True,
|
||||
),
|
||||
@@ -1596,7 +1596,7 @@ class TestPressBackspace(unittest.TestCase):
|
||||
|
||||
def test_ydotool_v1_uses_keycode_14(self):
|
||||
obj = _make_backspace_injector("WAYLAND", tool="ydotool", legacy=False)
|
||||
with patch.dict(os.environ, {"VOCALINUX_YDOTOOL_KEY_DELAY": "2"}):
|
||||
with patch.dict(os.environ, {"VOCULINUX_YDOTOOL_KEY_DELAY": "2"}):
|
||||
with patch("subprocess.run") as mock_run:
|
||||
self.assertTrue(obj.press_backspace(2))
|
||||
self.assertEqual(
|
||||
@@ -1788,7 +1788,7 @@ class TestBackgroundIBusInitialization(unittest.TestCase):
|
||||
with (
|
||||
patch.dict(
|
||||
os.environ,
|
||||
{"XDG_SESSION_TYPE": "x11", "VOCALINUX_FORCE_BACKEND": "ibus"},
|
||||
{"XDG_SESSION_TYPE": "x11", "VOCULINUX_FORCE_BACKEND": "ibus"},
|
||||
clear=True,
|
||||
),
|
||||
patch("vocalinux.text_injection.text_injector.is_ibus_available", return_value=True),
|
||||
@@ -1832,7 +1832,7 @@ class TestBackgroundIBusInitialization(unittest.TestCase):
|
||||
with (
|
||||
patch.dict(
|
||||
os.environ,
|
||||
{"XDG_SESSION_TYPE": "wayland", "VOCALINUX_FORCE_BACKEND": "ibus"},
|
||||
{"XDG_SESSION_TYPE": "wayland", "VOCULINUX_FORCE_BACKEND": "ibus"},
|
||||
clear=True,
|
||||
),
|
||||
patch("vocalinux.text_injection.text_injector.is_ibus_available", return_value=True),
|
||||
@@ -1872,7 +1872,7 @@ class TestBackgroundIBusInitialization(unittest.TestCase):
|
||||
with (
|
||||
patch.dict(
|
||||
os.environ,
|
||||
{"XDG_SESSION_TYPE": "x11", "VOCALINUX_FORCE_BACKEND": "ibus"},
|
||||
{"XDG_SESSION_TYPE": "x11", "VOCULINUX_FORCE_BACKEND": "ibus"},
|
||||
clear=True,
|
||||
),
|
||||
patch("vocalinux.text_injection.text_injector.is_ibus_available", return_value=True),
|
||||
@@ -1905,7 +1905,7 @@ class TestBackgroundIBusInitialization(unittest.TestCase):
|
||||
with (
|
||||
patch.dict(
|
||||
os.environ,
|
||||
{"XDG_SESSION_TYPE": "x11", "VOCALINUX_FORCE_BACKEND": "ibus"},
|
||||
{"XDG_SESSION_TYPE": "x11", "VOCULINUX_FORCE_BACKEND": "ibus"},
|
||||
clear=True,
|
||||
),
|
||||
patch("vocalinux.text_injection.text_injector.is_ibus_available", return_value=True),
|
||||
@@ -1939,7 +1939,7 @@ class TestBackgroundIBusInitialization(unittest.TestCase):
|
||||
obj._ibus_injector = MagicMock()
|
||||
obj.wayland_tool = "ydotool"
|
||||
obj._ibus_injector.prepare_engine.side_effect = RuntimeError("late failure")
|
||||
obj._backend_pin = ("ibus", "VOCALINUX_FORCE_BACKEND")
|
||||
obj._backend_pin = ("ibus", "VOCULINUX_FORCE_BACKEND")
|
||||
|
||||
with self.assertLogs("vocalinux.text_injection.text_injector", level="DEBUG") as logs:
|
||||
obj._warn_if_pin_not_honoured(*obj._backend_pin)
|
||||
|
||||
@@ -120,10 +120,10 @@ class TestTrayIndicator(unittest.TestCase):
|
||||
self.patcher_listdir = patch(
|
||||
"os.listdir",
|
||||
return_value=[
|
||||
"vocalinux.svg",
|
||||
"vocalinux-microphone-off.svg",
|
||||
"vocalinux-microphone.svg",
|
||||
"vocalinux-microphone-process.svg",
|
||||
"voculinux.svg",
|
||||
"voculinux-microphone-off.svg",
|
||||
"voculinux-microphone.svg",
|
||||
"voculinux-microphone-process.svg",
|
||||
],
|
||||
)
|
||||
self.mock_listdir = self.patcher_listdir.start()
|
||||
@@ -350,7 +350,7 @@ class TestTrayIndicator(unittest.TestCase):
|
||||
|
||||
self.tray_indicator._cancel_menu_item.set_sensitive.assert_called_with(False)
|
||||
self.tray_indicator._cancel_menu_item.set_visible.assert_called_with(False)
|
||||
self.tray_indicator.indicator.set_title.assert_called_with("Vocalinux")
|
||||
self.tray_indicator.indicator.set_title.assert_called_with("Voculinux")
|
||||
|
||||
def test_cancel_tooltip_uses_fallback_without_shortcut_name(self) -> None:
|
||||
self.tray_indicator.indicator = MagicMock()
|
||||
@@ -728,7 +728,7 @@ class TestTrayIndicator(unittest.TestCase):
|
||||
with patch.object(_resource_manager, "validate_resources") as mock_validate:
|
||||
mock_validate.return_value = {
|
||||
"resources_dir_exists": True,
|
||||
"missing_icons": ["vocalinux.svg"],
|
||||
"missing_icons": ["voculinux.svg"],
|
||||
"missing_sounds": [],
|
||||
}
|
||||
self.tray_indicator._validate_resources()
|
||||
@@ -1220,7 +1220,7 @@ class TestTrayIndicator(unittest.TestCase):
|
||||
)
|
||||
|
||||
def test_overlay_close_request_quits_app(self):
|
||||
"""A dock/taskbar close of the overlay window quits Vocalinux (#1028)."""
|
||||
"""A dock/taskbar close of the overlay window quits Voculinux (#1028)."""
|
||||
self.assertEqual(self.tray_indicator.overlay._on_close_request, self.tray_indicator._quit)
|
||||
|
||||
def test_quit_destroys_overlay(self):
|
||||
@@ -1355,7 +1355,7 @@ class TestTrayIndicatorFlatpakIcons(unittest.TestCase):
|
||||
|
||||
Inside a Flatpak the StatusNotifier host runs outside the sandbox and resolves
|
||||
icons from the exported ``<app-id>-*`` theme, so the indicator must reference
|
||||
those names rather than the bundled ``vocalinux-*`` names.
|
||||
those names rather than the bundled ``voculinux-*`` names.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
@@ -1369,18 +1369,18 @@ class TestTrayIndicatorFlatpakIcons(unittest.TestCase):
|
||||
|
||||
with patch.object(tray, "FLATPAK_ID", None):
|
||||
names = tray._themed_icon_names()
|
||||
self.assertEqual(names["default"], "vocalinux-microphone-off")
|
||||
self.assertEqual(names["active"], "vocalinux-microphone")
|
||||
self.assertEqual(names["processing"], "vocalinux-microphone-process")
|
||||
self.assertEqual(names["default"], "voculinux-microphone-off")
|
||||
self.assertEqual(names["active"], "voculinux-microphone")
|
||||
self.assertEqual(names["processing"], "voculinux-microphone-process")
|
||||
|
||||
def test_themed_icon_names_inside_flatpak(self):
|
||||
import vocalinux.ui.tray_indicator as tray
|
||||
|
||||
with patch.object(tray, "FLATPAK_ID", "com.vocalinux.Vocalinux"):
|
||||
with patch.object(tray, "FLATPAK_ID", "com.voculinux.Voculinux"):
|
||||
names = tray._themed_icon_names()
|
||||
self.assertEqual(names["default"], "com.vocalinux.Vocalinux-microphone-off")
|
||||
self.assertEqual(names["active"], "com.vocalinux.Vocalinux-microphone")
|
||||
self.assertEqual(names["processing"], "com.vocalinux.Vocalinux-microphone-process")
|
||||
self.assertEqual(names["default"], "com.voculinux.Voculinux-microphone-off")
|
||||
self.assertEqual(names["active"], "com.voculinux.Voculinux-microphone")
|
||||
self.assertEqual(names["processing"], "com.voculinux.Voculinux-microphone-process")
|
||||
|
||||
|
||||
class TestAppIndicatorImportFallback(unittest.TestCase):
|
||||
@@ -1447,7 +1447,7 @@ class TestAppIndicatorImportFallback(unittest.TestCase):
|
||||
AyatanaAppIndicator3=sentinel,
|
||||
)
|
||||
|
||||
with patch.dict(os.environ, {"APPDIR": "/tmp/.mount_vocalinux"}):
|
||||
with patch.dict(os.environ, {"APPDIR": "/tmp/.mount_voculinux"}):
|
||||
import vocalinux.ui.tray_indicator as tray
|
||||
|
||||
self.assertIs(tray.AppIndicator, sentinel)
|
||||
@@ -1519,7 +1519,7 @@ class TestIndicatorBackendWiring(TestTrayIndicator):
|
||||
patch(
|
||||
"vocalinux.ui.tray_indicator.AppIndicator.Indicator.new", return_value=raw
|
||||
) as new,
|
||||
patch("vocalinux.ui.tray_indicator.FLATPAK_ID", "com.vocalinux.Vocalinux"),
|
||||
patch("vocalinux.ui.tray_indicator.FLATPAK_ID", "com.voculinux.Voculinux"),
|
||||
):
|
||||
self.tray_indicator._init_indicator()
|
||||
|
||||
|
||||
@@ -669,7 +669,7 @@ class TestExternalTriggerHandlers(unittest.TestCase):
|
||||
"""Tests for the D-Bus external Start/Stop handlers on TrayIndicator.
|
||||
|
||||
These use normal (non push-to-talk) start semantics and guard on the
|
||||
current recognition state, so repeated `vocalinux --start`/`--stop` calls
|
||||
current recognition state, so repeated `voculinux --start`/`--stop` calls
|
||||
are idempotent.
|
||||
"""
|
||||
|
||||
|
||||
@@ -218,11 +218,11 @@ def test_gmenu_indicator_adapts_gtk3_calls():
|
||||
exported = raw.set_menu.call_args[0][0]
|
||||
assert _labels(exported) == [["Start"]]
|
||||
|
||||
indicator.set_icon_full("vocalinux-microphone", "Microphone on")
|
||||
raw.set_icon.assert_called_once_with("vocalinux-microphone", "Microphone on")
|
||||
indicator.set_icon_full("voculinux-microphone", "Microphone on")
|
||||
raw.set_icon.assert_called_once_with("voculinux-microphone", "Microphone on")
|
||||
|
||||
indicator.set_title("Vocalinux")
|
||||
raw.set_title.assert_called_once_with("Vocalinux")
|
||||
indicator.set_title("Voculinux")
|
||||
raw.set_title.assert_called_once_with("Voculinux")
|
||||
|
||||
|
||||
def test_refresh_before_set_menu_is_a_no_op():
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
The website runs ``bash /tmp/vul.sh`` from whatever directory the terminal is
|
||||
in. ``venv/``, ``build/``, ``dist/``, and Python bytecode are removed only when
|
||||
the script itself lives in a Vocalinux source checkout, and only inside that
|
||||
the script itself lives in a Voculinux source checkout, and only inside that
|
||||
checkout.
|
||||
"""
|
||||
|
||||
@@ -40,8 +40,8 @@ def _decoy_tree(root: Path) -> None:
|
||||
(root / ".pytest_cache").mkdir()
|
||||
(root / ".coverage").write_text("cov", encoding="utf-8")
|
||||
(root / "src" / "pkg.egg-info").mkdir(parents=True)
|
||||
(root / "activate-vocalinux.sh").write_text("echo hi\n", encoding="utf-8")
|
||||
(root / "vocalinux-run.py").write_text("print(1)\n", encoding="utf-8")
|
||||
(root / "activate-voculinux.sh").write_text("echo hi\n", encoding="utf-8")
|
||||
(root / "voculinux-run.py").write_text("print(1)\n", encoding="utf-8")
|
||||
|
||||
|
||||
def _assert_decoy_intact(root: Path) -> None:
|
||||
@@ -56,15 +56,15 @@ def _assert_decoy_intact(root: Path) -> None:
|
||||
assert (root / ".pytest_cache").is_dir()
|
||||
assert (root / ".coverage").is_file()
|
||||
assert (root / "src" / "pkg.egg-info").is_dir()
|
||||
assert (root / "activate-vocalinux.sh").is_file()
|
||||
assert (root / "vocalinux-run.py").is_file()
|
||||
assert (root / "activate-voculinux.sh").is_file()
|
||||
assert (root / "voculinux-run.py").is_file()
|
||||
|
||||
|
||||
def _make_checkout(root: Path) -> Path:
|
||||
checkout = root / "vocalinux"
|
||||
checkout = root / "voculinux"
|
||||
(checkout / "src" / "vocalinux").mkdir(parents=True)
|
||||
(checkout / "pyproject.toml").write_text(
|
||||
'[project]\nname = "vocalinux"\n',
|
||||
'[project]\nname = "voculinux"\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
(checkout / "src" / "vocalinux" / "__init__.py").write_text("", encoding="utf-8")
|
||||
@@ -81,7 +81,7 @@ def _run(script: Path, cwd: Path, home: Path, *args: str) -> subprocess.Complete
|
||||
"""Run uninstall.sh against a fake home, without the host's pgrep or kill.
|
||||
|
||||
The uninstaller looks up this uid's processes and can TERM then KILL a
|
||||
running Vocalinux. A stub earlier on PATH records the call and matches
|
||||
running Voculinux. A stub earlier on PATH records the call and matches
|
||||
nothing, so the suite cannot stop the app a developer has open.
|
||||
"""
|
||||
bin_dir = home.parent / "uninstall-test-bin"
|
||||
@@ -90,17 +90,17 @@ def _run(script: Path, cwd: Path, home: Path, *args: str) -> subprocess.Complete
|
||||
kill_log = home.parent / "kill.log"
|
||||
_write_stub(
|
||||
bin_dir / "pgrep",
|
||||
"#!/bin/sh\n" 'printf \'%s\\n\' "$*" >> "$VOCALINUX_TEST_PGREP_LOG"\n' "exit 1\n",
|
||||
"#!/bin/sh\n" 'printf \'%s\\n\' "$*" >> "$VOCULINUX_TEST_PGREP_LOG"\n' "exit 1\n",
|
||||
)
|
||||
_write_stub(
|
||||
bin_dir / "kill",
|
||||
"#!/bin/sh\n" 'printf \'%s\\n\' "$*" >> "$VOCALINUX_TEST_KILL_LOG"\n' "exit 0\n",
|
||||
"#!/bin/sh\n" 'printf \'%s\\n\' "$*" >> "$VOCULINUX_TEST_KILL_LOG"\n' "exit 0\n",
|
||||
)
|
||||
env = os.environ.copy()
|
||||
env["HOME"] = str(home)
|
||||
env["PATH"] = os.pathsep.join((str(bin_dir), env.get("PATH", "")))
|
||||
env["VOCALINUX_TEST_PGREP_LOG"] = str(pgrep_log)
|
||||
env["VOCALINUX_TEST_KILL_LOG"] = str(kill_log)
|
||||
env["VOCULINUX_TEST_PGREP_LOG"] = str(pgrep_log)
|
||||
env["VOCULINUX_TEST_KILL_LOG"] = str(kill_log)
|
||||
env.pop("XDG_DATA_HOME", None)
|
||||
env.pop("XDG_CONFIG_HOME", None)
|
||||
env.pop("VIRTUAL_ENV", None)
|
||||
@@ -114,9 +114,9 @@ def _run(script: Path, cwd: Path, home: Path, *args: str) -> subprocess.Complete
|
||||
stdin=subprocess.DEVNULL,
|
||||
check=False,
|
||||
)
|
||||
if "Checking for running Vocalinux processes" in result.stdout:
|
||||
if "Checking for running Voculinux processes" in result.stdout:
|
||||
logged = pgrep_log.read_text(encoding="utf-8") if pgrep_log.is_file() else ""
|
||||
assert "vocalinux" in logged, "uninstall.sh did not use the test pgrep stub"
|
||||
assert "voculinux" in logged, "uninstall.sh did not use the test pgrep stub"
|
||||
if kill_log.is_file():
|
||||
assert kill_log.read_text(encoding="utf-8").strip() == ""
|
||||
return result
|
||||
@@ -136,8 +136,8 @@ def test_curl_style_run_leaves_the_callers_directory_alone(tmp_path: Path) -> No
|
||||
script = tmp_path / "vul.sh"
|
||||
_write_script(script)
|
||||
|
||||
data = home / ".local" / "share" / "vocalinux"
|
||||
config = home / ".config" / "vocalinux"
|
||||
data = home / ".local" / "share" / "voculinux"
|
||||
config = home / ".config" / "voculinux"
|
||||
data.mkdir(parents=True)
|
||||
(data / "marker").write_text("data", encoding="utf-8")
|
||||
(data / "venv").mkdir()
|
||||
@@ -146,12 +146,12 @@ def test_curl_style_run_leaves_the_callers_directory_alone(tmp_path: Path) -> No
|
||||
(config / "config.json").write_text("{}\n", encoding="utf-8")
|
||||
bindir = home / ".local" / "bin"
|
||||
bindir.mkdir(parents=True)
|
||||
(bindir / "vocalinux").write_text("#!/bin/sh\n", encoding="utf-8")
|
||||
(bindir / "vocalinux-gui").symlink_to("vocalinux")
|
||||
(bindir / "activate-vocalinux.sh").write_text("echo\n", encoding="utf-8")
|
||||
(bindir / "voculinux").write_text("#!/bin/sh\n", encoding="utf-8")
|
||||
(bindir / "voculinux-gui").symlink_to("voculinux")
|
||||
(bindir / "activate-voculinux.sh").write_text("echo\n", encoding="utf-8")
|
||||
desktop = home / ".local" / "share" / "applications"
|
||||
desktop.mkdir(parents=True)
|
||||
(desktop / "vocalinux.desktop").write_text("[Desktop Entry]\n", encoding="utf-8")
|
||||
(desktop / "voculinux.desktop").write_text("[Desktop Entry]\n", encoding="utf-8")
|
||||
|
||||
result = _run(script, cwd, home)
|
||||
|
||||
@@ -161,10 +161,10 @@ def test_curl_style_run_leaves_the_callers_directory_alone(tmp_path: Path) -> No
|
||||
_assert_decoy_intact(cwd)
|
||||
assert not data.exists()
|
||||
assert not config.exists()
|
||||
assert not (bindir / "vocalinux").exists()
|
||||
assert not (bindir / "vocalinux-gui").exists()
|
||||
assert not (bindir / "activate-vocalinux.sh").exists()
|
||||
assert not (desktop / "vocalinux.desktop").exists()
|
||||
assert not (bindir / "voculinux").exists()
|
||||
assert not (bindir / "voculinux-gui").exists()
|
||||
assert not (bindir / "activate-voculinux.sh").exists()
|
||||
assert not (desktop / "voculinux.desktop").exists()
|
||||
|
||||
|
||||
def test_keep_flags_still_do_not_touch_the_working_directory(tmp_path: Path) -> None:
|
||||
@@ -175,8 +175,8 @@ def test_keep_flags_still_do_not_touch_the_working_directory(tmp_path: Path) ->
|
||||
_decoy_tree(cwd)
|
||||
script = tmp_path / "vul.sh"
|
||||
_write_script(script)
|
||||
config = home / ".config" / "vocalinux"
|
||||
data = home / ".local" / "share" / "vocalinux"
|
||||
config = home / ".config" / "voculinux"
|
||||
data = home / ".local" / "share" / "voculinux"
|
||||
config.mkdir(parents=True)
|
||||
data.mkdir(parents=True)
|
||||
(config / "config.json").write_text("{}\n", encoding="utf-8")
|
||||
@@ -217,8 +217,8 @@ def test_source_checkout_cleans_itself_and_leaves_the_caller_alone(tmp_path: Pat
|
||||
assert not (checkout / ".pytest_cache").exists()
|
||||
assert not (checkout / "proj" / "pkg" / "__pycache__").exists()
|
||||
assert not (checkout / "src" / "pkg.egg-info").exists()
|
||||
assert not (checkout / "activate-vocalinux.sh").exists()
|
||||
assert not (checkout / "vocalinux-run.py").exists()
|
||||
assert not (checkout / "activate-voculinux.sh").exists()
|
||||
assert not (checkout / "voculinux-run.py").exists()
|
||||
|
||||
|
||||
def test_dev_venv_records_survive_checkout_cleanup(tmp_path: Path) -> None:
|
||||
@@ -234,8 +234,8 @@ def test_dev_venv_records_survive_checkout_cleanup(tmp_path: Path) -> None:
|
||||
dev_cache.mkdir(parents=True)
|
||||
(dev_cache / "mod.cpython-312.pyc").write_bytes(b"pyc")
|
||||
(checkout / ".venv" / ".coverage").write_text("keep", encoding="utf-8")
|
||||
(checkout / "vocalinux.egg-info").mkdir()
|
||||
(checkout / "vocalinux.egg-info" / "PKG-INFO").write_text("name\n", encoding="utf-8")
|
||||
(checkout / "voculinux.egg-info").mkdir()
|
||||
(checkout / "voculinux.egg-info" / "PKG-INFO").write_text("name\n", encoding="utf-8")
|
||||
|
||||
result = _run(checkout / "uninstall.sh", tmp_path, home)
|
||||
|
||||
@@ -245,7 +245,7 @@ def test_dev_venv_records_survive_checkout_cleanup(tmp_path: Path) -> None:
|
||||
assert (dev_cache / "mod.cpython-312.pyc").is_file()
|
||||
assert (checkout / ".venv" / ".coverage").is_file()
|
||||
assert not (checkout / "src" / "pkg.egg-info").exists()
|
||||
assert not (checkout / "vocalinux.egg-info").exists()
|
||||
assert not (checkout / "voculinux.egg-info").exists()
|
||||
assert not (checkout / "proj" / "pkg" / "__pycache__").exists()
|
||||
assert not (checkout / ".coverage").exists()
|
||||
|
||||
@@ -257,11 +257,11 @@ def test_curl_clone_uninstall_does_not_print_a_cleanup_error(tmp_path: Path) ->
|
||||
home.mkdir()
|
||||
cwd.mkdir()
|
||||
(cwd / "notes.txt").write_text("keep", encoding="utf-8")
|
||||
clone = home / ".local" / "share" / "vocalinux-install"
|
||||
clone = home / ".local" / "share" / "voculinux-install"
|
||||
(clone / "src" / "vocalinux").mkdir(parents=True)
|
||||
(clone / "pyproject.toml").write_text('[project]\nname = "vocalinux"\n', encoding="utf-8")
|
||||
(clone / "pyproject.toml").write_text('[project]\nname = "voculinux"\n', encoding="utf-8")
|
||||
(clone / "src" / "vocalinux" / "__init__.py").write_text("", encoding="utf-8")
|
||||
(clone / "src" / "vocalinux.egg-info").mkdir()
|
||||
(clone / "src" / "voculinux.egg-info").mkdir()
|
||||
(clone / "build").mkdir()
|
||||
(clone / "build" / "out").write_text("build", encoding="utf-8")
|
||||
_write_script(clone / "uninstall.sh")
|
||||
@@ -298,7 +298,7 @@ def test_another_projects_tree_is_not_treated_as_a_checkout(tmp_path: Path) -> N
|
||||
home.mkdir()
|
||||
project = tmp_path / "other"
|
||||
(project / "src" / "vocalinux").mkdir(parents=True)
|
||||
(project / "pyproject.toml").write_text('name = "not-vocalinux"\n', encoding="utf-8")
|
||||
(project / "pyproject.toml").write_text('name = "not-voculinux"\n', encoding="utf-8")
|
||||
_write_script(project / "uninstall.sh")
|
||||
_decoy_tree(project)
|
||||
|
||||
@@ -399,7 +399,7 @@ def test_uninstaller_is_valid_bash() -> None:
|
||||
"pyproject",
|
||||
[
|
||||
"",
|
||||
'name = "vocalinux"\n',
|
||||
'name = "voculinux"\n',
|
||||
],
|
||||
)
|
||||
def test_checkout_detection_needs_both_the_name_and_the_package_dir(
|
||||
|
||||
@@ -114,7 +114,7 @@ class TestTrustedUrls:
|
||||
def test_rejects_untrusted_urls(self):
|
||||
assert not is_trusted_release_url("https://evil.example/payload")
|
||||
assert not is_trusted_release_url("http://github.com/VocaHQ/vocalinux")
|
||||
assert not is_trusted_release_url("https://github.com/evil/vocalinux")
|
||||
assert not is_trusted_release_url("https://github.com/evil/voculinux")
|
||||
assert not is_trusted_release_url("")
|
||||
|
||||
|
||||
@@ -315,7 +315,7 @@ class TestFetchLatestRelease:
|
||||
def test_evil_update_url_does_not_become_html_url(self, caplog):
|
||||
tag_url = "https://github.com/VocaHQ/vocalinux/releases/tag/v0.17.0"
|
||||
evil_paths = [
|
||||
"/evil/vocalinux/releases/tag/v9",
|
||||
"/evil/voculinux/releases/tag/v9",
|
||||
"https://evil.example/x",
|
||||
"/VocaHQ/vocalinux/releases/../evil",
|
||||
]
|
||||
@@ -343,7 +343,7 @@ class TestFetchLatestRelease:
|
||||
}
|
||||
untrusted_urls = (
|
||||
"https://evil.example/VocaHQ/vocalinux/releases/tag/v0.17.0",
|
||||
"https://github.com/evil/vocalinux/releases/tag/v0.17.0",
|
||||
"https://github.com/evil/voculinux/releases/tag/v0.17.0",
|
||||
"http://github.com/VocaHQ/vocalinux/releases/tag/v0.17.0",
|
||||
)
|
||||
caplog.set_level(logging.WARNING, logger="vocalinux.utils.update_checker")
|
||||
@@ -392,11 +392,11 @@ class TestFetchLatestRelease:
|
||||
first, second = mock_requests.get.call_args_list
|
||||
assert first[0][0] == "https://api.github.com/repos/VocaHQ/vocalinux/releases/latest"
|
||||
assert first.kwargs["headers"]["Accept"] == "application/vnd.github+json"
|
||||
assert first.kwargs["headers"]["User-Agent"] == "Vocalinux-UpdateChecker"
|
||||
assert first.kwargs["headers"]["User-Agent"] == "Voculinux-UpdateChecker"
|
||||
assert "Authorization" not in first.kwargs["headers"]
|
||||
assert second[0][0] == "https://github.com/VocaHQ/vocalinux/releases/latest"
|
||||
assert second.kwargs["headers"]["Accept"] == "application/json"
|
||||
assert second.kwargs["headers"]["User-Agent"] == "Vocalinux-UpdateChecker"
|
||||
assert second.kwargs["headers"]["User-Agent"] == "Voculinux-UpdateChecker"
|
||||
assert "Authorization" not in second.kwargs["headers"]
|
||||
assert fallback.url == tag_url
|
||||
assert _checker_warnings(caplog) == []
|
||||
|
||||
@@ -52,7 +52,7 @@ def test_policy_states_qualification_rule() -> None:
|
||||
|
||||
def test_policy_names_retained_internal_module_name() -> None:
|
||||
policy = _policy()
|
||||
assert "vocalinux" in policy
|
||||
assert "voculinux" in policy
|
||||
assert "internal" in policy.lower()
|
||||
|
||||
|
||||
|
||||
@@ -35,11 +35,11 @@ def test_the_dispatch_input_never_reaches_the_shell():
|
||||
|
||||
def test_the_manifest_parses_as_sha256sum_writes_it():
|
||||
parsed = verify.parse_manifest(
|
||||
"0ee0d8e6 Vocalinux-0.16.2-x86_64.AppImage\n22deb51a *vocalinux-0.16.2.whl\n\n"
|
||||
"0ee0d8e6 Voculinux-0.16.2-x86_64.AppImage\n22deb51a *voculinux-0.16.2.whl\n\n"
|
||||
)
|
||||
assert parsed == {
|
||||
"Vocalinux-0.16.2-x86_64.AppImage": "0ee0d8e6",
|
||||
"vocalinux-0.16.2.whl": "22deb51a",
|
||||
"Voculinux-0.16.2-x86_64.AppImage": "0ee0d8e6",
|
||||
"voculinux-0.16.2.whl": "22deb51a",
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ def test_provenance_asks_only_for_slsa_build_provenance(monkeypatch):
|
||||
|
||||
def test_a_release_missing_a_distribution_names_it():
|
||||
"""Comparing only what is left would pass while PyPI serves an unchecked file."""
|
||||
assert verify.check_pypi("0.16.2", {"Vocalinux-0.16.2-x86_64.AppImage": "aa"}) == [
|
||||
assert verify.check_pypi("0.16.2", {"Voculinux-0.16.2-x86_64.AppImage": "aa"}) == [
|
||||
"the release carries no .whl to compare against PyPI",
|
||||
"the release carries no .tar.gz to compare against PyPI",
|
||||
]
|
||||
@@ -130,9 +130,9 @@ def test_an_extra_pypi_distribution_fails(monkeypatch):
|
||||
|
||||
import urllib.request
|
||||
|
||||
wheel = "vocalinux-0.16.2-py3-none-any.whl"
|
||||
sdist = "vocalinux-0.16.2.tar.gz"
|
||||
extra = "vocalinux-0.16.2-extra.whl"
|
||||
wheel = "voculinux-0.16.2-py3-none-any.whl"
|
||||
sdist = "voculinux-0.16.2.tar.gz"
|
||||
extra = "voculinux-0.16.2-extra.whl"
|
||||
payload = {
|
||||
"urls": [
|
||||
{"filename": wheel, "digests": {"sha256": "aa"}},
|
||||
|
||||
@@ -7,7 +7,7 @@ import sys
|
||||
import types
|
||||
from unittest.mock import MagicMock, patch # noqa: F401
|
||||
|
||||
# Mock GTK and other dependencies before importing vocalinux
|
||||
# Mock GTK and other dependencies before importing voculinux
|
||||
sys.modules["gi"] = MagicMock()
|
||||
sys.modules["gi.repository"] = MagicMock()
|
||||
sys.modules["vosk"] = MagicMock()
|
||||
|
||||
@@ -210,7 +210,7 @@ class TestModelInfo(unittest.TestCase):
|
||||
"large-v2",
|
||||
"large-v2-q5_0",
|
||||
"large-v2-q8_0",
|
||||
"large", # Vocalinux alias for upstream large-v3
|
||||
"large", # Voculinux alias for upstream large-v3
|
||||
"large-v3-q5_0",
|
||||
"large-v3-turbo",
|
||||
"large-v3-turbo-q5_0",
|
||||
|
||||
Reference in New Issue
Block a user