zonza/tests/test_single_instance.py
Ralph Mayola 3ad274a774 Zonza ne se lance plus qu'une fois a la fois
Deux bundles portant l'identifiant cloud.mrtechlab.zonza etaient enregistres
aupres de LaunchServices : /Applications/Zonza.app et un fantome laisse dans la
Corbeille le 13 juin, dont le stub visait l'ancien chemin du depot — valide
jusqu'a aujourd'hui. Les deux etaient donc lancables simultanement. Chaque
instance chargeait son propre modele MLX-Whisper medium et captait la MEME
hotkey globale : un seul appui declenchait deux transcriptions, la machine
saturait.

Le fantome est desenregistre, mais la classe de bug revient au moindre bundle
egare. D'ou un verrou exclusif (flock non bloquant) pris au demarrage : la
seconde instance renonce et sort. Le noyau libere le verrou meme apres un
plantage, donc pas d'orphelin.

Prouve avec le vrai binaire : 2e lancement direct -> "[garde] une autre instance
tourne deja", 1 seul process ; et controle negatif, seule elle demarre bien.

27 tests passent (22 + 5 nouveaux).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 00:25:05 +02:00

52 lines
1.6 KiB
Python

"""Garde d'instance unique.
Contexte (bug du 2026-08-21) : deux bundles Zonza portant le MÊME identifiant
(`/Applications/Zonza.app` et un fantôme resté dans la Corbeille, encore
enregistré auprès de LaunchServices) pouvaient tourner simultanément. Chaque
instance chargeait son propre modèle MLX-Whisper `medium` et captait la même
hotkey globale : un seul appui déclenchait deux transcriptions, la machine
saturait.
Purger le fantôme corrige l'incident du jour ; cette garde corrige la CLASSE de
bug — quel que soit le nombre de bundles présents, la 2e instance renonce.
"""
import os
import tempfile
from core import acquire_single_instance_lock
def _tmp_lock():
return os.path.join(tempfile.mkdtemp(), "sous-dossier", "zonza.lock")
def test_premiere_instance_obtient_le_verrou():
handle = acquire_single_instance_lock(_tmp_lock())
assert handle is not None
def test_deuxieme_instance_est_refusee():
path = _tmp_lock()
premier = acquire_single_instance_lock(path)
assert premier is not None
assert acquire_single_instance_lock(path) is None
def test_verrou_repris_apres_liberation():
path = _tmp_lock()
premier = acquire_single_instance_lock(path)
premier.close()
assert acquire_single_instance_lock(path) is not None
def test_cree_le_dossier_parent_manquant():
path = _tmp_lock()
assert not os.path.isdir(os.path.dirname(path))
acquire_single_instance_lock(path)
assert os.path.isdir(os.path.dirname(path))
def test_deux_chemins_differents_ne_se_genent_pas():
assert acquire_single_instance_lock(_tmp_lock()) is not None
assert acquire_single_instance_lock(_tmp_lock()) is not None