v0.7.2 : l'alias jean.exe et les raccourcis restaient sur une version perimee

Regression introduite par l'alias de la 0.7.0, signalee en usage : « Une
version plus recente d'AJEAN est deja installee » a CHAQUE lancement, sans
moyen d'en sortir.

Deux defauts qui se renforcaient.

installLegacyAlias utilisait copyExe, qui echoue quand jean.exe est en cours
d'execution — Windows refuse d'ecraser un .exe actif. L'echec etait ignore
volontairement (« sans gravite, la copie repassera au prochain lancement »),
ce qui etait faux : l'alias restait fige indefiniment. replaceExe applique
desormais la technique deja utilisee pour le binaire principal — renommer
l'ancien, ecrire le nouveau, restaurer si la copie echoue. installSelf s'en
sert aussi, pour la meme raison.

ensureShortcuts ne touchait jamais un raccourci existant. Ceux crees avant la
0.7.0 visaient donc toujours jean.exe. Chaque lancement executait l'alias
perime, qui voyait ajean.exe plus recent et le signalait — la boucle. Un
raccourci existant est maintenant repointe s'il vise autre chose que le
binaire courant.

cleanupOldBinary balaie aussi le jean.exe.old que le nouveau remplacement
peut laisser.

Deux tests : remplacement d'un exe verrouille, et garantie que la cible ne
disparait jamais si la copie echoue apres le renommage.
This commit is contained in:
nathaninline committed 2026-08-04 16:50:39 +02:00
1 parent b4d2d5ed02
commit 7a141d19b5
9 files changed
+118 -25

No files matched your search

@@ -80,3 +80,59 @@ func TestBinaryVersionWithoutResource(t *testing.T) {
t.Fatalf("binaryVersion sur un fichier sans ressource = %q, attendu \"\"", v)
}
}
// Reproduit le cas signale en usage : l'alias « jean.exe » est EN COURS
// d'execution au moment de la mise a jour. copyExe seul echouait, l'alias
// restait fige sur une version perimee, et tout raccourci le visant relancait
// indefiniment l'ancienne version — qui affichait « une version plus recente
// est deja installee » a chaque lancement.
func TestReplaceExeSurchargeUnFichierVerrouille(t *testing.T) {
dir := t.TempDir()
src := filepath.Join(dir, "neuf.exe")
dst := filepath.Join(dir, "jean.exe")
if err := os.WriteFile(src, []byte("VERSION-NEUVE"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(dst, []byte("version-perimee"), 0o755); err != nil {
t.Fatal(err)
}
// Verrou exclusif : imite un .exe en cours d'execution sous Windows.
held, err := os.OpenFile(dst, os.O_RDONLY, 0)
if err != nil {
t.Fatal(err)
}
defer held.Close()
if err := replaceExe(src, dst); err != nil {
t.Fatalf("remplacement impossible: %v", err)
}
b, err := os.ReadFile(dst)
if err != nil {
t.Fatal(err)
}
if string(b) != "VERSION-NEUVE" {
t.Fatalf("l'alias est reste perime: %q", b)
}
}
// Le remplacement ne doit JAMAIS faire disparaitre la cible : si la copie
// echoue apres le renommage, l'ancien fichier revient a sa place.
func TestReplaceExeRestaureSiLaCopieEchoue(t *testing.T) {
dir := t.TempDir()
dst := filepath.Join(dir, "jean.exe")
if err := os.WriteFile(dst, []byte("a-preserver"), 0o755); err != nil {
t.Fatal(err)
}
// Source inexistante : la copie echouera forcement.
if err := replaceExe(filepath.Join(dir, "absent.exe"), dst); err == nil {
t.Fatal("attendu une erreur")
}
b, err := os.ReadFile(dst)
if err != nil {
t.Fatalf("le fichier a disparu: %v", err)
}
if string(b) != "a-preserver" {
t.Fatalf("contenu altere: %q", b)
}
}