ЖУРНАЛИ БЕЗ СТЕЛІ. На запитання «чому не пишемо в /var/log» правильна відповідь — «бо в контейнері це не той шар»: файл усередині зникає при перестворенні, невидимий для docker logs і вимагає власної ротації. Але за питанням стояла справжня вада: драйвер json-file був налаштований порожньо, тобто НЕ КРУТИВ НІЧОГО. Проксі за добу набрав 27 МБ; до повного диска були місяці, і першим ліг би Postgres. Тепер 10 МБ × 3 файли на службу — близько 250 МБ на інсталяцію, і драйвер міняється однією змінною (journald, syslog) для тих, кому потрібні справжні файли або чужий збирач. САМОПЕРЕВІРКА ЗОНДА чекала хвилину й одного разу вже дала хибне червоне: зонд зареєструвався, просто пізніше — перший старт припадає на найзавантаженішу мить установки. Тепер три хвилини з повідомленням кожні півхвилини: мовчазна пауза невідрізненна від зависання, а хибне червоне після успішної установки коштує години пошуку неіснуючої поломки. GITSTORE: TestDeleteBranchLocal кличе зовнішній git і не мав перевірки на його відсутність — падав у golang:1.25-alpine, тобто саме там, де його проганяють. Сусідній тест таку перевірку має. ЕТАП 13 у ROADMAP: реєстр образів, релізи, пакети. Записано, чому порядок саме такий (пакет без реєстру ставив би «зберіть самі») і чого треба досягти до першого тегу — оновлення з версії на версію не перевіряв ніхто, а ламається найчастіше саме воно.
151 lines
6.8 KiB
Go
151 lines
6.8 KiB
Go
package gitstore
|
||
|
||
import (
|
||
"context"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// Видалення гілки: локально й на «дзеркалі».
|
||
//
|
||
// Дзеркалом тут працює звичайний голий репозиторій на диску, до якого
|
||
// ходимо по file://. Це не спрощення заради тесту: у справжньому push
|
||
// відрізняється лише транспорт, а перевіряється тут інше — що видалення
|
||
// адресне. Тобто що після нього на тому кінці зникає РІВНО названа
|
||
// гілка, а сусідня, про яку ніхто не говорив, лишається на місці. Саме
|
||
// це відрізняє нашу поведінку від prune, якого ми свідомо не робимо.
|
||
|
||
func gitOut(t *testing.T, dir string, args ...string) string {
|
||
t.Helper()
|
||
cmd := exec.Command("git", args...)
|
||
cmd.Dir = dir
|
||
out, err := cmd.CombinedOutput()
|
||
if err != nil {
|
||
t.Fatalf("git %s: %v\n%s", strings.Join(args, " "), err, out)
|
||
}
|
||
return string(out)
|
||
}
|
||
|
||
func TestDeleteBranchLocal(t *testing.T) {
|
||
// Сам gitstore працює на go-git і зовнішнього git не потребує — а
|
||
// от перевірка результату (`git branch --list`) потребує. Без цієї
|
||
// умови тест падає там, де git не встановлено: у голому
|
||
// golang:1.25-alpine, тобто саме в контейнері, де його й проганяють.
|
||
// Сусідній тест таку умову має, цей її не мав.
|
||
if _, err := exec.LookPath("git"); err != nil {
|
||
t.Skip("для звірки гілок потрібен git у PATH")
|
||
}
|
||
s := New(t.TempDir())
|
||
|
||
write(t, s, "device/sw-01-10.0.0.1", "sw-01/running.cfg", "hostname sw-01\n")
|
||
write(t, s, "device/sw-02-10.0.0.2", "sw-02/running.cfg", "hostname sw-02\n")
|
||
|
||
removed, err := s.DeleteBranch("t.git", "device/sw-01-10.0.0.1")
|
||
if err != nil {
|
||
t.Fatalf("видалення: %v", err)
|
||
}
|
||
if !removed {
|
||
t.Fatal("гілка була — мало повернутись true")
|
||
}
|
||
|
||
refs := gitOut(t, filepath.Join(s.Root(), "t.git"), "branch", "--list")
|
||
if strings.Contains(refs, "sw-01") {
|
||
t.Errorf("гілка лишилась: %s", refs)
|
||
}
|
||
if !strings.Contains(refs, "sw-02") {
|
||
t.Errorf("сусідню гілку не мало зачепити: %s", refs)
|
||
}
|
||
|
||
// Повтор — не помилка: черга видалень повторює спробу, доки не
|
||
// дійде до дзеркала, і другий прохід не має падати.
|
||
removed, err = s.DeleteBranch("t.git", "device/sw-01-10.0.0.1")
|
||
if err != nil {
|
||
t.Fatalf("повторне видалення: %v", err)
|
||
}
|
||
if removed {
|
||
t.Error("гілки вже не було — мало повернутись false")
|
||
}
|
||
|
||
// Репозиторію немає взагалі — теж не помилка: хост могли видалити
|
||
// до першого зібраного конфігу.
|
||
if _, err := s.DeleteBranch("немає.git", "device/x"); err != nil {
|
||
t.Errorf("відсутній репозиторій: %v", err)
|
||
}
|
||
}
|
||
|
||
// localURLPath робить із шляху файлової системи шлях для file://.
|
||
func localURLPath(dir string) string {
|
||
p := filepath.ToSlash(dir)
|
||
if !strings.HasPrefix(p, "/") {
|
||
p = "/" + p
|
||
}
|
||
return p
|
||
}
|
||
|
||
func TestDeleteRemoteRefIsTargeted(t *testing.T) {
|
||
if _, err := exec.LookPath("git"); err != nil {
|
||
t.Skip("для file:// потрібен git у PATH")
|
||
}
|
||
root := t.TempDir()
|
||
s := New(root)
|
||
|
||
write(t, s, "device/sw-01-10.0.0.1", "sw-01/running.cfg", "hostname sw-01\n")
|
||
write(t, s, "device/sw-02-10.0.0.2", "sw-02/running.cfg", "hostname sw-02\n")
|
||
|
||
// «Дзеркало».
|
||
remoteDir := filepath.Join(root, "mirror.git")
|
||
gitOut(t, root, "init", "--bare", remoteDir)
|
||
// Приймати push у поточну гілку голого репозиторію нікому не
|
||
// заважає, але Forgejo цього не вимагає — вирівнюємо поведінку.
|
||
gitOut(t, remoteDir, "config", "receive.denyCurrentBranch", "ignore")
|
||
|
||
// Три скісні, не дві: у file:// одразу за ними йде ХОСТ, і на
|
||
// Windows "file://C:/..." означає репозиторій на машині "C:".
|
||
// На Linux провідна скісна в шляху вже є, тому помилки там не
|
||
// видно — тест зеленів, доки його не прогнали на Windows.
|
||
rem := Remote{URL: "file://" + localURLPath(remoteDir), Auth: AuthNone}
|
||
if _, err := s.Mirror(context.Background(), "t.git", rem); err != nil {
|
||
t.Fatalf("перший push: %v", err)
|
||
}
|
||
if out := gitOut(t, remoteDir, "branch", "--list"); !strings.Contains(out, "sw-01") ||
|
||
!strings.Contains(out, "sw-02") {
|
||
t.Fatalf("дзеркало не отримало обидві гілки: %s", out)
|
||
}
|
||
|
||
// Хост видалено: локальна гілка знята, і на дзеркало йде видалення
|
||
// рівно її.
|
||
if _, err := s.DeleteBranch("t.git", "device/sw-01-10.0.0.1"); err != nil {
|
||
t.Fatalf("локальне видалення: %v", err)
|
||
}
|
||
if err := s.DeleteRemoteRefs(context.Background(), "t.git", rem,
|
||
[]string{"device/sw-01-10.0.0.1"}); err != nil {
|
||
t.Fatalf("видалення на дзеркалі: %v", err)
|
||
}
|
||
|
||
out := gitOut(t, remoteDir, "branch", "--list")
|
||
if strings.Contains(out, "sw-01") {
|
||
t.Errorf("гілка лишилась на дзеркалі: %s", out)
|
||
}
|
||
if !strings.Contains(out, "sw-02") {
|
||
t.Errorf("сусідня гілка мала лишитись — ми її не називали: %s", out)
|
||
}
|
||
|
||
// Повтор після успіху. Черга повторює спробу після будь-якої
|
||
// невизначеності (обрив на відповіді, перезапуск процесу), і
|
||
// «такої гілки там немає» для видалення означає успіх, а не збій.
|
||
if err := s.DeleteRemoteRefs(context.Background(), "t.git", rem,
|
||
[]string{"device/sw-01-10.0.0.1"}); err != nil {
|
||
t.Errorf("повторне видалення на дзеркалі: %v", err)
|
||
}
|
||
|
||
// І головне: звичайне дзеркалення після цього не намагається
|
||
// повернути видалену гілку назад.
|
||
if _, err := s.Mirror(context.Background(), "t.git", rem); err != nil {
|
||
t.Fatalf("push після видалення: %v", err)
|
||
}
|
||
if out := gitOut(t, remoteDir, "branch", "--list"); strings.Contains(out, "sw-01") {
|
||
t.Errorf("push повернув видалену гілку: %s", out)
|
||
}
|
||
}
|