| /* | |
| * | |
| * Copyright 2024 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" | |
| "errors" | |
| "testing" | |
| "time" | |
| ) | |
| const ( | |
| testTimeout = 5 * time.Second | |
| testShortTimeout = 10 * time.Millisecond | |
| ) | |
| func (s) TestBlockingDialer_NoHold(t *testing.T) { | |
| lis, err := LocalTCPListener() | |
| if err != nil { | |
| t.Fatalf("Failed to listen: %v", err) | |
| } | |
| defer lis.Close() | |
| d := NewBlockingDialer() | |
| // This should not block. | |
| ctx, cancel := context.WithTimeout(context.Background(), testTimeout) | |
| defer cancel() | |
| conn, err := d.DialContext(ctx, lis.Addr().String()) | |
| if err != nil { | |
| t.Fatalf("Failed to dial: %v", err) | |
| } | |
| conn.Close() | |
| } | |
| func (s) TestBlockingDialer_HoldWaitResume(t *testing.T) { | |
| lis, err := LocalTCPListener() | |
| if err != nil { | |
| t.Fatalf("Failed to listen: %v", err) | |
| } | |
| defer lis.Close() | |
| d := NewBlockingDialer() | |
| h := d.Hold(lis.Addr().String()) | |
| if h.IsStarted() { | |
| t.Fatalf("hold.IsStarted() = true, want false") | |
| } | |
| done := make(chan struct{}) | |
| ctx, cancel := context.WithTimeout(context.Background(), testTimeout) | |
| defer cancel() | |
| go func() { | |
| defer close(done) | |
| conn, err := d.DialContext(ctx, lis.Addr().String()) | |
| if err != nil { | |
| t.Errorf("BlockingDialer.DialContext() got error: %v, want success", err) | |
| return | |
| } | |
| if !h.IsStarted() { | |
| t.Errorf("hold.IsStarted() = false, want true") | |
| } | |
| conn.Close() | |
| }() | |
| // This should block until the goroutine above is scheduled. | |
| if !h.Wait(ctx) { | |
| t.Fatalf("Timeout while waiting for a connection attempt to %q", h.addr) | |
| } | |
| if !h.IsStarted() { | |
| t.Errorf("hold.IsStarted() = false, want true") | |
| } | |
| select { | |
| case <-done: | |
| t.Fatalf("Expected dialer to be blocked.") | |
| case <-time.After(testShortTimeout): | |
| } | |
| h.Resume() // Unblock the above goroutine. | |
| select { | |
| case <-done: | |
| case <-ctx.Done(): | |
| t.Errorf("Timeout waiting for connection attempt to resume.") | |
| } | |
| } | |
| func (s) TestBlockingDialer_HoldWaitFail(t *testing.T) { | |
| lis, err := LocalTCPListener() | |
| if err != nil { | |
| t.Fatalf("Failed to listen: %v", err) | |
| } | |
| defer lis.Close() | |
| d := NewBlockingDialer() | |
| h := d.Hold(lis.Addr().String()) | |
| wantErr := errors.New("test error") | |
| dialError := make(chan error) | |
| ctx, cancel := context.WithTimeout(context.Background(), testTimeout) | |
| defer cancel() | |
| go func() { | |
| _, err := d.DialContext(ctx, lis.Addr().String()) | |
| dialError <- err | |
| }() | |
| if !h.Wait(ctx) { | |
| t.Fatal("Timeout while waiting for a connection attempt to " + h.addr) | |
| } | |
| select { | |
| case err = <-dialError: | |
| t.Errorf("DialContext got unblocked with err %v. Want DialContext to still be blocked after Wait()", err) | |
| case <-time.After(testShortTimeout): | |
| } | |
| h.Fail(wantErr) | |
| select { | |
| case err = <-dialError: | |
| if !errors.Is(err, wantErr) { | |
| t.Errorf("BlockingDialer.DialContext() after Fail(): got error %v, want %v", err, wantErr) | |
| } | |
| case <-ctx.Done(): | |
| t.Errorf("Timeout waiting for connection attempt to fail.") | |
| } | |
| } | |
| func (s) TestBlockingDialer_ContextCanceled(t *testing.T) { | |
| lis, err := LocalTCPListener() | |
| if err != nil { | |
| t.Fatalf("Failed to listen: %v", err) | |
| } | |
| defer lis.Close() | |
| d := NewBlockingDialer() | |
| h := d.Hold(lis.Addr().String()) | |
| dialErr := make(chan error) | |
| testCtx, cancel := context.WithTimeout(context.Background(), testTimeout) | |
| defer cancel() | |
| ctx, cancel := context.WithCancel(testCtx) | |
| defer cancel() | |
| go func() { | |
| _, err := d.DialContext(ctx, lis.Addr().String()) | |
| dialErr <- err | |
| }() | |
| if !h.Wait(testCtx) { | |
| t.Errorf("Timeout while waiting for a connection attempt to %q", h.addr) | |
| } | |
| cancel() | |
| select { | |
| case err = <-dialErr: | |
| if !errors.Is(err, context.Canceled) { | |
| t.Errorf("BlockingDialer.DialContext() after context cancel: got error %v, want %v", err, context.Canceled) | |
| } | |
| case <-testCtx.Done(): | |
| t.Errorf("Timeout while waiting for Wait to return.") | |
| } | |
| h.Resume() // noop, just make sure nothing bad happen. | |
| } | |
| func (s) TestBlockingDialer_CancelWait(t *testing.T) { | |
| lis, err := LocalTCPListener() | |
| if err != nil { | |
| t.Fatalf("Failed to listen: %v", err) | |
| } | |
| defer lis.Close() | |
| d := NewBlockingDialer() | |
| h := d.Hold(lis.Addr().String()) | |
| testCtx, cancel := context.WithTimeout(context.Background(), testTimeout) | |
| defer cancel() | |
| ctx, cancel := context.WithCancel(testCtx) | |
| cancel() | |
| done := make(chan struct{}) | |
| go func() { | |
| if h.Wait(ctx) { | |
| t.Errorf("Expected cancel to return false when context expires") | |
| } | |
| done <- struct{}{} | |
| }() | |
| select { | |
| case <-done: | |
| case <-testCtx.Done(): | |
| t.Errorf("Timeout while waiting for Wait to return.") | |
| } | |
| } |