1 /* 2 * Copyright 2014 The gRPC Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.grpc; 18 19 import static com.google.common.base.Charsets.US_ASCII; 20 import static com.google.common.base.Charsets.UTF_8; 21 import static org.junit.Assert.assertArrayEquals; 22 import static org.junit.Assert.assertEquals; 23 import static org.junit.Assert.assertFalse; 24 import static org.junit.Assert.assertNotEquals; 25 import static org.junit.Assert.assertNotSame; 26 import static org.junit.Assert.assertNull; 27 import static org.junit.Assert.assertSame; 28 import static org.junit.Assert.assertTrue; 29 import static org.junit.Assert.fail; 30 31 import com.google.common.collect.Lists; 32 import com.google.common.io.ByteStreams; 33 import io.grpc.internal.GrpcUtil; 34 import java.io.ByteArrayInputStream; 35 import java.io.IOException; 36 import java.io.InputStream; 37 import java.util.Arrays; 38 import java.util.Iterator; 39 import java.util.Locale; 40 import org.junit.Rule; 41 import org.junit.Test; 42 import org.junit.rules.ExpectedException; 43 import org.junit.runner.RunWith; 44 import org.junit.runners.JUnit4; 45 46 /** 47 * Tests for {@link Metadata}. 48 */ 49 @RunWith(JUnit4.class) 50 public class MetadataTest { 51 52 @SuppressWarnings("deprecation") // https://github.com/grpc/grpc-java/issues/7467 53 @Rule public final ExpectedException thrown = ExpectedException.none(); 54 55 private static final Metadata.BinaryMarshaller<Fish> FISH_MARSHALLER = 56 new Metadata.BinaryMarshaller<Fish>() { 57 @Override 58 public byte[] toBytes(Fish fish) { 59 return fish.name.getBytes(UTF_8); 60 } 61 62 @Override 63 public Fish parseBytes(byte[] serialized) { 64 return new Fish(new String(serialized, UTF_8)); 65 } 66 }; 67 68 private static class FishStreamMarsaller implements Metadata.BinaryStreamMarshaller<Fish> { 69 @Override toStream(Fish fish)70 public InputStream toStream(Fish fish) { 71 return new ByteArrayInputStream(FISH_MARSHALLER.toBytes(fish)); 72 } 73 74 @Override parseStream(InputStream stream)75 public Fish parseStream(InputStream stream) { 76 try { 77 return FISH_MARSHALLER.parseBytes(ByteStreams.toByteArray(stream)); 78 } catch (IOException ioe) { 79 throw new AssertionError(); 80 } 81 } 82 } 83 84 private static final Metadata.BinaryStreamMarshaller<Fish> FISH_STREAM_MARSHALLER = 85 new FishStreamMarsaller(); 86 87 /** A pattern commonly used to avoid unnecessary serialization of immutable objects. */ 88 private static final class FakeFishStream extends InputStream { 89 final Fish fish; 90 FakeFishStream(Fish fish)91 FakeFishStream(Fish fish) { 92 this.fish = fish; 93 } 94 95 @Override read()96 public int read() throws IOException { 97 throw new IOException("Not actually a stream"); 98 } 99 } 100 101 private static final Metadata.BinaryStreamMarshaller<Fish> IMMUTABLE_FISH_MARSHALLER = 102 new Metadata.BinaryStreamMarshaller<Fish>() { 103 @Override 104 public InputStream toStream(Fish fish) { 105 return new FakeFishStream(fish); 106 } 107 108 @Override 109 public Fish parseStream(InputStream stream) { 110 return ((FakeFishStream) stream).fish; 111 } 112 }; 113 114 private static final String LANCE = "lance"; 115 private static final byte[] LANCE_BYTES = LANCE.getBytes(US_ASCII); 116 private static final Metadata.Key<Fish> KEY = Metadata.Key.of("test-bin", FISH_MARSHALLER); 117 private static final Metadata.Key<Fish> KEY_STREAMED = 118 Metadata.Key.of("streamed-bin", FISH_STREAM_MARSHALLER); 119 private static final Metadata.Key<Fish> KEY_IMMUTABLE = 120 Metadata.Key.of("immutable-bin", IMMUTABLE_FISH_MARSHALLER); 121 122 @Test noPseudoHeaders()123 public void noPseudoHeaders() { 124 thrown.expect(IllegalArgumentException.class); 125 thrown.expectMessage("Invalid character"); 126 127 Metadata.Key.of(":test-bin", FISH_MARSHALLER); 128 } 129 130 @Test testMutations()131 public void testMutations() { 132 Fish lance = new Fish(LANCE); 133 Fish cat = new Fish("cat"); 134 Metadata metadata = new Metadata(); 135 136 assertNull(metadata.get(KEY)); 137 metadata.put(KEY, lance); 138 assertEquals(Arrays.asList(lance), Lists.newArrayList(metadata.getAll(KEY))); 139 assertEquals(lance, metadata.get(KEY)); 140 metadata.put(KEY, lance); 141 assertEquals(Arrays.asList(lance, lance), Lists.newArrayList(metadata.getAll(KEY))); 142 assertTrue(metadata.remove(KEY, lance)); 143 assertEquals(Arrays.asList(lance), Lists.newArrayList(metadata.getAll(KEY))); 144 145 assertFalse(metadata.remove(KEY, cat)); 146 metadata.put(KEY, cat); 147 assertEquals(cat, metadata.get(KEY)); 148 metadata.put(KEY, lance); 149 assertEquals(Arrays.asList(lance, cat, lance), Lists.newArrayList(metadata.getAll(KEY))); 150 assertEquals(lance, metadata.get(KEY)); 151 assertTrue(metadata.remove(KEY, lance)); 152 assertEquals(Arrays.asList(cat, lance), Lists.newArrayList(metadata.getAll(KEY))); 153 metadata.put(KEY, lance); 154 assertTrue(metadata.remove(KEY, cat)); 155 assertEquals(Arrays.asList(lance, lance), Lists.newArrayList(metadata.getAll(KEY))); 156 157 assertEquals(Arrays.asList(lance, lance), Lists.newArrayList(metadata.removeAll(KEY))); 158 assertNull(metadata.getAll(KEY)); 159 assertNull(metadata.get(KEY)); 160 } 161 162 @Test discardAll()163 public void discardAll() { 164 Fish lance = new Fish(LANCE); 165 Metadata metadata = new Metadata(); 166 167 metadata.put(KEY, lance); 168 metadata.discardAll(KEY); 169 assertNull(metadata.getAll(KEY)); 170 assertNull(metadata.get(KEY)); 171 } 172 173 @Test discardAll_empty()174 public void discardAll_empty() { 175 Metadata metadata = new Metadata(); 176 metadata.discardAll(KEY); 177 assertNull(metadata.getAll(KEY)); 178 assertNull(metadata.get(KEY)); 179 } 180 181 @Test testGetAllNoRemove()182 public void testGetAllNoRemove() { 183 Fish lance = new Fish(LANCE); 184 Metadata metadata = new Metadata(); 185 metadata.put(KEY, lance); 186 Iterator<Fish> i = metadata.getAll(KEY).iterator(); 187 assertEquals(lance, i.next()); 188 189 thrown.expect(UnsupportedOperationException.class); 190 i.remove(); 191 } 192 193 @Test testWriteParsed()194 public void testWriteParsed() { 195 Fish lance = new Fish(LANCE); 196 Metadata metadata = new Metadata(); 197 metadata.put(KEY, lance); 198 assertEquals(lance, metadata.get(KEY)); 199 Iterator<Fish> fishes = metadata.getAll(KEY).iterator(); 200 assertTrue(fishes.hasNext()); 201 assertEquals(fishes.next(), lance); 202 assertFalse(fishes.hasNext()); 203 byte[][] serialized = metadata.serialize(); 204 assertEquals(2, serialized.length); 205 assertEquals("test-bin", new String(serialized[0], US_ASCII)); 206 assertArrayEquals(LANCE_BYTES, serialized[1]); 207 assertEquals(lance, metadata.get(KEY)); 208 assertEquals(serialized[0], metadata.serialize()[0]); 209 assertEquals(serialized[1], metadata.serialize()[1]); 210 } 211 212 @Test testWriteRaw()213 public void testWriteRaw() { 214 Metadata raw = new Metadata(KEY.asciiName(), LANCE_BYTES); 215 Fish lance = raw.get(KEY); 216 assertEquals(lance, new Fish(LANCE)); 217 // Reading again should return the same parsed instance 218 assertEquals(lance, raw.get(KEY)); 219 } 220 221 @Test testSerializeRaw()222 public void testSerializeRaw() { 223 Metadata raw = new Metadata(KEY.asciiName(), LANCE_BYTES); 224 byte[][] serialized = raw.serialize(); 225 assertArrayEquals(serialized[0], KEY.asciiName()); 226 assertArrayEquals(serialized[1], LANCE_BYTES); 227 } 228 229 @Test testMergeByteConstructed()230 public void testMergeByteConstructed() { 231 Metadata raw = new Metadata(KEY.asciiName(), LANCE_BYTES); 232 Metadata serializable = new Metadata(); 233 serializable.merge(raw); 234 235 byte[][] serialized = serializable.serialize(); 236 assertArrayEquals(serialized[0], KEY.asciiName()); 237 assertArrayEquals(serialized[1], LANCE_BYTES); 238 assertEquals(new Fish(LANCE), serializable.get(KEY)); 239 } 240 241 @Test headerMergeShouldCopyValues()242 public void headerMergeShouldCopyValues() { 243 Fish lance = new Fish(LANCE); 244 Metadata h1 = new Metadata(); 245 246 Metadata h2 = new Metadata(); 247 h2.put(KEY, lance); 248 249 h1.merge(h2); 250 251 Iterator<Fish> fishes = h1.getAll(KEY).iterator(); 252 assertTrue(fishes.hasNext()); 253 assertEquals(fishes.next(), lance); 254 assertFalse(fishes.hasNext()); 255 } 256 257 @Test mergeExpands()258 public void mergeExpands() { 259 Fish lance = new Fish(LANCE); 260 Metadata h1 = new Metadata(); 261 h1.put(KEY, lance); 262 263 Metadata h2 = new Metadata(); 264 h2.put(KEY, lance); 265 h2.put(KEY, lance); 266 h2.put(KEY, lance); 267 h2.put(KEY, lance); 268 269 h1.merge(h2); 270 } 271 272 @Test shortBinaryKeyName()273 public void shortBinaryKeyName() { 274 thrown.expect(IllegalArgumentException.class); 275 276 Metadata.Key.of("-bin", FISH_MARSHALLER); 277 } 278 279 @Test invalidSuffixBinaryKeyName()280 public void invalidSuffixBinaryKeyName() { 281 thrown.expect(IllegalArgumentException.class); 282 thrown.expectMessage("Binary header is named"); 283 284 Metadata.Key.of("nonbinary", FISH_MARSHALLER); 285 } 286 287 @Test verifyToString()288 public void verifyToString() { 289 Metadata h = new Metadata(); 290 h.put(KEY, new Fish("binary")); 291 h.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii"); 292 assertEquals("Metadata(test-bin=YmluYXJ5,test=ascii)", h.toString()); 293 294 Metadata t = new Metadata(); 295 t.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii"); 296 assertEquals("Metadata(test=ascii)", t.toString()); 297 298 t = new Metadata("test".getBytes(US_ASCII), "ascii".getBytes(US_ASCII), 299 "test-bin".getBytes(US_ASCII), "binary".getBytes(US_ASCII)); 300 assertEquals("Metadata(test=ascii,test-bin=YmluYXJ5)", t.toString()); 301 } 302 303 @Test verifyToString_usingBinary()304 public void verifyToString_usingBinary() { 305 Metadata h = new Metadata(); 306 h.put(KEY, new Fish("binary")); 307 h.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii"); 308 assertEquals("Metadata(test-bin=YmluYXJ5,test=ascii)", h.toString()); 309 310 Metadata t = new Metadata(); 311 t.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii"); 312 assertEquals("Metadata(test=ascii)", t.toString()); 313 } 314 315 @Test testKeyCaseHandling()316 public void testKeyCaseHandling() { 317 Locale originalLocale = Locale.getDefault(); 318 Locale.setDefault(new Locale("tr", "TR")); 319 try { 320 // In Turkish, both I and i (which are in ASCII) change into non-ASCII characters when their 321 // case is changed as ı and İ, respectively. 322 assertEquals("İ", "i".toUpperCase()); 323 assertEquals("ı", "I".toLowerCase()); 324 325 Metadata.Key<String> keyTitleCase 326 = Metadata.Key.of("If-Modified-Since", Metadata.ASCII_STRING_MARSHALLER); 327 Metadata.Key<String> keyLowerCase 328 = Metadata.Key.of("if-modified-since", Metadata.ASCII_STRING_MARSHALLER); 329 Metadata.Key<String> keyUpperCase 330 = Metadata.Key.of("IF-MODIFIED-SINCE", Metadata.ASCII_STRING_MARSHALLER); 331 332 Metadata metadata = new Metadata(); 333 metadata.put(keyTitleCase, "plain string"); 334 assertEquals("plain string", metadata.get(keyTitleCase)); 335 assertEquals("plain string", metadata.get(keyLowerCase)); 336 assertEquals("plain string", metadata.get(keyUpperCase)); 337 338 byte[][] bytes = metadata.serialize(); 339 assertEquals(2, bytes.length); 340 assertArrayEquals("if-modified-since".getBytes(US_ASCII), bytes[0]); 341 assertArrayEquals("plain string".getBytes(US_ASCII), bytes[1]); 342 } finally { 343 Locale.setDefault(originalLocale); 344 } 345 } 346 347 @Test removeIgnoresMissingValue()348 public void removeIgnoresMissingValue() { 349 Metadata m = new Metadata(); 350 // Any key will work. 351 Metadata.Key<String> key = GrpcUtil.USER_AGENT_KEY; 352 353 boolean success = m.remove(key, "agent"); 354 assertFalse(success); 355 } 356 357 @Test removeAllIgnoresMissingValue()358 public void removeAllIgnoresMissingValue() { 359 Metadata m = new Metadata(); 360 // Any key will work. 361 Metadata.Key<String> key = GrpcUtil.USER_AGENT_KEY; 362 363 Iterable<String> removed = m.removeAll(key); 364 assertNull(removed); 365 } 366 367 @Test keyEqualsHashNameWorks()368 public void keyEqualsHashNameWorks() { 369 Metadata.Key<?> k1 = Metadata.Key.of("case", Metadata.ASCII_STRING_MARSHALLER); 370 371 Metadata.Key<?> k2 = Metadata.Key.of("CASE", Metadata.ASCII_STRING_MARSHALLER); 372 assertEquals(k1, k1); 373 assertNotEquals(k1, null); 374 assertNotEquals(k1, new Object(){}); 375 assertEquals(k1, k2); 376 377 assertEquals(k1.hashCode(), k2.hashCode()); 378 // Check that the casing is preserved. 379 assertEquals("CASE", k2.originalName()); 380 assertEquals("case", k2.name()); 381 } 382 383 @Test invalidKeyName()384 public void invalidKeyName() { 385 try { 386 Metadata.Key.of("io.grpc/key1", Metadata.ASCII_STRING_MARSHALLER); 387 fail("Should have thrown"); 388 } catch (IllegalArgumentException e) { 389 assertEquals("Invalid character '/' in key name 'io.grpc/key1'", e.getMessage()); 390 } 391 } 392 393 @Test streamedValue()394 public void streamedValue() { 395 Fish salmon = new Fish("salmon"); 396 Metadata h = new Metadata(); 397 h.put(KEY_STREAMED, salmon); 398 assertEquals(salmon, h.get(KEY_STREAMED)); 399 } 400 401 @Test streamedValueDifferentKey()402 public void streamedValueDifferentKey() { 403 Fish salmon = new Fish("salmon"); 404 Metadata h = new Metadata(); 405 h.put(KEY_STREAMED, salmon); 406 407 // Get using a different key instance (but the same marshaller). 408 Fish fish = h.get(copyKey(KEY_STREAMED, FISH_STREAM_MARSHALLER)); 409 assertEquals(salmon, fish); 410 } 411 412 @Test streamedValueDifferentMarshaller()413 public void streamedValueDifferentMarshaller() { 414 Fish salmon = new Fish("salmon"); 415 Metadata h = new Metadata(); 416 h.put(KEY_STREAMED, salmon); 417 418 // Get using a different marshaller instance. 419 Fish fish = h.get(copyKey(KEY_STREAMED, new FishStreamMarsaller())); 420 assertEquals(salmon, fish); 421 } 422 423 @Test serializeParseMetadataWithStreams()424 public void serializeParseMetadataWithStreams() { 425 Metadata h = new Metadata(); 426 Fish salmon = new Fish("salmon"); 427 h.put(KEY_STREAMED, salmon); 428 429 Metadata parsed = new Metadata(h.serialize()); 430 assertEquals(salmon, parsed.get(KEY_STREAMED)); 431 } 432 433 @Test immutableMarshaller()434 public void immutableMarshaller() { 435 Metadata h = new Metadata(KEY.asciiName(), LANCE_BYTES); 436 Fish salmon = new Fish("salmon"); 437 h.put(KEY_IMMUTABLE, salmon); 438 assertSame(salmon, h.get(KEY_IMMUTABLE)); 439 // Even though the key differs, the marshaller can chose to avoid serialization. 440 assertSame(salmon, h.get(copyKey(KEY_IMMUTABLE, IMMUTABLE_FISH_MARSHALLER))); 441 } 442 443 @Test partialSerialization()444 public void partialSerialization() { 445 Metadata h = new Metadata(KEY.asciiName(), LANCE_BYTES); 446 Fish salmon = new Fish("salmon"); 447 h.put(KEY_STREAMED, salmon); 448 h.put(KEY_IMMUTABLE, salmon); 449 450 Object[] serialized = InternalMetadata.serializePartial(h); 451 assertEquals(6, serialized.length); 452 assertEquals("test-bin", new String((byte[]) serialized[0], US_ASCII)); 453 assertArrayEquals(LANCE_BYTES, (byte[]) serialized[1]); 454 455 assertEquals("streamed-bin", new String((byte[]) serialized[2], US_ASCII)); 456 assertEquals(salmon, FISH_STREAM_MARSHALLER.parseStream((InputStream) serialized[3])); 457 assertNotSame(salmon, FISH_STREAM_MARSHALLER.parseStream((InputStream) serialized[3])); 458 459 assertEquals("immutable-bin", new String((byte[]) serialized[4], US_ASCII)); 460 assertSame(salmon, IMMUTABLE_FISH_MARSHALLER.parseStream((InputStream) serialized[5])); 461 } 462 463 @Test createFromPartial()464 public void createFromPartial() { 465 Metadata h = new Metadata(KEY.asciiName(), LANCE_BYTES); 466 Fish salmon = new Fish("salmon"); 467 h.put(KEY_STREAMED, salmon); 468 h.put(KEY_IMMUTABLE, salmon); 469 470 Fish anotherSalmon = new Fish("salmon"); 471 472 Object[] partial = InternalMetadata.serializePartial(h); 473 partial[3] = InternalMetadata.parsedValue(FISH_STREAM_MARSHALLER, anotherSalmon); 474 partial[5] = InternalMetadata.parsedValue(IMMUTABLE_FISH_MARSHALLER, anotherSalmon); 475 476 Metadata h2 = new Metadata(3, partial); 477 assertEquals(new Fish(LANCE), h2.get(KEY)); 478 assertEquals(anotherSalmon, h2.get(KEY_STREAMED)); 479 assertSame(anotherSalmon, h2.get(KEY_IMMUTABLE)); 480 } 481 482 private static final class Fish { 483 private String name; 484 Fish(String name)485 private Fish(String name) { 486 this.name = name; 487 } 488 489 @Override equals(Object o)490 public boolean equals(Object o) { 491 if (this == o) { 492 return true; 493 } 494 if (o == null || getClass() != o.getClass()) { 495 return false; 496 } 497 Fish fish = (Fish) o; 498 if (name != null ? !name.equals(fish.name) : fish.name != null) { 499 return false; 500 } 501 return true; 502 } 503 504 @Override hashCode()505 public int hashCode() { 506 return name.hashCode(); 507 } 508 509 @Override toString()510 public String toString() { 511 return "Fish(" + name + ")"; 512 } 513 } 514 copyKey( Metadata.Key<T> key, Metadata.BinaryStreamMarshaller<T> marshaller)515 private static <T> Metadata.Key<T> copyKey( 516 Metadata.Key<T> key, Metadata.BinaryStreamMarshaller<T> marshaller) { 517 return Metadata.Key.of(key.originalName(), marshaller); 518 } 519 } 520