| /* | |
| * | |
| * Copyright 2025 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 grpc | |
| import ( | |
| "context" | |
| "fmt" | |
| "net" | |
| "strings" | |
| "testing" | |
| "time" | |
| "golang.org/x/net/http2" | |
| "google.golang.org/grpc/attributes" | |
| "google.golang.org/grpc/backoff" | |
| "google.golang.org/grpc/credentials" | |
| "google.golang.org/grpc/credentials/insecure" | |
| "google.golang.org/grpc/internal/grpcsync" | |
| "google.golang.org/grpc/internal/grpctest" | |
| "google.golang.org/grpc/internal/transport" | |
| "google.golang.org/grpc/keepalive" | |
| "google.golang.org/grpc/resolver" | |
| "google.golang.org/grpc/resolver/manual" | |
| "google.golang.org/grpc/testdata" | |
| ) | |
| func (s) TestDialWithTimeout(t *testing.T) { | |
| lis, err := net.Listen("tcp", "localhost:0") | |
| if err != nil { | |
| t.Fatalf("Error while listening. Err: %v", err) | |
| } | |
| defer lis.Close() | |
| lisAddr := resolver.Address{Addr: lis.Addr().String()} | |
| lisDone := make(chan struct{}) | |
| dialDone := make(chan struct{}) | |
| // 1st listener accepts the connection and then does nothing | |
| go func() { | |
| defer close(lisDone) | |
| conn, err := lis.Accept() | |
| if err != nil { | |
| t.Errorf("Error while accepting. Err: %v", err) | |
| return | |
| } | |
| framer := http2.NewFramer(conn, conn) | |
| if err := framer.WriteSettings(http2.Setting{}); err != nil { | |
| t.Errorf("Error while writing settings. Err: %v", err) | |
| return | |
| } | |
| <-dialDone // Close conn only after dial returns. | |
| }() | |
| r := manual.NewBuilderWithScheme("whatever") | |
| r.InitialState(resolver.State{Addresses: []resolver.Address{lisAddr}}) | |
| client, err := Dial(r.Scheme()+":///test.server", WithTransportCredentials(insecure.NewCredentials()), WithResolvers(r), WithTimeout(5*time.Second)) | |
| close(dialDone) | |
| if err != nil { | |
| t.Fatalf("Dial failed. Err: %v", err) | |
| } | |
| defer client.Close() | |
| timeout := time.After(1 * time.Second) | |
| select { | |
| case <-timeout: | |
| t.Fatal("timed out waiting for server to finish") | |
| case <-lisDone: | |
| } | |
| } | |
| func (s) TestDialWaitsForServerSettings(t *testing.T) { | |
| lis, err := net.Listen("tcp", "localhost:0") | |
| if err != nil { | |
| t.Fatalf("Error while listening. Err: %v", err) | |
| } | |
| defer lis.Close() | |
| done := make(chan struct{}) | |
| sent := make(chan struct{}) | |
| dialDone := make(chan struct{}) | |
| go func() { // Launch the server. | |
| defer func() { | |
| close(done) | |
| }() | |
| conn, err := lis.Accept() | |
| if err != nil { | |
| t.Errorf("Error while accepting. Err: %v", err) | |
| return | |
| } | |
| defer conn.Close() | |
| // Sleep for a little bit to make sure that Dial on client | |
| // side blocks until settings are received. | |
| time.Sleep(100 * time.Millisecond) | |
| framer := http2.NewFramer(conn, conn) | |
| close(sent) | |
| if err := framer.WriteSettings(http2.Setting{}); err != nil { | |
| t.Errorf("Error while writing settings. Err: %v", err) | |
| return | |
| } | |
| <-dialDone // Close conn only after dial returns. | |
| }() | |
| ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) | |
| defer cancel() | |
| client, err := DialContext(ctx, lis.Addr().String(), WithTransportCredentials(insecure.NewCredentials()), WithBlock()) | |
| close(dialDone) | |
| if err != nil { | |
| t.Fatalf("Error while dialing. Err: %v", err) | |
| } | |
| defer client.Close() | |
| select { | |
| case <-sent: | |
| default: | |
| t.Fatalf("Dial returned before server settings were sent") | |
| } | |
| <-done | |
| } | |
| func (s) TestDialWaitsForServerSettingsAndFails(t *testing.T) { | |
| lis, err := net.Listen("tcp", "localhost:0") | |
| if err != nil { | |
| t.Fatalf("Error while listening. Err: %v", err) | |
| } | |
| done := make(chan struct{}) | |
| numConns := 0 | |
| go func() { // Launch the server. | |
| defer func() { | |
| close(done) | |
| }() | |
| for { | |
| conn, err := lis.Accept() | |
| if err != nil { | |
| break | |
| } | |
| numConns++ | |
| defer conn.Close() | |
| } | |
| }() | |
| ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) | |
| defer cancel() | |
| client, err := DialContext(ctx, | |
| lis.Addr().String(), | |
| WithTransportCredentials(insecure.NewCredentials()), | |
| WithReturnConnectionError(), | |
| WithConnectParams(ConnectParams{ | |
| Backoff: backoff.Config{}, | |
| MinConnectTimeout: 250 * time.Millisecond, | |
| })) | |
| lis.Close() | |
| if err == nil { | |
| client.Close() | |
| t.Fatalf("Unexpected success (err=nil) while dialing") | |
| } | |
| expectedMsg := "server preface" | |
| if !strings.Contains(err.Error(), context.DeadlineExceeded.Error()) || !strings.Contains(err.Error(), expectedMsg) { | |
| t.Fatalf("DialContext(_) = %v; want a message that includes both %q and %q", err, context.DeadlineExceeded.Error(), expectedMsg) | |
| } | |
| <-done | |
| if numConns < 2 { | |
| t.Fatalf("dial attempts: %v; want > 1", numConns) | |
| } | |
| } | |
| func (s) TestWithTimeout(t *testing.T) { | |
| conn, err := Dial("passthrough:///Non-Existent.Server:80", | |
| WithTimeout(time.Millisecond), | |
| WithBlock(), | |
| WithTransportCredentials(insecure.NewCredentials())) | |
| if err == nil { | |
| conn.Close() | |
| } | |
| if err != context.DeadlineExceeded { | |
| t.Fatalf("Dial(_, _) = %v, %v, want %v", conn, err, context.DeadlineExceeded) | |
| } | |
| } | |
| func (s) TestWithTransportCredentialsTLS(t *testing.T) { | |
| ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) | |
| defer cancel() | |
| creds, err := credentials.NewClientTLSFromFile(testdata.Path("x509/server_ca_cert.pem"), "x.test.example.com") | |
| if err != nil { | |
| t.Fatalf("Failed to create credentials %v", err) | |
| } | |
| conn, err := DialContext(ctx, "passthrough:///Non-Existent.Server:80", WithTransportCredentials(creds), WithBlock()) | |
| if err == nil { | |
| conn.Close() | |
| } | |
| if err != context.DeadlineExceeded { | |
| t.Fatalf("Dial(_, _) = %v, %v, want %v", conn, err, context.DeadlineExceeded) | |
| } | |
| } | |
| func (s) TestDialContextCancel(t *testing.T) { | |
| ctx, cancel := context.WithCancel(context.Background()) | |
| cancel() | |
| if _, err := DialContext(ctx, "Non-Existent.Server:80", WithBlock(), WithTransportCredentials(insecure.NewCredentials())); err != context.Canceled { | |
| t.Fatalf("DialContext(%v, _) = _, %v, want _, %v", ctx, err, context.Canceled) | |
| } | |
| } | |
| type failFastError struct{} | |
| func (failFastError) Error() string { return "failfast" } | |
| func (failFastError) Temporary() bool { return false } | |
| func (s) TestDialContextFailFast(t *testing.T) { | |
| ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) | |
| defer cancel() | |
| failErr := failFastError{} | |
| dialer := func(string, time.Duration) (net.Conn, error) { | |
| return nil, failErr | |
| } | |
| _, err := DialContext(ctx, "Non-Existent.Server:80", WithBlock(), WithTransportCredentials(insecure.NewCredentials()), WithDialer(dialer), FailOnNonTempDialError(true)) | |
| if terr, ok := err.(transport.ConnectionError); !ok || terr.Origin() != failErr { | |
| t.Fatalf("DialContext() = _, %v, want _, %v", err, failErr) | |
| } | |
| } | |
| func (s) TestClientUpdatesParamsAfterGoAway(t *testing.T) { | |
| grpctest.ExpectError("Client received GoAway with error code ENHANCE_YOUR_CALM and debug data equal to ASCII \"too_many_pings\"") | |
| lis, err := net.Listen("tcp", "localhost:0") | |
| if err != nil { | |
| t.Fatalf("Failed to listen. Err: %v", err) | |
| } | |
| defer lis.Close() | |
| connected := grpcsync.NewEvent() | |
| defer connected.Fire() | |
| go func() { | |
| conn, err := lis.Accept() | |
| if err != nil { | |
| t.Errorf("error accepting connection: %v", err) | |
| return | |
| } | |
| defer conn.Close() | |
| f := http2.NewFramer(conn, conn) | |
| // Start a goroutine to read from the conn to prevent the client from | |
| // blocking after it writes its preface. | |
| go func() { | |
| for { | |
| if _, err := f.ReadFrame(); err != nil { | |
| return | |
| } | |
| } | |
| }() | |
| if err := f.WriteSettings(http2.Setting{}); err != nil { | |
| t.Errorf("error writing settings: %v", err) | |
| return | |
| } | |
| <-connected.Done() | |
| if err := f.WriteGoAway(0, http2.ErrCodeEnhanceYourCalm, []byte("too_many_pings")); err != nil { | |
| t.Errorf("error writing GOAWAY: %v", err) | |
| return | |
| } | |
| }() | |
| addr := lis.Addr().String() | |
| ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | |
| defer cancel() | |
| cc, err := DialContext(ctx, addr, WithBlock(), WithTransportCredentials(insecure.NewCredentials()), WithKeepaliveParams(keepalive.ClientParameters{ | |
| Time: 10 * time.Second, | |
| Timeout: 100 * time.Millisecond, | |
| PermitWithoutStream: true, | |
| })) | |
| if err != nil { | |
| t.Fatalf("DialContext(%s) failed: %v, want: nil", addr, err) | |
| } | |
| defer cc.Close() | |
| connected.Fire() | |
| for { | |
| time.Sleep(10 * time.Millisecond) | |
| cc.mu.RLock() | |
| v := cc.keepaliveParams.Time | |
| cc.mu.RUnlock() | |
| if v == 20*time.Second { | |
| // Success | |
| return | |
| } | |
| if ctx.Err() != nil { | |
| // Timeout | |
| t.Fatalf("cc.dopts.copts.Keepalive.Time = %v , want 20s", v) | |
| } | |
| } | |
| } | |
| // Test ensures that there is no panic if the attributes within | |
| // resolver.State.Addresses contains a typed-nil value. | |
| func (s) TestResolverAddressesWithTypedNilAttribute(t *testing.T) { | |
| r := manual.NewBuilderWithScheme(t.Name()) | |
| resolver.Register(r) | |
| addrAttr := attributes.New("typed_nil", (*stringerVal)(nil)) | |
| r.InitialState(resolver.State{Addresses: []resolver.Address{{Addr: "addr1", Attributes: addrAttr}}}) | |
| cc, err := Dial(r.Scheme()+":///", WithTransportCredentials(insecure.NewCredentials()), WithResolvers(r)) | |
| if err != nil { | |
| t.Fatalf("Unexpected error dialing: %v", err) | |
| } | |
| defer cc.Close() | |
| } | |
| type stringerVal struct{ s string } | |
| func (s stringerVal) String() string { return s.s } | |
| const errResolverBuilderScheme = "test-resolver-build-failure" | |
| // errResolverBuilder is a resolver builder that returns an error from its Build | |
| // method. | |
| type errResolverBuilder struct { | |
| err error | |
| } | |
| func (b *errResolverBuilder) Build(resolver.Target, resolver.ClientConn, resolver.BuildOptions) (resolver.Resolver, error) { | |
| return nil, b.err | |
| } | |
| func (b *errResolverBuilder) Scheme() string { | |
| return errResolverBuilderScheme | |
| } | |
| // Tests that Dial returns an error if the resolver builder returns an error | |
| // from its Build method. | |
| func (s) TestDial_ResolverBuilder_Error(t *testing.T) { | |
| resolverErr := fmt.Errorf("resolver builder error") | |
| dopts := []DialOption{ | |
| WithTransportCredentials(insecure.NewCredentials()), | |
| WithResolvers(&errResolverBuilder{err: resolverErr}), | |
| } | |
| _, err := Dial(errResolverBuilderScheme+":///test.server", dopts...) | |
| if err == nil { | |
| t.Fatalf("Dial() succeeded when it should have failed") | |
| } | |
| if !strings.Contains(err.Error(), resolverErr.Error()) { | |
| t.Fatalf("Dial() failed with error %v, want %v", err, resolverErr) | |
| } | |
| } |