403 lines
20 KiB
GDScript
403 lines
20 KiB
GDScript
extends SceneTree
|
|
## Editor-Tests (headless): Selbst-Update — Karte t_d95f087a.
|
|
## Ausführen: godot --headless --path . --script res://tests/test_updater.gd
|
|
## Exit 0 = alles grün. Läuft OHNE UI (EditorUpdater + echte git-Repos in
|
|
## user://test_updater):
|
|
## 1. Check-Funktion mit ECHTEN Repos: gleich (up_to_date), divergiert
|
|
## (update_available), nicht erreichbar (unreachable) — kein Mock, echte
|
|
## git-Prozesse gegen ein Bare-Repo / gegen einen abgelehnten Port.
|
|
## 2. Banner-Logik (Text, kurze SHAs).
|
|
## 3. Import-Abbruch-Pfad: faked Import-Fehler (Exit-Code != 0 UND Parse-
|
|
## -Error-Schreiber mit Exit 0) → kein ok, kein Restart (Restart wird nur
|
|
## NACH ok angestoßen — hier wird bewusst NUR der ok-Pfad geprüft).
|
|
## 4. Restart-Auflösung: cfg-Kommando gewinnt, Default ist self/command.
|
|
|
|
var failures := 0
|
|
var checks := 0
|
|
|
|
const TEST_DIR_PREFIX := "user://test_updater"
|
|
var base := ""
|
|
var work := ""
|
|
|
|
|
|
func _init() -> void:
|
|
_cleanup_old()
|
|
# Karte t_1e82f747: Time.get_ticks_msec() ist nur die Zeit seit Godot-Start
|
|
# (kleine Zahl, z.B. 115) — bei jedem CLI-Run derselbe Wert! Kollidierte
|
|
# Namen mit nicht löschbaren Alt-Läufen → git init lief in einer ALTEN
|
|
# Sitzung (remote existiert schon, alte Dateien im Baum → rote Setup-Zeilen).
|
|
# Deswegen: Unix-Zeit + ticks_usec = garantiert einzigartig pro Prozess/Run.
|
|
base = ProjectSettings.globalize_path(TEST_DIR_PREFIX + "_" +
|
|
str(int(Time.get_unix_time_from_system())) + "_" +
|
|
str(Time.get_ticks_usec()))
|
|
DirAccess.make_dir_recursive_absolute(base)
|
|
_run_tests()
|
|
_test_ls_remote_warnzeile()
|
|
_force_writable(base)
|
|
_cleanup_dir(base) # best effort — git-Pack-Dateien können read-only sein
|
|
if failures == 0:
|
|
print("ALL OK: %d checks" % checks)
|
|
quit(0)
|
|
else:
|
|
print("FAILED: %d/%d checks" % [failures, checks])
|
|
quit(1)
|
|
|
|
|
|
func _check(cond: bool, label: String) -> void:
|
|
checks += 1
|
|
if cond:
|
|
print(" ok: %s" % label)
|
|
else:
|
|
failures += 1
|
|
print(" FAIL: %s" % label)
|
|
|
|
|
|
## Best effort: alte Läufe aufräumen (git macht Pack-Dateien read-only —
|
|
## dann bleibt der Ordner liegen; der LAUF nutzt deshalb IMMER ein frisches,
|
|
## Zeit-stempel-basiertes Verzeichnis und hängt nie an Überbleibseln).
|
|
func _cleanup_old() -> void:
|
|
var root := DirAccess.open(ProjectSettings.globalize_path("user://"))
|
|
if root == null:
|
|
return
|
|
for name in root.get_directories():
|
|
if name.begins_with("test_updater"):
|
|
var alt := ProjectSettings.globalize_path("user://").path_join(name)
|
|
_force_writable(alt)
|
|
_cleanup_dir(alt)
|
|
|
|
|
|
## Karte t_1e82f747: git macht .git-Packs read-only, dann schlägt
|
|
## DirAccess.remove_absolute fehlt → der Ordner bleibt liegen und KOLLIDIERT
|
|
## mit künftigen Läufen (derselbe ticks_msec-Name!). chmod -R u+w macht die
|
|
## Bäume löschbar. Linux/OSX-Fallback per /usr/bin/chmod (Tests sind CLI,
|
|
## Windows-spezifische ACLs sind hier nicht der Fall).
|
|
func _force_writable(path: String) -> void:
|
|
if not DirAccess.dir_exists_absolute(path):
|
|
return
|
|
var chmod := "/usr/bin/chmod"
|
|
if not FileAccess.file_exists(chmod):
|
|
chmod = "chmod"
|
|
# WICHTIG: das 2-Argument-OS.execute ist in Godot 4 ASYNCHRON (gibt nur
|
|
# eine PID zurück) — ohne output-Array wartet der Lauf nicht, und die
|
|
# Löschung passiert vor dem chmod. Mit output-Array blockiert er.
|
|
var out: PackedStringArray = []
|
|
OS.execute(chmod, PackedStringArray(["-R", "u+w", path]), out)
|
|
|
|
|
|
func _cleanup_dir(base_dir: String) -> void:
|
|
if not DirAccess.dir_exists_absolute(base_dir):
|
|
return
|
|
var d := DirAccess.open(base_dir)
|
|
if d == null:
|
|
return
|
|
# Karte t_1e82f747: get_files()/get_directories() überspringen defaultmäßig
|
|
# VERSTECKTE Ordner — und .git ist versteckt. Ohne dieses Flag blieb der
|
|
# Repo-Kern liegen, der Basisordner war nie leer und löschbar.
|
|
d.include_hidden = true
|
|
for file in d.get_files():
|
|
DirAccess.remove_absolute(base_dir.path_join(file))
|
|
for sub in d.get_directories():
|
|
_cleanup_dir(base_dir.path_join(sub))
|
|
DirAccess.remove_absolute(base_dir)
|
|
# Rückfallebene: git-Pack-Bäume haben teils noch andere Sonderfälle — wenn
|
|
# der Ordner trotzdem liegt, räumt rm -rf (CLI-Umgebung) garantiert ab.
|
|
if DirAccess.dir_exists_absolute(base_dir):
|
|
var out: PackedStringArray = []
|
|
OS.execute("/bin/rm", PackedStringArray(["-rf", base_dir]), out)
|
|
|
|
|
|
func _git(dir: String, args: Array) -> Dictionary:
|
|
var packed := PackedStringArray()
|
|
for a in args:
|
|
packed.append(str(a))
|
|
var r := EditorUpdater.git_run("git", dir, packed)
|
|
if not bool(r["ok"]):
|
|
# Karte t_1e82f747: rote Setup-Nummern ohne Grund sind wertlos —
|
|
# der echte git-Fehlertext gehört in die Ausgabe.
|
|
print(" git [%s] %s → code=%s: %s" % [dir, " ".join(args),
|
|
r["code"], str(r["output"]).strip_edges()])
|
|
return r
|
|
|
|
|
|
func _write_file(path: String, text: String) -> void:
|
|
var f := FileAccess.open(path, FileAccess.WRITE)
|
|
f.store_string(text)
|
|
f.close()
|
|
|
|
|
|
func _file_text(path: String) -> String:
|
|
var f := FileAccess.open(path, FileAccess.READ)
|
|
if f == null:
|
|
return "<fehlt>"
|
|
var text := f.get_as_text()
|
|
f.close()
|
|
return text
|
|
|
|
|
|
## Nur getrackte Verschmutzungen: false, wenn eine NICHT-untracked Zeile in
|
|
## git status --porcelain auftaucht (halb gepoppter Konflikt hinterlässt
|
|
## genau solche Zeilen).
|
|
func _clean_tracked(dir: String) -> bool:
|
|
var r := _git(dir, ["status", "--porcelain"])
|
|
for line in str(r["output"]).split("\n"):
|
|
var text := str(line).strip_edges()
|
|
if not text.is_empty() and not text.begins_with("??"):
|
|
return false
|
|
return true
|
|
|
|
|
|
## Anzahl der EIGENEN Auto-Stashes (Kennung "auto: Selbst-Update").
|
|
func _auto_stash_count(git: String, dir: String) -> int:
|
|
var r := EditorUpdater.git_run(git, dir, PackedStringArray([
|
|
"stash", "list", "--pretty=format:%gd|%s"]))
|
|
var count := 0
|
|
for line in str(r["output"]).split("\n"):
|
|
var parts := str(line).strip_edges().split("|", true, 1)
|
|
if parts.size() == 2 and str(parts[1]).contains("auto: Selbst-Update"):
|
|
count += 1
|
|
return count
|
|
|
|
|
|
func _run_tests() -> void:
|
|
var git := "git"
|
|
|
|
# --- echte Repos aufbauen: origin.git (bare) + work (Klon mit main) ---
|
|
var origin := base.path_join("origin.git")
|
|
_check(bool(_git(base, ["init", "--bare", "--initial-branch=main", origin])["ok"]),
|
|
"Setup: Bare-Repo origin.git angelegt")
|
|
work = base.path_join("work")
|
|
_check(bool(_git(base, ["init", "--initial-branch=main", work])["ok"]),
|
|
"Setup: Arbeits-Klon angelegt")
|
|
_check(bool(_git(work, ["remote", "add", "origin", origin])["ok"]),
|
|
"Setup: remote origin gesetzt")
|
|
_git(work, ["config", "user.email", "test@example.local"])
|
|
_git(work, ["config", "user.name", "Update-Test"])
|
|
_write_file(work.path_join("a.txt"), "Version 1\n")
|
|
_check(bool(_git(work, ["add", "a.txt"])["ok"]), "Setup: Datei gestagt")
|
|
_check(bool(_git(work, ["commit", "-m", "v1", "a.txt"])["ok"]), "Setup: Commit v1")
|
|
_check(bool(_git(work, ["push", "-u", "origin", "main"])["ok"]),
|
|
"Setup: main nach origin gepusht")
|
|
|
|
var local_sha := EditorUpdater.head_sha(git, work)
|
|
_check(local_sha.length() == 40, "head_sha: 40-Zeichen-SHA gelesen")
|
|
|
|
print("== Check: auf dem neusten Stand ==")
|
|
var up := EditorUpdater.check_update(git, work)
|
|
_check(str(up["status"]) == "up_to_date", "gleiches SHA → up_to_date")
|
|
_check(str(up["local"]) == local_sha and str(up["remote"]) == local_sha,
|
|
"beide SHAs befüllt und identisch")
|
|
|
|
print("== Check: Update verfügbar ==")
|
|
# Fremder Klon pusht einen neuen Commit nach origin — work bleibt hinten.
|
|
var other := base.path_join("other")
|
|
_check(bool(_git(base, ["clone", "-q", origin, other])["ok"]), "Setup: zweiter Klon")
|
|
_git(other, ["config", "user.email", "test@example.local"])
|
|
_git(other, ["config", "user.name", "Update-Test"])
|
|
_write_file(other.path_join("b.txt"), "neue Version\n")
|
|
_git(other, ["add", "b.txt"])
|
|
_git(other, ["commit", "-m", "v2", "b.txt"])
|
|
_check(bool(_git(other, ["push", "origin", "main"])["ok"]), "Setup: v2 gepusht")
|
|
var avail := EditorUpdater.check_update(git, work)
|
|
_check(str(avail["status"]) == "update_available", "origin voraus → update_available")
|
|
_check(str(avail["local"]) == local_sha, "lokaler SHA bleibt (Pull ist NICHT Teil des Checks)")
|
|
_check(str(avail["remote"]) != local_sha, "remote SHA ist der neue")
|
|
|
|
print("== Banner-Logik ==")
|
|
var text := EditorUpdater.banner_text(local_sha, str(avail["remote"]))
|
|
_check(text.contains("Update verfügbar"), "Banner-Text enthält „Update verfügbar“")
|
|
_check(text.contains(local_sha.substr(0, 7)), "Banner-Text enthält kurzen ALT-SHA")
|
|
_check(text.contains(EditorUpdater.short_sha(str(avail["remote"]))), "Banner-Text enthält kurzen NEU-SHA")
|
|
_check(EditorUpdater.short_sha(local_sha).length() == 7, "short_sha = 7 Zeichen")
|
|
|
|
print("== Check: nicht erreichbar ==")
|
|
# Port 1 (bewusst zu) → Verbindung sofort abgelehnt = der Offline-Pfad.
|
|
_git(work, ["remote", "set-url", "origin", "http://127.0.0.1:1/nicht-da.git"])
|
|
var dead := EditorUpdater.check_update(git, work)
|
|
_check(str(dead["status"]) == "unreachable", "Origin tot → unreachable (still, kein Fehler)")
|
|
_check(str(dead["local"]) == local_sha, "lokaler SHA trotzdem bekannt")
|
|
# Falsches Binary → error (kein Repo-Check möglich)
|
|
var bad := EditorUpdater.check_update("gibt-es-nicht-xyz", work)
|
|
_check(str(bad["status"]) == "error", "git nicht gefunden → error (nicht unreachable)")
|
|
|
|
print("== Credential-Guard (Karte t_1457e935): git wird NIE interaktiv ==")
|
|
_check(EditorUpdater.looks_like_auth_error(
|
|
"fatal: could not read Username for 'http://127.0.0.1:13020': No such device or address"),
|
|
"auth-Erkennung: could-not-read-Username")
|
|
_check(EditorUpdater.looks_like_auth_error(
|
|
"remote: Unauthorized\nfatal: Authentication failed for 'http://x/'"),
|
|
"auth-Erkennung: Authentication failed / Unauthorized")
|
|
_check(EditorUpdater.looks_like_auth_error("fatal: could not read Username; terminal prompts disabled"),
|
|
"auth-Erkennung: terminal prompts disabled")
|
|
_check(not EditorUpdater.looks_like_auth_error(
|
|
"fatal: unable to access 'http://127.0.0.1:1/': Connection refused"),
|
|
"auth-Erkennung: Connection refused ist KEIN Auth-Fehler")
|
|
var spawn := EditorUpdater.spawn_argv("git", "/projekt", PackedStringArray(["ls-remote", "origin"]))
|
|
if OS.has_feature("linux"):
|
|
_check(str(spawn[0]) == "/usr/bin/env", "argv: POSIX-Spawn über /usr/bin/env")
|
|
_check(spawn.has("GIT_TERMINAL_PROMPT=0"), "argv: GIT_TERMINAL_PROMPT=0 (kein Terminal-Prompt)")
|
|
_check(spawn.has("GIT_ASKPASS=/bin/true"), "argv: GIT_ASKPASS=/bin/true (überstimmt geerbtes SSH_ASKPASS)")
|
|
_check(spawn.has("-c") and spawn.has("credential.helper=") and spawn.has("credential.helper=store"),
|
|
"argv: Helper-Reset + ausschließlich store")
|
|
_check(spawn.has("core.askPass=/bin/true"), "argv: core.askPass-Fallback gesetzt")
|
|
var hat_home := false
|
|
for eintrag in spawn:
|
|
if str(eintrag).begins_with("HOME="):
|
|
hat_home = true
|
|
_check(hat_home, "argv: HOME aus der Umgebung mitgegeben (kein Hardcode)")
|
|
_check(str(spawn[spawn.size() - 2]) == "ls-remote" and str(spawn[spawn.size() - 1]) == "origin",
|
|
"argv: originale git-Argumente bleiben hinten")
|
|
|
|
print("== Pull ==")
|
|
_git(work, ["remote", "set-url", "origin", origin])
|
|
var pulled := EditorUpdater.pull(git, work)
|
|
_check(bool(pulled["ok"]), "pull --ff-only erfolgreich (v2 wird geladen)")
|
|
_check(EditorUpdater.head_sha(git, work) == str(avail["remote"]), "nach Pull ist work auf v2")
|
|
_git(work, ["remote", "set-url", "origin", "http://127.0.0.1:1/nicht-da.git"])
|
|
var dead_pull := EditorUpdater.pull(git, work)
|
|
_check(not bool(dead_pull["ok"]), "pull gegen toten Origin schlägt fehl")
|
|
_check((dead_pull["errors"] as Array).size() > 0, "Pull-Fehlerliste fürs Dialog-Fenster")
|
|
_git(work, ["remote", "set-url", "origin", origin])
|
|
|
|
print("== Selbst-Update: lose Dateien blockieren den Pull nicht (Karte t_f73a1cc5) ==")
|
|
# Origin bekommt eine GETRACKTE Import-Datei — lokal existiert sie als
|
|
# untracked (Godot erzeugt sie beim Import). Genau die Kollision, die am
|
|
# 19.10. den Update-Knopf blockiert hat.
|
|
DirAccess.make_dir_recursive_absolute(other.path_join("library/thumbs"))
|
|
_write_file(other.path_join("library/thumbs/zz_probe.png.import"), "origin-version\n")
|
|
_git(other, ["add", "library/thumbs/zz_probe.png.import"])
|
|
_git(other, ["commit", "-m", "v3: Import-Datei getrackt"])
|
|
_check(bool(_git(other, ["push", "origin", "main"])["ok"]),
|
|
"Setup: v3 gepusht (Import-Datei jetzt getrackt)")
|
|
|
|
# NEGATIVPROBE: der NACKTE Pull (ohne Fix) bricht an der losen Datei ab.
|
|
DirAccess.make_dir_recursive_absolute(work.path_join("library/thumbs"))
|
|
_write_file(work.path_join("library/thumbs/zz_probe.png.import"), "lokal-erzeugt\n")
|
|
var raw := EditorUpdater.git_run(git, work, PackedStringArray(["pull", "--ff-only"]))
|
|
_check(not bool(raw["ok"]), "NEGATIVPROBE: nackter pull --ff-only bricht an der Kollision ab")
|
|
_check(not str(raw["output"]).strip_edges().is_empty(),
|
|
"NEGATIVPROBE: git liefert die rohe (verwirrende) Fehlerzeile")
|
|
|
|
# MIT Fix: EditorUpdater.pull legt selbst beiseite und zieht durch.
|
|
var fixed := EditorUpdater.pull(git, work)
|
|
_check(bool(fixed["ok"]), "MIT Fix: pull läuft ohne Handeingriff durch")
|
|
_check(str(fixed["info"]).contains("beiseitegelegt"),
|
|
"Klartext-Info fürs Statuszeilen-Fenster gesetzt")
|
|
_check(_file_text(work.path_join("library/thumbs/zz_probe.png.import")) == "origin-version\n",
|
|
"kein halb gepoppter Zustand: getrackte Version liegt an")
|
|
_check(_clean_tracked(work), "Arbeitsbaum (getrackt) sauber — Godot baut die Datei beim Import neu")
|
|
_check(_auto_stash_count(git, work) == 1, "Sicherungs-Stash bleibt bei Konflikt liegen")
|
|
|
|
print("== Selbst-Update: geänderte getrackte Datei bleibt erhalten ==")
|
|
# v4 fasst a.txt NICHT an → das Zurückholen der lokalen b.txt-Änderung
|
|
# MUSS klappen: apply + drop, kein Stash-Rest.
|
|
_write_file(work.path_join("b.txt"), "lokal geändert\n")
|
|
_write_file(other.path_join("c.txt"), "neu\n")
|
|
_git(other, ["add", "c.txt"])
|
|
_git(other, ["commit", "-m", "v4: nur c.txt"])
|
|
_check(bool(_git(other, ["push", "origin", "main"])["ok"]), "Setup: v4 gepusht")
|
|
var fixed2 := EditorUpdater.pull(git, work)
|
|
_check(bool(fixed2["ok"]), "pull mit lokaler Änderung läuft durch")
|
|
_check(_file_text(work.path_join("b.txt")) == "lokal geändert\n",
|
|
"lokal geänderte Datei ist zurückgeholt (kein Datenverlust)")
|
|
_check(_auto_stash_count(git, work) == 0,
|
|
"Auto-Stash nach erfolgreichem Rückholen aufgeräumt (keine Anhäufung)")
|
|
|
|
print("== Selbst-Update: Kollision mit Origin → Stash bleibt als Sicherung ==")
|
|
# v5 ändert dieselbe Datei wie lokal → Zurückholen kollidiert; der Stash
|
|
# bleibt als Sicherung liegen, der Arbeitsbaum ist sauber auf v5.
|
|
_write_file(work.path_join("a.txt"), "lokal-geändert\n")
|
|
_write_file(other.path_join("a.txt"), "origin-geändert\n")
|
|
_git(other, ["add", "a.txt"])
|
|
_git(other, ["commit", "-m", "v5: a.txt geändert"])
|
|
_check(bool(_git(other, ["push", "origin", "main"])["ok"]), "Setup: v5 gepusht")
|
|
var fixed3 := EditorUpdater.pull(git, work)
|
|
_check(bool(fixed3["ok"]), "Update läuft trotzdem durch (Änderung liegt sicher im Stash)")
|
|
_check(_file_text(work.path_join("a.txt")) == "origin-geändert\n",
|
|
"Arbeitsbaum = Origin-Stand (kein Konflikt-Brei, keine halbe Datei)")
|
|
_check(str(fixed3["info"]).contains("neu aufgebaut"),
|
|
"ehrliche Meldung: beiseitegelegt, beim nächsten Start neu aufgebaut")
|
|
var show := EditorUpdater.git_run(git, work, PackedStringArray([
|
|
"stash", "show", "-p", "stash@{0}"]))
|
|
_check(str(show["output"]).contains("lokal-geändert"),
|
|
"geänderte Datei liegt unverlustfrei im Sicherungs-Stash")
|
|
|
|
print("== Auto-Stash: alte Kennung wird verworfen, nichts häuft sich an ==")
|
|
_write_file(work.path_join("library/thumbs/zwei.png.import"), "alt\n")
|
|
var s1 := EditorUpdater._stash_local_changes(git, work)
|
|
_write_file(work.path_join("library/thumbs/drei.png.import"), "neu\n")
|
|
var s2 := EditorUpdater._stash_local_changes(git, work)
|
|
_check(not s1.is_empty() and not s2.is_empty(), "beide Läufe legen einen Auto-Stash an")
|
|
_check(_auto_stash_count(git, work) == 1, "genau EIN Auto-Stash übrig (alter verworfen)")
|
|
# Für die Folge-Abschnitte aufräumen: Stash zurückholen (klappert, weil
|
|
# die zwei untracked Dateien an keine getrackte Datei grenzen) und die
|
|
# Probendateien wegwerfen.
|
|
_check(bool(EditorUpdater._restore_stash(git, work)["ok"]), "Aufräumen: Stash zurückgeholt")
|
|
DirAccess.remove_absolute(work.path_join("library/thumbs/zwei.png.import"))
|
|
DirAccess.remove_absolute(work.path_join("library/thumbs/drei.png.import"))
|
|
_check(_auto_stash_count(git, work) == 0, "Aufräumen: kein Auto-Stash übrig")
|
|
|
|
print("== Import-Abbruch-Pfad ==")
|
|
_check(EditorUpdater.import_errors([]).is_empty(), "leere Import-Ausgabe = keine Fehler")
|
|
_check(EditorUpdater.import_errors(["Eine normale Zeile"]).is_empty(), "normale Ausgabe = keine Fehler")
|
|
var errs := EditorUpdater.import_errors(["Parse Error: Identifier not declared in the current scope."])
|
|
_check(errs.size() == 1, "Parse Error wird erkannt (Exit-Code-0-Falle, Lektion 17.10.)")
|
|
errs = EditorUpdater.import_errors(["SCRIPT ERROR: blub", "und weiter"])
|
|
_check(errs.size() == 1, "SCRIPT ERROR wird erkannt")
|
|
# Echter Run mit Exit-Code != 0 (Benutzung von "false": endet mit 1).
|
|
if FileAccess.file_exists("/bin/false"):
|
|
var failed := EditorUpdater.run_import("/bin/false", work)
|
|
_check(not bool(failed["ok"]), "Import mit Exit-Code 1 = NICHT ok (kein Restart)")
|
|
_check((failed["errors"] as Array).size() > 0, "Import-Fehlerliste befüllt")
|
|
# Echter Run, Exit-Code 0, ABER Parse-Error in der Ausgabe (die 17.10.-Falle):
|
|
if FileAccess.file_exists("/bin/sh"):
|
|
var fake := base.path_join("fake_godot.sh")
|
|
_write_file(fake, "#!/bin/sh\necho \"Parse Error: Identifier not declared.\"\nexit 0\n")
|
|
var code := OS.execute("chmod", PackedStringArray(["+x", fake]))
|
|
if code == 0:
|
|
var trap := EditorUpdater.run_import(fake, work)
|
|
_check(not bool(trap["ok"]), "Import „grün“ MIT Parse-Error = NICHT ok (kein Restart!)")
|
|
|
|
print("== Restart-Auflösung ==")
|
|
var cfg := ConfigFile.new()
|
|
cfg.set_value("update", "restart_command", "echo hallo welt")
|
|
var resolved := EditorUpdater.resolve_restart(cfg)
|
|
_check(str(resolved["mode"]) == "command", "cfg-Kommando gewinnt (mode command)")
|
|
var argv: PackedStringArray = resolved["argv"]
|
|
var want := ["echo", "hallo", "welt"]
|
|
var ok := argv.size() == want.size()
|
|
if ok:
|
|
for i in want.size():
|
|
if str(argv[i]) != want[i]:
|
|
ok = false
|
|
_check(ok, "Kommando sauber an Leerzeichen gesplittet")
|
|
var resolved2 := EditorUpdater.resolve_restart(ConfigFile.new())
|
|
_check(str(resolved2["mode"]) == "self" or str(resolved2["mode"]) == "command",
|
|
"ohne cfg: plausibler Default (self ODER systemd-Unit erkannt)")
|
|
if str(resolved2["mode"]) == "self":
|
|
_check((resolved2["argv"] as PackedStringArray).is_empty(),
|
|
"mode self: UI baut die Selbst-Restart-Argumente selbst")
|
|
var self_args := EditorUpdater.self_restart_args("/pfad/zum/klon")
|
|
_check(str(self_args[0]) == "--path" and str(self_args[1]) == "/pfad/zum/klon",
|
|
"Selbst-Restart: --path <klon> (startet die Map-Editor-APP, nicht die Godot-Editor-Shell)")
|
|
|
|
|
|
## Regression (20.10., Live-Bug im Map-Builder): git schreibt bei einer
|
|
## HTTP-Umleitung zuerst "warning: Umleitung nach ..." in die GEMISCHTE
|
|
## Ausgabe (git_run mischt stdout+stderr). Die alte Fassung nahm die ERSTE
|
|
## Zeile als Ziel-Commit → Banner "Update verfügbar: <sha> → warning" hing
|
|
## endlos, obwohl der Klon aktuell war. Erwartung: echte "<sha>\t<ref>"-Zeile
|
|
## gewinnt, Warnzeilen werden nie als SHA gelesen.
|
|
func _test_ls_remote_warnzeile() -> void:
|
|
var sha := "d0d4fbd2213e596d117b779c6d931629bf9b20ed"
|
|
var mit_warnung := "warning: Umleitung nach http://127.0.0.1:13020/x.git/\n%s\trefs/heads/main\n" % sha
|
|
_check(EditorUpdater.sha_aus_ls_remote(mit_warnung, "main") == sha,
|
|
"Warnzeile ignoriert, echter Commit gelesen")
|
|
_check(not EditorUpdater.ist_sha("warning"), "'warning' gilt nicht als SHA")
|
|
_check(EditorUpdater.sha_aus_ls_remote("warning: Umleitung nach x\n", "main") == "",
|
|
"nur Warnzeilen → leerer Commit (kein Update-Banner)")
|
|
_check(EditorUpdater.sha_aus_ls_remote("fatal: could not read Username\n", "main") == "",
|
|
"fatal-Zeile wird nicht als SHA gelesen")
|
|
_check(EditorUpdater.sha_aus_ls_remote("%s\trefs/heads/main" % sha, "main") == sha,
|
|
"normale Zeile funktioniert weiterhin")
|
|
_check(EditorUpdater.sha_aus_ls_remote("%s\trefs/heads/anderer" % sha, "main") == sha,
|
|
"anderer Branch: SHA bleibt als Notnagel nutzbar")
|