mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
- module github.com/R0m1k3/Loki, cmd/loki, internal/loki (package loki) - LOKI_HOME, LOKI_MODEL_DIRS, LOKI_SERVICE, LOKI_DL_CONNS ; /etc/loki ; units loki-engine / loki-ui ; binaire et aide CLI - updateRepo pointe sur R0m1k3/Loki (l'auto-update ne tirera plus les binaires AJEAN amont) Conservé à l'identique : le domaine ajean.link (service de tunnel amont), les littéraux de migration 0.7.x (migrate_07.go), RELEASE_NOTES.md et LICENSE (historique et licence de l'amont). go build/vet/test : verts.
113 lines
3.5 KiB
Go
113 lines
3.5 KiB
Go
package loki
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Le bouton stop doit VRAIMENT arrêter une commande shell. Elle naissait d'un
|
|
// contexte indépendant du tour : arrêter la génération ne l'arrêtait pas, et le
|
|
// tour restait suspendu jusqu'à l'expiration du délai (5 min au maximum).
|
|
func TestRunShellObeitAuStop(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("commande de veille propre à Unix")
|
|
}
|
|
testHome(t)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
go func() {
|
|
time.Sleep(200 * time.Millisecond)
|
|
cancel()
|
|
}()
|
|
start := time.Now()
|
|
out := runShell(ctx, "sleep 30", 300)
|
|
if d := time.Since(start); d > 5*time.Second {
|
|
t.Fatalf("la commande a survécu à l'annulation (%s) : %q", d, out)
|
|
}
|
|
if !strings.Contains(out, "interrompue") {
|
|
t.Errorf("résultat attendu « interrompue », reçu %q", out)
|
|
}
|
|
}
|
|
|
|
// Une commande qui laisse un process en arrière-plan garde les tubes de sortie
|
|
// ouverts. Sans WaitDelay, Wait attend leur fermeture — donc pour toujours — et
|
|
// le tour ne se terminait JAMAIS (seul un redémarrage du service débloquait).
|
|
func TestRunShellNeBloquePasSurUnProcessDetache(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("syntaxe de détachement propre à Unix")
|
|
}
|
|
testHome(t)
|
|
start := time.Now()
|
|
out := runShell(context.Background(), "sleep 30 & echo lance", 2)
|
|
if d := time.Since(start); d > 10*time.Second {
|
|
t.Fatalf("runShell est resté accroché aux tubes du process détaché (%s)", d)
|
|
}
|
|
if !strings.Contains(out, "lance") && !strings.Contains(out, "timeout") {
|
|
t.Errorf("sortie inattendue : %q", out)
|
|
}
|
|
}
|
|
|
|
// Le dossier de travail est résolu une fois par process : s'il disparaît, plus
|
|
// AUCUNE commande ne passait (« chdir : no such file or directory ») jusqu'au
|
|
// redémarrage du service. Il doit être recréé au besoin.
|
|
func TestRunShellRecreeLeWorkspaceDisparu(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("echo se comporte différemment sous cmd.exe")
|
|
}
|
|
testHome(t)
|
|
ws := agentWorkspace()
|
|
if ws == "" {
|
|
t.Skip("pas de workspace résolu")
|
|
}
|
|
if err := os.RemoveAll(ws); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out := runShell(context.Background(), "echo vivant", 10)
|
|
if !strings.Contains(out, "vivant") {
|
|
t.Fatalf("commande cassée par la disparition du workspace : %q", out)
|
|
}
|
|
}
|
|
|
|
// Reset doit TOUJOURS rendre la main, même sur un tour resté coincé : c'est le
|
|
// geste que l'on tente quand le chat est bloqué. Avant, Generating restait vrai
|
|
// et tout envoi suivant était refusé « génération en cours » jusqu'au
|
|
// redémarrage du service.
|
|
func TestResetDebloqueUnTourCoince(t *testing.T) {
|
|
c := newTestConv()
|
|
// Simule un tour parti et jamais terminé (commande accrochée, moteur disparu).
|
|
c.mu.Lock()
|
|
c.Generating = true
|
|
c.mu.Unlock()
|
|
|
|
c.Reset()
|
|
|
|
if st := c.state(); st["generating"] != false {
|
|
t.Fatal("après Reset, la conversation doit être déclarée libre")
|
|
}
|
|
}
|
|
|
|
// Le tour abandonné se termine parfois APRÈS le démarrage du suivant. Sa fin ne
|
|
// doit pas déclarer « libre » une génération toute neuve.
|
|
func TestTourPerimeNeLiberePasLeTourCourant(t *testing.T) {
|
|
c := newTestConv()
|
|
oldEpoch := c.epoch
|
|
c.Reset() // le tour d'epoch `oldEpoch` devient périmé
|
|
c.mu.Lock()
|
|
c.Generating = true // un nouveau tour démarre
|
|
c.mu.Unlock()
|
|
|
|
// Fin (tardive) du tour périmé : même code que le defer de generate.
|
|
c.mu.Lock()
|
|
if c.epoch == oldEpoch {
|
|
c.Generating = false
|
|
}
|
|
c.mu.Unlock()
|
|
|
|
if st := c.state(); st["generating"] != true {
|
|
t.Fatal("un tour périmé a libéré le tour courant")
|
|
}
|
|
}
|