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.assertNull; 26 import static org.junit.Assert.assertTrue; 27 import static org.junit.Assert.fail; 28 29 import com.google.common.collect.Lists; 30 import io.grpc.Metadata.Key; 31 import io.grpc.internal.GrpcUtil; 32 import java.util.Arrays; 33 import java.util.Iterator; 34 import java.util.Locale; 35 import org.junit.Rule; 36 import org.junit.Test; 37 import org.junit.rules.ExpectedException; 38 import org.junit.runner.RunWith; 39 import org.junit.runners.JUnit4; 40 41 /** 42 * Tests for {@link Metadata}. 43 */ 44 @RunWith(JUnit4.class) 45 public class MetadataTest { 46 47 @Rule public final ExpectedException thrown = ExpectedException.none(); 48 49 private static final Metadata.BinaryMarshaller<Fish> FISH_MARSHALLER = 50 new Metadata.BinaryMarshaller<Fish>() { 51 @Override 52 public byte[] toBytes(Fish fish) { 53 return fish.name.getBytes(UTF_8); 54 } 55 56 @Override 57 public Fish parseBytes(byte[] serialized) { 58 return new Fish(new String(serialized, UTF_8)); 59 } 60 }; 61 62 private static final String LANCE = "lance"; 63 private static final byte[] LANCE_BYTES = LANCE.getBytes(US_ASCII); 64 private static final Metadata.Key<Fish> KEY = Metadata.Key.of("test-bin", FISH_MARSHALLER); 65 66 @Test 67 public void noPseudoHeaders() { 68 thrown.expect(IllegalArgumentException.class); 69 thrown.expectMessage("Invalid character"); 70 71 Metadata.Key.of(":test-bin", FISH_MARSHALLER); 72 } 73 74 @Test 75 public void testMutations() { 76 Fish lance = new Fish(LANCE); 77 Fish cat = new Fish("cat"); 78 Metadata metadata = new Metadata(); 79 80 assertEquals(null, metadata.get(KEY)); 81 metadata.put(KEY, lance); 82 assertEquals(Arrays.asList(lance), Lists.newArrayList(metadata.getAll(KEY))); 83 assertEquals(lance, metadata.get(KEY)); 84 metadata.put(KEY, lance); 85 assertEquals(Arrays.asList(lance, lance), Lists.newArrayList(metadata.getAll(KEY))); 86 assertTrue(metadata.remove(KEY, lance)); 87 assertEquals(Arrays.asList(lance), Lists.newArrayList(metadata.getAll(KEY))); 88 89 assertFalse(metadata.remove(KEY, cat)); 90 metadata.put(KEY, cat); 91 assertEquals(cat, metadata.get(KEY)); 92 metadata.put(KEY, lance); 93 assertEquals(Arrays.asList(lance, cat, lance), Lists.newArrayList(metadata.getAll(KEY))); 94 assertEquals(lance, metadata.get(KEY)); 95 assertTrue(metadata.remove(KEY, lance)); 96 assertEquals(Arrays.asList(cat, lance), Lists.newArrayList(metadata.getAll(KEY))); 97 metadata.put(KEY, lance); 98 assertTrue(metadata.remove(KEY, cat)); 99 assertEquals(Arrays.asList(lance, lance), Lists.newArrayList(metadata.getAll(KEY))); 100 101 assertEquals(Arrays.asList(lance, lance), Lists.newArrayList(metadata.removeAll(KEY))); 102 assertEquals(null, metadata.getAll(KEY)); 103 assertEquals(null, metadata.get(KEY)); 104 } 105 106 @Test 107 public void discardAll() { 108 Fish lance = new Fish(LANCE); 109 Metadata metadata = new Metadata(); 110 111 metadata.put(KEY, lance); 112 metadata.discardAll(KEY); 113 assertEquals(null, metadata.getAll(KEY)); 114 assertEquals(null, metadata.get(KEY)); 115 } 116 117 @Test 118 public void discardAll_empty() { 119 Metadata metadata = new Metadata(); 120 metadata.discardAll(KEY); 121 assertEquals(null, metadata.getAll(KEY)); 122 assertEquals(null, metadata.get(KEY)); 123 } 124 125 @Test 126 public void testGetAllNoRemove() { 127 Fish lance = new Fish(LANCE); 128 Metadata metadata = new Metadata(); 129 metadata.put(KEY, lance); 130 Iterator<Fish> i = metadata.getAll(KEY).iterator(); 131 assertEquals(lance, i.next()); 132 133 thrown.expect(UnsupportedOperationException.class); 134 i.remove(); 135 } 136 137 @Test 138 public void testWriteParsed() { 139 Fish lance = new Fish(LANCE); 140 Metadata metadata = new Metadata(); 141 metadata.put(KEY, lance); 142 assertEquals(lance, metadata.get(KEY)); 143 Iterator<Fish> fishes = metadata.<Fish>getAll(KEY).iterator(); 144 assertTrue(fishes.hasNext()); 145 assertEquals(fishes.next(), lance); 146 assertFalse(fishes.hasNext()); 147 byte[][] serialized = metadata.serialize(); 148 assertEquals(2, serialized.length); 149 assertEquals("test-bin", new String(serialized[0], US_ASCII)); 150 assertArrayEquals(LANCE_BYTES, serialized[1]); 151 assertEquals(lance, metadata.get(KEY)); 152 assertEquals(serialized[0], metadata.serialize()[0]); 153 assertEquals(serialized[1], metadata.serialize()[1]); 154 } 155 156 @Test 157 public void testWriteRaw() { 158 Metadata raw = new Metadata(KEY.asciiName(), LANCE_BYTES); 159 Fish lance = raw.get(KEY); 160 assertEquals(lance, new Fish(LANCE)); 161 // Reading again should return the same parsed instance 162 assertEquals(lance, raw.get(KEY)); 163 } 164 165 @Test 166 public void testSerializeRaw() { 167 Metadata raw = new Metadata(KEY.asciiName(), LANCE_BYTES); 168 byte[][] serialized = raw.serialize(); 169 assertArrayEquals(serialized[0], KEY.asciiName()); 170 assertArrayEquals(serialized[1], LANCE_BYTES); 171 } 172 173 @Test 174 public void testMergeByteConstructed() { 175 Metadata raw = new Metadata(KEY.asciiName(), LANCE_BYTES); 176 Metadata serializable = new Metadata(); 177 serializable.merge(raw); 178 179 byte[][] serialized = serializable.serialize(); 180 assertArrayEquals(serialized[0], KEY.asciiName()); 181 assertArrayEquals(serialized[1], LANCE_BYTES); 182 assertEquals(new Fish(LANCE), serializable.get(KEY)); 183 } 184 185 @Test 186 public void headerMergeShouldCopyValues() { 187 Fish lance = new Fish(LANCE); 188 Metadata h1 = new Metadata(); 189 190 Metadata h2 = new Metadata(); 191 h2.put(KEY, lance); 192 193 h1.merge(h2); 194 195 Iterator<Fish> fishes = h1.<Fish>getAll(KEY).iterator(); 196 assertTrue(fishes.hasNext()); 197 assertEquals(fishes.next(), lance); 198 assertFalse(fishes.hasNext()); 199 } 200 201 @Test 202 public void mergeExpands() { 203 Fish lance = new Fish(LANCE); 204 Metadata h1 = new Metadata(); 205 h1.put(KEY, lance); 206 207 Metadata h2 = new Metadata(); 208 h2.put(KEY, lance); 209 h2.put(KEY, lance); 210 h2.put(KEY, lance); 211 h2.put(KEY, lance); 212 213 h1.merge(h2); 214 } 215 216 @Test 217 public void shortBinaryKeyName() { 218 thrown.expect(IllegalArgumentException.class); 219 220 Metadata.Key.of("-bin", FISH_MARSHALLER); 221 } 222 223 @Test 224 public void invalidSuffixBinaryKeyName() { 225 thrown.expect(IllegalArgumentException.class); 226 thrown.expectMessage("Binary header is named"); 227 228 Metadata.Key.of("nonbinary", FISH_MARSHALLER); 229 } 230 231 @Test 232 public void verifyToString() { 233 Metadata h = new Metadata(); 234 h.put(KEY, new Fish("binary")); 235 h.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii"); 236 assertEquals("Metadata(test-bin=YmluYXJ5,test=ascii)", h.toString()); 237 238 Metadata t = new Metadata(); 239 t.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii"); 240 assertEquals("Metadata(test=ascii)", t.toString()); 241 242 t = new Metadata("test".getBytes(US_ASCII), "ascii".getBytes(US_ASCII), 243 "test-bin".getBytes(US_ASCII), "binary".getBytes(US_ASCII)); 244 assertEquals("Metadata(test=ascii,test-bin=YmluYXJ5)", t.toString()); 245 } 246 247 @Test 248 public void verifyToString_usingBinary() { 249 Metadata h = new Metadata(); 250 h.put(KEY, new Fish("binary")); 251 h.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii"); 252 assertEquals("Metadata(test-bin=YmluYXJ5,test=ascii)", h.toString()); 253 254 Metadata t = new Metadata(); 255 t.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii"); 256 assertEquals("Metadata(test=ascii)", t.toString()); 257 } 258 259 @Test 260 public void testKeyCaseHandling() { 261 Locale originalLocale = Locale.getDefault(); 262 Locale.setDefault(new Locale("tr", "TR")); 263 try { 264 // In Turkish, both I and i (which are in ASCII) change into non-ASCII characters when their 265 // case is changed as and , respectively. 266 assertEquals("", "i".toUpperCase()); 267 assertEquals("", "I".toLowerCase()); 268 269 Metadata.Key<String> keyTitleCase 270 = Metadata.Key.of("If-Modified-Since", Metadata.ASCII_STRING_MARSHALLER); 271 Metadata.Key<String> keyLowerCase 272 = Metadata.Key.of("if-modified-since", Metadata.ASCII_STRING_MARSHALLER); 273 Metadata.Key<String> keyUpperCase 274 = Metadata.Key.of("IF-MODIFIED-SINCE", Metadata.ASCII_STRING_MARSHALLER); 275 276 Metadata metadata = new Metadata(); 277 metadata.put(keyTitleCase, "plain string"); 278 assertEquals("plain string", metadata.get(keyTitleCase)); 279 assertEquals("plain string", metadata.get(keyLowerCase)); 280 assertEquals("plain string", metadata.get(keyUpperCase)); 281 282 byte[][] bytes = metadata.serialize(); 283 assertEquals(2, bytes.length); 284 assertArrayEquals("if-modified-since".getBytes(US_ASCII), bytes[0]); 285 assertArrayEquals("plain string".getBytes(US_ASCII), bytes[1]); 286 } finally { 287 Locale.setDefault(originalLocale); 288 } 289 } 290 291 @Test 292 public void removeIgnoresMissingValue() { 293 Metadata m = new Metadata(); 294 // Any key will work. 295 Key<String> key = GrpcUtil.USER_AGENT_KEY; 296 297 boolean success = m.remove(key, "agent"); 298 assertFalse(success); 299 } 300 301 @Test 302 public void removeAllIgnoresMissingValue() { 303 Metadata m = new Metadata(); 304 // Any key will work. 305 Key<String> key = GrpcUtil.USER_AGENT_KEY; 306 307 Iterable<String> removed = m.removeAll(key); 308 assertNull(removed); 309 } 310 311 @Test 312 public void keyEqualsHashNameWorks() { 313 Key<?> k1 = Key.of("case", Metadata.ASCII_STRING_MARSHALLER); 314 315 Key<?> k2 = Key.of("CASE", Metadata.ASCII_STRING_MARSHALLER); 316 assertEquals(k1, k1); 317 assertNotEquals(k1, null); 318 assertNotEquals(k1, new Object(){}); 319 assertEquals(k1, k2); 320 321 assertEquals(k1.hashCode(), k2.hashCode()); 322 // Check that the casing is preserved. 323 assertEquals("CASE", k2.originalName()); 324 assertEquals("case", k2.name()); 325 } 326 327 @Test 328 public void invalidKeyName() { 329 try { 330 Key.of("io.grpc/key1", Metadata.ASCII_STRING_MARSHALLER); 331 fail("Should have thrown"); 332 } catch (IllegalArgumentException e) { 333 assertEquals("Invalid character '/' in key name 'io.grpc/key1'", e.getMessage()); 334 } 335 } 336 337 private static class Fish { 338 private String name; 339 340 private Fish(String name) { 341 this.name = name; 342 } 343 344 @Override 345 public boolean equals(Object o) { 346 if (this == o) { 347 return true; 348 } 349 if (o == null || getClass() != o.getClass()) { 350 return false; 351 } 352 Fish fish = (Fish) o; 353 if (name != null ? !name.equals(fish.name) : fish.name != null) { 354 return false; 355 } 356 return true; 357 } 358 359 @Override 360 public int hashCode() { 361 return name.hashCode(); 362 } 363 364 @Override 365 public String toString() { 366 return "Fish(" + name + ")"; 367 } 368 } 369 } 370