...

Source file src/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/client_unit_test.go

Documentation: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc

     1  // Copyright The OpenTelemetry Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package otlptracegrpc
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/require"
    24  	"google.golang.org/genproto/googleapis/rpc/errdetails"
    25  	"google.golang.org/grpc/codes"
    26  	"google.golang.org/grpc/status"
    27  	"google.golang.org/protobuf/types/known/durationpb"
    28  )
    29  
    30  func TestThrottleDelay(t *testing.T) {
    31  	c := codes.ResourceExhausted
    32  	testcases := []struct {
    33  		status       *status.Status
    34  		wantOK       bool
    35  		wantDuration time.Duration
    36  	}{
    37  		{
    38  			status:       status.New(c, "NoRetryInfo"),
    39  			wantOK:       false,
    40  			wantDuration: 0,
    41  		},
    42  		{
    43  			status: func() *status.Status {
    44  				s, err := status.New(c, "SingleRetryInfo").WithDetails(
    45  					&errdetails.RetryInfo{
    46  						RetryDelay: durationpb.New(15 * time.Millisecond),
    47  					},
    48  				)
    49  				require.NoError(t, err)
    50  				return s
    51  			}(),
    52  			wantOK:       true,
    53  			wantDuration: 15 * time.Millisecond,
    54  		},
    55  		{
    56  			status: func() *status.Status {
    57  				s, err := status.New(c, "ErrorInfo").WithDetails(
    58  					&errdetails.ErrorInfo{Reason: "no throttle detail"},
    59  				)
    60  				require.NoError(t, err)
    61  				return s
    62  			}(),
    63  			wantOK:       false,
    64  			wantDuration: 0,
    65  		},
    66  		{
    67  			status: func() *status.Status {
    68  				s, err := status.New(c, "ErrorAndRetryInfo").WithDetails(
    69  					&errdetails.ErrorInfo{Reason: "with throttle detail"},
    70  					&errdetails.RetryInfo{
    71  						RetryDelay: durationpb.New(13 * time.Minute),
    72  					},
    73  				)
    74  				require.NoError(t, err)
    75  				return s
    76  			}(),
    77  			wantOK:       true,
    78  			wantDuration: 13 * time.Minute,
    79  		},
    80  		{
    81  			status: func() *status.Status {
    82  				s, err := status.New(c, "DoubleRetryInfo").WithDetails(
    83  					&errdetails.RetryInfo{
    84  						RetryDelay: durationpb.New(13 * time.Minute),
    85  					},
    86  					&errdetails.RetryInfo{
    87  						RetryDelay: durationpb.New(15 * time.Minute),
    88  					},
    89  				)
    90  				require.NoError(t, err)
    91  				return s
    92  			}(),
    93  			wantOK:       true,
    94  			wantDuration: 13 * time.Minute,
    95  		},
    96  	}
    97  
    98  	for _, tc := range testcases {
    99  		t.Run(tc.status.Message(), func(t *testing.T) {
   100  			ok, d := throttleDelay(tc.status)
   101  			assert.Equal(t, tc.wantOK, ok)
   102  			assert.Equal(t, tc.wantDuration, d)
   103  		})
   104  	}
   105  }
   106  
   107  func TestRetryable(t *testing.T) {
   108  	retryableCodes := map[codes.Code]bool{
   109  		codes.OK:                 false,
   110  		codes.Canceled:           true,
   111  		codes.Unknown:            false,
   112  		codes.InvalidArgument:    false,
   113  		codes.DeadlineExceeded:   true,
   114  		codes.NotFound:           false,
   115  		codes.AlreadyExists:      false,
   116  		codes.PermissionDenied:   false,
   117  		codes.ResourceExhausted:  false,
   118  		codes.FailedPrecondition: false,
   119  		codes.Aborted:            true,
   120  		codes.OutOfRange:         true,
   121  		codes.Unimplemented:      false,
   122  		codes.Internal:           false,
   123  		codes.Unavailable:        true,
   124  		codes.DataLoss:           true,
   125  		codes.Unauthenticated:    false,
   126  	}
   127  
   128  	for c, want := range retryableCodes {
   129  		got, _ := retryable(status.Error(c, ""))
   130  		assert.Equalf(t, want, got, "evaluate(%s)", c)
   131  	}
   132  }
   133  
   134  func TestRetryableGRPCStatusResourceExhaustedWithRetryInfo(t *testing.T) {
   135  	delay := 15 * time.Millisecond
   136  	s, err := status.New(codes.ResourceExhausted, "WithRetryInfo").WithDetails(
   137  		&errdetails.RetryInfo{
   138  			RetryDelay: durationpb.New(delay),
   139  		},
   140  	)
   141  	require.NoError(t, err)
   142  
   143  	ok, d := retryableGRPCStatus(s)
   144  	assert.True(t, ok)
   145  	assert.Equal(t, delay, d)
   146  }
   147  
   148  func TestUnstartedStop(t *testing.T) {
   149  	client := NewClient()
   150  	assert.ErrorIs(t, client.Stop(context.Background()), errAlreadyStopped)
   151  }
   152  
   153  func TestUnstartedUploadTrace(t *testing.T) {
   154  	client := NewClient()
   155  	assert.ErrorIs(t, client.UploadTraces(context.Background(), nil), errShutdown)
   156  }
   157  
   158  func TestExportContextHonorsParentDeadline(t *testing.T) {
   159  	now := time.Now()
   160  	ctx, cancel := context.WithDeadline(context.Background(), now)
   161  	t.Cleanup(cancel)
   162  
   163  	// Without a client timeout, the parent deadline should be used.
   164  	client := newClient(WithTimeout(0))
   165  	eCtx, eCancel := client.exportContext(ctx)
   166  	t.Cleanup(eCancel)
   167  
   168  	deadline, ok := eCtx.Deadline()
   169  	assert.True(t, ok, "deadline not propagated to child context")
   170  	assert.Equal(t, now, deadline)
   171  }
   172  
   173  func TestExportContextHonorsClientTimeout(t *testing.T) {
   174  	// Setting a timeout should ensure a deadline is set on the context.
   175  	client := newClient(WithTimeout(1 * time.Second))
   176  	ctx, cancel := client.exportContext(context.Background())
   177  	t.Cleanup(cancel)
   178  
   179  	_, ok := ctx.Deadline()
   180  	assert.True(t, ok, "timeout not set as deadline for child context")
   181  }
   182  
   183  func TestExportContextLinksStopSignal(t *testing.T) {
   184  	rootCtx := context.Background()
   185  
   186  	client := newClient(WithInsecure())
   187  	t.Cleanup(func() { require.NoError(t, client.Stop(rootCtx)) })
   188  	require.NoError(t, client.Start(rootCtx))
   189  
   190  	ctx, cancel := client.exportContext(rootCtx)
   191  	t.Cleanup(cancel)
   192  
   193  	require.False(t, func() bool {
   194  		select {
   195  		case <-ctx.Done():
   196  			return true
   197  		default:
   198  		}
   199  		return false
   200  	}(), "context should not be done prior to canceling it")
   201  
   202  	// The client.stopFunc cancels the client.stopCtx. This should have been
   203  	// setup as a parent of ctx. Therefore, it should cancel ctx as well.
   204  	client.stopFunc()
   205  
   206  	// Assert this with Eventually to account for goroutine scheduler timing.
   207  	assert.Eventually(t, func() bool {
   208  		select {
   209  		case <-ctx.Done():
   210  			return true
   211  		default:
   212  		}
   213  		return false
   214  	}, 10*time.Second, time.Microsecond)
   215  }
   216  

View as plain text