mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
134 lines
4.6 KiB
Go
134 lines
4.6 KiB
Go
package domain
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func candidate(id string, rating float64, wait time.Duration, eu, na float64, now time.Time) Candidate {
|
|
return Candidate{TicketID: id, PlayerID: "player-" + id, Rating: rating, EnqueuedAt: now.Add(-wait), PredictedRTT: map[string]float64{"EU": eu, "NA": na}}
|
|
}
|
|
|
|
func TestSelectCandidatesNeverCrossesRTTOrMutualRatingCeilings(t *testing.T) {
|
|
now := time.Unix(100000, 0)
|
|
anchor := candidate("a", 1500, 0, 40, 140, now)
|
|
players := []Candidate{
|
|
candidate("b", 1590, 10*time.Second, 45, 40, now),
|
|
candidate("c", 1590, 70*time.Second, 50, 50, now),
|
|
candidate("d", 1800, 70*time.Second, 40, 40, now),
|
|
}
|
|
selection, err := SelectCandidates(anchor, players, 3, now)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if selection.Region != "EU" || selection.WorstRTT > MaxPlacementRTT {
|
|
t.Fatalf("bad region/RTT: %+v", selection)
|
|
}
|
|
if ticketIDs(selection.Players) != "a\x00b\x00c\x00" {
|
|
t.Fatalf("selected incompatible or non-optimal set: %q", ticketIDs(selection.Players))
|
|
}
|
|
}
|
|
|
|
func TestSelectCandidatesRequiresAnchorAndUsesDeterministicTieBreak(t *testing.T) {
|
|
now := time.Unix(100000, 0)
|
|
anchor := candidate("anchor", 1500, 60*time.Second, 50, 50, now)
|
|
players := []Candidate{
|
|
candidate("z", 1500, 10*time.Second, 50, 50, now),
|
|
candidate("y", 1500, 10*time.Second, 50, 50, now),
|
|
candidate("x", 1500, 10*time.Second, 50, 50, now),
|
|
}
|
|
selection, err := SelectCandidates(anchor, players, 3, now)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !containsTicket(selection.Players, "anchor") {
|
|
t.Fatal("anchor was omitted")
|
|
}
|
|
if ticketIDs(selection.Players) != "anchor\x00x\x00y\x00" {
|
|
t.Fatalf("tie break was not lexical: %q", ticketIDs(selection.Players))
|
|
}
|
|
}
|
|
|
|
func TestRatingToleranceWideningIsCapped(t *testing.T) {
|
|
if RatingTolerance(29) != 100 || RatingTolerance(30) != 125 {
|
|
t.Fatal("30-second widening boundary is wrong")
|
|
}
|
|
if RatingTolerance(1000) != MaxRatingTolerance {
|
|
t.Fatal("rating tolerance is not capped")
|
|
}
|
|
}
|
|
|
|
func TestSelectCandidatesRejectsNoCommonRegion(t *testing.T) {
|
|
now := time.Unix(100000, 0)
|
|
anchor := candidate("a", 1500, 0, 101, 40, now)
|
|
other := candidate("b", 1500, 0, 40, 101, now)
|
|
if _, err := SelectCandidates(anchor, []Candidate{other}, 2, now); err == nil {
|
|
t.Fatal("selected players without a common <=100ms region")
|
|
}
|
|
}
|
|
|
|
func TestFormFromQueueUsesServerProjectionAndBalancesTeams(t *testing.T) {
|
|
now := time.Unix(100000, 0)
|
|
queue := NewQueue()
|
|
for _, id := range []string{"c", "a", "b", "d"} {
|
|
candidate := candidate(id, 1500, time.Second, 40, 45, now)
|
|
if _, err := queue.Create(candidate.PlayerID, candidate.TicketID, "create-key-"+id+"-123456", candidate, now.Add(-time.Duration(len(id))*time.Millisecond)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
formation, err := FormFromQueue(queue, 4, now)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if formation.Selection.Players[0].TicketID != "a" || formation.Selection.Region != "EU" || len(formation.Teams.Team0) != 2 || len(formation.Teams.Team1) != 2 {
|
|
t.Fatalf("formation = %+v", formation)
|
|
}
|
|
seen := map[string]bool{}
|
|
for _, player := range append(formation.Teams.Team0, formation.Teams.Team1...) {
|
|
if seen[player.PlayerID] {
|
|
t.Fatalf("duplicate player in teams: %s", player.PlayerID)
|
|
}
|
|
seen[player.PlayerID] = true
|
|
}
|
|
}
|
|
|
|
func TestSelectCandidatesRejectsMalformedCandidateInsteadOfTrustingIt(t *testing.T) {
|
|
now := time.Unix(100000, 0)
|
|
anchor := candidate("a", 1500, 0, 40, 40, now)
|
|
malformed := candidate("b", 1500, 0, 40, 40, now)
|
|
malformed.PlayerID = anchor.PlayerID
|
|
if _, err := SelectCandidates(anchor, []Candidate{malformed}, 2, now); err == nil {
|
|
t.Fatal("duplicate player candidate accepted")
|
|
}
|
|
}
|
|
|
|
func TestSelectCandidatesDoesNotMixQueueCompatibilityContracts(t *testing.T) {
|
|
now := time.Unix(100000, 0)
|
|
anchor := candidate("a", 1500, 0, 40, 40, now)
|
|
anchor.Playlist = Ranked
|
|
anchor.ClientBuild = "build-1"
|
|
anchor.ProtocolVersion = 2
|
|
compatible := anchor
|
|
compatible.TicketID = "b"
|
|
compatible.PlayerID = "player-b"
|
|
mismatchPlaylist := compatible
|
|
mismatchPlaylist.TicketID = "c"
|
|
mismatchPlaylist.PlayerID = "player-c"
|
|
mismatchPlaylist.Playlist = Casual
|
|
mismatchBuild := compatible
|
|
mismatchBuild.TicketID = "d"
|
|
mismatchBuild.PlayerID = "player-d"
|
|
mismatchBuild.ClientBuild = "build-2"
|
|
mismatchProtocol := compatible
|
|
mismatchProtocol.TicketID = "e"
|
|
mismatchProtocol.PlayerID = "player-e"
|
|
mismatchProtocol.ProtocolVersion = 3
|
|
selection, err := SelectCandidates(anchor, []Candidate{mismatchPlaylist, mismatchBuild, mismatchProtocol, compatible}, 2, now)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ticketIDs(selection.Players) != "a\x00b\x00" {
|
|
t.Fatalf("selected incompatible metadata: %q", ticketIDs(selection.Players))
|
|
}
|
|
}
|