[FIX] long_test et cache git : sudo pour créer, seuil nommé au refus
Les tests longs lançaient la CLI sans privilège, alors que le dossier des images appartient à root en 755 : arrêt à l'étape 1 sur « Permission refusée », avant d'avoir rien créé. Ils ne pouvaient donc pas tourner du tout — pas même échouer utilement. Le menu passe sudo depuis toujours ; c'est cette forme qui est reprise, et jamais à blanc. Le refus de place d'un miroir reprenait un champ que tout appelant laisse à zéro : « moins de 0 o libres » n'annonce aucun seuil et ne dit pas ce qui a été mesuré. Treize tests héritaient du plancher de production, 10 Gio, et rougissaient sur un /tmp en tmpfs. --- EN --- The long tests ran the CLI unprivileged, while the image directory is owned by root, mode 755: they stopped at step 1 on "permission denied", before creating anything. They therefore could not run at all — not even fail usefully. The menu has always passed sudo; that is the form taken here, and never on a dry run. A mirror's refusal for lack of space echoed a field every caller leaves at zero: "less than 0 B free" announces no threshold and does not say what was measured. Thirteen tests inherited the production floor, 10 GiB, and went red on a tmpfs /tmp. Assisted-by: Claude Opus 5
This commit is contained in:
parent
83b9674176
commit
2f08ec0890
7 changed files with 179 additions and 38 deletions
|
|
@ -473,7 +473,12 @@ class Descente:
|
|||
def creer_etage1(self, res):
|
||||
"""Une VM locale, par la CLI QEMU/KVM. Le seul étage sur du métal."""
|
||||
nom = self.nom_etage(1)
|
||||
argv = [
|
||||
# SOUS SUDO, comme le menu le fait : le dossier des images
|
||||
# appartient à root en 755 sur une installation ordinaire de libvirt,
|
||||
# et la CLI s'arrête à l'étape 1 sur « Permission refusée » avant
|
||||
# d'avoir rien créé. Jamais à blanc — un essai qui n'écrit rien n'a
|
||||
# aucune raison de demander un mot de passe.
|
||||
argv = ([] if self.dry_run else ["sudo"]) + [
|
||||
os.path.join(RACINE, ".venv.erplibre/bin/python"),
|
||||
os.path.join(RACINE, "script/qemu/deploy_qemu.py"),
|
||||
"--distro",
|
||||
|
|
|
|||
|
|
@ -183,7 +183,15 @@ def creer_vm(nom, journal, dry_run, memoire=MEMOIRE_MO):
|
|||
Rend (nom, uuid) ou (None, ""). L'UUID est ce qui identifie la machine
|
||||
pour la destruction : un nom se réutilise, un UUID non.
|
||||
"""
|
||||
argv = [
|
||||
# SOUS SUDO, comme le menu le fait (« parts = [] if dry_run else
|
||||
# ["sudo"] ») : le dossier des images appartient à root en 755 sur une
|
||||
# installation ordinaire de libvirt, et la CLI s'arrête à l'étape 1 sur
|
||||
# « Permission refusée » avant d'avoir rien créé. Sans cela le test ne
|
||||
# peut pas tourner du tout — pas même échouer utilement.
|
||||
#
|
||||
# Jamais à blanc : un essai qui n'écrit rien n'a aucune raison de
|
||||
# demander un mot de passe.
|
||||
argv = ([] if dry_run else ["sudo"]) + [
|
||||
os.path.join(RACINE, ".venv.erplibre/bin/python"),
|
||||
os.path.join(RACINE, "script/qemu/deploy_qemu.py"),
|
||||
"--distro",
|
||||
|
|
|
|||
|
|
@ -30,15 +30,15 @@ var anglais = map[string]string{
|
|||
"[à blanc] rien n'a été effacé.": "[dry run] nothing was erased.",
|
||||
"erplibre_go_qemu_cache : requête adressée au cache lui-même, refusée.\n demandé : %s\nLa relayer la renverrait vers cette même écoute, sans fin.\n": "erplibre_go_qemu_cache: request addressed to the cache itself, refused.\n requested: %s\nRelaying it would send it back to this same listener, endlessly.\n",
|
||||
"boucle refusée : %s vise le cache lui-même": "loop refused: %s targets the cache itself",
|
||||
"adresse MAC vide": "empty MAC address",
|
||||
"adresse MAC illisible %q": "unreadable MAC address %q",
|
||||
"adresse MAC de %d octets, six attendus": "MAC address of %d bytes, six expected",
|
||||
"%s est une adresse de groupe : jamais une adresse source": "%s is a group address: never a source address",
|
||||
"l'adresse nulle n'identifie aucune machine": "the null address identifies no machine",
|
||||
"dépôt illisible %q": "unreadable repository %q",
|
||||
"dépôt sans chemin %q": "repository without a path %q",
|
||||
"chemin de dépôt refusé %q": "repository path refused %q",
|
||||
"miroir refusé pour %s : moins de %s libres sur le disque": "mirror refused for %s: less than %s free on disk",
|
||||
"adresse MAC vide": "empty MAC address",
|
||||
"adresse MAC illisible %q": "unreadable MAC address %q",
|
||||
"adresse MAC de %d octets, six attendus": "MAC address of %d bytes, six expected",
|
||||
"%s est une adresse de groupe : jamais une adresse source": "%s is a group address: never a source address",
|
||||
"l'adresse nulle n'identifie aucune machine": "the null address identifies no machine",
|
||||
"dépôt illisible %q": "unreadable repository %q",
|
||||
"dépôt sans chemin %q": "repository without a path %q",
|
||||
"chemin de dépôt refusé %q": "repository path refused %q",
|
||||
"miroir refusé pour %s : %s libres sous %s, plancher de %s": "mirror refused for %s: %s free under %s, floor of %s",
|
||||
"miroir git : sonde de %s en échec (%v) ; ses miroirs sont servis sans rafraîchissement jusqu'à ce qu'il réponde": "git mirror: probe of %s failed (%v); its mirrors are served without refresh until it answers",
|
||||
"git %s : %v : %s": "git %s: %v: %s",
|
||||
"miroir éteint": "mirror disabled",
|
||||
|
|
|
|||
|
|
@ -216,10 +216,11 @@ func (g *GitMirror) Assurer(ctx context.Context, depot string) (string, bool) {
|
|||
return chemin, true
|
||||
}
|
||||
if !existe {
|
||||
if !g.placeSuffisante() {
|
||||
if libres, plancher, assez := g.placeSuffisante(); !assez {
|
||||
log.Printf(
|
||||
T("miroir refusé pour %s : moins de %s libres sur le disque"),
|
||||
depot, HumanBytes(g.PlancherLibre))
|
||||
T("miroir refusé pour %s : %s libres sous %s,"+
|
||||
" plancher de %s"),
|
||||
depot, HumanBytes(libres), g.Dir, HumanBytes(plancher))
|
||||
return "", false
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(chemin), 0o755); err != nil {
|
||||
|
|
@ -611,12 +612,18 @@ func DepotsDuFichier(chemin string) ([]string, error) {
|
|||
// qu'une VM en cours de déploiement finisse, et pour que le système respire.
|
||||
const PlancherParDefaut int64 = 10 << 30
|
||||
|
||||
// placeSuffisante dit s'il reste de quoi créer un miroir de plus.
|
||||
// placeSuffisante dit s'il reste de quoi créer un miroir de plus, et rend de
|
||||
// quoi le DIRE : (octets libres, plancher effectif, assez).
|
||||
//
|
||||
// Les trois valeurs et non le seul booléen : le refus est la seule trace que
|
||||
// l'opérateur verra, et un refus qui ne nomme ni le seuil ni la mesure envoie
|
||||
// chercher au mauvais endroit. Le plancher effectif n'est pas le champ —
|
||||
// laissé à zéro, il vaut PlancherParDefaut — et c'est celui-là qui a refusé.
|
||||
//
|
||||
// La place est relue à CHAQUE appel : le disque se remplit pendant qu'on le
|
||||
// remplit, et une valeur retenue au démarrage ne dirait rien de l'état où l'on
|
||||
// est rendu.
|
||||
func (g *GitMirror) placeSuffisante() bool {
|
||||
func (g *GitMirror) placeSuffisante() (int64, int64, bool) {
|
||||
plancher := g.PlancherLibre
|
||||
if plancher <= 0 {
|
||||
plancher = PlancherParDefaut
|
||||
|
|
@ -624,10 +631,12 @@ func (g *GitMirror) placeSuffisante() bool {
|
|||
var st syscall.Statfs_t
|
||||
if err := syscall.Statfs(g.Dir, &st); err != nil {
|
||||
// Illisible : on laisse passer plutôt que de bloquer sur une mesure
|
||||
// qu'on ne sait pas faire.
|
||||
return true
|
||||
// qu'on ne sait pas faire. Le -1 dit « non mesuré », qu'aucun compte
|
||||
// d'octets ne peut valoir.
|
||||
return -1, plancher, true
|
||||
}
|
||||
return int64(st.Bavail)*int64(st.Bsize) > plancher
|
||||
libres := int64(st.Bavail) * int64(st.Bsize)
|
||||
return libres, plancher, libres > plancher
|
||||
}
|
||||
|
||||
// Depot décrit un miroir tenu sur le disque.
|
||||
|
|
|
|||
|
|
@ -153,6 +153,53 @@ func depotDEssai(t *testing.T) string {
|
|||
return nu
|
||||
}
|
||||
|
||||
// Le refus de place est la SEULE trace qu'un opérateur verra, et il nommait
|
||||
// le champ PlancherLibre — laissé à zéro par tout appelant ordinaire — au
|
||||
// lieu du plancher qui avait refusé. « moins de 0 o libres sur le disque »
|
||||
// n'est ni vrai ni exploitable : il n'annonce aucun seuil et ne dit pas ce
|
||||
// qu'il a mesuré.
|
||||
func TestLeRefusConnaitLePlancherQuiARefuse(t *testing.T) {
|
||||
g := &GitMirror{Dir: t.TempDir()}
|
||||
libres, plancher, assez := g.placeSuffisante()
|
||||
if plancher != PlancherParDefaut {
|
||||
t.Fatalf("plancher rendu %d, le défaut est %d",
|
||||
plancher, PlancherParDefaut)
|
||||
}
|
||||
if libres < 0 {
|
||||
t.Skip("place non mesurable sur ce répertoire")
|
||||
}
|
||||
if assez != (libres > plancher) {
|
||||
t.Fatalf("verdict %v pour %d libres sous un plancher de %d",
|
||||
assez, libres, plancher)
|
||||
}
|
||||
}
|
||||
|
||||
// Une mesure impossible ne bloque pas — c'est le choix déjà écrit — mais elle
|
||||
// doit se DISTINGUER d'un disque vide : zéro octet libre est une mesure, pas
|
||||
// une absence de mesure.
|
||||
func TestUnePlaceNonMesurableNeBloquePasEtSeDistingue(t *testing.T) {
|
||||
g := &GitMirror{Dir: filepath.Join(t.TempDir(), "nexiste", "pas")}
|
||||
libres, _, assez := g.placeSuffisante()
|
||||
if !assez {
|
||||
t.Fatal("une mesure impossible ne doit pas refuser le miroir")
|
||||
}
|
||||
if libres != -1 {
|
||||
t.Fatalf("libres %d : une mesure absente doit se distinguer", libres)
|
||||
}
|
||||
}
|
||||
|
||||
// Le plancher des tests qui ne portent PAS sur le plancher.
|
||||
//
|
||||
// Le défaut de production — 10 Gio laissés au disque de l'orchestrateur — n'a
|
||||
// rien à voir avec un miroir de trois commits dans un répertoire temporaire,
|
||||
// et il fait échouer treize tests sur toute machine dont le répertoire
|
||||
// temporaire offre moins que cela : un /tmp en tmpfs y suffit. Le refus était
|
||||
// alors pris pour une panne du miroir.
|
||||
//
|
||||
// Le plancher lui-même est éprouvé plus bas, avec une valeur qu'aucun disque
|
||||
// n'atteint.
|
||||
const plancherDEssai int64 = 1
|
||||
|
||||
// L'épreuve qui compte : un client clone à travers le miroir, sans que rien
|
||||
// dans l'invité soit configuré, et le dépôt arrive complet.
|
||||
func TestClonerAuTraversDuMiroir(t *testing.T) {
|
||||
|
|
@ -160,7 +207,8 @@ func TestClonerAuTraversDuMiroir(t *testing.T) {
|
|||
t.Skip("git-http-backend absent de cette machine")
|
||||
}
|
||||
amont, _ := amontGit(t)
|
||||
g := &GitMirror{Dir: t.TempDir(), Delai: 2 * time.Minute}
|
||||
g := &GitMirror{
|
||||
PlancherLibre: plancherDEssai, Dir: t.TempDir(), Delai: 2 * time.Minute}
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -200,7 +248,8 @@ func TestClonerAuTraversDuMiroir(t *testing.T) {
|
|||
// peut faire de ce protocole.
|
||||
func TestUnMiroirExistantSertQuandLAmontEstMuet(t *testing.T) {
|
||||
amont, srvAmont := amontGit(t)
|
||||
g := &GitMirror{Dir: t.TempDir(), Delai: time.Minute}
|
||||
g := &GitMirror{
|
||||
PlancherLibre: plancherDEssai, Dir: t.TempDir(), Delai: time.Minute}
|
||||
if _, pret := g.Assurer(context.Background(), amont); !pret {
|
||||
t.Fatal("le miroir n'a pas pu être créé")
|
||||
}
|
||||
|
|
@ -221,7 +270,8 @@ func TestUnMiroirExistantSertQuandLAmontEstMuet(t *testing.T) {
|
|||
// relais, qui donnera au client la vraie erreur du réseau plutôt qu'une erreur
|
||||
// inventée ici.
|
||||
func TestAucunMiroirEtAucunAmont(t *testing.T) {
|
||||
g := &GitMirror{Dir: t.TempDir(), Delai: 20 * time.Second}
|
||||
g := &GitMirror{
|
||||
PlancherLibre: plancherDEssai, Dir: t.TempDir(), Delai: 20 * time.Second}
|
||||
chemin, pret := g.Assurer(
|
||||
context.Background(), "https://127.0.0.1:1/nexiste/pas.git")
|
||||
if pret {
|
||||
|
|
@ -270,7 +320,8 @@ func TestDepotsDuFichier(t *testing.T) {
|
|||
// arrêter pour lui perdrait le travail déjà fait.
|
||||
func TestUnDepotEnEchecNEmportePasLesAutres(t *testing.T) {
|
||||
bon, _ := amontGit(t)
|
||||
g := &GitMirror{Dir: t.TempDir(), Delai: 30 * time.Second}
|
||||
g := &GitMirror{
|
||||
PlancherLibre: plancherDEssai, Dir: t.TempDir(), Delai: 30 * time.Second}
|
||||
depots := []string{
|
||||
bon,
|
||||
"https://127.0.0.1:1/absent.git",
|
||||
|
|
@ -293,7 +344,8 @@ func TestUnDepotEnEchecNEmportePasLesAutres(t *testing.T) {
|
|||
// être servie, ce qui est tout l'objet de l'avance.
|
||||
func TestApresPrefetchLeMiroirEstPret(t *testing.T) {
|
||||
amont, _ := amontGit(t)
|
||||
g := &GitMirror{Dir: t.TempDir(), Delai: 30 * time.Second}
|
||||
g := &GitMirror{
|
||||
PlancherLibre: plancherDEssai, Dir: t.TempDir(), Delai: 30 * time.Second}
|
||||
if r, e := g.Prefetch(
|
||||
context.Background(), []string{amont}, 2, nil,
|
||||
); r != 1 || e != 0 {
|
||||
|
|
@ -331,7 +383,8 @@ func TestLePlancherRefuseUnMiroirDeplus(t *testing.T) {
|
|||
// qui a changé, et le refuser priverait de tout ce qui est déjà là.
|
||||
func TestLePlancherNEmpechePasDeServirLexistant(t *testing.T) {
|
||||
amont, _ := amontGit(t)
|
||||
g := &GitMirror{Dir: t.TempDir(), Delai: 30 * time.Second}
|
||||
g := &GitMirror{
|
||||
PlancherLibre: plancherDEssai, Dir: t.TempDir(), Delai: 30 * time.Second}
|
||||
if _, pret := g.Assurer(context.Background(), amont); !pret {
|
||||
t.Fatal("le miroir n'a pas pu être créé")
|
||||
}
|
||||
|
|
@ -350,7 +403,8 @@ func TestLePlancherNEmpechePasDeServirLexistant(t *testing.T) {
|
|||
// mesure ne peut pas avoir un chemin muet.
|
||||
func TestCeQueLeMiroirSertEstCompte(t *testing.T) {
|
||||
amont, _ := amontGit(t)
|
||||
g := &GitMirror{Dir: t.TempDir(), Delai: 30 * time.Second}
|
||||
g := &GitMirror{
|
||||
PlancherLibre: plancherDEssai, Dir: t.TempDir(), Delai: 30 * time.Second}
|
||||
chemin, pret := g.Assurer(context.Background(), amont)
|
||||
if !pret {
|
||||
t.Fatal("le miroir n'a pas pu être créé")
|
||||
|
|
@ -383,7 +437,8 @@ func TestCeQueLeMiroirSertEstCompte(t *testing.T) {
|
|||
// La liste est triée par TAILLE : c'est ce qu'on cherche quand on surveille
|
||||
// la place à la main, et trois dépôts font les trois quarts du total.
|
||||
func TestLesDepotsSontTriesParTaille(t *testing.T) {
|
||||
g := &GitMirror{Dir: t.TempDir()}
|
||||
g := &GitMirror{
|
||||
PlancherLibre: plancherDEssai, Dir: t.TempDir()}
|
||||
for nom, poids := range map[string]int{
|
||||
"h/petit.git": 10, "h/gros.git": 5000, "h/moyen.git": 500,
|
||||
} {
|
||||
|
|
@ -415,7 +470,8 @@ func TestLesDepotsSontTriesParTaille(t *testing.T) {
|
|||
// l'effacement doit rester DANS les miroirs — un appel mal formé ne doit pas
|
||||
// pouvoir emporter autre chose.
|
||||
func TestRetirerRefuseCeQuiEstDehors(t *testing.T) {
|
||||
g := &GitMirror{Dir: t.TempDir()}
|
||||
g := &GitMirror{
|
||||
PlancherLibre: plancherDEssai, Dir: t.TempDir()}
|
||||
dehors := filepath.Join(t.TempDir(), "ailleurs.git")
|
||||
if err := os.MkdirAll(dehors, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -432,7 +488,8 @@ func TestRetirerRefuseCeQuiEstDehors(t *testing.T) {
|
|||
|
||||
func TestRetirerEffaceLeMiroir(t *testing.T) {
|
||||
amont, _ := amontGit(t)
|
||||
g := &GitMirror{Dir: t.TempDir(), Delai: 30 * time.Second}
|
||||
g := &GitMirror{
|
||||
PlancherLibre: plancherDEssai, Dir: t.TempDir(), Delai: 30 * time.Second}
|
||||
chemin, pret := g.Assurer(context.Background(), amont)
|
||||
if !pret {
|
||||
t.Fatal("le miroir n'a pas pu être créé")
|
||||
|
|
@ -458,9 +515,10 @@ func TestRetirerEffaceLeMiroir(t *testing.T) {
|
|||
func TestUnRafraichissementNattendPasCommeUnClonage(t *testing.T) {
|
||||
amont, srv := amontGit(t)
|
||||
g := &GitMirror{
|
||||
Dir: t.TempDir(),
|
||||
Delai: 30 * time.Minute,
|
||||
DelaiMaj: 2 * time.Second,
|
||||
PlancherLibre: plancherDEssai,
|
||||
Dir: t.TempDir(),
|
||||
Delai: 30 * time.Minute,
|
||||
DelaiMaj: 2 * time.Second,
|
||||
}
|
||||
if _, pret := g.Assurer(context.Background(), amont); !pret {
|
||||
t.Fatal("le miroir n'a pas pu être créé")
|
||||
|
|
|
|||
|
|
@ -355,7 +355,8 @@ func amontGitEnPanne(t *testing.T) (string, string, *int64, *atomic.Bool) {
|
|||
func TestUnAmontConnuMuetNeRafraichitPasLeMiroir(t *testing.T) {
|
||||
depot, appels, _ := amontGitCompte(t)
|
||||
g := &GitMirror{
|
||||
Dir: t.TempDir(), Delai: 30 * time.Second, Muets: NouvelleJoignabilite(),
|
||||
PlancherLibre: plancherDEssai,
|
||||
Dir: t.TempDir(), Delai: 30 * time.Second, Muets: NouvelleJoignabilite(),
|
||||
}
|
||||
if _, pret := g.Assurer(context.Background(), depot); !pret {
|
||||
t.Fatal("le miroir n'a pas pu être créé")
|
||||
|
|
@ -377,7 +378,8 @@ func TestUnAmontConnuMuetNeRafraichitPasLeMiroir(t *testing.T) {
|
|||
func TestUneSondeRefuseeRetientLHoteEtSertLeMiroir(t *testing.T) {
|
||||
depot, appels, _ := amontGitCompte(t)
|
||||
g := &GitMirror{
|
||||
Dir: t.TempDir(), Delai: 30 * time.Second, Muets: NouvelleJoignabilite(),
|
||||
PlancherLibre: plancherDEssai,
|
||||
Dir: t.TempDir(), Delai: 30 * time.Second, Muets: NouvelleJoignabilite(),
|
||||
}
|
||||
if _, pret := g.Assurer(context.Background(), depot); !pret {
|
||||
t.Fatal("le miroir n'a pas pu être créé")
|
||||
|
|
@ -414,7 +416,8 @@ func sansMandataire(*http.Request) (*url.URL, error) { return nil, nil }
|
|||
func miroirCree(t *testing.T, depot string) string {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
creation := &GitMirror{Dir: dir, Delai: 30 * time.Second}
|
||||
creation := &GitMirror{
|
||||
PlancherLibre: plancherDEssai, Dir: dir, Delai: 30 * time.Second}
|
||||
if _, pret := creation.Assurer(context.Background(), depot); !pret {
|
||||
t.Fatal("le miroir n'a pas pu être créé")
|
||||
}
|
||||
|
|
@ -427,7 +430,8 @@ func miroirCree(t *testing.T, depot string) string {
|
|||
func TestUnRafraichissementManqueSurUnAmontJoignableEstRetente(t *testing.T) {
|
||||
depot, appels, panne := amontGitCompte(t)
|
||||
g := &GitMirror{
|
||||
Dir: miroirCree(t, depot), Delai: 30 * time.Second, Frais: time.Minute,
|
||||
PlancherLibre: plancherDEssai,
|
||||
Dir: miroirCree(t, depot), Delai: 30 * time.Second, Frais: time.Minute,
|
||||
Muets: NouvelleJoignabilite(),
|
||||
Sonder: func(context.Context, string) error { return nil },
|
||||
}
|
||||
|
|
@ -452,7 +456,8 @@ func TestUnRafraichissementManqueSurUnAmontQuiRefuseEstNote(t *testing.T) {
|
|||
depot, appels, panne := amontGitCompte(t)
|
||||
var sondes int64
|
||||
g := &GitMirror{
|
||||
Dir: miroirCree(t, depot), Delai: 30 * time.Second, Frais: time.Minute,
|
||||
PlancherLibre: plancherDEssai,
|
||||
Dir: miroirCree(t, depot), Delai: 30 * time.Second, Frais: time.Minute,
|
||||
Muets: NouvelleJoignabilite(),
|
||||
// La sonde d'avant la mise à jour passe ; celle d'après trouve
|
||||
// l'amont tombé entre les deux.
|
||||
|
|
@ -519,7 +524,8 @@ func pousserUnCommit(t *testing.T, nu string) string {
|
|||
func TestUnRafraichissementEn500NeFigePasLeMiroir(t *testing.T) {
|
||||
depot, nu, _, panne := amontGitEnPanne(t)
|
||||
g := &GitMirror{
|
||||
Dir: miroirCree(t, depot), Delai: 30 * time.Second, Frais: time.Minute,
|
||||
PlancherLibre: plancherDEssai,
|
||||
Dir: miroirCree(t, depot), Delai: 30 * time.Second, Frais: time.Minute,
|
||||
Muets: NouvelleJoignabilite(), Mandataire: sansMandataire,
|
||||
}
|
||||
panne.Store(true)
|
||||
|
|
|
|||
|
|
@ -312,5 +312,60 @@ class LeVerrouEtLeMenu(unittest.TestCase):
|
|||
self.assertTrue(TRANSLATIONS[cle].get("en"))
|
||||
|
||||
|
||||
class LaCreationDeLaVmEstPrivilegiee(unittest.TestCase):
|
||||
"""Le dossier des images appartient à root en 755 sur une installation
|
||||
ordinaire de libvirt.
|
||||
|
||||
Sans sudo, la CLI s'arrête à l'étape 1 sur « Permission refusée » avant
|
||||
d'avoir rien créé : le test ne peut pas tourner du tout, pas même échouer
|
||||
utilement. Le menu — le chemin du produit — passe sudo dès que ce n'est
|
||||
pas un essai à blanc, et c'est cette forme qui est reprise ici.
|
||||
"""
|
||||
|
||||
def _module(self):
|
||||
sys.path.insert(0, str(RACINE / "long_test"))
|
||||
import install_nixos
|
||||
|
||||
return install_nixos
|
||||
|
||||
def test_the_real_run_asks_for_privilege(self):
|
||||
mod = self._module()
|
||||
vu = {}
|
||||
|
||||
class Fini:
|
||||
returncode = 1
|
||||
|
||||
def faux_run(argv, **kw):
|
||||
vu["argv"] = argv
|
||||
return Fini()
|
||||
|
||||
vrai = mod.subprocess.run
|
||||
self.addCleanup(setattr, mod.subprocess, "run", vrai)
|
||||
mod.subprocess.run = faux_run
|
||||
mod.creer_vm("essai", None, dry_run=False)
|
||||
self.assertEqual("sudo", vu["argv"][0])
|
||||
self.assertIn("deploy_qemu.py", " ".join(vu["argv"]))
|
||||
|
||||
def test_the_dry_run_asks_for_none(self):
|
||||
"""Un essai qui n'écrit rien n'a aucune raison de demander un mot de
|
||||
passe, et le menu ne le demande pas non plus."""
|
||||
mod = self._module()
|
||||
vu = []
|
||||
vrai = mod.dire
|
||||
self.addCleanup(setattr, mod, "dire", vrai)
|
||||
mod.dire = lambda texte, journal=None: vu.append(texte)
|
||||
nom, uuid = mod.creer_vm("essai", None, dry_run=True)
|
||||
self.assertEqual(("essai", ""), (nom, uuid))
|
||||
ligne = " ".join(vu)
|
||||
self.assertIn("deploy_qemu.py", ligne)
|
||||
self.assertNotIn("sudo", ligne)
|
||||
|
||||
def test_its_sibling_does_the_same(self):
|
||||
"""descente.py crée l'étage 1 de la même façon, et l'oubli y était
|
||||
le même : un correctif posé d'un seul côté laisse l'autre au mur."""
|
||||
src = (RACINE / "long_test/descente.py").read_text(encoding="utf-8")
|
||||
self.assertIn('([] if self.dry_run else ["sudo"]) + [', src)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Reference in a new issue