| /* |
| * Copyright 2014, Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package io.grpc; |
| |
| import static com.google.common.base.Charsets.US_ASCII; |
| import static com.google.common.base.Charsets.UTF_8; |
| import static org.junit.Assert.assertArrayEquals; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotEquals; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import com.google.common.collect.Lists; |
| |
| import io.grpc.Metadata.Key; |
| import io.grpc.internal.GrpcUtil; |
| |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.ExpectedException; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.Locale; |
| |
| /** |
| * Tests for {@link Metadata}. |
| */ |
| @RunWith(JUnit4.class) |
| public class MetadataTest { |
| |
| @Rule public final ExpectedException thrown = ExpectedException.none(); |
| |
| private static final Metadata.BinaryMarshaller<Fish> FISH_MARSHALLER = |
| new Metadata.BinaryMarshaller<Fish>() { |
| @Override |
| public byte[] toBytes(Fish fish) { |
| return fish.name.getBytes(UTF_8); |
| } |
| |
| @Override |
| public Fish parseBytes(byte[] serialized) { |
| return new Fish(new String(serialized, UTF_8)); |
| } |
| }; |
| |
| private static final String LANCE = "lance"; |
| private static final byte[] LANCE_BYTES = LANCE.getBytes(US_ASCII); |
| private static final Metadata.Key<Fish> KEY = Metadata.Key.of("test-bin", FISH_MARSHALLER); |
| |
| @Test |
| public void testMutations() { |
| Fish lance = new Fish(LANCE); |
| Fish cat = new Fish("cat"); |
| Metadata metadata = new Metadata(); |
| |
| assertEquals(null, metadata.get(KEY)); |
| metadata.put(KEY, lance); |
| assertEquals(Arrays.asList(lance), Lists.newArrayList(metadata.getAll(KEY))); |
| assertEquals(lance, metadata.get(KEY)); |
| metadata.put(KEY, lance); |
| assertEquals(Arrays.asList(lance, lance), Lists.newArrayList(metadata.getAll(KEY))); |
| assertTrue(metadata.remove(KEY, lance)); |
| assertEquals(Arrays.asList(lance), Lists.newArrayList(metadata.getAll(KEY))); |
| |
| assertFalse(metadata.remove(KEY, cat)); |
| metadata.put(KEY, cat); |
| assertEquals(cat, metadata.get(KEY)); |
| metadata.put(KEY, lance); |
| assertEquals(Arrays.asList(lance, cat, lance), Lists.newArrayList(metadata.getAll(KEY))); |
| assertEquals(lance, metadata.get(KEY)); |
| assertTrue(metadata.remove(KEY, lance)); |
| assertEquals(Arrays.asList(cat, lance), Lists.newArrayList(metadata.getAll(KEY))); |
| metadata.put(KEY, lance); |
| assertTrue(metadata.remove(KEY, cat)); |
| assertEquals(Arrays.asList(lance, lance), Lists.newArrayList(metadata.getAll(KEY))); |
| |
| assertEquals(Arrays.asList(lance, lance), Lists.newArrayList(metadata.removeAll(KEY))); |
| assertEquals(null, metadata.getAll(KEY)); |
| assertEquals(null, metadata.get(KEY)); |
| } |
| |
| @Test |
| public void discardAll() { |
| Fish lance = new Fish(LANCE); |
| Metadata metadata = new Metadata(); |
| |
| metadata.put(KEY, lance); |
| metadata.discardAll(KEY); |
| assertEquals(null, metadata.getAll(KEY)); |
| assertEquals(null, metadata.get(KEY)); |
| } |
| |
| @Test |
| public void discardAll_empty() { |
| Metadata metadata = new Metadata(); |
| metadata.discardAll(KEY); |
| assertEquals(null, metadata.getAll(KEY)); |
| assertEquals(null, metadata.get(KEY)); |
| } |
| |
| @Test |
| public void testGetAllNoRemove() { |
| Fish lance = new Fish(LANCE); |
| Metadata metadata = new Metadata(); |
| metadata.put(KEY, lance); |
| Iterator<Fish> i = metadata.getAll(KEY).iterator(); |
| assertEquals(lance, i.next()); |
| |
| thrown.expect(UnsupportedOperationException.class); |
| i.remove(); |
| } |
| |
| @Test |
| public void testWriteParsed() { |
| Fish lance = new Fish(LANCE); |
| Metadata metadata = new Metadata(); |
| metadata.put(KEY, lance); |
| assertEquals(lance, metadata.get(KEY)); |
| Iterator<Fish> fishes = metadata.<Fish>getAll(KEY).iterator(); |
| assertTrue(fishes.hasNext()); |
| assertEquals(fishes.next(), lance); |
| assertFalse(fishes.hasNext()); |
| byte[][] serialized = metadata.serialize(); |
| assertEquals(2, serialized.length); |
| assertEquals(new String(serialized[0], US_ASCII), "test-bin"); |
| assertArrayEquals(LANCE_BYTES, serialized[1]); |
| assertEquals(lance, metadata.get(KEY)); |
| assertEquals(serialized[0], metadata.serialize()[0]); |
| assertEquals(serialized[1], metadata.serialize()[1]); |
| } |
| |
| @Test |
| public void testWriteRaw() { |
| Metadata raw = new Metadata(KEY.asciiName(), LANCE_BYTES); |
| Fish lance = raw.get(KEY); |
| assertEquals(lance, new Fish(LANCE)); |
| // Reading again should return the same parsed instance |
| assertEquals(lance, raw.get(KEY)); |
| } |
| |
| @Test |
| public void testSerializeRaw() { |
| Metadata raw = new Metadata(KEY.asciiName(), LANCE_BYTES); |
| byte[][] serialized = raw.serialize(); |
| assertArrayEquals(serialized[0], KEY.asciiName()); |
| assertArrayEquals(serialized[1], LANCE_BYTES); |
| } |
| |
| @Test |
| public void testMergeByteConstructed() { |
| Metadata raw = new Metadata(KEY.asciiName(), LANCE_BYTES); |
| Metadata serializable = new Metadata(); |
| serializable.merge(raw); |
| |
| byte[][] serialized = serializable.serialize(); |
| assertArrayEquals(serialized[0], KEY.asciiName()); |
| assertArrayEquals(serialized[1], LANCE_BYTES); |
| assertEquals(new Fish(LANCE), serializable.get(KEY)); |
| } |
| |
| @Test |
| public void headerMergeShouldCopyValues() { |
| Fish lance = new Fish(LANCE); |
| Metadata h1 = new Metadata(); |
| |
| Metadata h2 = new Metadata(); |
| h2.put(KEY, lance); |
| |
| h1.merge(h2); |
| |
| Iterator<Fish> fishes = h1.<Fish>getAll(KEY).iterator(); |
| assertTrue(fishes.hasNext()); |
| assertEquals(fishes.next(), lance); |
| assertFalse(fishes.hasNext()); |
| } |
| |
| @Test |
| public void mergeExpands() { |
| Fish lance = new Fish(LANCE); |
| Metadata h1 = new Metadata(); |
| h1.put(KEY, lance); |
| |
| Metadata h2 = new Metadata(); |
| h2.put(KEY, lance); |
| h2.put(KEY, lance); |
| h2.put(KEY, lance); |
| h2.put(KEY, lance); |
| |
| h1.merge(h2); |
| } |
| |
| @Test |
| public void integerMarshallerIsDecimal() { |
| assertEquals("12345678", Metadata.INTEGER_MARSHALLER.toAsciiString(12345678)); |
| } |
| |
| @Test |
| public void roundTripIntegerMarshaller() { |
| roundTripInteger(0); |
| roundTripInteger(1); |
| roundTripInteger(-1); |
| roundTripInteger(0x12345678); |
| roundTripInteger(0x87654321); |
| } |
| |
| @Test |
| public void shortBinaryKeyName() { |
| thrown.expect(IllegalArgumentException.class); |
| |
| Metadata.Key.of("-bin", FISH_MARSHALLER); |
| } |
| |
| @Test |
| public void invalidSuffixBinaryKeyName() { |
| thrown.expect(IllegalArgumentException.class); |
| thrown.expectMessage("Binary header is named"); |
| |
| Metadata.Key.of("nonbinary", FISH_MARSHALLER); |
| } |
| |
| private void roundTripInteger(Integer i) { |
| assertEquals(i, Metadata.INTEGER_MARSHALLER.parseAsciiString( |
| Metadata.INTEGER_MARSHALLER.toAsciiString(i))); |
| } |
| |
| @Test |
| public void verifyToString() { |
| Metadata h = new Metadata(); |
| h.put(KEY, new Fish("binary")); |
| h.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii"); |
| assertEquals("Metadata(test-bin=YmluYXJ5,test=ascii)", h.toString()); |
| |
| Metadata t = new Metadata(); |
| t.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii"); |
| assertEquals("Metadata(test=ascii)", t.toString()); |
| |
| t = new Metadata("test".getBytes(US_ASCII), "ascii".getBytes(US_ASCII), |
| "test-bin".getBytes(US_ASCII), "binary".getBytes(US_ASCII)); |
| assertEquals("Metadata(test=ascii,test-bin=YmluYXJ5)", t.toString()); |
| } |
| |
| @Test |
| public void verifyToString_usingBinary() { |
| Metadata h = new Metadata(); |
| h.put(KEY, new Fish("binary")); |
| h.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii"); |
| assertEquals("Metadata(test-bin=YmluYXJ5,test=ascii)", h.toString()); |
| |
| Metadata t = new Metadata(); |
| t.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii"); |
| assertEquals("Metadata(test=ascii)", t.toString()); |
| } |
| |
| @Test |
| public void testKeyCaseHandling() { |
| Locale originalLocale = Locale.getDefault(); |
| Locale.setDefault(new Locale("tr", "TR")); |
| try { |
| // In Turkish, both I and i (which are in ASCII) change into non-ASCII characters when their |
| // case is changed as ı and İ, respectively. |
| assertEquals("İ", "i".toUpperCase()); |
| assertEquals("ı", "I".toLowerCase()); |
| |
| Metadata.Key<String> keyTitleCase |
| = Metadata.Key.of("If-Modified-Since", Metadata.ASCII_STRING_MARSHALLER); |
| Metadata.Key<String> keyLowerCase |
| = Metadata.Key.of("if-modified-since", Metadata.ASCII_STRING_MARSHALLER); |
| Metadata.Key<String> keyUpperCase |
| = Metadata.Key.of("IF-MODIFIED-SINCE", Metadata.ASCII_STRING_MARSHALLER); |
| |
| Metadata metadata = new Metadata(); |
| metadata.put(keyTitleCase, "plain string"); |
| assertEquals("plain string", metadata.get(keyTitleCase)); |
| assertEquals("plain string", metadata.get(keyLowerCase)); |
| assertEquals("plain string", metadata.get(keyUpperCase)); |
| |
| byte[][] bytes = metadata.serialize(); |
| assertEquals(2, bytes.length); |
| assertArrayEquals("if-modified-since".getBytes(US_ASCII), bytes[0]); |
| assertArrayEquals("plain string".getBytes(US_ASCII), bytes[1]); |
| } finally { |
| Locale.setDefault(originalLocale); |
| } |
| } |
| |
| @Test |
| public void removeIgnoresMissingValue() { |
| Metadata m = new Metadata(); |
| // Any key will work. |
| Key<String> key = GrpcUtil.USER_AGENT_KEY; |
| |
| boolean success = m.remove(key, "agent"); |
| assertFalse(success); |
| } |
| |
| @Test |
| public void removeAllIgnoresMissingValue() { |
| Metadata m = new Metadata(); |
| // Any key will work. |
| Key<String> key = GrpcUtil.USER_AGENT_KEY; |
| |
| Iterable<String> removed = m.removeAll(key); |
| assertNull(removed); |
| } |
| |
| @Test |
| public void keyEqualsHashNameWorks() { |
| Key<Integer> k1 = Key.of("case", Metadata.INTEGER_MARSHALLER); |
| |
| Key<Integer> k2 = Key.of("CASE", Metadata.INTEGER_MARSHALLER); |
| assertEquals(k1, k1); |
| assertNotEquals(k1, null); |
| assertNotEquals(k1, new Object(){}); |
| assertEquals(k1, k2); |
| |
| assertEquals(k1.hashCode(), k2.hashCode()); |
| // Check that the casing is preserved. |
| assertEquals("CASE", k2.originalName()); |
| assertEquals("case", k2.name()); |
| } |
| |
| @Test |
| public void invalidKeyName() { |
| try { |
| Key.of("io.grpc/key1", Metadata.INTEGER_MARSHALLER); |
| fail("Should have thrown"); |
| } catch (IllegalArgumentException e) { |
| assertEquals("Invalid character '/' in key name 'io.grpc/key1'", e.getMessage()); |
| } |
| } |
| |
| private static class Fish { |
| private String name; |
| |
| private Fish(String name) { |
| this.name = name; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) { |
| return true; |
| } |
| if (o == null || getClass() != o.getClass()) { |
| return false; |
| } |
| Fish fish = (Fish) o; |
| if (name != null ? !name.equals(fish.name) : fish.name != null) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| return name.hashCode(); |
| } |
| |
| @Override |
| public String toString() { |
| return "Fish(" + name + ")"; |
| } |
| } |
| } |