pkg/autoscalerstatus/parse_test.go
Rama main · réplica de lectura del repositorio original.
package autoscalerstatus
import (
"os"
"path/filepath"
"strings"
"testing"
)
func loadFixture(t *testing.T, name string) string {
t.Helper()
b, err := os.ReadFile(filepath.Join("testdata", name))
if err != nil {
t.Fatalf("read fixture %s: %v", name, err)
}
return string(b)
}
func mustInt(t *testing.T, p *int, label string) int {
t.Helper()
if p == nil {
t.Fatalf("%s: expected non-nil int, got nil", label)
}
return *p
}
func TestParseZonalSinglePool(t *testing.T) {
st, err := Parse(loadFixture(t, "gke-zonal-single-pool.yaml"))
if err != nil {
t.Fatalf("Parse: %v", err)
}
if st.Format != FormatStructured {
t.Errorf("Format = %q, want %q", st.Format, FormatStructured)
}
if st.AutoscalerState != "Running" {
t.Errorf("AutoscalerState = %q, want %q", st.AutoscalerState, "Running")
}
if st.Time == nil {
t.Errorf("Time = nil, want non-nil parsed timestamp")
}
if len(st.NodeGroups) != 1 {
t.Fatalf("len(NodeGroups) = %d, want 1", len(st.NodeGroups))
}
ng := st.NodeGroups[0]
if got := mustInt(t, ng.MinSize, "MinSize"); got != 5 {
t.Errorf("MinSize = %d, want 5", got)
}
if got := mustInt(t, ng.MaxSize, "MaxSize"); got != 11 {
t.Errorf("MaxSize = %d, want 11", got)
}
if got := mustInt(t, ng.Target, "Target"); got != 9 {
t.Errorf("Target = %d, want 9", got)
}
if got := mustInt(t, ng.Health.Ready, "Health.Ready"); got != 9 {
t.Errorf("Health.Ready = %d, want 9", got)
}
if got := mustInt(t, ng.Health.Registered, "Health.Registered"); got != 9 {
t.Errorf("Health.Registered = %d, want 9", got)
}
if ng.Health.Status != "Healthy" {
t.Errorf("Health.Status = %q, want %q", ng.Health.Status, "Healthy")
}
if ng.ScaleUp.Status != "NoActivity" {
t.Errorf("ScaleUp.Status = %q, want %q", ng.ScaleUp.Status, "NoActivity")
}
if ng.ScaleUp.Backoff != nil {
t.Errorf("ScaleUp.Backoff = %+v, want nil for empty backoffInfo {}", ng.ScaleUp.Backoff)
}
if !strings.HasSuffix(ng.Basename, "-grp") {
t.Errorf("Basename = %q, want suffix %q", ng.Basename, "-grp")
}
if ng.Basename != "gke-demo-cluster--default-pool-16275362-grp" {
t.Errorf("Basename = %q, want %q", ng.Basename, "gke-demo-cluster--default-pool-16275362-grp")
}
}
func TestParseRegionalMultiMigScaleToZero(t *testing.T) {
st, err := Parse(loadFixture(t, "gke-regional-multi-mig-scale-to-zero.yaml"))
if err != nil {
t.Fatalf("Parse: %v", err)
}
if st.Format != FormatStructured {
t.Errorf("Format = %q, want %q", st.Format, FormatStructured)
}
if len(st.NodeGroups) != 13 {
t.Fatalf("len(NodeGroups) = %d, want 13", len(st.NodeGroups))
}
zeroTargets := 0
for _, ng := range st.NodeGroups {
if got := mustInt(t, ng.MinSize, "MinSize"); got != 0 {
t.Errorf("group %s: MinSize = %d, want 0", ng.Basename, got)
}
if got := mustInt(t, ng.MaxSize, "MaxSize"); got != 1000 {
t.Errorf("group %s: MaxSize = %d, want 1000", ng.Basename, got)
}
if ng.Target != nil && *ng.Target == 0 {
zeroTargets++
// Scale-to-zero groups publish an explicit `lastProbeTime: null`;
// that must survive as nil, never as a zero time.
if ng.Health.LastProbeTime != nil {
t.Errorf("group %s: target 0 should keep Health.LastProbeTime nil, got %v",
ng.Basename, ng.Health.LastProbeTime)
}
}
}
if zeroTargets != 10 {
t.Errorf("groups with Target==0 = %d, want 10", zeroTargets)
}
}
func TestParseTruncatedNames(t *testing.T) {
st, err := Parse(loadFixture(t, "gke-truncated-names.yaml"))
if err != nil {
t.Fatalf("Parse: %v", err)
}
if len(st.NodeGroups) != 2 {
t.Fatalf("len(NodeGroups) = %d, want 2", len(st.NodeGroups))
}
g0 := st.NodeGroups[0]
wantName0 := "https://www.googleapis.com/compute/v1/projects/example-project/zones/me-west1-a/instanceGroups/gke-longname-clust-longname-clust-4712e1a8-grp"
if g0.Name != wantName0 {
t.Errorf("group[0].Name = %q, want verbatim %q", g0.Name, wantName0)
}
if g0.Basename != "gke-longname-clust-longname-clust-4712e1a8-grp" {
t.Errorf("group[0].Basename = %q, want %q", g0.Basename, "gke-longname-clust-longname-clust-4712e1a8-grp")
}
if !strings.Contains(g0.Basename, "longname-clust") {
t.Errorf("group[0].Basename = %q, want it to contain %q", g0.Basename, "longname-clust")
}
if got := mustInt(t, g0.MinSize, "group[0].MinSize"); got != 15 {
t.Errorf("group[0].MinSize = %d, want 15", got)
}
if got := mustInt(t, g0.MaxSize, "group[0].MaxSize"); got != 20 {
t.Errorf("group[0].MaxSize = %d, want 20", got)
}
g1 := st.NodeGroups[1]
if g1.Basename != "gke-longname-clust-second-pool-trun-fb123523-grp" {
t.Errorf("group[1].Basename = %q, want %q", g1.Basename, "gke-longname-clust-second-pool-trun-fb123523-grp")
}
if got := mustInt(t, g1.MinSize, "group[1].MinSize"); got != 3 {
t.Errorf("group[1].MinSize = %d, want 3", got)
}
if got := mustInt(t, g1.MaxSize, "group[1].MaxSize"); got != 5 {
t.Errorf("group[1].MaxSize = %d, want 5", got)
}
}
func TestParseEKSManagedNodeGroups(t *testing.T) {
raw, err := os.ReadFile("testdata/eks-managed-node-groups.yaml")
if err != nil {
t.Fatalf("read fixture: %v", err)
}
status, err := Parse(string(raw))
if err != nil {
t.Fatalf("parse: %v", err)
}
if status.Format != FormatStructured || status.AutoscalerState != "Running" {
t.Fatalf("status = %q %q", status.Format, status.AutoscalerState)
}
if len(status.NodeGroups) != 3 {
t.Fatalf("node groups = %d, want 3", len(status.NodeGroups))
}
byName := map[string]NodeGroup{}
for _, group := range status.NodeGroups {
byName[group.Basename] = group
}
system, found := byName["eks-system-pool-28cf09b0-ae43-f32a-94c0-27fea5fc8bdc"]
if !found || system.MinSize == nil || *system.MinSize != 2 || system.MaxSize == nil || *system.MaxSize != 4 {
t.Fatalf("system pool group = %+v", system)
}
if system.Health.Ready == nil || *system.Health.Ready != 2 {
t.Fatalf("system pool ready = %+v", system.Health)
}
// Zero-node EKS groups omit nodeCounts entirely (GKE publishes explicit
// nulls instead) — both must land as nil, never zero.
loadtest := byName["eks-loadtest-od-26cf0a34-4af9-ec18-066e-a8b0142e7d03"]
if loadtest.Target == nil || *loadtest.Target != 0 || loadtest.Health.Ready != nil {
t.Fatalf("scale-to-zero group = %+v", loadtest)
}
}
func TestParseLegacyText(t *testing.T) {
st, err := Parse(loadFixture(t, "legacy-text.txt"))
if err != nil {
t.Fatalf("Parse: %v", err)
}
if st.Format != FormatLegacyText {
t.Errorf("Format = %q, want %q", st.Format, FormatLegacyText)
}
if st.Time == nil {
t.Errorf("Time = nil, want non-nil parsed header timestamp")
}
if len(st.NodeGroups) != 2 {
t.Fatalf("len(NodeGroups) = %d, want 2", len(st.NodeGroups))
}
g0 := st.NodeGroups[0]
if g0.Basename != "gke-legacy-cluster-default-pool-a1b2c3d4-grp" {
t.Errorf("group[0].Basename = %q, want %q", g0.Basename, "gke-legacy-cluster-default-pool-a1b2c3d4-grp")
}
if g0.Health.Status != "Healthy" {
t.Errorf("group[0].Health.Status = %q, want %q", g0.Health.Status, "Healthy")
}
if got := mustInt(t, g0.Target, "group[0].Target"); got != 3 {
t.Errorf("group[0].Target = %d, want 3", got)
}
if got := mustInt(t, g0.MinSize, "group[0].MinSize"); got != 1 {
t.Errorf("group[0].MinSize = %d, want 1", got)
}
if got := mustInt(t, g0.MaxSize, "group[0].MaxSize"); got != 5 {
t.Errorf("group[0].MaxSize = %d, want 5", got)
}
if got := mustInt(t, g0.Health.Ready, "group[0].Health.Ready"); got != 3 {
t.Errorf("group[0].Health.Ready = %d, want 3", got)
}
if got := mustInt(t, g0.Health.Registered, "group[0].Health.Registered"); got != 3 {
t.Errorf("group[0].Health.Registered = %d, want 3", got)
}
if g0.ScaleUp.Status != "NoActivity" {
t.Errorf("group[0].ScaleUp.Status = %q, want %q", g0.ScaleUp.Status, "NoActivity")
}
if g0.ScaleDown.Status != "NoCandidates" {
t.Errorf("group[0].ScaleDown.Status = %q, want %q", g0.ScaleDown.Status, "NoCandidates")
}
if g0.Health.LastProbeTime == nil {
t.Errorf("group[0].Health.LastProbeTime = nil, want non-nil")
}
if g0.Health.LastTransition == nil {
t.Errorf("group[0].Health.LastTransition = nil, want non-nil")
}
g1 := st.NodeGroups[1]
if got := mustInt(t, g1.Target, "group[1].Target"); got != 2 {
t.Errorf("group[1].Target = %d, want 2", got)
}
if got := mustInt(t, g1.MinSize, "group[1].MinSize"); got != 0 {
t.Errorf("group[1].MinSize = %d, want 0", got)
}
if got := mustInt(t, g1.MaxSize, "group[1].MaxSize"); got != 10 {
t.Errorf("group[1].MaxSize = %d, want 10", got)
}
if got := mustInt(t, g1.Health.Ready, "group[1].Health.Ready"); got != 2 {
t.Errorf("group[1].Health.Ready = %d, want 2", got)
}
}
func TestParseAKSStructured(t *testing.T) {
st, err := Parse(loadFixture(t, "aks-structured.yaml"))
if err != nil {
t.Fatalf("Parse: %v", err)
}
if st.Format != FormatStructured {
t.Errorf("Format = %q, want %q", st.Format, FormatStructured)
}
if st.AutoscalerState != "Running" {
t.Errorf("AutoscalerState = %q, want %q", st.AutoscalerState, "Running")
}
if st.Time == nil {
t.Errorf("Time = nil, want non-nil parsed timestamp")
}
if len(st.NodeGroups) != 3 {
t.Fatalf("len(NodeGroups) = %d, want 3", len(st.NodeGroups))
}
// AKS reports each node group by its bare VMSS name (ScaleSet.Id() ==
// ScaleSet.Name). There is no "-grp" suffix and no "/"-delimited path, so
// Basename must equal the published name verbatim — never truncated, never
// stripped. Node names are the VMSS name plus a base36 instance suffix
// (aks-nodepool1-33037761-vmss000000), so HasPrefix(node, Basename) — the
// rule the logical-group join relies on — must hold.
byName := map[string]NodeGroup{}
for _, ng := range st.NodeGroups {
if ng.Basename != ng.Name {
t.Errorf("group %q: Basename = %q, want it to equal Name verbatim (no path, no suffix to strip)", ng.Name, ng.Basename)
}
if strings.HasSuffix(ng.Basename, "-grp") {
t.Errorf("group %q: Basename unexpectedly ends in -grp (that is a GKE MIG convention, not AKS)", ng.Name)
}
if trimmed := strings.TrimSuffix(ng.Basename, "-grp"); trimmed != ng.Basename {
t.Errorf("group %q: TrimSuffix(-grp) mutated the AKS basename to %q; the join key must be the VMSS name unchanged", ng.Name, trimmed)
}
syntheticNode := ng.Basename + "000000"
if !strings.HasPrefix(syntheticNode, ng.Basename) {
t.Errorf("group %q: node %q does not start with Basename %q — the group join would orphan this child", ng.Name, syntheticNode, ng.Basename)
}
byName[ng.Name] = ng
}
sys, ok := byName["aks-nodepool1-33037761-vmss"]
if !ok {
t.Fatalf("missing system node group aks-nodepool1-33037761-vmss; got %v", byName)
}
if got := mustInt(t, sys.MinSize, "system.MinSize"); got != 1 {
t.Errorf("system MinSize = %d, want 1", got)
}
if got := mustInt(t, sys.MaxSize, "system.MaxSize"); got != 3 {
t.Errorf("system MaxSize = %d, want 3", got)
}
if got := mustInt(t, sys.Target, "system.Target"); got != 2 {
t.Errorf("system Target = %d, want 2", got)
}
if got := mustInt(t, sys.Health.Ready, "system.Health.Ready"); got != 2 {
t.Errorf("system Health.Ready = %d, want 2", got)
}
if sys.Health.Status != "Healthy" {
t.Errorf("system Health.Status = %q, want %q", sys.Health.Status, "Healthy")
}
if sys.ScaleUp.Backoff != nil {
t.Errorf("system ScaleUp.Backoff = %+v, want nil for empty backoffInfo {}", sys.ScaleUp.Backoff)
}
// Scale-to-zero user pool: target 0 and minSize 0 must survive as real zero
// values, never confused with "not published".
zero, ok := byName["aks-spotgpu-41529630-vmss"]
if !ok {
t.Fatalf("missing scale-to-zero node group aks-spotgpu-41529630-vmss")
}
if got := mustInt(t, zero.MinSize, "spotgpu.MinSize"); got != 0 {
t.Errorf("spotgpu MinSize = %d, want 0", got)
}
if got := mustInt(t, zero.MaxSize, "spotgpu.MaxSize"); got != 4 {
t.Errorf("spotgpu MaxSize = %d, want 4", got)
}
if got := mustInt(t, zero.Target, "spotgpu.Target"); got != 0 {
t.Errorf("spotgpu Target = %d, want 0", got)
}
if got := mustInt(t, zero.Health.Ready, "spotgpu.Health.Ready"); got != 0 {
t.Errorf("spotgpu Health.Ready = %d, want 0", got)
}
}
func TestParseAKSLegacyText(t *testing.T) {
st, err := Parse(loadFixture(t, "aks-legacy-text.txt"))
if err != nil {
t.Fatalf("Parse: %v", err)
}
if st.Format != FormatLegacyText {
t.Errorf("Format = %q, want %q", st.Format, FormatLegacyText)
}
if st.Time == nil {
t.Errorf("Time = nil, want non-nil parsed header timestamp")
}
if len(st.NodeGroups) != 2 {
t.Fatalf("len(NodeGroups) = %d, want 2", len(st.NodeGroups))
}
g0 := st.NodeGroups[0]
if g0.Name != "aks-nodepool1-33037761-vmss" {
t.Errorf("group[0].Name = %q, want %q", g0.Name, "aks-nodepool1-33037761-vmss")
}
if g0.Basename != "aks-nodepool1-33037761-vmss" {
t.Errorf("group[0].Basename = %q, want it to equal the VMSS name verbatim", g0.Basename)
}
if strings.TrimSuffix(g0.Basename, "-grp") != g0.Basename {
t.Errorf("group[0].Basename %q was mutated by TrimSuffix(-grp); AKS VMSS names carry no such suffix", g0.Basename)
}
if !strings.HasPrefix(g0.Basename+"000000", g0.Basename) {
t.Errorf("group[0]: node name does not start with Basename %q", g0.Basename)
}
if g0.Health.Status != "Healthy" {
t.Errorf("group[0].Health.Status = %q, want %q", g0.Health.Status, "Healthy")
}
if got := mustInt(t, g0.MinSize, "group[0].MinSize"); got != 1 {
t.Errorf("group[0].MinSize = %d, want 1", got)
}
if got := mustInt(t, g0.MaxSize, "group[0].MaxSize"); got != 3 {
t.Errorf("group[0].MaxSize = %d, want 3", got)
}
if got := mustInt(t, g0.Target, "group[0].Target"); got != 2 {
t.Errorf("group[0].Target = %d, want 2", got)
}
if got := mustInt(t, g0.Health.Ready, "group[0].Health.Ready"); got != 2 {
t.Errorf("group[0].Health.Ready = %d, want 2", got)
}
if g0.ScaleUp.Status != "NoActivity" {
t.Errorf("group[0].ScaleUp.Status = %q, want %q", g0.ScaleUp.Status, "NoActivity")
}
// Second pool is a scale-to-zero user pool stuck in scale-up backoff (the
// canonical AKS quota-exhaustion failure). Bounds still parse; the backoff is
// surfaced as the ScaleUp status token.
g1 := st.NodeGroups[1]
if g1.Basename != "aks-spotgpu-41529630-vmss" {
t.Errorf("group[1].Basename = %q, want %q", g1.Basename, "aks-spotgpu-41529630-vmss")
}
if got := mustInt(t, g1.MinSize, "group[1].MinSize"); got != 0 {
t.Errorf("group[1].MinSize = %d, want 0", got)
}
if got := mustInt(t, g1.MaxSize, "group[1].MaxSize"); got != 6 {
t.Errorf("group[1].MaxSize = %d, want 6", got)
}
if got := mustInt(t, g1.Target, "group[1].Target"); got != 1 {
t.Errorf("group[1].Target = %d, want 1", got)
}
if g1.ScaleUp.Status != "Backoff" {
t.Errorf("group[1].ScaleUp.Status = %q, want %q", g1.ScaleUp.Status, "Backoff")
}
}
func TestParseUnrecognized(t *testing.T) {
cases := map[string]string{
"empty": "",
"whitespace": " \n ",
"prose": "this is not a cluster-autoscaler status at all",
"yaml-no-keys": "{some: yaml, without: known keys}",
"unrelated-yaml": "foo: bar\nbaz: 1\n",
}
for name, raw := range cases {
t.Run(name, func(t *testing.T) {
if _, err := Parse(raw); err == nil {
t.Errorf("Parse(%q) err = nil, want error", raw)
}
})
}
}
func TestParseLegacyTextReportsRunningAutoscalerState(t *testing.T) {
// The legacy format has no autoscalerStatus field. Leaving the state blank
// left every pre-1.30 cluster's manager rollup permanently "unknown", since
// the rollup reads AutoscalerState to reach "healthy".
for _, fixture := range []string{"legacy-text.txt", "aks-legacy-text.txt"} {
t.Run(fixture, func(t *testing.T) {
st, err := Parse(loadFixture(t, fixture))
if err != nil {
t.Fatalf("Parse: %v", err)
}
if st.AutoscalerState != "Running" {
t.Errorf("AutoscalerState = %q, want %q — a payload with health sections is a running controller", st.AutoscalerState, "Running")
}
})
}
}
func TestParseLegacyInitializingPayload(t *testing.T) {
// Before its first health reading the legacy autoscaler writes the bare
// word into data.status. Rejecting it reported a parse error for a
// perfectly healthy starting controller.
st, err := Parse("Initializing\n")
if err != nil {
t.Fatalf("Parse: %v", err)
}
if st.Format != FormatLegacyText {
t.Errorf("Format = %q, want %q", st.Format, FormatLegacyText)
}
if st.AutoscalerState != "Initializing" {
t.Errorf("AutoscalerState = %q, want %q", st.AutoscalerState, "Initializing")
}
if len(st.NodeGroups) != 0 {
t.Errorf("NodeGroups = %+v, want none — nothing was published yet", st.NodeGroups)
}
}
func TestParseLegacyBackoffCarriesStatusWithoutStructuredDetails(t *testing.T) {
st, err := Parse(loadFixture(t, "aks-legacy-text.txt"))
if err != nil {
t.Fatalf("Parse: %v", err)
}
if st.ClusterWide.ScaleUp.Status != "Backoff" {
t.Errorf("cluster-wide ScaleUp.Status = %q, want %q", st.ClusterWide.ScaleUp.Status, "Backoff")
}
if st.ClusterWide.ScaleUp.Backoff != nil {
t.Errorf("legacy text carries no structured backoffInfo; Backoff must stay nil, got %+v", st.ClusterWide.ScaleUp.Backoff)
}
var spot *NodeGroup
for i := range st.NodeGroups {
if st.NodeGroups[i].Basename == "aks-spotgpu-41529630-vmss" {
spot = &st.NodeGroups[i]
}
}
if spot == nil {
t.Fatalf("spotgpu group missing from %+v", st.NodeGroups)
}
if spot.ScaleUp.Status != "Backoff" {
t.Errorf("spotgpu ScaleUp.Status = %q, want %q", spot.ScaleUp.Status, "Backoff")
}
}
func TestParseScaleDownCandidates(t *testing.T) {
for _, fixture := range []string{"gke-zonal-single-pool.yaml", "legacy-text.txt"} {
for _, test := range []struct {
name string
value string
want *int
}{
{"missing CandidatesPresent", "", nil},
{"zero", "0", func() *int { n := 0; return &n }()},
{"positive", "2", func() *int { n := 2; return &n }()},
} {
t.Run(fixture+"/"+test.name, func(t *testing.T) {
raw := loadFixture(t, fixture)
if strings.HasSuffix(fixture, ".yaml") {
if test.value != "" && test.value != "0" {
raw = strings.ReplaceAll(raw, "scaleDown:\n", "scaleDown:\n candidates: "+test.value+"\n")
}
} else {
replacement := ""
if test.value != "" {
replacement = " (candidates=" + test.value + ")"
}
raw = strings.ReplaceAll(raw, " (candidates=0)", replacement)
}
wantStatus := "NoCandidates"
if test.name == "positive" || test.want == nil {
wantStatus = "CandidatesPresent"
raw = strings.ReplaceAll(raw, "NoCandidates", wantStatus)
}
st, err := Parse(raw)
if err != nil {
t.Fatal(err)
}
conditions := []Condition{st.ClusterWide.ScaleDown}
for _, group := range st.NodeGroups {
conditions = append(conditions, group.ScaleDown)
}
for _, condition := range conditions {
if test.want == nil {
if condition.Candidates != nil {
t.Fatalf("unpublished candidates = %v", *condition.Candidates)
}
} else if got := mustInt(t, condition.Candidates, "candidates"); got != *test.want {
t.Fatalf("candidates = %d, want %d", got, *test.want)
}
if condition.Status != wantStatus || condition.LastTransition == nil {
t.Fatalf("scale-down status/transition lost: %+v", condition)
}
}
})
}
}
}
func TestParseScaleDownNullCandidatesAndTransition(t *testing.T) {
raw := "autoscalerStatus: Running\nnodeGroups:\n- name: idle\n scaleDown:\n candidates: null\n lastTransitionTime: null\n status: CandidatesPresent\n"
st, err := Parse(raw)
if err != nil {
t.Fatal(err)
}
if st.NodeGroups[0].ScaleDown.Candidates != nil || st.NodeGroups[0].ScaleDown.LastTransition != nil {
t.Fatalf("null fields must stay unpublished: %+v", st.NodeGroups[0].ScaleDown)
}
}
func TestStructuredNoCandidatesOmitsZero(t *testing.T) {
st, err := Parse(loadFixture(t, "gke-zonal-single-pool.yaml"))
if err != nil {
t.Fatal(err)
}
conditions := []Condition{st.ClusterWide.ScaleDown}
for _, group := range st.NodeGroups {
conditions = append(conditions, group.ScaleDown)
}
for _, condition := range conditions {
if condition.Status != "NoCandidates" || mustInt(t, condition.Candidates, "candidates") != 0 {
t.Fatalf("omitted zero lost: %+v", condition)
}
}
}
func TestLegacyNoCandidatesWithoutCountStaysUnknown(t *testing.T) {
raw := strings.ReplaceAll(loadFixture(t, "legacy-text.txt"), " (candidates=0)", "")
st, err := Parse(raw)
if err != nil {
t.Fatal(err)
}
conditions := []Condition{st.ClusterWide.ScaleDown}
for _, group := range st.NodeGroups {
conditions = append(conditions, group.ScaleDown)
}
for _, condition := range conditions {
if condition.Status != "NoCandidates" || condition.Candidates != nil {
t.Fatalf("legacy status must not infer a count: %+v", condition)
}
}
}