| /* | |
| * | |
| * 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()) | |
| } | |
| } |