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 }