update
This commit is contained in:
25
tests/README.md
Normal file
25
tests/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Tests
|
||||
|
||||
make test
|
||||
|
||||
Läuft alles headless: Logik, Nochmal, Bot-Partie (Stufe schwer, 8×), und
|
||||
eine echte Mehrspieler-Partie mit Server, Host-Client und Gast-Client - der
|
||||
Gast steigt in Runde zwei aus und wieder ein.
|
||||
|
||||
Die Screenshots-Prüfungen (Look, Layout) sind nicht dabei; die braucht ein
|
||||
Fenster.
|
||||
|
||||
## Fallen
|
||||
|
||||
* **Wurzelname.** Der Spielknoten muss auf Server und Client denselben Pfad
|
||||
haben (`/root/Main/Raeume/<CODE>/…`). Daran hängt jeder RPC. Die
|
||||
Mehrspieler-Tests laden deshalb die echte Hauptszene als aktuelle Szene und
|
||||
hängen ihren Läufer daneben unter `root`. Wer die Hauptszene unter eine
|
||||
Testwurzel hängt, bekommt auf der Gegenseite nur
|
||||
`Requested node was not found` - und der Server spielt die Menschen per
|
||||
Zeitablauf selbst, was wie eine laufende Partie aussieht.
|
||||
* **Port 8000** ist auf Entwicklungsrechnern oft belegt. `PORT=… make test`.
|
||||
* **Lambdas** in GDScript kopieren lokale Variablen. Eine Zuweisung darin
|
||||
landet nicht draußen; die Tests halten `game` deshalb als Feld.
|
||||
* **Ausgabe** erscheint erst, wenn der Prozess regulär endet. Ein per
|
||||
`timeout` abgeschossener Client verliert seine letzten Zeilen.
|
||||
35
tests/basis.gd
Normal file
35
tests/basis.gd
Normal file
@@ -0,0 +1,35 @@
|
||||
extends Node
|
||||
## Gemeinsame Helfer für die Testszenen. Jede Testszene erbt hiervon.
|
||||
|
||||
var fehler := 0
|
||||
|
||||
|
||||
func _find(n: Node, wunsch: String) -> Node:
|
||||
if n.name == wunsch:
|
||||
return n
|
||||
for c in n.get_children():
|
||||
var r := _find(c, wunsch)
|
||||
if r != null:
|
||||
return r
|
||||
return null
|
||||
|
||||
|
||||
func _warte(cond: Callable, sek: float) -> bool:
|
||||
var t := 0.0
|
||||
while t < sek:
|
||||
if cond.call():
|
||||
return true
|
||||
await get_tree().create_timer(0.05).timeout
|
||||
t += 0.05
|
||||
return false
|
||||
|
||||
|
||||
func _pruef(name: String, ok: bool) -> void:
|
||||
if not ok:
|
||||
fehler += 1
|
||||
print("%s %s" % ["OK " if ok else "FEHL", name])
|
||||
|
||||
|
||||
func _fertig() -> void:
|
||||
print("TEST: fertig, Fehler = %d" % fehler)
|
||||
get_tree().quit(1 if fehler > 0 else 0)
|
||||
47
tests/bots.gd
Normal file
47
tests/bots.gd
Normal file
@@ -0,0 +1,47 @@
|
||||
extends "res://tests/basis.gd"
|
||||
## Alle Plätze sind Bots, achtfacher Zeitraffer. Läuft die Partie fehlerfrei
|
||||
## bis zum Ende? Stufe über "-- 0|1|2".
|
||||
|
||||
var game: Node
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
Engine.time_scale = 12.0
|
||||
add_child(preload("res://scenes/main.tscn").instantiate())
|
||||
await get_tree().process_frame
|
||||
var args := OS.get_cmdline_user_args()
|
||||
Net.start_singleplayer("Tester", 3, int(args[0]) if args.size() > 0 else 1)
|
||||
_lauf()
|
||||
|
||||
|
||||
func _lauf() -> void:
|
||||
await _warte(func(): game = _find(get_tree().root, "Game"); return game != null, 20.0)
|
||||
game.seats[game.local_seat].ai = true
|
||||
var t := 0.0
|
||||
var gesehen := {}
|
||||
var zaehler := {"Genau": 0, "Topf": 0, " wirft ": 0, "vom Tisch": 0}
|
||||
var runden := 0
|
||||
var letzte := ""
|
||||
var naechste_meldung := 500.0
|
||||
while game.phase != "over" and t < 6000.0:
|
||||
await get_tree().create_timer(0.2).timeout
|
||||
t += 0.2
|
||||
if game.phase != letzte:
|
||||
letzte = game.phase
|
||||
if letzte == "roll":
|
||||
runden += 1
|
||||
if t >= naechste_meldung:
|
||||
naechste_meldung += 500.0
|
||||
print(" t=%.0fs Runden=%d Würfel=%d Phase=%s" % [t, runden, game._total_dice(), game.phase])
|
||||
for z in game.log_lines:
|
||||
if gesehen.has(z):
|
||||
continue
|
||||
gesehen[z] = true
|
||||
for k in zaehler:
|
||||
if k in z:
|
||||
zaehler[k] += 1
|
||||
_pruef("Partie endet (%s, Stufe %d, %d Runden, %.0fs Spielzeit)" % [game.banner, DiceAI.stufe, runden, t], game.phase == "over")
|
||||
_pruef("Münzen wurden geworfen (%d)" % zaehler[" wirft "], zaehler[" wirft "] > 0)
|
||||
_pruef("Topf wurde vergeben (%d)" % zaehler["Topf"], zaehler["Topf"] > 0)
|
||||
print(" Genau-Rufe=%d, vom Tisch=%d, Topf am Ende=%d" % [zaehler["Genau"], zaehler["vom Tisch"], game.topf.size()])
|
||||
_fertig()
|
||||
6
tests/bots.tscn
Normal file
6
tests/bots.tscn
Normal file
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tests/bots.gd" id="1"]
|
||||
|
||||
[node name="Test" type="Node"]
|
||||
script = ExtResource("1")
|
||||
126
tests/logik.gd
Normal file
126
tests/logik.gd
Normal file
@@ -0,0 +1,126 @@
|
||||
extends "res://tests/basis.gd"
|
||||
## Solopartie headless: Münzen, Genau, Topf, Zählen, Stufe, Tipp-Schranke.
|
||||
## Die Bots werden zu "Menschen" ohne Peer gemacht - dann wartet der Host
|
||||
## auf unsere Eingaben, und der Ablauf ist steuerbar.
|
||||
|
||||
var game: Node
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
add_child(preload("res://scenes/main.tscn").instantiate())
|
||||
await get_tree().process_frame
|
||||
Net.start_singleplayer("Tester", 3, 2)
|
||||
_lauf()
|
||||
|
||||
|
||||
func _menschen() -> void:
|
||||
for i in [1, 2, 3]:
|
||||
game.seats[i].ai = false
|
||||
|
||||
|
||||
func _lauf() -> void:
|
||||
await _warte(func(): game = _find(get_tree().root, "Game"); return game != null, 20.0)
|
||||
await _warte(func(): return game.phase == "bid", 40.0)
|
||||
var me: int = game.local_seat
|
||||
var hud: Node = game.hud
|
||||
_pruef("Stufe aus der Konfiguration", DiceAI.stufe == 2)
|
||||
_pruef("Jeder startet mit einer Münze", game.hand[me].size() == 1 and game.hand[1].size() == 1)
|
||||
_menschen()
|
||||
|
||||
# --- Zugbindung
|
||||
game.hand[1] = ["peek"]
|
||||
await game._do_play(1, "peek", 0)
|
||||
_pruef("Gegner wirft nicht am fremden Zug", game.hand[1] == ["peek"] and game.topf.is_empty())
|
||||
game.hand[me] = ["count"]
|
||||
await game._do_play(me, "count", 0)
|
||||
_pruef("Zählen ohne Ansage abgelehnt", game.hand[me].has("count"))
|
||||
|
||||
# --- Wirkungen beider Seiten
|
||||
game.bid = {"seat": 1, "count": 1, "face": 4}
|
||||
var n4: int = game._count_face(4)
|
||||
await game._wirkung(me, "count", 0, false)
|
||||
var f: Array = game.gezaehlt[me][4]
|
||||
_pruef("Zählen: Fenster enthält die Wahrheit", int(f[0]) <= n4 and n4 <= int(f[1]) and int(f[1]) - int(f[0]) == 2)
|
||||
await game._wirkung(me, "count", 0, true)
|
||||
f = game.gezaehlt[me][4]
|
||||
_pruef("Zählen: Wappen ist exakt", int(f[0]) == n4 and int(f[1]) == n4)
|
||||
await game._wirkung(me, "set_face", 0, true)
|
||||
_pruef("Falschspiel dreht zwei Würfel auf die Ansage", game.host_values[me].count(4) >= 2)
|
||||
await game._wirkung(me, "gain_die", 0, true)
|
||||
_pruef("Nachschub mit Wappen: neuer Würfel ist 6", game.host_values[me].size() == 6 and game.host_values[me][5] == 6)
|
||||
await game._wirkung(me, "bid_up", 0, true)
|
||||
_pruef("Erhöhen mit Wappen: +2", int(game.bid.count) == 3 and game.verlauf.back().erhoeht)
|
||||
await game._wirkung(me, "gag", 0, false)
|
||||
_pruef("Maulkorb trifft den Nächsten", game.must_bid.get(1, false) and not game.must_bid.get(2, false))
|
||||
await game._wirkung(me, "peek", 0, true)
|
||||
_pruef("Spähen mit Wappen: zwei je Gegner", game.peeked[me][1].size() == 2)
|
||||
game.bid = {}
|
||||
game.must_bid.clear()
|
||||
|
||||
# --- Topf: Deckel und Übertrag
|
||||
game.topf.clear()
|
||||
game.hand[me] = ["gag", "gag", "gag", "gag", "gag", "gag", "gag"]
|
||||
for i in 7:
|
||||
await game._do_play(me, "gag", 0)
|
||||
_pruef("Topf höchstens %d" % BoosterDB.TOPF_MAX, game.topf.size() == BoosterDB.TOPF_MAX)
|
||||
_pruef("Rausfallen steht im Protokoll", game.log_lines.any(func(z): return "vom Tisch" in z))
|
||||
game.hand[2] = ["peek", "peek"]
|
||||
game._topf_an(2)
|
||||
_pruef("Gewinner nimmt bis zum Limit, Rest bleibt",
|
||||
game.hand[2].size() == BoosterDB.HANDLIMIT and game.topf.size() == BoosterDB.TOPF_MAX - 2)
|
||||
game.topf.clear()
|
||||
game.must_bid.clear()
|
||||
|
||||
# --- Protokoll und Tipp-Schranke im HUD
|
||||
game._push_state()
|
||||
await get_tree().create_timer(0.3).timeout
|
||||
_pruef("Protokoll kommt im HUD an", hud.log_box.get_child_count() > 0)
|
||||
hud._touch = true
|
||||
var geworfen := []
|
||||
hud.booster_chosen.connect(func(id, _t): geworfen.append(id))
|
||||
game.hand[me] = ["peek"]
|
||||
game._push_state()
|
||||
await get_tree().create_timer(0.3).timeout
|
||||
hud._pick("peek")
|
||||
_pruef("Handy: erster Tipp wirft nicht", geworfen.is_empty())
|
||||
hud._pick("peek")
|
||||
_pruef("Handy: zweiter Tipp wirft", geworfen == ["peek"])
|
||||
hud._touch = false
|
||||
await get_tree().create_timer(2.5).timeout
|
||||
|
||||
# --- Genau-Treffer: Ansager verliert, Rufer bekommt einen
|
||||
game.must_bid.clear()
|
||||
var n3: int = game._count_face(3)
|
||||
if n3 == 0:
|
||||
game._set_die(me, 0, 3)
|
||||
n3 = 1
|
||||
var vorher: Array = []
|
||||
for i in 4:
|
||||
vorher.append(int(game.seats[i].count))
|
||||
game._do_bid(me, n3, 3)
|
||||
await _warte(func(): return game.turn == 1, 5.0)
|
||||
game._do_exact(1)
|
||||
await _warte(func(): return game.phase == "reveal", 5.0)
|
||||
_pruef("Etappe 1: noch nicht gezählt", int(game.reveal_total) == -1)
|
||||
await _warte(func(): return int(game.reveal_total) >= 0, 3.0)
|
||||
_pruef("Etappe 2: gezählt", int(game.reveal_total) == n3)
|
||||
await _warte(func(): return game.banner.begins_with("Genau!"), 3.0)
|
||||
await _warte(func(): return game.phase != "reveal", 8.0)
|
||||
_pruef("Genau: Ansager -1, Rufer +1, Rest gleich",
|
||||
int(game.seats[me].count) == vorher[0] - 1 and int(game.seats[1].count) == vorher[1] + 1
|
||||
and int(game.seats[2].count) == vorher[2] and int(game.seats[3].count) == vorher[3])
|
||||
_pruef("Rufer beginnt", game.starter == 1)
|
||||
|
||||
# --- Genau daneben
|
||||
await _warte(func(): return game.phase == "bid", 20.0)
|
||||
_menschen()
|
||||
await _warte(func(): return game.turn == 1, 5.0)
|
||||
var n5: int = game._count_face(5)
|
||||
game._do_bid(1, mini(n5 + 2, game._total_dice()), 5)
|
||||
await _warte(func(): return game.turn == 2, 5.0)
|
||||
var c2: int = game.seats[2].count
|
||||
game._do_exact(2)
|
||||
await _warte(func(): return game.banner.begins_with("Daneben"), 8.0)
|
||||
await _warte(func(): return game.phase != "reveal", 8.0)
|
||||
_pruef("Genau daneben: nur der Rufer verliert", int(game.seats[2].count) == c2 - 1)
|
||||
_fertig()
|
||||
6
tests/logik.tscn
Normal file
6
tests/logik.tscn
Normal file
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tests/logik.gd" id="1"]
|
||||
|
||||
[node name="Test" type="Node"]
|
||||
script = ExtResource("1")
|
||||
130
tests/mp_client.gd
Normal file
130
tests/mp_client.gd
Normal file
@@ -0,0 +1,130 @@
|
||||
extends "res://tests/basis.gd"
|
||||
## Ein Client. "-- host" macht den Raum auf, "-- join" tritt bei und steigt
|
||||
## in Runde zwei einmal aus und wieder ein. Beide spielen über die echten
|
||||
## RPC-Wege. Der Raumcode wandert über eine Datei im Scratch-Verzeichnis.
|
||||
|
||||
var game: Node
|
||||
var rolle := ""
|
||||
var runden := 0
|
||||
var zuege := 0
|
||||
var wuerfe := 0
|
||||
var genau := 0
|
||||
var wieder_da := false
|
||||
var _letzte_phase := ""
|
||||
var _rng := RandomNumberGenerator.new()
|
||||
var _code_datei := ""
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
Engine.time_scale = 8.0
|
||||
_rng.randomize()
|
||||
var args := OS.get_cmdline_user_args()
|
||||
rolle = args[0]
|
||||
_code_datei = args[1]
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
_lauf()
|
||||
|
||||
|
||||
func _lauf() -> void:
|
||||
if rolle == "host":
|
||||
Net.create_room("Host")
|
||||
await _warte(func(): return Net.room_code != "", 40.0)
|
||||
var f := FileAccess.open(_code_datei, FileAccess.WRITE)
|
||||
f.store_string(Net.room_code)
|
||||
f.close()
|
||||
await _warte(func(): return Net.lobby.size() >= 2, 80.0)
|
||||
Net.request_start(1, 1)
|
||||
else:
|
||||
await _beitreten()
|
||||
await _warte(func(): game = _find(get_tree().root, "Game"); return game != null, 120.0)
|
||||
if game == null:
|
||||
_pruef("%s: Spiel kommt an" % rolle, false)
|
||||
_fertig()
|
||||
return
|
||||
var erste := await _warte(func(): return game.phase == "bid" or game.phase == "roll", 60.0)
|
||||
_pruef("%s: erster Zustand kommt an (Platz %d)" % [rolle, game.local_seat], erste)
|
||||
|
||||
var t := 0.0
|
||||
var gewartet := 0.0
|
||||
while t < 4400.0 and is_instance_valid(game) and game.phase != "over":
|
||||
await get_tree().create_timer(0.2).timeout
|
||||
t += 0.2
|
||||
if not is_instance_valid(game):
|
||||
break
|
||||
if game.phase != _letzte_phase:
|
||||
_letzte_phase = game.phase
|
||||
if game.phase == "roll":
|
||||
runden += 1
|
||||
# Der Gast geht in Runde zwei einmal raus und kommt zurück.
|
||||
if rolle == "join" and not wieder_da and runden >= 2 and game.phase == "bid":
|
||||
await _wiederkommen()
|
||||
continue
|
||||
var me: int = game.local_seat
|
||||
if game.phase != "bid" or game.turn != me or not game.seats[me].alive:
|
||||
gewartet = 0.0
|
||||
continue
|
||||
gewartet += 0.2
|
||||
if gewartet < 0.6:
|
||||
continue
|
||||
var hand: Array = game.hand.get(me, [])
|
||||
if not hand.is_empty() and (not BoosterDB.needs_bid(hand[0]) or not game.bid.is_empty()) and _rng.randf() < 0.7:
|
||||
game._on_booster_chosen(hand[0], 0)
|
||||
wuerfe += 1
|
||||
await get_tree().create_timer(2.5).timeout
|
||||
if not is_instance_valid(game) or game.turn != me or game.phase != "bid":
|
||||
continue
|
||||
var total := 0
|
||||
for s in game.seats:
|
||||
total += int(s.count)
|
||||
var a: Dictionary = DiceAI.choose_action(game.my_values, total, game.bid, false, _rng)
|
||||
if a.type == "challenge" and not game.bid.is_empty() and _rng.randf() < 0.3:
|
||||
a = {"type": "exact"}
|
||||
match a.type:
|
||||
"bid":
|
||||
game._on_bid_pressed(int(a.count), int(a.face))
|
||||
"challenge":
|
||||
game._on_challenge_pressed()
|
||||
"exact":
|
||||
game._on_exact_pressed()
|
||||
genau += 1
|
||||
zuege += 1
|
||||
gewartet = -2.0
|
||||
_pruef("%s: Partie endet über RPC (Runden=%d Züge=%d Würfe=%d Genau=%d)" % [rolle, runden, zuege, wuerfe, genau],
|
||||
is_instance_valid(game) and game.phase == "over")
|
||||
if rolle == "join":
|
||||
_pruef("join: Wiedereinstieg hat geklappt", wieder_da)
|
||||
if is_instance_valid(game):
|
||||
print("%s: '%s'" % [rolle, game.banner])
|
||||
for i in game.seats.size():
|
||||
print("%s: Platz %d %s count=%d alive=%s" % [rolle, i, game.seats[i].name, game.seats[i].count, game.seats[i].alive])
|
||||
_fertig()
|
||||
|
||||
|
||||
func _beitreten() -> void:
|
||||
await _warte(func(): return FileAccess.file_exists(_code_datei), 80.0)
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
Net.join_room(FileAccess.get_file_as_string(_code_datei).strip_edges(), "Gast")
|
||||
|
||||
|
||||
## Verbindung kappen, frisches Menü, mit demselben Namen wieder beitreten.
|
||||
func _wiederkommen() -> void:
|
||||
var alter_platz: int = game.local_seat
|
||||
var alte_zuege := zuege
|
||||
Net.leave()
|
||||
get_tree().change_scene_to_file("res://scenes/main.tscn")
|
||||
await get_tree().create_timer(1.5).timeout
|
||||
game = null
|
||||
await _beitreten()
|
||||
var ok := await _warte(func(): game = _find(get_tree().root, "Game"); return game != null, 60.0)
|
||||
if not ok:
|
||||
_pruef("join: nach Rückkehr kommt ein Spiel", false)
|
||||
wieder_da = true
|
||||
return
|
||||
var zustand := await _warte(func(): return game.phase == "bid" or game.phase == "roll" or game.phase == "reveal", 60.0)
|
||||
_pruef("join: nach Rückkehr kommt der Zustand", zustand)
|
||||
_pruef("join: alter Platz zurück, kein Bot mehr",
|
||||
game.local_seat == alter_platz and not game.seats[game.local_seat].ai)
|
||||
_pruef("join: HUD steht wieder", game.hud != null and game.hud.hand_box != null)
|
||||
_letzte_phase = ""
|
||||
wieder_da = true
|
||||
zuege = alte_zuege
|
||||
32
tests/mp_server.gd
Normal file
32
tests/mp_server.gd
Normal file
@@ -0,0 +1,32 @@
|
||||
extends "res://tests/basis.gd"
|
||||
## Spielserver im Zeitraffer. Meldet Ende und Endstand, wartet dann noch,
|
||||
## damit die Clients ihr Ende selbst sehen.
|
||||
|
||||
var game: Node
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
Engine.time_scale = 8.0
|
||||
var t := 0.0
|
||||
var runden := 0
|
||||
var letzte := ""
|
||||
while t < 4500.0:
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
t += 0.5
|
||||
if game == null:
|
||||
game = _find(get_tree().root, "Game")
|
||||
continue
|
||||
if not is_instance_valid(game) or game.phase == "over":
|
||||
break
|
||||
if game.phase != letzte:
|
||||
letzte = game.phase
|
||||
if letzte == "roll":
|
||||
runden += 1
|
||||
if is_instance_valid(game):
|
||||
print("SERVER: Ende %s '%s', Runden=%d" % [game.phase, game.banner, runden])
|
||||
for i in game.seats.size():
|
||||
print("SERVER: Platz %d %s count=%d alive=%s" % [i, game.seats[i].name, game.seats[i].count, game.seats[i].alive])
|
||||
var zurueck: bool = game.log_lines.any(func(z): return "ist zurück" in z)
|
||||
print("SERVER: Rückkehr im Protokoll=%s" % zurueck)
|
||||
await get_tree().create_timer(80.0).timeout
|
||||
get_tree().quit()
|
||||
17
tests/mp_start.gd
Normal file
17
tests/mp_start.gd
Normal file
@@ -0,0 +1,17 @@
|
||||
extends Node
|
||||
## Startszene für Server und Clients. Hängt den Läufer unter root und lädt
|
||||
## dann die echte Hauptszene - so heißt der Spielknoten auf allen Seiten
|
||||
## /root/Main/Raeume/<CODE>/Container/Viewport/Game. Daran hängt jeder RPC:
|
||||
## ein Test, der die Hauptszene unter eine anders benannte Wurzel hängt,
|
||||
## bekommt auf der Gegenseite nur "Requested node was not found".
|
||||
|
||||
func _ready() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
var r := Node.new()
|
||||
r.name = "Laeufer"
|
||||
# Erst zur Laufzeit laden: ein Fehler im einen Skript soll das andere
|
||||
# nicht mitreissen.
|
||||
r.set_script(load("res://tests/mp_server.gd" if args.size() > 0 and args[0] == "server"
|
||||
else "res://tests/mp_client.gd"))
|
||||
get_tree().root.add_child.call_deferred(r)
|
||||
get_tree().change_scene_to_file.call_deferred("res://scenes/main.tscn")
|
||||
6
tests/mp_start.tscn
Normal file
6
tests/mp_start.tscn
Normal file
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tests/mp_start.gd" id="1"]
|
||||
|
||||
[node name="Test" type="Node"]
|
||||
script = ExtResource("1")
|
||||
9
tests/nochmal.gd
Normal file
9
tests/nochmal.gd
Normal file
@@ -0,0 +1,9 @@
|
||||
extends Node
|
||||
## "Nochmal" wechselt die Szene. Der eigentliche Läufer hängt deshalb direkt
|
||||
## unter root und überlebt den Wechsel.
|
||||
|
||||
func _ready() -> void:
|
||||
var r := Node.new()
|
||||
r.name = "Laeufer"
|
||||
r.set_script(preload("res://tests/nochmal_laeufer.gd"))
|
||||
get_tree().root.add_child.call_deferred(r)
|
||||
6
tests/nochmal.tscn
Normal file
6
tests/nochmal.tscn
Normal file
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tests/nochmal.gd" id="1"]
|
||||
|
||||
[node name="Test" type="Node"]
|
||||
script = ExtResource("1")
|
||||
22
tests/nochmal_laeufer.gd
Normal file
22
tests/nochmal_laeufer.gd
Normal file
@@ -0,0 +1,22 @@
|
||||
extends "res://tests/basis.gd"
|
||||
|
||||
var game: Node
|
||||
var neu: Node
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
get_tree().change_scene_to_file("res://scenes/main.tscn")
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
Net.start_singleplayer("Tester", 2, 0)
|
||||
await _warte(func(): game = _find(get_tree().root, "Game"); return game != null, 20.0)
|
||||
await _warte(func(): return game.phase == "bid", 40.0)
|
||||
_pruef("Erste Partie läuft, Stufe leicht", DiceAI.stufe == 0 and game.seats.size() == 3)
|
||||
game._on_rematch()
|
||||
await _warte(func(): neu = _find(get_tree().root, "Game"); return neu != null and is_instance_valid(neu) and neu != game, 15.0)
|
||||
_pruef("Nochmal startet eine neue Partie", neu != null)
|
||||
if neu != null:
|
||||
await _warte(func(): return neu.phase == "bid", 40.0)
|
||||
_pruef("Gleiche Bots, gleiche Stufe, Wunsch gelöscht",
|
||||
neu.seats.size() == 3 and DiceAI.stufe == 0 and Net.rematch.is_empty())
|
||||
_pruef("Kein Endbildschirm", not neu.hud.over_slot.visible)
|
||||
_fertig()
|
||||
51
tests/run.sh
Executable file
51
tests/run.sh
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bash
|
||||
# Alle headless Tests. Aufruf: make test (oder: bash tests/run.sh)
|
||||
# Der Mehrspieler-Test braucht einen freien Port; 8000 ist auf Rechnern mit
|
||||
# laufendem Entwicklungsserver oft belegt, deshalb 8765.
|
||||
set -u
|
||||
GODOT="${GODOT:-$HOME/bin/godot}"
|
||||
PORT="${PORT:-8765}"
|
||||
TMP="$(mktemp -d)"
|
||||
cd "$(dirname "$0")/.."
|
||||
ergebnis=0
|
||||
|
||||
lauf() { # name, timeout, argumente...
|
||||
local name="$1"; local zeit="$2"; shift 2
|
||||
echo "=== $name"
|
||||
timeout "$zeit" "$GODOT" --headless --path . "res://tests/$name.tscn" -- "$@" > "$TMP/$name.log" 2>&1
|
||||
local rc=$?
|
||||
grep -E "^(OK |FEHL| |TEST:)" "$TMP/$name.log"
|
||||
local fehler; fehler=$(grep -c "SCRIPT ERROR" "$TMP/$name.log")
|
||||
[ "$fehler" -gt 0 ] && { echo " $fehler Skriptfehler im Log: $TMP/$name.log"; rc=1; }
|
||||
[ $rc -ne 0 ] && ergebnis=1
|
||||
}
|
||||
|
||||
lauf logik 200
|
||||
lauf nochmal 120
|
||||
lauf bots 900 2
|
||||
|
||||
echo "=== mehrspieler"
|
||||
rm -f "$TMP/code.txt"
|
||||
timeout 700 "$GODOT" --headless --path . res://tests/mp_start.tscn -- server "$PORT" > "$TMP/srv.log" 2>&1 &
|
||||
SRV=$!
|
||||
sleep 3
|
||||
PIRATE_DICE_SERVER="ws://127.0.0.1:$PORT" timeout 680 "$GODOT" --headless --path . res://tests/mp_start.tscn -- host "$TMP/code.txt" > "$TMP/host.log" 2>&1 &
|
||||
H=$!
|
||||
sleep 2
|
||||
PIRATE_DICE_SERVER="ws://127.0.0.1:$PORT" timeout 680 "$GODOT" --headless --path . res://tests/mp_start.tscn -- join "$TMP/code.txt" > "$TMP/join.log" 2>&1 &
|
||||
J=$!
|
||||
wait $H; rc_h=$?
|
||||
wait $J; rc_j=$?
|
||||
# Der Server beendet sich nach dem Ende selbst. Ihn abzuschiessen würde
|
||||
# seine letzten Zeilen verlieren - Ausgabe in eine Datei ist gepuffert.
|
||||
for _ in $(seq 1 40); do kill -0 $SRV 2>/dev/null || break; sleep 1; done
|
||||
kill $SRV 2>/dev/null; wait $SRV 2>/dev/null
|
||||
grep -E "^(OK |FEHL|SERVER:|TEST:)" "$TMP/host.log" "$TMP/join.log" "$TMP/srv.log" | sed 's|^.*/||'
|
||||
pfad=$(cat "$TMP/host.log" "$TMP/join.log" "$TMP/srv.log" | grep -c "Failed to get path")
|
||||
fehler=$(cat "$TMP/host.log" "$TMP/join.log" "$TMP/srv.log" | grep -c "SCRIPT ERROR")
|
||||
echo " RPC-Pfadfehler=$pfad Skriptfehler=$fehler (Logs: $TMP)"
|
||||
[ $rc_h -ne 0 ] || [ $rc_j -ne 0 ] || [ "$pfad" -gt 0 ] || [ "$fehler" -gt 0 ] && ergebnis=1
|
||||
|
||||
echo
|
||||
[ $ergebnis -eq 0 ] && echo "ALLE TESTS GRÜN" || echo "TESTS ROT (Logs: $TMP)"
|
||||
exit $ergebnis
|
||||
Reference in New Issue
Block a user