github.com/newrelic/go-agent@v3.26.0+incompatible/_integrations/nrgrpc/nrgrpc_client_test.go (about)

     1  // Copyright 2020 New Relic Corporation. All rights reserved.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package nrgrpc
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"io"
    10  	"testing"
    11  
    12  	newrelic "github.com/newrelic/go-agent"
    13  	"github.com/newrelic/go-agent/_integrations/nrgrpc/testapp"
    14  	"github.com/newrelic/go-agent/internal"
    15  	"github.com/newrelic/go-agent/internal/integrationsupport"
    16  	"google.golang.org/grpc/metadata"
    17  )
    18  
    19  func TestGetURL(t *testing.T) {
    20  	testcases := []struct {
    21  		method   string
    22  		target   string
    23  		expected string
    24  	}{
    25  		{
    26  			method:   "/TestApplication/DoUnaryUnary",
    27  			target:   "",
    28  			expected: "grpc:///TestApplication/DoUnaryUnary",
    29  		},
    30  		{
    31  			method:   "TestApplication/DoUnaryUnary",
    32  			target:   "",
    33  			expected: "grpc://TestApplication/DoUnaryUnary",
    34  		},
    35  		{
    36  			method:   "/TestApplication/DoUnaryUnary",
    37  			target:   ":8080",
    38  			expected: "grpc://:8080/TestApplication/DoUnaryUnary",
    39  		},
    40  		{
    41  			method:   "/TestApplication/DoUnaryUnary",
    42  			target:   "localhost:8080",
    43  			expected: "grpc://localhost:8080/TestApplication/DoUnaryUnary",
    44  		},
    45  		{
    46  			method:   "TestApplication/DoUnaryUnary",
    47  			target:   "localhost:8080",
    48  			expected: "grpc://localhost:8080/TestApplication/DoUnaryUnary",
    49  		},
    50  		{
    51  			method:   "/TestApplication/DoUnaryUnary",
    52  			target:   "dns:///localhost:8080",
    53  			expected: "grpc://localhost:8080/TestApplication/DoUnaryUnary",
    54  		},
    55  		{
    56  			method:   "/TestApplication/DoUnaryUnary",
    57  			target:   "unix:/path/to/socket",
    58  			expected: "grpc://localhost/TestApplication/DoUnaryUnary",
    59  		},
    60  		{
    61  			method:   "/TestApplication/DoUnaryUnary",
    62  			target:   "unix:///path/to/socket",
    63  			expected: "grpc://localhost/TestApplication/DoUnaryUnary",
    64  		},
    65  	}
    66  
    67  	for _, test := range testcases {
    68  		actual := getURL(test.method, test.target)
    69  		if actual.String() != test.expected {
    70  			t.Errorf("incorrect URL:\n\tmethod=%s,\n\ttarget=%s,\n\texpected=%s,\n\tactual=%s",
    71  				test.method, test.target, test.expected, actual.String())
    72  		}
    73  	}
    74  }
    75  
    76  func testApp() integrationsupport.ExpectApp {
    77  	return integrationsupport.NewTestApp(replyFn, configFn)
    78  }
    79  
    80  var replyFn = func(reply *internal.ConnectReply) {
    81  	reply.AdaptiveSampler = internal.SampleEverything{}
    82  	reply.AccountID = "123"
    83  	reply.TrustedAccountKey = "123"
    84  	reply.PrimaryAppID = "456"
    85  }
    86  
    87  var configFn = func(cfg *newrelic.Config) {
    88  	cfg.Enabled = false
    89  	cfg.DistributedTracer.Enabled = true
    90  	cfg.TransactionTracer.SegmentThreshold = 0
    91  	cfg.TransactionTracer.Threshold.IsApdexFailing = false
    92  	cfg.TransactionTracer.Threshold.Duration = 0
    93  }
    94  
    95  func TestUnaryClientInterceptor(t *testing.T) {
    96  	app := testApp()
    97  	txn := app.StartTransaction("UnaryUnary", nil, nil)
    98  	ctx := newrelic.NewContext(context.Background(), txn)
    99  
   100  	s, conn := newTestServerAndConn(t, nil)
   101  	defer s.Stop()
   102  	defer conn.Close()
   103  
   104  	client := testapp.NewTestApplicationClient(conn)
   105  	resp, err := client.DoUnaryUnary(ctx, &testapp.Message{})
   106  	if nil != err {
   107  		t.Fatal("client call to DoUnaryUnary failed", err)
   108  	}
   109  	var hdrs map[string][]string
   110  	err = json.Unmarshal([]byte(resp.Text), &hdrs)
   111  	if nil != err {
   112  		t.Fatal("cannot unmarshall client response", err)
   113  	}
   114  	if hdr, ok := hdrs["newrelic"]; !ok || len(hdr) != 1 || "" == hdr[0] {
   115  		t.Error("distributed trace header not sent", hdrs)
   116  	}
   117  	txn.End()
   118  
   119  	app.ExpectMetrics(t, []internal.WantMetric{
   120  		{Name: "OtherTransaction/Go/UnaryUnary", Scope: "", Forced: true, Data: nil},
   121  		{Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil},
   122  		{Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil},
   123  		{Name: "OtherTransactionTotalTime/Go/UnaryUnary", Scope: "", Forced: false, Data: nil},
   124  		{Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil},
   125  		{Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil},
   126  		{Name: "External/all", Scope: "", Forced: true, Data: nil},
   127  		{Name: "External/allOther", Scope: "", Forced: true, Data: nil},
   128  		{Name: "External/bufnet/all", Scope: "", Forced: false, Data: nil},
   129  		{Name: "External/bufnet/gRPC/TestApplication/DoUnaryUnary", Scope: "OtherTransaction/Go/UnaryUnary", Forced: false, Data: nil},
   130  		{Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Forced: true, Data: nil},
   131  	})
   132  	app.ExpectSpanEvents(t, []internal.WantEvent{
   133  		{
   134  			Intrinsics: map[string]interface{}{
   135  				"category":      "generic",
   136  				"name":          "OtherTransaction/Go/UnaryUnary",
   137  				"nr.entryPoint": true,
   138  			},
   139  			UserAttributes:  map[string]interface{}{},
   140  			AgentAttributes: map[string]interface{}{},
   141  		},
   142  		{
   143  			Intrinsics: map[string]interface{}{
   144  				"category":  "http",
   145  				"component": "gRPC",
   146  				"name":      "External/bufnet/gRPC/TestApplication/DoUnaryUnary",
   147  				"parentId":  internal.MatchAnything,
   148  				"span.kind": "client",
   149  			},
   150  			UserAttributes:  map[string]interface{}{},
   151  			AgentAttributes: map[string]interface{}{},
   152  		},
   153  	})
   154  	app.ExpectTxnTraces(t, []internal.WantTxnTrace{{
   155  		MetricName: "OtherTransaction/Go/UnaryUnary",
   156  		Root: internal.WantTraceSegment{
   157  			SegmentName: "ROOT",
   158  			Attributes:  map[string]interface{}{},
   159  			Children: []internal.WantTraceSegment{{
   160  				SegmentName: "OtherTransaction/Go/UnaryUnary",
   161  				Attributes:  map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything},
   162  				Children: []internal.WantTraceSegment{
   163  					{
   164  						SegmentName: "External/bufnet/gRPC/TestApplication/DoUnaryUnary",
   165  						Attributes:  map[string]interface{}{},
   166  					},
   167  				},
   168  			}},
   169  		},
   170  	}})
   171  }
   172  
   173  func TestUnaryStreamClientInterceptor(t *testing.T) {
   174  	app := testApp()
   175  	txn := app.StartTransaction("UnaryStream", nil, nil)
   176  	ctx := newrelic.NewContext(context.Background(), txn)
   177  
   178  	s, conn := newTestServerAndConn(t, nil)
   179  	defer s.Stop()
   180  	defer conn.Close()
   181  
   182  	client := testapp.NewTestApplicationClient(conn)
   183  	stream, err := client.DoUnaryStream(ctx, &testapp.Message{})
   184  	if nil != err {
   185  		t.Fatal("client call to DoUnaryStream failed", err)
   186  	}
   187  	var recved int
   188  	for {
   189  		msg, err := stream.Recv()
   190  		if err == io.EOF {
   191  			break
   192  		}
   193  		if nil != err {
   194  			t.Fatal("error receiving message", err)
   195  		}
   196  		var hdrs map[string][]string
   197  		err = json.Unmarshal([]byte(msg.Text), &hdrs)
   198  		if nil != err {
   199  			t.Fatal("cannot unmarshall client response", err)
   200  		}
   201  		if hdr, ok := hdrs["newrelic"]; !ok || len(hdr) != 1 || "" == hdr[0] {
   202  			t.Error("distributed trace header not sent", hdrs)
   203  		}
   204  		recved++
   205  	}
   206  	if recved != 3 {
   207  		t.Fatal("received incorrect number of messages from server", recved)
   208  	}
   209  	txn.End()
   210  
   211  	app.ExpectMetrics(t, []internal.WantMetric{
   212  		{Name: "OtherTransaction/Go/UnaryStream", Scope: "", Forced: true, Data: nil},
   213  		{Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil},
   214  		{Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil},
   215  		{Name: "OtherTransactionTotalTime/Go/UnaryStream", Scope: "", Forced: false, Data: nil},
   216  		{Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil},
   217  		{Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil},
   218  		{Name: "External/all", Scope: "", Forced: true, Data: nil},
   219  		{Name: "External/allOther", Scope: "", Forced: true, Data: nil},
   220  		{Name: "External/bufnet/all", Scope: "", Forced: false, Data: nil},
   221  		{Name: "External/bufnet/gRPC/TestApplication/DoUnaryStream", Scope: "OtherTransaction/Go/UnaryStream", Forced: false, Data: nil},
   222  		{Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Forced: true, Data: nil},
   223  	})
   224  	app.ExpectSpanEvents(t, []internal.WantEvent{
   225  		{
   226  			Intrinsics: map[string]interface{}{
   227  				"category":      "generic",
   228  				"name":          "OtherTransaction/Go/UnaryStream",
   229  				"nr.entryPoint": true,
   230  			},
   231  			UserAttributes:  map[string]interface{}{},
   232  			AgentAttributes: map[string]interface{}{},
   233  		},
   234  		{
   235  			Intrinsics: map[string]interface{}{
   236  				"category":  "http",
   237  				"component": "gRPC",
   238  				"name":      "External/bufnet/gRPC/TestApplication/DoUnaryStream",
   239  				"parentId":  internal.MatchAnything,
   240  				"span.kind": "client",
   241  			},
   242  			UserAttributes:  map[string]interface{}{},
   243  			AgentAttributes: map[string]interface{}{},
   244  		},
   245  	})
   246  	app.ExpectTxnTraces(t, []internal.WantTxnTrace{{
   247  		MetricName: "OtherTransaction/Go/UnaryStream",
   248  		Root: internal.WantTraceSegment{
   249  			SegmentName: "ROOT",
   250  			Attributes:  map[string]interface{}{},
   251  			Children: []internal.WantTraceSegment{{
   252  				SegmentName: "OtherTransaction/Go/UnaryStream",
   253  				Attributes:  map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything},
   254  				Children: []internal.WantTraceSegment{
   255  					{
   256  						SegmentName: "External/bufnet/gRPC/TestApplication/DoUnaryStream",
   257  						Attributes:  map[string]interface{}{},
   258  					},
   259  				},
   260  			}},
   261  		},
   262  	}})
   263  }
   264  
   265  func TestStreamUnaryClientInterceptor(t *testing.T) {
   266  	app := testApp()
   267  	txn := app.StartTransaction("StreamUnary", nil, nil)
   268  	ctx := newrelic.NewContext(context.Background(), txn)
   269  
   270  	s, conn := newTestServerAndConn(t, nil)
   271  	defer s.Stop()
   272  	defer conn.Close()
   273  
   274  	client := testapp.NewTestApplicationClient(conn)
   275  	stream, err := client.DoStreamUnary(ctx)
   276  	if nil != err {
   277  		t.Fatal("client call to DoStreamUnary failed", err)
   278  	}
   279  	for i := 0; i < 3; i++ {
   280  		if err := stream.Send(&testapp.Message{Text: "Hello DoStreamUnary"}); nil != err {
   281  			if err == io.EOF {
   282  				break
   283  			}
   284  			t.Fatal("failure to Send", err)
   285  		}
   286  	}
   287  	msg, err := stream.CloseAndRecv()
   288  	if nil != err {
   289  		t.Fatal("failure to CloseAndRecv", err)
   290  	}
   291  	var hdrs map[string][]string
   292  	err = json.Unmarshal([]byte(msg.Text), &hdrs)
   293  	if nil != err {
   294  		t.Fatal("cannot unmarshall client response", err)
   295  	}
   296  	if hdr, ok := hdrs["newrelic"]; !ok || len(hdr) != 1 || "" == hdr[0] {
   297  		t.Error("distributed trace header not sent", hdrs)
   298  	}
   299  	txn.End()
   300  
   301  	app.ExpectMetrics(t, []internal.WantMetric{
   302  		{Name: "OtherTransaction/Go/StreamUnary", Scope: "", Forced: true, Data: nil},
   303  		{Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil},
   304  		{Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil},
   305  		{Name: "OtherTransactionTotalTime/Go/StreamUnary", Scope: "", Forced: false, Data: nil},
   306  		{Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil},
   307  		{Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil},
   308  		{Name: "External/all", Scope: "", Forced: true, Data: nil},
   309  		{Name: "External/allOther", Scope: "", Forced: true, Data: nil},
   310  		{Name: "External/bufnet/all", Scope: "", Forced: false, Data: nil},
   311  		{Name: "External/bufnet/gRPC/TestApplication/DoStreamUnary", Scope: "OtherTransaction/Go/StreamUnary", Forced: false, Data: nil},
   312  		{Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Forced: true, Data: nil},
   313  	})
   314  	app.ExpectSpanEvents(t, []internal.WantEvent{
   315  		{
   316  			Intrinsics: map[string]interface{}{
   317  				"category":      "generic",
   318  				"name":          "OtherTransaction/Go/StreamUnary",
   319  				"nr.entryPoint": true,
   320  			},
   321  			UserAttributes:  map[string]interface{}{},
   322  			AgentAttributes: map[string]interface{}{},
   323  		},
   324  		{
   325  			Intrinsics: map[string]interface{}{
   326  				"category":  "http",
   327  				"component": "gRPC",
   328  				"name":      "External/bufnet/gRPC/TestApplication/DoStreamUnary",
   329  				"parentId":  internal.MatchAnything,
   330  				"span.kind": "client",
   331  			},
   332  			UserAttributes:  map[string]interface{}{},
   333  			AgentAttributes: map[string]interface{}{},
   334  		},
   335  	})
   336  	app.ExpectTxnTraces(t, []internal.WantTxnTrace{{
   337  		MetricName: "OtherTransaction/Go/StreamUnary",
   338  		Root: internal.WantTraceSegment{
   339  			SegmentName: "ROOT",
   340  			Attributes:  map[string]interface{}{},
   341  			Children: []internal.WantTraceSegment{{
   342  				SegmentName: "OtherTransaction/Go/StreamUnary",
   343  				Attributes:  map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything},
   344  				Children: []internal.WantTraceSegment{
   345  					{
   346  						SegmentName: "External/bufnet/gRPC/TestApplication/DoStreamUnary",
   347  						Attributes:  map[string]interface{}{},
   348  					},
   349  				},
   350  			}},
   351  		},
   352  	}})
   353  }
   354  
   355  func TestStreamStreamClientInterceptor(t *testing.T) {
   356  	app := testApp()
   357  	txn := app.StartTransaction("StreamStream", nil, nil)
   358  	ctx := newrelic.NewContext(context.Background(), txn)
   359  
   360  	s, conn := newTestServerAndConn(t, nil)
   361  	defer s.Stop()
   362  	defer conn.Close()
   363  
   364  	client := testapp.NewTestApplicationClient(conn)
   365  	stream, err := client.DoStreamStream(ctx)
   366  	if nil != err {
   367  		t.Fatal("client call to DoStreamStream failed", err)
   368  	}
   369  	waitc := make(chan struct{})
   370  	go func() {
   371  		defer close(waitc)
   372  		var recved int
   373  		for {
   374  			msg, err := stream.Recv()
   375  			if err == io.EOF {
   376  				break
   377  			}
   378  			if err != nil {
   379  				t.Fatal("failure to Recv", err)
   380  			}
   381  			var hdrs map[string][]string
   382  			err = json.Unmarshal([]byte(msg.Text), &hdrs)
   383  			if nil != err {
   384  				t.Fatal("cannot unmarshall client response", err)
   385  			}
   386  			if hdr, ok := hdrs["newrelic"]; !ok || len(hdr) != 1 || "" == hdr[0] {
   387  				t.Error("distributed trace header not sent", hdrs)
   388  			}
   389  			recved++
   390  		}
   391  		if recved != 3 {
   392  			t.Fatal("received incorrect number of messages from server", recved)
   393  		}
   394  	}()
   395  	for i := 0; i < 3; i++ {
   396  		if err := stream.Send(&testapp.Message{Text: "Hello DoStreamStream"}); err != nil {
   397  			t.Fatal("failure to Send", err)
   398  		}
   399  	}
   400  	stream.CloseSend()
   401  	<-waitc
   402  	txn.End()
   403  
   404  	app.ExpectMetrics(t, []internal.WantMetric{
   405  		{Name: "OtherTransaction/Go/StreamStream", Scope: "", Forced: true, Data: nil},
   406  		{Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil},
   407  		{Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil},
   408  		{Name: "OtherTransactionTotalTime/Go/StreamStream", Scope: "", Forced: false, Data: nil},
   409  		{Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil},
   410  		{Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil},
   411  		{Name: "External/all", Scope: "", Forced: true, Data: nil},
   412  		{Name: "External/allOther", Scope: "", Forced: true, Data: nil},
   413  		{Name: "External/bufnet/all", Scope: "", Forced: false, Data: nil},
   414  		{Name: "External/bufnet/gRPC/TestApplication/DoStreamStream", Scope: "OtherTransaction/Go/StreamStream", Forced: false, Data: nil},
   415  		{Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Forced: true, Data: nil},
   416  	})
   417  	app.ExpectSpanEvents(t, []internal.WantEvent{
   418  		{
   419  			Intrinsics: map[string]interface{}{
   420  				"category":      "generic",
   421  				"name":          "OtherTransaction/Go/StreamStream",
   422  				"nr.entryPoint": true,
   423  			},
   424  			UserAttributes:  map[string]interface{}{},
   425  			AgentAttributes: map[string]interface{}{},
   426  		},
   427  		{
   428  			Intrinsics: map[string]interface{}{
   429  				"category":  "http",
   430  				"component": "gRPC",
   431  				"name":      "External/bufnet/gRPC/TestApplication/DoStreamStream",
   432  				"parentId":  internal.MatchAnything,
   433  				"span.kind": "client",
   434  			},
   435  			UserAttributes:  map[string]interface{}{},
   436  			AgentAttributes: map[string]interface{}{},
   437  		},
   438  	})
   439  	app.ExpectTxnTraces(t, []internal.WantTxnTrace{{
   440  		MetricName: "OtherTransaction/Go/StreamStream",
   441  		Root: internal.WantTraceSegment{
   442  			SegmentName: "ROOT",
   443  			Attributes:  map[string]interface{}{},
   444  			Children: []internal.WantTraceSegment{{
   445  				SegmentName: "OtherTransaction/Go/StreamStream",
   446  				Attributes:  map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything},
   447  				Children: []internal.WantTraceSegment{
   448  					{
   449  						SegmentName: "External/bufnet/gRPC/TestApplication/DoStreamStream",
   450  						Attributes:  map[string]interface{}{},
   451  					},
   452  				},
   453  			}},
   454  		},
   455  	}})
   456  }
   457  
   458  func TestClientUnaryMetadata(t *testing.T) {
   459  	// Test that metadata on the outgoing request are presevered
   460  	app := testApp()
   461  	txn := app.StartTransaction("metadata", nil, nil)
   462  	ctx := newrelic.NewContext(context.Background(), txn)
   463  
   464  	md := metadata.New(map[string]string{
   465  		"testing":  "hello world",
   466  		"newrelic": "payload",
   467  	})
   468  	ctx = metadata.NewOutgoingContext(ctx, md)
   469  
   470  	s, conn := newTestServerAndConn(t, nil)
   471  	defer s.Stop()
   472  	defer conn.Close()
   473  
   474  	client := testapp.NewTestApplicationClient(conn)
   475  	resp, err := client.DoUnaryUnary(ctx, &testapp.Message{})
   476  	if nil != err {
   477  		t.Fatal("client call to DoUnaryUnary failed", err)
   478  	}
   479  	var hdrs map[string][]string
   480  	err = json.Unmarshal([]byte(resp.Text), &hdrs)
   481  	if nil != err {
   482  		t.Fatal("cannot unmarshall client response", err)
   483  	}
   484  	if hdr, ok := hdrs["newrelic"]; !ok || len(hdr) != 1 || "" == hdr[0] || "payload" == hdr[0] {
   485  		t.Error("distributed trace header not sent", hdrs)
   486  	}
   487  	if hdr, ok := hdrs["testing"]; !ok || len(hdr) != 1 || "hello world" != hdr[0] {
   488  		t.Error("testing header not sent", hdrs)
   489  	}
   490  }
   491  
   492  func TestNilTxnClientUnary(t *testing.T) {
   493  	s, conn := newTestServerAndConn(t, nil)
   494  	defer s.Stop()
   495  	defer conn.Close()
   496  
   497  	client := testapp.NewTestApplicationClient(conn)
   498  	resp, err := client.DoUnaryUnary(context.Background(), &testapp.Message{})
   499  	if nil != err {
   500  		t.Fatal("client call to DoUnaryUnary failed", err)
   501  	}
   502  	var hdrs map[string][]string
   503  	err = json.Unmarshal([]byte(resp.Text), &hdrs)
   504  	if nil != err {
   505  		t.Fatal("cannot unmarshall client response", err)
   506  	}
   507  	if _, ok := hdrs["newrelic"]; ok {
   508  		t.Error("distributed trace header sent", hdrs)
   509  	}
   510  }
   511  
   512  func TestNilTxnClientStreaming(t *testing.T) {
   513  	s, conn := newTestServerAndConn(t, nil)
   514  	defer s.Stop()
   515  	defer conn.Close()
   516  
   517  	client := testapp.NewTestApplicationClient(conn)
   518  	stream, err := client.DoStreamUnary(context.Background())
   519  	if nil != err {
   520  		t.Fatal("client call to DoStreamUnary failed", err)
   521  	}
   522  	for i := 0; i < 3; i++ {
   523  		if err := stream.Send(&testapp.Message{Text: "Hello DoStreamUnary"}); nil != err {
   524  			if err == io.EOF {
   525  				break
   526  			}
   527  			t.Fatal("failure to Send", err)
   528  		}
   529  	}
   530  	msg, err := stream.CloseAndRecv()
   531  	if nil != err {
   532  		t.Fatal("failure to CloseAndRecv", err)
   533  	}
   534  	var hdrs map[string][]string
   535  	err = json.Unmarshal([]byte(msg.Text), &hdrs)
   536  	if nil != err {
   537  		t.Fatal("cannot unmarshall client response", err)
   538  	}
   539  	if _, ok := hdrs["newrelic"]; ok {
   540  		t.Error("distributed trace header sent", hdrs)
   541  	}
   542  }
   543  
   544  func TestClientStreamingError(t *testing.T) {
   545  	// Test that when creating the stream returns an error, no external
   546  	// segments are created
   547  	app := testApp()
   548  	txn := app.StartTransaction("UnaryStream", nil, nil)
   549  
   550  	s, conn := newTestServerAndConn(t, nil)
   551  	defer s.Stop()
   552  	defer conn.Close()
   553  
   554  	client := testapp.NewTestApplicationClient(conn)
   555  
   556  	ctx, cancel := context.WithTimeout(context.Background(), 0)
   557  	defer cancel()
   558  	ctx = newrelic.NewContext(ctx, txn)
   559  	_, err := client.DoUnaryStream(ctx, &testapp.Message{})
   560  	if nil == err {
   561  		t.Fatal("client call to DoUnaryStream did not return error")
   562  	}
   563  	txn.End()
   564  
   565  	app.ExpectMetrics(t, []internal.WantMetric{
   566  		{Name: "OtherTransaction/Go/UnaryStream", Scope: "", Forced: true, Data: nil},
   567  		{Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil},
   568  		{Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil},
   569  		{Name: "OtherTransactionTotalTime/Go/UnaryStream", Scope: "", Forced: false, Data: nil},
   570  		{Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil},
   571  		{Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil},
   572  		{Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Forced: true, Data: nil},
   573  	})
   574  	app.ExpectSpanEvents(t, []internal.WantEvent{
   575  		{
   576  			Intrinsics: map[string]interface{}{
   577  				"category":      "generic",
   578  				"name":          "OtherTransaction/Go/UnaryStream",
   579  				"nr.entryPoint": true,
   580  			},
   581  			UserAttributes:  map[string]interface{}{},
   582  			AgentAttributes: map[string]interface{}{},
   583  		},
   584  	})
   585  	app.ExpectTxnTraces(t, []internal.WantTxnTrace{{
   586  		MetricName: "OtherTransaction/Go/UnaryStream",
   587  		Root: internal.WantTraceSegment{
   588  			SegmentName: "ROOT",
   589  			Attributes:  map[string]interface{}{},
   590  			Children: []internal.WantTraceSegment{{
   591  				SegmentName: "OtherTransaction/Go/UnaryStream",
   592  				Attributes:  map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything},
   593  				Children:    []internal.WantTraceSegment{},
   594  			}},
   595  		},
   596  	}})
   597  }