Files
Loki/internal/loki/sys_storagefuse_test.go
T
MichaelandClaude Opus 5.5 9f6bd725e6 Stockage : corrections de relecture — le remède Unraid décrit tel qu'il existe
« Exclusive access » n'est pas un réglage qu'on passe à Yes sur la page du
partage : c'est un état, que Unraid 6.12 accorde quand « Permit exclusive
shares » est activé et que le partage vit tout entier sur un pool, sans
stockage secondaire (/mnt/user/<partage> devient un lien vers le pool). La
consigne précédente envoyait chercher une option introuvable.
- conseil, compose et README : le vrai chemin (Global Share Settings,
  mover vers le pool puis Secondary = None, « Exclusive access : Yes »),
  et redémarrer le conteneur, Docker ne résolvant le lien qu'au démarrage ;
- un seul chemin par montage fuse.shfs : /data/models répétait /data ;
- l'encart de l'UI, permanent tant que la cause dure, devient masquable
  (localStorage, pour ce texte seulement — un autre conseil réapparaît).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 02:35:38 +02:00

108 lines
3.9 KiB
Go

package loki
import (
"runtime"
"strings"
"testing"
)
// Extrait réaliste d'un conteneur Loki sur Unraid : racine overlay, /data et
// /models en bind depuis /mnt/user (fuse.shfs), un /data/models imbriqué sur
// un pool btrfs, champs optionnels de longueur variable, un chemin à espace.
const mountinfoUnraid = `612 553 0:156 / / rw,relatime master:221 - overlay overlay rw,lowerdir=/var/lib/docker/l
613 612 0:159 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
620 612 0:52 /appdata/loki/data /data rw,noatime - fuse.shfs shfs rw,user_id=0,group_id=0,allow_other
621 612 0:52 /appdata/loki/models /models rw,noatime shared:5 master:7 - fuse.shfs shfs rw,user_id=0
622 620 0:61 /appdata/loki/fast /data/models rw,noatime - btrfs /dev/nvme0n1p1 rw,ssd
623 612 0:52 /media /mnt/mes\040modeles rw,noatime - fuse.shfs shfs rw
garbage line
624 612 8:1 / /broken rw
`
func TestParseMountinfo(t *testing.T) {
m := parseMountinfo(strings.NewReader(mountinfoUnraid))
if len(m) != 6 {
t.Fatalf("attendu 6 montages valides, obtenu %d : %+v", len(m), m)
}
// Champs optionnels (shared:5 master:7) : le type se lit après « - ».
if m[3].point != "/models" || m[3].fsType != "fuse.shfs" {
t.Fatalf("ligne à champs optionnels mal lue : %+v", m[3])
}
if m[5].point != "/mnt/mes modeles" {
t.Fatalf("échappement \\040 non décodé : %q", m[5].point)
}
}
func TestMountFSType(t *testing.T) {
m := parseMountinfo(strings.NewReader(mountinfoUnraid))
cases := []struct {
path, want string
}{
{"/data", "fuse.shfs"},
{"/data/loki.db", "fuse.shfs"},
{"/data/models", "btrfs"}, // montage imbriqué : le plus long gagne
{"/data/models/x.gguf", "btrfs"},
{"/data/modelsX", "fuse.shfs"}, // préfixe de chaîne ≠ préfixe de chemin
{"/models", "fuse.shfs"},
{"/mnt/mes modeles/a.gguf", "fuse.shfs"},
{"/opt/loki", "overlay"},
}
for _, c := range cases {
if got := mountFSType(m, c.path); got != c.want {
t.Errorf("mountFSType(%q) = %q, attendu %q", c.path, got, c.want)
}
}
if got := mountFSType(nil, "/data"); got != "" {
t.Errorf("sans montage connu : %q, attendu \"\"", got)
}
}
func TestShfsPaths(t *testing.T) {
m := parseMountinfo(strings.NewReader(mountinfoUnraid))
got := shfsPaths(m, []string{"/data", "/data/models", "/models", "/models/", "/opt"})
want := []string{"/data", "/models"}
if strings.Join(got, ",") != strings.Join(want, ",") {
t.Fatalf("shfsPaths = %v, attendu %v", got, want)
}
// Un sous-dossier du même montage (LOKI_MODEL_DIRS=/data/gguf) n'est pas
// répété : un seul chemin par montage fuse.shfs.
got = shfsPaths(m, []string{"/data/gguf", "/data", "/mnt/mes modeles/a"})
want = []string{"/data/gguf", "/mnt/mes modeles/a"}
if strings.Join(got, ",") != strings.Join(want, ",") {
t.Fatalf("shfsPaths (même montage) = %v, attendu %v", got, want)
}
// Partages en « Exclusive access » : plus de fuse.shfs, plus de conseil.
excl := `1 0 0:1 / / rw - overlay overlay rw
2 1 0:2 /appdata/loki/data /data rw - btrfs /dev/nvme0n1p1 rw
3 1 0:3 /loki/models /models rw - xfs /dev/md1p1 rw
`
if p := shfsPaths(parseMountinfo(strings.NewReader(excl)), []string{"/data", "/models"}); len(p) != 0 {
t.Fatalf("aucun chemin ne devrait être signalé, obtenu %v", p)
}
}
func TestStorageHintText(t *testing.T) {
if s := storageHintText(nil); s != "" {
t.Fatalf("rien à signaler : %q", s)
}
s := storageHintText([]string{"/data", "/models"})
for _, w := range []string{"/data, /models", "fuse.shfs", "Permit exclusive shares", "Exclusive access", "Rien n'est perdu"} {
if !strings.Contains(s, w) {
t.Errorf("conseil sans %q : %s", w, s)
}
}
}
func TestStorageFuseHintOutsideContainer(t *testing.T) {
t.Setenv("LOKI_CONTAINER", "")
if s := storageFuseHint(); s != "" {
t.Fatalf("hors conteneur, aucun conseil attendu : %q", s)
}
if runtime.GOOS != "linux" {
t.Setenv("LOKI_CONTAINER", "1")
if s := storageFuseHint(); s != "" {
t.Fatalf("hors Linux, aucun conseil attendu : %q", s)
}
}
}