| /* |
| * |
| * Copyright 2022 gRPC authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package balancer |
| |
| import ( |
| "testing" |
| |
| "google.golang.org/grpc/connectivity" |
| "google.golang.org/grpc/internal/grpctest" |
| ) |
| |
| type s struct { |
| grpctest.Tester |
| } |
| |
| func Test(t *testing.T) { |
| grpctest.RunSubTests(t, s{}) |
| } |
| |
| // TestRecordTransition_FirstStateChange tests the first call to |
| // RecordTransition where the `oldState` is usually set to `Shutdown` (a state |
| // that the ConnectivityStateEvaluator is set to ignore). |
| func (s) TestRecordTransition_FirstStateChange(t *testing.T) { |
| tests := []struct { |
| newState connectivity.State |
| wantState connectivity.State |
| }{ |
| { |
| newState: connectivity.Idle, |
| wantState: connectivity.Idle, |
| }, |
| { |
| newState: connectivity.Connecting, |
| wantState: connectivity.Connecting, |
| }, |
| { |
| newState: connectivity.Ready, |
| wantState: connectivity.Ready, |
| }, |
| { |
| newState: connectivity.TransientFailure, |
| wantState: connectivity.TransientFailure, |
| }, |
| { |
| newState: connectivity.Shutdown, |
| wantState: connectivity.TransientFailure, |
| }, |
| } |
| for _, test := range tests { |
| cse := &ConnectivityStateEvaluator{} |
| if gotState := cse.RecordTransition(connectivity.Shutdown, test.newState); gotState != test.wantState { |
| t.Fatalf("RecordTransition(%v, %v) = %v, want %v", connectivity.Shutdown, test.newState, gotState, test.wantState) |
| } |
| } |
| } |
| |
| // TestRecordTransition_SameState tests the scenario where state transitions to |
| // the same state are recorded multiple times. |
| func (s) TestRecordTransition_SameState(t *testing.T) { |
| tests := []struct { |
| newState connectivity.State |
| wantState connectivity.State |
| }{ |
| { |
| newState: connectivity.Idle, |
| wantState: connectivity.Idle, |
| }, |
| { |
| newState: connectivity.Connecting, |
| wantState: connectivity.Connecting, |
| }, |
| { |
| newState: connectivity.Ready, |
| wantState: connectivity.Ready, |
| }, |
| { |
| newState: connectivity.TransientFailure, |
| wantState: connectivity.TransientFailure, |
| }, |
| { |
| newState: connectivity.Shutdown, |
| wantState: connectivity.TransientFailure, |
| }, |
| } |
| const numStateChanges = 5 |
| for _, test := range tests { |
| cse := &ConnectivityStateEvaluator{} |
| var prevState, gotState connectivity.State |
| prevState = connectivity.Shutdown |
| for i := 0; i < numStateChanges; i++ { |
| gotState = cse.RecordTransition(prevState, test.newState) |
| prevState = test.newState |
| } |
| if gotState != test.wantState { |
| t.Fatalf("RecordTransition() = %v, want %v", gotState, test.wantState) |
| } |
| } |
| } |
| |
| // TestRecordTransition_SingleSubConn_DifferentStates tests some common |
| // connectivity state change scenarios, on a single subConn. |
| func (s) TestRecordTransition_SingleSubConn_DifferentStates(t *testing.T) { |
| tests := []struct { |
| name string |
| states []connectivity.State |
| wantState connectivity.State |
| }{ |
| { |
| name: "regular transition to ready", |
| states: []connectivity.State{connectivity.Idle, connectivity.Connecting, connectivity.Ready}, |
| wantState: connectivity.Ready, |
| }, |
| { |
| name: "regular transition to transient failure", |
| states: []connectivity.State{connectivity.Idle, connectivity.Connecting, connectivity.TransientFailure}, |
| wantState: connectivity.TransientFailure, |
| }, |
| { |
| name: "regular transition to ready", |
| states: []connectivity.State{connectivity.Idle, connectivity.Connecting, connectivity.Ready, connectivity.Idle}, |
| wantState: connectivity.Idle, |
| }, |
| { |
| name: "transition from ready to transient failure", |
| states: []connectivity.State{connectivity.Idle, connectivity.Connecting, connectivity.Ready, connectivity.TransientFailure}, |
| wantState: connectivity.TransientFailure, |
| }, |
| { |
| name: "transition from transient failure back to ready", |
| states: []connectivity.State{connectivity.Idle, connectivity.Connecting, connectivity.Ready, connectivity.TransientFailure, connectivity.Ready}, |
| wantState: connectivity.Ready, |
| }, |
| { |
| // This state transition is usually suppressed at the LB policy level, by |
| // not calling RecordTransition. |
| name: "transition from transient failure back to idle", |
| states: []connectivity.State{connectivity.Idle, connectivity.Connecting, connectivity.Ready, connectivity.TransientFailure, connectivity.Idle}, |
| wantState: connectivity.Idle, |
| }, |
| { |
| // This state transition is usually suppressed at the LB policy level, by |
| // not calling RecordTransition. |
| name: "transition from transient failure back to connecting", |
| states: []connectivity.State{connectivity.Idle, connectivity.Connecting, connectivity.Ready, connectivity.TransientFailure, connectivity.Connecting}, |
| wantState: connectivity.Connecting, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| cse := &ConnectivityStateEvaluator{} |
| var prevState, gotState connectivity.State |
| prevState = connectivity.Shutdown |
| for _, newState := range test.states { |
| gotState = cse.RecordTransition(prevState, newState) |
| prevState = newState |
| } |
| if gotState != test.wantState { |
| t.Fatalf("RecordTransition() = %v, want %v", gotState, test.wantState) |
| } |
| }) |
| } |
| } |
| |
| // TestRecordTransition_MultipleSubConns_DifferentStates tests state transitions |
| // among multiple subConns, and verifies that the connectivity state aggregation |
| // algorithm produces the expected aggregate connectivity state. |
| func (s) TestRecordTransition_MultipleSubConns_DifferentStates(t *testing.T) { |
| tests := []struct { |
| name string |
| // Each entry in this slice corresponds to the state changes happening on an |
| // individual subConn. |
| subConnStates [][]connectivity.State |
| wantState connectivity.State |
| }{ |
| { |
| name: "atleast one ready", |
| subConnStates: [][]connectivity.State{ |
| {connectivity.Idle, connectivity.Connecting, connectivity.Ready}, |
| {connectivity.Idle}, |
| {connectivity.Idle, connectivity.Connecting}, |
| {connectivity.Idle, connectivity.Connecting, connectivity.TransientFailure}, |
| }, |
| wantState: connectivity.Ready, |
| }, |
| { |
| name: "atleast one connecting", |
| subConnStates: [][]connectivity.State{ |
| {connectivity.Idle, connectivity.Connecting, connectivity.Ready, connectivity.Connecting}, |
| {connectivity.Idle}, |
| {connectivity.Idle, connectivity.Connecting, connectivity.TransientFailure}, |
| }, |
| wantState: connectivity.Connecting, |
| }, |
| { |
| name: "atleast one idle", |
| subConnStates: [][]connectivity.State{ |
| {connectivity.Idle, connectivity.Connecting, connectivity.Ready, connectivity.Idle}, |
| {connectivity.Idle, connectivity.Connecting, connectivity.TransientFailure}, |
| }, |
| wantState: connectivity.Idle, |
| }, |
| { |
| name: "atleast one transient failure", |
| subConnStates: [][]connectivity.State{ |
| {connectivity.Idle, connectivity.Connecting, connectivity.Ready, connectivity.TransientFailure}, |
| {connectivity.TransientFailure}, |
| }, |
| wantState: connectivity.TransientFailure, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| cse := &ConnectivityStateEvaluator{} |
| var prevState, gotState connectivity.State |
| for _, scStates := range test.subConnStates { |
| prevState = connectivity.Shutdown |
| for _, newState := range scStates { |
| gotState = cse.RecordTransition(prevState, newState) |
| prevState = newState |
| } |
| } |
| if gotState != test.wantState { |
| t.Fatalf("RecordTransition() = %v, want %v", gotState, test.wantState) |
| } |
| }) |
| } |
| } |