mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Rebaptise AJEAN en Loki (fork, lignée conservée)
- 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.
This commit is contained in:
157 files changed
+928
-928
No files matched your search
@@ -0,0 +1,112 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user