Zwei Teile: Release bauen und in Gitea veroeffentlichen, und das System in vier Stufen durchtesten — Rauchtest, Rundlauf, Agent, Proxmox. Jede Stufe endet mit dem, was danach belegt ist, und der Rundlauf hat eine Gegenprobe: nicht leeres Ziel, rm -rf gegen ein gehaertetes Repository und ein gekippter Block muessen scheitern beziehungsweise erkannt werden. Ein Test, der nur den Erfolgsfall zeigt, belegt wenig. Der Vorschlag fuer den ersten Tag ist v1.0.0-rc1, nicht v1.0.0: Solange Windows-Dienst, systemd-Einheit und der Proxmox-Boot ungeprueft sind, waere 1.0.0 eine Zusage, die diese drei Punkte nicht deckt. Jeder genannte Endpunkt wurde gegen den eingefrorenen API-Vertrag gehalten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
141 lines
5.3 KiB
Go
141 lines
5.3 KiB
Go
// Package scripts prüft die Vollständigkeit der Auslieferung (Phase 23).
|
|
//
|
|
// Der Test läuft im gewöhnlichen Testlauf mit und braucht kein gebautes Paket:
|
|
// Er prüft die **Bestandteile**, aus denen es entsteht. Ein Paket zu bauen,
|
|
// nur um es zu prüfen, dauerte Minuten und liefe deshalb bald nicht mehr mit.
|
|
package scripts
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// repositoryRoot ist der Pfad zum Wurzelverzeichnis.
|
|
const repositoryRoot = ".."
|
|
|
|
// TestReleaseDocumentationIsComplete prueft die Liste aus §25.
|
|
//
|
|
// Der Umsetzungsplan nennt zwoelf Bestandteile, die ein Auslieferungspaket
|
|
// enthalten muss. Fuenf davon sind Dokumente, und die schreibt man einmal —
|
|
// danach faellt es niemandem auf, wenn eines beim Umbenennen verlorengeht.
|
|
// Genau deshalb steht die Liste hier und nicht nur in einer Aufzaehlung.
|
|
func TestReleaseDocumentationIsComplete(testInstance *testing.T) {
|
|
requiredDocuments := map[string]string{
|
|
"docs/installation.md": "Installationsanleitung",
|
|
"docs/recovery-runbook.md": "Wiederherstellungsdokumentation",
|
|
"docs/security-guide.md": "Sicherheitsleitfaden",
|
|
"docs/api.md": "API-Dokumentation",
|
|
"docs/troubleshooting.md": "Stoerungsleitfaden",
|
|
"docs/release-howto.md": "Anleitung zum Veroeffentlichen und Testen",
|
|
"docs/agent-installation.md": "Installationsanleitung der Agenten",
|
|
"CHANGELOG.md": "Aenderungsliste",
|
|
"README.md": "Ueberblick",
|
|
}
|
|
|
|
for documentPath, documentPurpose := range requiredDocuments {
|
|
fileInformation, statError := os.Stat(filepath.Join(repositoryRoot, documentPath))
|
|
if statError != nil {
|
|
testInstance.Errorf("%s fehlt (%s): %v", documentPath, documentPurpose, statError)
|
|
|
|
continue
|
|
}
|
|
|
|
// Eine leere Datei erfüllt die Liste formal und hilft niemandem. Die
|
|
// Grenze ist bewusst niedrig — sie fängt die versehentlich angelegte
|
|
// Hülle, nicht ein knappes Dokument.
|
|
if fileInformation.Size() < 500 {
|
|
testInstance.Errorf("%s ist mit %d Byte zu klein für %s",
|
|
documentPath, fileInformation.Size(), documentPurpose)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestReleaseScriptCoversEveryCommand prueft, dass kein Programm fehlt.
|
|
//
|
|
// Ein Programm, das gebaut, aber nicht ausgeliefert wird, faellt erst beim
|
|
// Kunden auf — und dann fehlt genau das Werkzeug, das er gerade braucht.
|
|
func TestReleaseScriptCoversEveryCommand(testInstance *testing.T) {
|
|
buildScript := readReleaseScript(testInstance)
|
|
|
|
commandDirectories := []string{"apps/api/cmd", "apps/agent/cmd"}
|
|
|
|
for _, commandDirectory := range commandDirectories {
|
|
directoryEntries, readError := os.ReadDir(filepath.Join(repositoryRoot, commandDirectory))
|
|
if readError != nil {
|
|
testInstance.Fatalf("%s ließ sich nicht lesen: %v", commandDirectory, readError)
|
|
}
|
|
|
|
for _, singleEntry := range directoryEntries {
|
|
if !singleEntry.IsDir() {
|
|
continue
|
|
}
|
|
|
|
commandPath := commandDirectory + "/" + singleEntry.Name()
|
|
|
|
if !strings.Contains(buildScript, commandPath) {
|
|
testInstance.Errorf("das Programm %s wird gebaut, aber nicht ausgeliefert.\n"+
|
|
" Entweder in scripts/build-release.sh aufnehmen oder begründen, "+
|
|
"warum es nicht ins Paket gehört.", singleEntry.Name())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestReleaseScriptShipsEveryTargetPlatform prueft die Zielplattformen.
|
|
//
|
|
// Dieselben drei, die `make cross-build` bei jedem Lauf uebersetzt. Eine
|
|
// Plattform, die uebersetzt, aber nicht ausgeliefert wird, ist eine Zusage ohne
|
|
// Gegenstueck.
|
|
func TestReleaseScriptShipsEveryTargetPlatform(testInstance *testing.T) {
|
|
buildScript := readReleaseScript(testInstance)
|
|
|
|
requiredPlatforms := []string{"linux/amd64", "linux/arm64", "windows/amd64"}
|
|
|
|
for _, platformName := range requiredPlatforms {
|
|
if !strings.Contains(buildScript, platformName) {
|
|
testInstance.Errorf("die Zielplattform %s wird nicht ausgeliefert", platformName)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestReleaseScriptIncludesMigrationsAndInterface prueft die uebrigen Bestandteile.
|
|
func TestReleaseScriptIncludesMigrationsAndInterface(testInstance *testing.T) {
|
|
buildScript := readReleaseScript(testInstance)
|
|
|
|
requiredParts := map[string]string{
|
|
"migrations/*.sql": "Migrationsdateien",
|
|
"migrations/checksums.txt": "Prüfsummen der Migrationen",
|
|
"apps/web/dist": "Weboberfläche",
|
|
"docs/": "Dokumentation",
|
|
"CHANGELOG.md": "Änderungsliste",
|
|
"SHA256SUMS": "Prüfsummen des Pakets",
|
|
}
|
|
|
|
for partPattern, partPurpose := range requiredParts {
|
|
if !strings.Contains(buildScript, partPattern) {
|
|
testInstance.Errorf("im Auslieferungsskript fehlt %s (%s)", partPattern, partPurpose)
|
|
}
|
|
}
|
|
|
|
// Ohne diesen Schalter bindet Go gegen die libc des Bausystems. Der Start
|
|
// scheitert dann auf einer älteren Distribution mit einer Meldung über
|
|
// GLIBC — die niemand einem Backupprogramm zuordnet.
|
|
if !strings.Contains(buildScript, "CGO_ENABLED=0") {
|
|
testInstance.Error("die Programme werden nicht statisch gebunden (CGO_ENABLED=0 fehlt)")
|
|
}
|
|
}
|
|
|
|
// readReleaseScript liest das Auslieferungsskript.
|
|
func readReleaseScript(testInstance *testing.T) string {
|
|
testInstance.Helper()
|
|
|
|
scriptContent, readError := os.ReadFile(filepath.Join(repositoryRoot, "scripts/build-release.sh"))
|
|
if readError != nil {
|
|
testInstance.Fatalf("das Auslieferungsskript ließ sich nicht lesen: %v", readError)
|
|
}
|
|
|
|
return string(scriptContent)
|
|
}
|