google.golang.org/grpc@v1.72.2/metadata/metadata_test.go (about)

     1  /*
     2   *
     3   * Copyright 2014 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   */
    18  
    19  package metadata
    20  
    21  import (
    22  	"context"
    23  	"reflect"
    24  	"strconv"
    25  	"testing"
    26  	"time"
    27  
    28  	"google.golang.org/grpc/internal/grpctest"
    29  )
    30  
    31  const defaultTestTimeout = 10 * time.Second
    32  
    33  type s struct {
    34  	grpctest.Tester
    35  }
    36  
    37  func Test(t *testing.T) {
    38  	grpctest.RunSubTests(t, s{})
    39  }
    40  
    41  func (s) TestPairsMD(t *testing.T) {
    42  	for _, test := range []struct {
    43  		// input
    44  		kv []string
    45  		// output
    46  		md MD
    47  	}{
    48  		{[]string{}, MD{}},
    49  		{[]string{"k1", "v1", "k1", "v2"}, MD{"k1": []string{"v1", "v2"}}},
    50  	} {
    51  		md := Pairs(test.kv...)
    52  		if !reflect.DeepEqual(md, test.md) {
    53  			t.Fatalf("Pairs(%v) = %v, want %v", test.kv, md, test.md)
    54  		}
    55  	}
    56  }
    57  
    58  func (s) TestCopy(t *testing.T) {
    59  	const key, val = "key", "val"
    60  	orig := Pairs(key, val)
    61  	cpy := orig.Copy()
    62  	if !reflect.DeepEqual(orig, cpy) {
    63  		t.Errorf("copied value not equal to the original, got %v, want %v", cpy, orig)
    64  	}
    65  	orig[key][0] = "foo"
    66  	if v := cpy[key][0]; v != val {
    67  		t.Errorf("change in original should not affect copy, got %q, want %q", v, val)
    68  	}
    69  }
    70  
    71  func (s) TestJoin(t *testing.T) {
    72  	for _, test := range []struct {
    73  		mds  []MD
    74  		want MD
    75  	}{
    76  		{[]MD{}, MD{}},
    77  		{[]MD{Pairs("foo", "bar")}, Pairs("foo", "bar")},
    78  		{[]MD{Pairs("foo", "bar"), Pairs("foo", "baz")}, Pairs("foo", "bar", "foo", "baz")},
    79  		{[]MD{Pairs("foo", "bar"), Pairs("foo", "baz"), Pairs("zip", "zap")}, Pairs("foo", "bar", "foo", "baz", "zip", "zap")},
    80  	} {
    81  		md := Join(test.mds...)
    82  		if !reflect.DeepEqual(md, test.want) {
    83  			t.Errorf("context's metadata is %v, want %v", md, test.want)
    84  		}
    85  	}
    86  }
    87  
    88  func (s) TestGet(t *testing.T) {
    89  	for _, test := range []struct {
    90  		md       MD
    91  		key      string
    92  		wantVals []string
    93  	}{
    94  		{md: Pairs("My-Optional-Header", "42"), key: "My-Optional-Header", wantVals: []string{"42"}},
    95  		{md: Pairs("Header", "42", "Header", "43", "Header", "44", "other", "1"), key: "HEADER", wantVals: []string{"42", "43", "44"}},
    96  		{md: Pairs("HEADER", "10"), key: "HEADER", wantVals: []string{"10"}},
    97  	} {
    98  		vals := test.md.Get(test.key)
    99  		if !reflect.DeepEqual(vals, test.wantVals) {
   100  			t.Errorf("value of metadata %v is %v, want %v", test.key, vals, test.wantVals)
   101  		}
   102  	}
   103  }
   104  
   105  func (s) TestSet(t *testing.T) {
   106  	for _, test := range []struct {
   107  		md      MD
   108  		setKey  string
   109  		setVals []string
   110  		want    MD
   111  	}{
   112  		{
   113  			md:      Pairs("My-Optional-Header", "42", "other-key", "999"),
   114  			setKey:  "Other-Key",
   115  			setVals: []string{"1"},
   116  			want:    Pairs("my-optional-header", "42", "other-key", "1"),
   117  		},
   118  		{
   119  			md:      Pairs("My-Optional-Header", "42"),
   120  			setKey:  "Other-Key",
   121  			setVals: []string{"1", "2", "3"},
   122  			want:    Pairs("my-optional-header", "42", "other-key", "1", "other-key", "2", "other-key", "3"),
   123  		},
   124  		{
   125  			md:      Pairs("My-Optional-Header", "42"),
   126  			setKey:  "Other-Key",
   127  			setVals: []string{},
   128  			want:    Pairs("my-optional-header", "42"),
   129  		},
   130  	} {
   131  		test.md.Set(test.setKey, test.setVals...)
   132  		if !reflect.DeepEqual(test.md, test.want) {
   133  			t.Errorf("value of metadata is %v, want %v", test.md, test.want)
   134  		}
   135  	}
   136  }
   137  
   138  func (s) TestAppend(t *testing.T) {
   139  	for _, test := range []struct {
   140  		md         MD
   141  		appendKey  string
   142  		appendVals []string
   143  		want       MD
   144  	}{
   145  		{
   146  			md:         Pairs("My-Optional-Header", "42"),
   147  			appendKey:  "Other-Key",
   148  			appendVals: []string{"1"},
   149  			want:       Pairs("my-optional-header", "42", "other-key", "1"),
   150  		},
   151  		{
   152  			md:         Pairs("My-Optional-Header", "42"),
   153  			appendKey:  "my-OptIoNal-HeAder",
   154  			appendVals: []string{"1", "2", "3"},
   155  			want: Pairs("my-optional-header", "42", "my-optional-header", "1",
   156  				"my-optional-header", "2", "my-optional-header", "3"),
   157  		},
   158  		{
   159  			md:         Pairs("My-Optional-Header", "42"),
   160  			appendKey:  "my-OptIoNal-HeAder",
   161  			appendVals: []string{},
   162  			want:       Pairs("my-optional-header", "42"),
   163  		},
   164  	} {
   165  		test.md.Append(test.appendKey, test.appendVals...)
   166  		if !reflect.DeepEqual(test.md, test.want) {
   167  			t.Errorf("value of metadata is %v, want %v", test.md, test.want)
   168  		}
   169  	}
   170  }
   171  
   172  func (s) TestDelete(t *testing.T) {
   173  	for _, test := range []struct {
   174  		md        MD
   175  		deleteKey string
   176  		want      MD
   177  	}{
   178  		{
   179  			md:        Pairs("My-Optional-Header", "42"),
   180  			deleteKey: "My-Optional-Header",
   181  			want:      Pairs(),
   182  		},
   183  		{
   184  			md:        Pairs("My-Optional-Header", "42"),
   185  			deleteKey: "Other-Key",
   186  			want:      Pairs("my-optional-header", "42"),
   187  		},
   188  		{
   189  			md:        Pairs("My-Optional-Header", "42"),
   190  			deleteKey: "my-OptIoNal-HeAder",
   191  			want:      Pairs(),
   192  		},
   193  	} {
   194  		test.md.Delete(test.deleteKey)
   195  		if !reflect.DeepEqual(test.md, test.want) {
   196  			t.Errorf("value of metadata is %v, want %v", test.md, test.want)
   197  		}
   198  	}
   199  }
   200  
   201  func (s) TestFromIncomingContext(t *testing.T) {
   202  	md := Pairs(
   203  		"X-My-Header-1", "42",
   204  	)
   205  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   206  	defer cancel()
   207  	// Verify that we lowercase if callers directly modify md
   208  	md["X-INCORRECT-UPPERCASE"] = []string{"foo"}
   209  	ctx = NewIncomingContext(ctx, md)
   210  
   211  	result, found := FromIncomingContext(ctx)
   212  	if !found {
   213  		t.Fatal("FromIncomingContext must return metadata")
   214  	}
   215  	expected := MD{
   216  		"x-my-header-1":         []string{"42"},
   217  		"x-incorrect-uppercase": []string{"foo"},
   218  	}
   219  	if !reflect.DeepEqual(result, expected) {
   220  		t.Errorf("FromIncomingContext returned %#v, expected %#v", result, expected)
   221  	}
   222  
   223  	// ensure modifying result does not modify the value in the context
   224  	result["new_key"] = []string{"foo"}
   225  	result["x-my-header-1"][0] = "mutated"
   226  
   227  	result2, found := FromIncomingContext(ctx)
   228  	if !found {
   229  		t.Fatal("FromIncomingContext must return metadata")
   230  	}
   231  	if !reflect.DeepEqual(result2, expected) {
   232  		t.Errorf("FromIncomingContext after modifications returned %#v, expected %#v", result2, expected)
   233  	}
   234  }
   235  
   236  func (s) TestValueFromIncomingContext(t *testing.T) {
   237  	md := Pairs(
   238  		"X-My-Header-1", "42",
   239  		"X-My-Header-2", "43-1",
   240  		"X-My-Header-2", "43-2",
   241  		"x-my-header-3", "44",
   242  	)
   243  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   244  	defer cancel()
   245  	// Verify that we lowercase if callers directly modify md
   246  	md["X-INCORRECT-UPPERCASE"] = []string{"foo"}
   247  	ctx = NewIncomingContext(ctx, md)
   248  
   249  	for _, test := range []struct {
   250  		key  string
   251  		want []string
   252  	}{
   253  		{
   254  			key:  "x-my-header-1",
   255  			want: []string{"42"},
   256  		},
   257  		{
   258  			key:  "x-my-header-2",
   259  			want: []string{"43-1", "43-2"},
   260  		},
   261  		{
   262  			key:  "x-my-header-3",
   263  			want: []string{"44"},
   264  		},
   265  		{
   266  			key:  "x-unknown",
   267  			want: nil,
   268  		},
   269  		{
   270  			key:  "x-incorrect-uppercase",
   271  			want: []string{"foo"},
   272  		},
   273  	} {
   274  		v := ValueFromIncomingContext(ctx, test.key)
   275  		if !reflect.DeepEqual(v, test.want) {
   276  			t.Errorf("value of metadata is %v, want %v", v, test.want)
   277  		}
   278  	}
   279  }
   280  
   281  func (s) TestAppendToOutgoingContext(t *testing.T) {
   282  	// Pre-existing metadata
   283  	tCtx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   284  	defer cancel()
   285  	ctx := NewOutgoingContext(tCtx, Pairs("k1", "v1", "k2", "v2"))
   286  	ctx = AppendToOutgoingContext(ctx, "k1", "v3")
   287  	ctx = AppendToOutgoingContext(ctx, "k1", "v4")
   288  	md, ok := FromOutgoingContext(ctx)
   289  	if !ok {
   290  		t.Errorf("Expected MD to exist in ctx, but got none")
   291  	}
   292  	want := Pairs("k1", "v1", "k1", "v3", "k1", "v4", "k2", "v2")
   293  	if !reflect.DeepEqual(md, want) {
   294  		t.Errorf("context's metadata is %v, want %v", md, want)
   295  	}
   296  
   297  	// No existing metadata
   298  	ctx = AppendToOutgoingContext(tCtx, "k1", "v1")
   299  	md, ok = FromOutgoingContext(ctx)
   300  	if !ok {
   301  		t.Errorf("Expected MD to exist in ctx, but got none")
   302  	}
   303  	want = Pairs("k1", "v1")
   304  	if !reflect.DeepEqual(md, want) {
   305  		t.Errorf("context's metadata is %v, want %v", md, want)
   306  	}
   307  }
   308  
   309  func (s) TestAppendToOutgoingContext_Repeated(t *testing.T) {
   310  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   311  	defer cancel()
   312  
   313  	for i := 0; i < 100; i = i + 2 {
   314  		ctx1 := AppendToOutgoingContext(ctx, "k", strconv.Itoa(i))
   315  		ctx2 := AppendToOutgoingContext(ctx, "k", strconv.Itoa(i+1))
   316  
   317  		md1, _ := FromOutgoingContext(ctx1)
   318  		md2, _ := FromOutgoingContext(ctx2)
   319  
   320  		if reflect.DeepEqual(md1, md2) {
   321  			t.Fatalf("md1, md2 = %v, %v; should not be equal", md1, md2)
   322  		}
   323  
   324  		ctx = ctx1
   325  	}
   326  }
   327  
   328  func (s) TestAppendToOutgoingContext_FromKVSlice(t *testing.T) {
   329  	const k, v = "a", "b"
   330  	kv := []string{k, v}
   331  	tCtx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   332  	defer cancel()
   333  	ctx := AppendToOutgoingContext(tCtx, kv...)
   334  	md, _ := FromOutgoingContext(ctx)
   335  	if md[k][0] != v {
   336  		t.Fatalf("md[%q] = %q; want %q", k, md[k], v)
   337  	}
   338  	kv[1] = "xxx"
   339  	md, _ = FromOutgoingContext(ctx)
   340  	if md[k][0] != v {
   341  		t.Fatalf("md[%q] = %q; want %q", k, md[k], v)
   342  	}
   343  }
   344  
   345  // Old/slow approach to adding metadata to context
   346  func Benchmark_AddingMetadata_ContextManipulationApproach(b *testing.B) {
   347  	// TODO: Add in N=1-100 tests once Go1.6 support is removed.
   348  	const num = 10
   349  	for n := 0; n < b.N; n++ {
   350  		ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   351  		defer cancel()
   352  		for i := 0; i < num; i++ {
   353  			md, _ := FromOutgoingContext(ctx)
   354  			NewOutgoingContext(ctx, Join(Pairs("k1", "v1", "k2", "v2"), md))
   355  		}
   356  	}
   357  }
   358  
   359  // Newer/faster approach to adding metadata to context
   360  func BenchmarkAppendToOutgoingContext(b *testing.B) {
   361  	const num = 10
   362  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   363  	defer cancel()
   364  	for n := 0; n < b.N; n++ {
   365  		for i := 0; i < num; i++ {
   366  			ctx = AppendToOutgoingContext(ctx, "k1", "v1", "k2", "v2")
   367  		}
   368  	}
   369  }
   370  
   371  func BenchmarkFromOutgoingContext(b *testing.B) {
   372  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   373  	defer cancel()
   374  	ctx = NewOutgoingContext(ctx, MD{"k3": {"v3", "v4"}})
   375  	ctx = AppendToOutgoingContext(ctx, "k1", "v1", "k2", "v2")
   376  
   377  	for n := 0; n < b.N; n++ {
   378  		FromOutgoingContext(ctx)
   379  	}
   380  }
   381  
   382  func BenchmarkFromIncomingContext(b *testing.B) {
   383  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   384  	defer cancel()
   385  	md := Pairs("X-My-Header-1", "42")
   386  	ctx = NewIncomingContext(ctx, md)
   387  
   388  	b.ResetTimer()
   389  	for n := 0; n < b.N; n++ {
   390  		FromIncomingContext(ctx)
   391  	}
   392  }
   393  
   394  func BenchmarkValueFromIncomingContext(b *testing.B) {
   395  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   396  	defer cancel()
   397  	md := Pairs("X-My-Header-1", "42")
   398  	ctx = NewIncomingContext(ctx, md)
   399  
   400  	b.Run("key-found", func(b *testing.B) {
   401  		for n := 0; n < b.N; n++ {
   402  			result := ValueFromIncomingContext(ctx, "x-my-header-1")
   403  			if len(result) != 1 {
   404  				b.Fatal("ensures not optimized away")
   405  			}
   406  		}
   407  	})
   408  
   409  	b.Run("key-not-found", func(b *testing.B) {
   410  		for n := 0; n < b.N; n++ {
   411  			result := ValueFromIncomingContext(ctx, "key-not-found")
   412  			if len(result) != 0 {
   413  				b.Fatal("ensures not optimized away")
   414  			}
   415  		}
   416  	})
   417  }