Files
Loki/internal/ajean/backend_models_shards_test.go
T
nathaninline 52baab5786 v0.8.4 : l'historique s'exporte, le moteur se laisse joindre, les modeles en plusieurs fichiers marchent
Trois retours d'utilisateurs apres la 0.8.3.

Export de la conversation (regression de la 0.8 : conversation.json a
disparu dans ajean.db, verrouille et binaire). Bouton dans l'UI +
commande 'ajean export [--json] [fichier]', endpoint /api/chat/export.

Ecoute reseau du moteur : interrupteur 'joignable depuis le reseau local'
dans le panneau Acces OpenAI (HOST + regle de pare-feu Windows), commande
'ajean network [on|off|status]'. HOST devient un reglage de machine
preserve au changement de preset.

Modeles GGUF decoupes en tranches -00001-of-000NN : une famille = un
modele. Telechargement de toutes les tranches depuis n'importe quel lien,
sonde d'espace disque sur le total, liste et suppression regroupees,
demarrage refuse avec le nom du fichier manquant.
2026-08-08 20:01:54 +02:00

121 lines
3.9 KiB
Go

package ajean
import (
"os"
"path/filepath"
"reflect"
"testing"
)
func TestShardInfo(t *testing.T) {
cases := []struct {
name string
idx, total int
ok bool
}{
{"Laguna-S-2.1-UD-IQ4_XS-00001-of-00003.gguf", 1, 3, true},
{"Laguna-S-2.1-UD-IQ4_XS-00002-of-00003.gguf", 2, 3, true},
{"m-01-of-03.gguf", 1, 3, true},
{"Qwen2.5-7B-Instruct-Q4_K_M.gguf", 0, 0, false},
{"bidon-00004-of-00003.gguf", 0, 0, false}, // numérotation incohérente
{"pas-un-gguf-00001-of-00002.bin", 0, 0, false},
}
for _, c := range cases {
idx, total, ok := shardInfo(c.name)
if idx != c.idx || total != c.total || ok != c.ok {
t.Errorf("shardInfo(%q) = %d,%d,%v ; attendu %d,%d,%v", c.name, idx, total, ok, c.idx, c.total, c.ok)
}
}
}
func TestShardFamilyGardeLaLargeur(t *testing.T) {
got := shardFamily("Laguna-00002-of-00003.gguf")
want := []string{
"Laguna-00001-of-00003.gguf",
"Laguna-00002-of-00003.gguf",
"Laguna-00003-of-00003.gguf",
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("famille = %v ; attendu %v", got, want)
}
if solo := shardFamily("simple.gguf"); !reflect.DeepEqual(solo, []string{"simple.gguf"}) {
t.Fatalf("fichier simple = %v", solo)
}
}
// Le lien collé peut désigner n'importe quelle tranche : on remonte toujours à la
// première, et on ne réécrit QUE le nom de fichier final (le même libellé peut
// apparaître plus haut dans le chemin du dépôt).
func TestShardURLSet(t *testing.T) {
base := "https://huggingface.co/unsloth/Laguna-S-2.1-GGUF/resolve/main/UD-IQ4_XS/"
in := base + "Laguna-UD-IQ4_XS-00002-of-00003.gguf"
urls, names := shardURLSet(in, "Laguna-UD-IQ4_XS-00002-of-00003.gguf")
if len(urls) != 3 || len(names) != 3 {
t.Fatalf("attendu 3 tranches, got %d/%d", len(urls), len(names))
}
if names[0] != "Laguna-UD-IQ4_XS-00001-of-00003.gguf" {
t.Fatalf("première tranche = %q", names[0])
}
if urls[0] != base+"Laguna-UD-IQ4_XS-00001-of-00003.gguf" {
t.Fatalf("URL de la première tranche = %q", urls[0])
}
if urls[2] != base+"Laguna-UD-IQ4_XS-00003-of-00003.gguf" {
t.Fatalf("URL de la dernière tranche = %q", urls[2])
}
// Fichier simple : une seule URL, inchangée.
u, n := shardURLSet(base+"m.gguf", "m.gguf")
if len(u) != 1 || u[0] != base+"m.gguf" || n[0] != "m.gguf" {
t.Fatalf("fichier simple mal traité : %v %v", u, n)
}
}
func TestShardFamilyTailleEtManquantes(t *testing.T) {
dir := t.TempDir()
write := func(n string, size int) {
if err := os.WriteFile(filepath.Join(dir, n), make([]byte, size), 0o644); err != nil {
t.Fatal(err)
}
}
write("m-00001-of-00003.gguf", 100)
write("m-00003-of-00003.gguf", 300)
if got := shardFamilySize(dir, "m-00001-of-00003.gguf"); got != 400 {
t.Fatalf("taille de la famille = %d ; attendu 400", got)
}
missing := shardFamilyMissing(dir, "m-00001-of-00003.gguf")
if !reflect.DeepEqual(missing, []string{"m-00002-of-00003.gguf"}) {
t.Fatalf("tranches manquantes = %v", missing)
}
write("m-00002-of-00003.gguf", 200)
if m := shardFamilyMissing(dir, "m-00001-of-00003.gguf"); len(m) != 0 {
t.Fatalf("famille complète signalée incomplète : %v", m)
}
}
// Supprimer un modèle découpé doit emporter TOUTES ses tranches, sinon des
// dizaines de Go restent sur le disque sans que rien ne sache plus les effacer.
func TestDeleteModelFileEmporteLesTranches(t *testing.T) {
testHome(t)
dir := modelsDir()
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
names := []string{"m-00001-of-00002.gguf", "m-00002-of-00002.gguf", "autre.gguf"}
for _, n := range names {
if err := os.WriteFile(filepath.Join(dir, n), []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
}
if err := deleteModelFile("m-00001-of-00002.gguf"); err != nil {
t.Fatal(err)
}
for _, n := range names[:2] {
if _, err := os.Stat(filepath.Join(dir, n)); !os.IsNotExist(err) {
t.Fatalf("tranche %s non supprimée", n)
}
}
if _, err := os.Stat(filepath.Join(dir, "autre.gguf")); err != nil {
t.Fatal("un modèle sans rapport a été supprimé")
}
}