155 lines
5.2 KiB
Python
155 lines
5.2 KiB
Python
"""Couche Windows du contrat Systeme — éprouvée depuis macOS par injection.
|
|
|
|
`winsound`, `msvcrt` et `ctypes.windll` n'existent pas sur macOS : c'est
|
|
pourquoi `SystemeWindows` les reçoit en paramètres injectables (jouer, clavier,
|
|
verrouiller) plutôt que de les importer en tête de module. Ces tests éprouvent
|
|
donc la LOGIQUE française de la classe — quel alias pour quel son, quel bit
|
|
pour quelle touche, comment les chemins et le verrou sont construits — jamais
|
|
le comportement réel de winsound.PlaySound, GetAsyncKeyState ou msvcrt.locking :
|
|
aucune machine ici ne peut le vérifier. Seule une vraie machine Windows le peut.
|
|
"""
|
|
import os
|
|
|
|
import pytest
|
|
|
|
from systeme.windows import SystemeWindows
|
|
|
|
|
|
# --- jouer_son ---------------------------------------------------------
|
|
|
|
def test_jouer_son_start_produit_lalias_systemasterisk():
|
|
appels = []
|
|
s = SystemeWindows(jouer=appels.append)
|
|
s.jouer_son("start")
|
|
assert appels == ["SystemAsterisk"]
|
|
|
|
|
|
def test_jouer_son_done_produit_lalias_systemexclamation():
|
|
appels = []
|
|
s = SystemeWindows(jouer=appels.append)
|
|
s.jouer_son("done")
|
|
assert appels == ["SystemExclamation"]
|
|
|
|
|
|
def test_jouer_son_error_produit_lalias_systemhand():
|
|
appels = []
|
|
s = SystemeWindows(jouer=appels.append)
|
|
s.jouer_son("error")
|
|
assert appels == ["SystemHand"]
|
|
|
|
|
|
def test_jouer_son_nom_inconnu_ne_produit_aucun_appel():
|
|
appels = []
|
|
s = SystemeWindows(jouer=appels.append)
|
|
s.jouer_son("inconnu")
|
|
assert appels == []
|
|
|
|
|
|
def test_jouer_son_qui_echoue_ne_leve_pas():
|
|
def echoue(alias):
|
|
raise OSError("winsound indisponible")
|
|
s = SystemeWindows(jouer=echoue)
|
|
s.jouer_son("start") # ne doit jamais lever : le son ne casse pas la dictée
|
|
|
|
|
|
# --- modificateurs_enfonces --------------------------------------------
|
|
|
|
@pytest.mark.parametrize("vk", [0x11, 0x10, 0x12, 0x5B, 0x5C])
|
|
def test_modificateurs_enfonces_vrai_pour_chacune_des_cinq_touches(vk):
|
|
# bit de poids fort posé UNIQUEMENT pour la touche ciblée par ce cas.
|
|
s = SystemeWindows(clavier=lambda sonde, cible=vk: 0x8000 if sonde == cible else 0)
|
|
assert s.modificateurs_enfonces() is True
|
|
|
|
|
|
def test_modificateurs_enfonces_faux_si_aucun_bit_de_poids_fort_pose():
|
|
# une valeur non nulle mais sans le bit 0x8000 ne doit pas compter.
|
|
s = SystemeWindows(clavier=lambda vk: 0x0001)
|
|
assert s.modificateurs_enfonces() is False
|
|
|
|
|
|
def test_modificateurs_enfonces_faux_si_toutes_les_touches_sont_relachees():
|
|
s = SystemeWindows(clavier=lambda vk: 0)
|
|
assert s.modificateurs_enfonces() is False
|
|
|
|
|
|
def test_modificateurs_enfonces_faux_si_la_sonde_leve():
|
|
def leve(vk):
|
|
raise OSError("user32 indisponible")
|
|
s = SystemeWindows(clavier=leve)
|
|
assert s.modificateurs_enfonces() is False
|
|
|
|
|
|
# --- raccourci_defaut ----------------------------------------------------
|
|
|
|
def test_raccourci_defaut_est_ctrl_alt_d():
|
|
# <cmd> n'existe pas sur Windows, contrairement à macOS.
|
|
assert SystemeWindows().raccourci_defaut() == "<ctrl>+<alt>+d"
|
|
|
|
|
|
# --- chemins ---------------------------------------------------------------
|
|
|
|
def test_chemin_log_derive_de_localappdata(monkeypatch, tmp_path):
|
|
monkeypatch.setenv("LOCALAPPDATA", str(tmp_path))
|
|
assert SystemeWindows().chemin_log() == os.path.join(str(tmp_path), "Zonza", "Zonza.log")
|
|
|
|
|
|
def test_dossier_donnees_derive_de_localappdata(monkeypatch, tmp_path):
|
|
monkeypatch.setenv("LOCALAPPDATA", str(tmp_path))
|
|
assert SystemeWindows().dossier_donnees() == os.path.join(str(tmp_path), "Zonza")
|
|
|
|
|
|
def test_chemin_log_retombe_sur_le_dossier_utilisateur_si_localappdata_absent(monkeypatch):
|
|
monkeypatch.delenv("LOCALAPPDATA", raising=False)
|
|
attendu = os.path.join(os.path.expanduser("~"), "Zonza", "Zonza.log")
|
|
assert SystemeWindows().chemin_log() == attendu
|
|
|
|
|
|
# --- verrou d'instance -------------------------------------------------------
|
|
|
|
def test_le_verrou_rend_un_objet_la_premiere_fois(tmp_path):
|
|
chemin = str(tmp_path / "sous-dossier" / "z.lock")
|
|
s = SystemeWindows(verrouiller=lambda f: None)
|
|
verrou = s.acquerir_verrou_instance(chemin)
|
|
assert verrou is not None
|
|
verrou.close()
|
|
|
|
|
|
def test_le_verrou_cree_le_dossier_parent_manquant(tmp_path):
|
|
chemin = str(tmp_path / "sous-dossier" / "z.lock")
|
|
s = SystemeWindows(verrouiller=lambda f: None)
|
|
assert not os.path.isdir(os.path.dirname(chemin))
|
|
verrou = s.acquerir_verrou_instance(chemin)
|
|
assert os.path.isdir(os.path.dirname(chemin))
|
|
verrou.close()
|
|
|
|
|
|
def test_le_verrou_refuse_une_seconde_prise(tmp_path):
|
|
chemin = str(tmp_path / "z.lock")
|
|
pris = {"valeur": False}
|
|
|
|
def verrouiller(f):
|
|
if pris["valeur"]:
|
|
raise OSError("verrou déjà pris")
|
|
pris["valeur"] = True
|
|
|
|
s = SystemeWindows(verrouiller=verrouiller)
|
|
premier = s.acquerir_verrou_instance(chemin)
|
|
assert premier is not None
|
|
second = s.acquerir_verrou_instance(chemin)
|
|
assert second is None
|
|
premier.close()
|
|
|
|
|
|
def test_un_verrou_dont_la_prise_echoue_referme_le_fichier(tmp_path):
|
|
chemin = str(tmp_path / "z.lock")
|
|
captures = {}
|
|
|
|
def echoue(f):
|
|
captures["fichier"] = f
|
|
raise OSError("verrou pris")
|
|
|
|
s = SystemeWindows(verrouiller=echoue)
|
|
resultat = s.acquerir_verrou_instance(chemin)
|
|
assert resultat is None
|
|
assert captures["fichier"].closed
|