| /* |
| * |
| * Copyright 2023 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 testutils |
| |
| import ( |
| "context" |
| "testing" |
| |
| "google.golang.org/grpc/connectivity" |
| ) |
| |
| // A StateChanger reports state changes, e.g. a grpc.ClientConn. |
| type StateChanger interface { |
| // Connect begins connecting the StateChanger. |
| Connect() |
| // GetState returns the current state of the StateChanger. |
| GetState() connectivity.State |
| // WaitForStateChange returns true when the state becomes s, or returns |
| // false if ctx is canceled first. |
| WaitForStateChange(ctx context.Context, s connectivity.State) bool |
| } |
| |
| // StayConnected makes sc stay connected by repeatedly calling sc.Connect() |
| // until the state becomes Shutdown or until the context expires. |
| func StayConnected(ctx context.Context, sc StateChanger) { |
| for { |
| state := sc.GetState() |
| switch state { |
| case connectivity.Idle: |
| sc.Connect() |
| case connectivity.Shutdown: |
| return |
| } |
| if !sc.WaitForStateChange(ctx, state) { |
| return |
| } |
| } |
| } |
| |
| // AwaitState waits for sc to enter stateWant or fatal errors if it doesn't |
| // happen before ctx expires. |
| func AwaitState(ctx context.Context, t *testing.T, sc StateChanger, stateWant connectivity.State) { |
| t.Helper() |
| for state := sc.GetState(); state != stateWant; state = sc.GetState() { |
| if !sc.WaitForStateChange(ctx, state) { |
| t.Fatalf("Timed out waiting for state change. got %v; want %v", state, stateWant) |
| } |
| } |
| } |
| |
| // AwaitNotState waits for sc to leave stateDoNotWant or fatal errors if it |
| // doesn't happen before ctx expires. |
| func AwaitNotState(ctx context.Context, t *testing.T, sc StateChanger, stateDoNotWant connectivity.State) { |
| t.Helper() |
| for state := sc.GetState(); state == stateDoNotWant; state = sc.GetState() { |
| if !sc.WaitForStateChange(ctx, state) { |
| t.Fatalf("Timed out waiting for state change. got %v; want NOT %v", state, stateDoNotWant) |
| } |
| } |
| } |
| |
| // AwaitNoStateChange expects ctx to be canceled before sc's state leaves |
| // currState, and fatal errors otherwise. |
| func AwaitNoStateChange(ctx context.Context, t *testing.T, sc StateChanger, currState connectivity.State) { |
| t.Helper() |
| if sc.WaitForStateChange(ctx, currState) { |
| t.Fatalf("State changed from %q to %q when no state change was expected", currState, sc.GetState()) |
| } |
| } |