google.golang.org/grpc@v1.62.1/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  	// Verify that we lowercase if callers directly modify md
   206  	md["X-INCORRECT-UPPERCASE"] = []string{"foo"}
   207  	ctx := NewIncomingContext(context.Background(), md)
   208  
   209  	result, found := FromIncomingContext(ctx)
   210  	if !found {
   211  		t.Fatal("FromIncomingContext must return metadata")
   212  	}
   213  	expected := MD{
   214  		"x-my-header-1":         []string{"42"},
   215  		"x-incorrect-uppercase": []string{"foo"},
   216  	}
   217  	if !reflect.DeepEqual(result, expected) {
   218  		t.Errorf("FromIncomingContext returned %#v, expected %#v", result, expected)
   219  	}
   220  
   221  	// ensure modifying result does not modify the value in the context
   222  	result["new_key"] = []string{"foo"}
   223  	result["x-my-header-1"][0] = "mutated"
   224  
   225  	result2, found := FromIncomingContext(ctx)
   226  	if !found {
   227  		t.Fatal("FromIncomingContext must return metadata")
   228  	}
   229  	if !reflect.DeepEqual(result2, expected) {
   230  		t.Errorf("FromIncomingContext after modifications returned %#v, expected %#v", result2, expected)
   231  	}
   232  }
   233  
   234  func (s) TestValueFromIncomingContext(t *testing.T) {
   235  	md := Pairs(
   236  		"X-My-Header-1", "42",
   237  		"X-My-Header-2", "43-1",
   238  		"X-My-Header-2", "43-2",
   239  		"x-my-header-3", "44",
   240  	)
   241  	// Verify that we lowercase if callers directly modify md
   242  	md["X-INCORRECT-UPPERCASE"] = []string{"foo"}
   243  	ctx := NewIncomingContext(context.Background(), md)
   244  
   245  	for _, test := range []struct {
   246  		key  string
   247  		want []string
   248  	}{
   249  		{
   250  			key:  "x-my-header-1",
   251  			want: []string{"42"},
   252  		},
   253  		{
   254  			key:  "x-my-header-2",
   255  			want: []string{"43-1", "43-2"},
   256  		},
   257  		{
   258  			key:  "x-my-header-3",
   259  			want: []string{"44"},
   260  		},
   261  		{
   262  			key:  "x-unknown",
   263  			want: nil,
   264  		},
   265  		{
   266  			key:  "x-incorrect-uppercase",
   267  			want: []string{"foo"},
   268  		},
   269  	} {
   270  		v := ValueFromIncomingContext(ctx, test.key)
   271  		if !reflect.DeepEqual(v, test.want) {
   272  			t.Errorf("value of metadata is %v, want %v", v, test.want)
   273  		}
   274  	}
   275  }
   276  
   277  func (s) TestAppendToOutgoingContext(t *testing.T) {
   278  	// Pre-existing metadata
   279  	tCtx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   280  	defer cancel()
   281  	ctx := NewOutgoingContext(tCtx, Pairs("k1", "v1", "k2", "v2"))
   282  	ctx = AppendToOutgoingContext(ctx, "k1", "v3")
   283  	ctx = AppendToOutgoingContext(ctx, "k1", "v4")
   284  	md, ok := FromOutgoingContext(ctx)
   285  	if !ok {
   286  		t.Errorf("Expected MD to exist in ctx, but got none")
   287  	}
   288  	want := Pairs("k1", "v1", "k1", "v3", "k1", "v4", "k2", "v2")
   289  	if !reflect.DeepEqual(md, want) {
   290  		t.Errorf("context's metadata is %v, want %v", md, want)
   291  	}
   292  
   293  	// No existing metadata
   294  	ctx = AppendToOutgoingContext(tCtx, "k1", "v1")
   295  	md, ok = FromOutgoingContext(ctx)
   296  	if !ok {
   297  		t.Errorf("Expected MD to exist in ctx, but got none")
   298  	}
   299  	want = Pairs("k1", "v1")
   300  	if !reflect.DeepEqual(md, want) {
   301  		t.Errorf("context's metadata is %v, want %v", md, want)
   302  	}
   303  }
   304  
   305  func (s) TestAppendToOutgoingContext_Repeated(t *testing.T) {
   306  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   307  	defer cancel()
   308  
   309  	for i := 0; i < 100; i = i + 2 {
   310  		ctx1 := AppendToOutgoingContext(ctx, "k", strconv.Itoa(i))
   311  		ctx2 := AppendToOutgoingContext(ctx, "k", strconv.Itoa(i+1))
   312  
   313  		md1, _ := FromOutgoingContext(ctx1)
   314  		md2, _ := FromOutgoingContext(ctx2)
   315  
   316  		if reflect.DeepEqual(md1, md2) {
   317  			t.Fatalf("md1, md2 = %v, %v; should not be equal", md1, md2)
   318  		}
   319  
   320  		ctx = ctx1
   321  	}
   322  }
   323  
   324  func (s) TestAppendToOutgoingContext_FromKVSlice(t *testing.T) {
   325  	const k, v = "a", "b"
   326  	kv := []string{k, v}
   327  	tCtx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   328  	defer cancel()
   329  	ctx := AppendToOutgoingContext(tCtx, kv...)
   330  	md, _ := FromOutgoingContext(ctx)
   331  	if md[k][0] != v {
   332  		t.Fatalf("md[%q] = %q; want %q", k, md[k], v)
   333  	}
   334  	kv[1] = "xxx"
   335  	md, _ = FromOutgoingContext(ctx)
   336  	if md[k][0] != v {
   337  		t.Fatalf("md[%q] = %q; want %q", k, md[k], v)
   338  	}
   339  }
   340  
   341  // Old/slow approach to adding metadata to context
   342  func Benchmark_AddingMetadata_ContextManipulationApproach(b *testing.B) {
   343  	// TODO: Add in N=1-100 tests once Go1.6 support is removed.
   344  	const num = 10
   345  	for n := 0; n < b.N; n++ {
   346  		ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   347  		defer cancel()
   348  		for i := 0; i < num; i++ {
   349  			md, _ := FromOutgoingContext(ctx)
   350  			NewOutgoingContext(ctx, Join(Pairs("k1", "v1", "k2", "v2"), md))
   351  		}
   352  	}
   353  }
   354  
   355  // Newer/faster approach to adding metadata to context
   356  func BenchmarkAppendToOutgoingContext(b *testing.B) {
   357  	const num = 10
   358  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   359  	defer cancel()
   360  	for n := 0; n < b.N; n++ {
   361  		for i := 0; i < num; i++ {
   362  			ctx = AppendToOutgoingContext(ctx, "k1", "v1", "k2", "v2")
   363  		}
   364  	}
   365  }
   366  
   367  func BenchmarkFromOutgoingContext(b *testing.B) {
   368  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   369  	defer cancel()
   370  	ctx = NewOutgoingContext(ctx, MD{"k3": {"v3", "v4"}})
   371  	ctx = AppendToOutgoingContext(ctx, "k1", "v1", "k2", "v2")
   372  
   373  	for n := 0; n < b.N; n++ {
   374  		FromOutgoingContext(ctx)
   375  	}
   376  }
   377  
   378  func BenchmarkFromIncomingContext(b *testing.B) {
   379  	md := Pairs("X-My-Header-1", "42")
   380  	ctx := NewIncomingContext(context.Background(), md)
   381  	b.ResetTimer()
   382  	for n := 0; n < b.N; n++ {
   383  		FromIncomingContext(ctx)
   384  	}
   385  }
   386  
   387  func BenchmarkValueFromIncomingContext(b *testing.B) {
   388  	md := Pairs("X-My-Header-1", "42")
   389  	ctx := NewIncomingContext(context.Background(), md)
   390  
   391  	b.Run("key-found", func(b *testing.B) {
   392  		for n := 0; n < b.N; n++ {
   393  			result := ValueFromIncomingContext(ctx, "x-my-header-1")
   394  			if len(result) != 1 {
   395  				b.Fatal("ensures not optimized away")
   396  			}
   397  		}
   398  	})
   399  
   400  	b.Run("key-not-found", func(b *testing.B) {
   401  		for n := 0; n < b.N; n++ {
   402  			result := ValueFromIncomingContext(ctx, "key-not-found")
   403  			if len(result) != 0 {
   404  				b.Fatal("ensures not optimized away")
   405  			}
   406  		}
   407  	})
   408  }