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 }