//go:build cgo package backend import ( "strings" "testing" ) // candidates_test.go exercises parseCandidates (candidates.go), the CGO // wrapper around the shared C parser xce_parse_peers() that replaces // apiserver's retired hand-written parsePeer() (Req 6.1, 6.2). Kept // //go:build cgo since parseCandidates only exists in the CGO build -- // this file itself does not `import "C"` (a _test.go file can never do // that directly, golang/go#13938): it only calls the plain-Go-signature // parseCandidates defined in candidates.go, same split already used by // wire_dispatch_test.go/wire_dispatch_support.go in this package. func TestParseCandidates_ValidHostPort(t *testing.T) { got, err := parseCandidates("ctrl01:9990") if err != nil { t.Fatalf("unexpected error: %v", err) } want := []Candidate{{Name: "", Host: "ctrl01", Port: 9990}} if len(got) != len(want) || got[0] != want[0] { t.Errorf("got %+v, want %+v", got, want) } } func TestParseCandidates_NamePrefixSyntax(t *testing.T) { // The old hand-written parsePeer() could not parse this syntax at all // -- this is the format-parity case that proves real parity with // worker/scheduler's --peers (Req 6.1). got, err := parseCandidates("ctrl2=host2:9991") if err != nil { t.Fatalf("unexpected error: %v", err) } want := []Candidate{{Name: "ctrl2", Host: "host2", Port: 9991}} if len(got) != len(want) || got[0] != want[0] { t.Errorf("got %+v, want %+v", got, want) } } func TestParseCandidates_MultipleCommaJoined(t *testing.T) { got, err := parseCandidates("ctrl01:9990,ctrl2=host2:9991,ctrl03:9992") if err != nil { t.Fatalf("unexpected error: %v", err) } want := []Candidate{ {Name: "", Host: "ctrl01", Port: 9990}, {Name: "ctrl2", Host: "host2", Port: 9991}, {Name: "", Host: "ctrl03", Port: 9992}, } if len(got) != len(want) { t.Fatalf("got %d candidates, want %d: %+v", len(got), len(want), got) } for i := range want { if got[i] != want[i] { t.Errorf("candidate[%d]: got %+v, want %+v", i, got[i], want[i]) } } } func TestParseCandidates_MalformedEntry_MissingColon(t *testing.T) { _, err := parseCandidates("ctrl01") if err == nil { t.Fatal("expected error for missing colon, got nil") } // Req 6.2: reject identifying the malformed entry, consistent with how // worker/scheduler report it -- since xce_parse_peers() itself cannot // identify which token was malformed (same limit worker/scheduler's own // --peers parsing has), the whole attempted string is echoed back. if !strings.Contains(err.Error(), "ctrl01") { t.Errorf("error %q does not identify the malformed input", err.Error()) } } func TestParseCandidates_EmptyString(t *testing.T) { _, err := parseCandidates("") if err == nil { t.Fatal("expected error for empty peers string, got nil") } } func TestParseCandidates_MalformedEntry_MidList(t *testing.T) { // A malformed second entry rejects the WHOLE list -- xce_parse_peers() // does not skip just the bad token and keep the rest (Req 6.2, matching // worker/scheduler's own all-or-nothing --peers parsing). _, err := parseCandidates("ctrl01:9990,badentry,ctrl03:9992") if err == nil { t.Fatal("expected error for malformed mid-list entry, got nil") } } func TestParseCandidates_MalformedEntry_ZeroPort(t *testing.T) { // xce_parse_peers() alone would happily atoi() "0" into port=0 -- it is // xce_cluster_config_validate()'s port<=0 check (src/lib/cluster.c:94) // that rejects it, same as worker/scheduler (Req 6.2). _, err := parseCandidates("ctrl01:0") if err == nil { t.Fatal("expected error for port 0, got nil") } } func TestParseCandidates_MalformedEntry_NonNumericPort(t *testing.T) { // atoi("abc") silently returns 0 -- caught by the same port<=0 check as // the explicit-zero case above, not by xce_parse_peers() itself. _, err := parseCandidates("ctrl01:abc") if err == nil { t.Fatal("expected error for non-numeric port, got nil") } } func TestParseCandidates_MalformedEntry_EmptyHost(t *testing.T) { // xce_cluster_config_validate()'s hostname[0]=='\0' check // (src/lib/cluster.c:92) rejects an empty host, same as worker/scheduler. _, err := parseCandidates(":9990") if err == nil { t.Fatal("expected error for empty host, got nil") } } func TestParseCandidates_OverCapSilentlyTruncates(t *testing.T) { // MAX_HA_CANDIDATES (src/include/config.h) is 4. xce_parse_peers() // silently stops reading further tokens once the cap is reached -- // no error, no warning at this layer (the WARN-on-truncation behavior // belongs to xce_cluster_config_to_client_config(), a different // function, not exercised by this task). Don't invent stricter // behavior than the C function actually has. got, err := parseCandidates("c1:1,c2:2,c3:3,c4:4,c5:5") if err != nil { t.Fatalf("unexpected error: %v", err) } if len(got) != 4 { t.Fatalf("got %d candidates, want 4 (MAX_HA_CANDIDATES cap)", len(got)) } if got[3] != (Candidate{Name: "", Host: "c4", Port: 4}) { t.Errorf("candidate[3] = %+v, want the 4th entry (c5 silently dropped)", got[3]) } }