Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Stale bot / stale (push) Failing after 0s
349 lines
10 KiB
Go
349 lines
10 KiB
Go
/*
|
|
*
|
|
* 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)
|
|
}
|
|
}
|