diff --git a/cmd/gbrgames/boottest.go b/cmd/gbrgames/boottest.go index bdd5526..22662ff 100644 --- a/cmd/gbrgames/boottest.go +++ b/cmd/gbrgames/boottest.go @@ -136,10 +136,20 @@ func bootTestPayload(gamesDir, id string, fillBlank bool) (map[string]any, error } } // Once up, the server must listen on what nodes publish: the container side of every default port + // test.yaml's unboundPorts are published but only opened once a player connects; each is listed by + // hand, with the reason in a comment + unbound := map[string]bool{} + if list, ok := test["unboundPorts"].([]any); ok { + for _, p := range list { + unbound[fmt.Sprint(p)] = true + } + } expectPorts := []any{} if ports, ok := g["defaultPorts"].([]any); ok { for _, p := range ports { - expectPorts = append(expectPorts, fmt.Sprint(p)) + if !unbound[fmt.Sprint(p)] { + expectPorts = append(expectPorts, fmt.Sprint(p)) + } } } diff --git a/cmd/gbrgames/boottest_test.go b/cmd/gbrgames/boottest_test.go index 34693a3..60fd5ba 100644 --- a/cmd/gbrgames/boottest_test.go +++ b/cmd/gbrgames/boottest_test.go @@ -106,3 +106,18 @@ func TestBootTestPayloadAsksForPublishedPortsAndHold(t *testing.T) { t.Errorf("missing secret: %v", err) } } + +func TestBootTestPayloadLeavesOutUnboundPorts(t *testing.T) { + dir := t.TempDir() + game := filepath.Join(dir, "example") + os.MkdirAll(game, 0o755) + os.WriteFile(filepath.Join(game, "game.yaml"), []byte("id: example\nname: Example\ndefaultDockerImage: x\ndefaultPorts:\n - 30000/tcp\n - 30001/udp\n"), 0o644) + os.WriteFile(filepath.Join(game, "test.yaml"), []byte("unboundPorts:\n - 30001/udp\n"), 0o644) + p, err := bootTestPayload(dir, "example", false) + if err != nil { + t.Fatal(err) + } + if ports := p["expectPorts"].([]any); len(ports) != 1 || ports[0] != "30000/tcp" { + t.Errorf("expectPorts %v", ports) + } +} diff --git a/games/minecraft-crossplay/test.yaml b/games/minecraft-crossplay/test.yaml index 04dc007..8d214bc 100644 --- a/games/minecraft-crossplay/test.yaml +++ b/games/minecraft-crossplay/test.yaml @@ -4,3 +4,7 @@ config: EULA: true readyExample: '[21:37:42 INFO]: Done (16.390s)! For help, type "help"' timeoutSec: 900 +# Bedrock players go through Geyser's NetherNet transport (see JVM_OPTS), which opens no UDP socket at +# startup: after boot the server listens on 25565/tcp and 19132/tcp only. To verify with a Bedrock client. +unboundPorts: + - 19132/udp diff --git a/schema/test.schema.json b/schema/test.schema.json index fcf3124..90e632b 100644 --- a/schema/test.schema.json +++ b/schema/test.schema.json @@ -33,6 +33,11 @@ "minimum": 10, "maximum": 3600 }, + "unboundPorts": { + "description": "Published ports the server only opens once a player connects, so the boot test doesn't expect them open. Each one exactly as in defaultPorts, with the reason in a comment.", + "type": "array", + "items": { "type": "string" } + }, "holdSec": { "description": "How long the server must stay up after its ready line to pass (default 60). Its published ports are checked after it.", "type": "integer",