1 /* 2 * Copyright (C) 2019 The Android Open Source Project 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 libcore.libcore.content.type; 18 19 import org.junit.After; 20 import org.junit.Before; 21 import org.junit.Test; 22 23 import java.util.Arrays; 24 import java.util.Collections; 25 import java.util.HashMap; 26 import java.util.HashSet; 27 import java.util.Locale; 28 import java.util.Map; 29 import java.util.Set; 30 import libcore.content.type.MimeMap; 31 32 import static org.junit.Assert.assertEquals; 33 import static org.junit.Assert.assertFalse; 34 import static org.junit.Assert.assertNotSame; 35 import static org.junit.Assert.assertNull; 36 import static org.junit.Assert.assertSame; 37 import static org.junit.Assert.assertTrue; 38 import static org.junit.Assert.fail; 39 40 /** 41 * Tests {@link MimeMap} and {@link MimeMap.Builder}. 42 */ 43 public class MimeMapTest { 44 45 private MimeMap mimeMap; 46 private MimeMap emptyMap; 47 setUp()48 @Before public void setUp() { 49 mimeMap = MimeMap.getDefault(); 50 emptyMap = MimeMap.builder().build(); 51 } 52 tearDown()53 @After public void tearDown() { 54 mimeMap = null; 55 } 56 lookup_invalidExtension()57 @Test public void lookup_invalidExtension() { 58 assertNull(mimeMap.guessMimeTypeFromExtension(null)); 59 assertNull(mimeMap.guessMimeTypeFromExtension("")); 60 assertFalse(mimeMap.hasExtension(null)); 61 assertFalse(mimeMap.hasExtension("")); 62 } 63 lookup_invalidMimeType()64 @Test public void lookup_invalidMimeType() { 65 assertNull(mimeMap.guessExtensionFromMimeType(null)); 66 assertNull(mimeMap.guessExtensionFromMimeType("")); 67 assertFalse(mimeMap.hasMimeType(null)); 68 assertFalse(mimeMap.hasMimeType("")); 69 } 70 caseNormalization_key()71 @Test public void caseNormalization_key() { 72 mimeMap = MimeMap.builder() 73 .addMimeMapping("application/msWord", Arrays.asList("Doc")) 74 .build(); 75 assertEquals("application/msword", mimeMap.guessMimeTypeFromExtension("dOc")); 76 assertEquals("doc", mimeMap.guessExtensionFromMimeType("appliCATion/mSWOrd")); 77 assertTrue(mimeMap.hasMimeType("application/msword")); 78 assertTrue(mimeMap.hasMimeType("Application/mSWord")); 79 80 assertTrue(mimeMap.hasExtension("doc")); 81 assertTrue(mimeMap.hasExtension("DOC")); 82 assertTrue(mimeMap.hasExtension("dOc")); 83 } 84 caseNormalization_value()85 @Test public void caseNormalization_value() { 86 // Default map 87 for (String extension : mimeMap.extensions()) { 88 assertLowerCase(mimeMap.guessMimeTypeFromExtension(extension)); 89 } 90 for (String mimeType : mimeMap.mimeTypes()) { 91 assertLowerCase(mimeMap.guessExtensionFromMimeType(mimeType)); 92 } 93 94 // Known keys for a custom map 95 mimeMap = MimeMap.builder() 96 .addMimeMapping("application/msWord", Arrays.asList("Doc")) 97 .build(); 98 assertEquals("doc", mimeMap.guessExtensionFromMimeType("Application/mSWord")); 99 assertEquals("application/msword", mimeMap.guessMimeTypeFromExtension("DoC")); 100 } 101 assertLowerCase(String s)102 private static void assertLowerCase(String s) { 103 assertEquals(s.toLowerCase(Locale.ROOT), s); 104 } 105 unmapped()106 @Test public void unmapped() { 107 mimeMap = MimeMap.builder() 108 .addMimeMapping("mime/test", Arrays.asList("test", "tst")) 109 .build(); 110 assertNull(mimeMap.guessExtensionFromMimeType("mime/unknown")); 111 assertFalse(mimeMap.hasMimeType("mime/unknown")); 112 113 assertNull(mimeMap.guessMimeTypeFromExtension("absent")); 114 assertFalse(mimeMap.hasExtension("absent")); 115 } 116 getDefault_returnsSameInstance()117 @Test public void getDefault_returnsSameInstance() { 118 assertSame(MimeMap.getDefault(), MimeMap.getDefault()); 119 } 120 getDefault_afterSetDefaultSupplier()121 @Test public void getDefault_afterSetDefaultSupplier() { 122 MimeMap originalDefault = MimeMap.getDefault(); 123 try { 124 // Constructs a new instance every time it is called 125 MimeMap.setDefaultSupplier(() -> MimeMap.builder().addMimeMapping("mime/sup", "sup").build()); 126 // Same instance is returned both times 127 assertSame(MimeMap.getDefault(), MimeMap.getDefault()); 128 // Check that the supplier is in effect 129 assertTrue(originalDefault != MimeMap.getDefault()); 130 assertEquals("mime/sup", MimeMap.getDefault().guessMimeTypeFromExtension("sup")); 131 } finally { 132 MimeMap.setDefaultSupplier(() -> originalDefault); 133 } 134 assertSame(originalDefault, MimeMap.getDefault()); 135 } 136 setDefaultSupplier_returningNull()137 @Test public void setDefaultSupplier_returningNull() { 138 MimeMap originalDefault = MimeMap.getDefault(); 139 try { 140 // A Supplier that returns null is invalid, but we only notice during getDefault(). 141 MimeMap.setDefaultSupplier(() -> null); 142 try { 143 MimeMap.getDefault(); 144 fail(); 145 } catch (NullPointerException expected) { 146 } 147 } finally { 148 MimeMap.setDefaultSupplier(() -> originalDefault); 149 } 150 } 151 buildUpon()152 @Test public void buildUpon() { 153 mimeMap = MimeMap.builder() 154 .build(); 155 assertMap( 156 makeMap(), 157 makeMap(), 158 mimeMap); 159 160 mimeMap = mimeMap.buildUpon() 161 .build(); 162 assertMap( 163 makeMap(), 164 makeMap(), 165 mimeMap); 166 167 mimeMap = mimeMap.buildUpon() 168 .addMimeMapping("text/plain", "txt") 169 .build(); 170 assertMap( 171 makeMap("text/plain", "txt"), 172 makeMap("txt", "text/plain"), 173 mimeMap); 174 175 mimeMap = mimeMap.buildUpon() 176 .addMimeMapping("audio/mpeg", Arrays.asList("mp2", "mp3")) 177 .build(); 178 assertMap( 179 makeMap("audio/mpeg", "mp2", 180 "text/plain", "txt"), 181 makeMap("mp2", "audio/mpeg", 182 "mp3", "audio/mpeg", 183 "txt", "text/plain"), 184 mimeMap); 185 186 mimeMap = mimeMap.buildUpon() 187 .addMimeMapping("text/plain", "text") 188 .build(); 189 assertMap( 190 makeMap("audio/mpeg", "mp2", 191 "text/plain", "text"), 192 makeMap("mp2", "audio/mpeg", 193 "mp3", "audio/mpeg", 194 "text", "text/plain", 195 "txt", "text/plain"), 196 mimeMap); 197 } 198 put()199 @Test public void put() { 200 MimeMap a = MimeMap.builder() 201 .addMimeMapping("text/plain", Arrays.asList("txt", "text")) 202 .addMimeMapping("application/msword", "doc") 203 .build(); 204 MimeMap b = MimeMap.builder() 205 .addMimeMapping("text/plain", Arrays.asList("txt", "text")) 206 .addMimeMapping("application/msword", "doc") 207 .build(); 208 assertEqualsButNotSame(a, b); 209 assertEqualsButNotSame(a, a.buildUpon().build()); 210 assertMap( 211 makeMap( 212 "text/plain", "txt", 213 "application/msword", "doc"), 214 makeMap("txt", "text/plain", 215 "text", "text/plain", 216 "doc", "application/msword"), 217 a); 218 } 219 put_noExtensions()220 @Test public void put_noExtensions() { 221 checkPut_noExtensions(emptyMap); 222 checkPut_noExtensions(MimeMap.builder().addMimeMapping("text/plain", "txt").build()); 223 checkPut_noExtensions(mimeMap); 224 } 225 226 /** 227 * Checks that put(String, emptyList()) doesn't change or add any mappings. 228 */ checkPut_noExtensions(MimeMap baseMap)229 private static void checkPut_noExtensions(MimeMap baseMap) { 230 MimeMap mimeMap = baseMap.buildUpon() 231 .addMimeMapping("mime/type", Collections.emptyList()) 232 .build(); 233 assertEquals(baseMap, mimeMap); 234 } 235 put_String_List_nullOrEmpty()236 @Test public void put_String_List_nullOrEmpty() { 237 // We still check mimeType for validity even if no extensions are specified 238 assertPutThrowsNpe(null); 239 assertPutThrowsIae(""); 240 241 // null or "" are not allowed for either MIME type or extension 242 assertPutThrowsNpe(null, "ext"); 243 assertPutThrowsIae("", "ext"); 244 assertPutThrowsNpe("mime/type", (String) null); 245 assertPutThrowsIae("mime/type", ""); 246 247 assertPutThrowsNpe("mime/type", "ext", null); 248 assertPutThrowsIae("mime/type", "ext", ""); 249 } 250 put_String_String_nullOrEmpty()251 @Test public void put_String_String_nullOrEmpty() { 252 assertThrowsNpe(() -> MimeMap.builder().addMimeMapping(null, "ext")); 253 assertThrowsIae(() -> MimeMap.builder().addMimeMapping("", "ext")); 254 255 assertThrowsNpe(() -> MimeMap.builder().addMimeMapping("mime/type", (String) null)); 256 assertThrowsIae(() -> MimeMap.builder().addMimeMapping("mime/type", "")); 257 } 258 259 /** 260 * Tests put() arguments that have a prefix {@code "?"} which leads to putIfAbsent semantics. 261 */ putIfAbsent()262 @Test public void putIfAbsent() { 263 // Starting from an empty mapping, add a bunch more, some with and some without '?'. 264 mimeMap = MimeMap.builder() 265 .addMimeMapping("?text/plain", "?txt") 266 .addMimeMapping("audio/mpeg", Arrays.asList("mpga", "mpega", "?mp2", "mp3")) 267 .build(); 268 assertEquals("txt", mimeMap.guessExtensionFromMimeType("text/plain")); 269 assertEquals("text/plain", mimeMap.guessMimeTypeFromExtension("txt")); 270 assertEquals("mpga", mimeMap.guessExtensionFromMimeType("audio/mpeg")); 271 assertEquals("audio/mpeg", mimeMap.guessMimeTypeFromExtension("mp2")); 272 assertEquals("audio/mpeg", mimeMap.guessMimeTypeFromExtension("mp3")); 273 274 // Override a ext -> MIME mapping without overriding the MIME -> ext mapping. 275 mimeMap = mimeMap.buildUpon() 276 .addMimeMapping("?audio/mpeg", "m4a") 277 .build(); 278 assertEquals("mpga", mimeMap.guessExtensionFromMimeType("audio/mpeg")); 279 assertEquals("audio/mpeg", mimeMap.guessMimeTypeFromExtension("m4a")); 280 281 // Override a MIME -> ext mapping without overriding the ext -> MIME mapping. 282 mimeMap = mimeMap.buildUpon() 283 .addMimeMapping("audio/mpeg", "?txt") 284 .build(); 285 assertEquals("txt", mimeMap.guessExtensionFromMimeType("audio/mpeg")); 286 assertEquals("text/plain", mimeMap.guessMimeTypeFromExtension("txt")); 287 288 289 // Check final state 290 assertMap( 291 makeMap( 292 "text/plain", "txt", 293 "audio/mpeg", "txt" 294 ), 295 makeMap( 296 "txt", "text/plain", 297 "m4a", "audio/mpeg", 298 "mp2", "audio/mpeg", 299 "mp3", "audio/mpeg", 300 "mpega", "audio/mpeg", 301 "mpga", "audio/mpeg" 302 ), 303 mimeMap 304 ); 305 } 306 extensions()307 @Test public void extensions() { 308 assertEquals(Collections.emptySet(), emptyMap.extensions()); 309 mimeMap = MimeMap.builder() 310 .addMimeMapping("text/plain", Arrays.asList("txt", "text")) 311 .addMimeMapping("audi/mpeg", "m4a") 312 .addMimeMapping("application/msword", "doc") 313 .addMimeMapping("text/plain", "tx") 314 .build(); 315 Set<String> extensions = new HashSet<>(Arrays.asList( 316 "txt", "text", "m4a", "doc", "tx")); 317 assertEquals(extensions, mimeMap.extensions()); 318 // Check that the extensions() view is unmodifiable 319 try { 320 mimeMap.extensions().add("ext"); 321 fail(); 322 } catch (UnsupportedOperationException expected) { 323 } 324 } 325 mimeTypes()326 @Test public void mimeTypes() { 327 assertEquals(Collections.emptySet(), emptyMap.mimeTypes()); 328 mimeMap = MimeMap.builder() 329 .addMimeMapping("text/plain", Arrays.asList("txt", "text")) 330 .addMimeMapping("audio/mpeg", "m4a") 331 .addMimeMapping("application/msword", "doc") 332 .addMimeMapping("text/plain", "tx") 333 .build(); 334 Set<String> mimeTypes = new HashSet<>(Arrays.asList( 335 "text/plain", 336 "audio/mpeg", 337 "application/msword")); 338 assertEquals(mimeTypes, mimeMap.mimeTypes()); 339 // Check that the mimeTypes() view is unmodifiable 340 try { 341 mimeMap.mimeTypes().add("foo/bar"); 342 fail(); 343 } catch (UnsupportedOperationException expected) { 344 } 345 } 346 347 /** 348 * Tests invalid put() invocations that have '?' in additional/invalid places. 349 */ put_invalid_additionalQuestionMarks()350 @Test public void put_invalid_additionalQuestionMarks() { 351 // Potentially we could tolerate additional ? as a prefix in future, but right now we don't. 352 assertPutThrowsIae("??text/plain", "txt"); 353 assertPutThrowsIae("text/p?lain", "txt"); 354 assertPutThrowsIae("text/plain", "txt", "t?ext"); 355 assertPutThrowsIae("text/plain", "??txt"); 356 assertPutThrowsIae("text/plain", "t?xt"); 357 } 358 359 /** Checks that MIME types must have a '/', while extensions must not. */ put_invalid_slash()360 @Test public void put_invalid_slash() { 361 assertPutThrowsIae("mime/type", "invalid/ext"); 362 assertPutThrowsIae("invalidmime", "ext"); 363 364 // During lookups, wrong arguments return null rather than throwing. 365 mimeMap = MimeMap.builder().addMimeMapping("mime/type", "ext").build(); 366 assertNull(mimeMap.guessExtensionFromMimeType("ext")); // ext is no mime type 367 assertNull(mimeMap.guessMimeTypeFromExtension("mime/type")); // mime/type is no extension 368 } 369 assertPutThrowsNpe(String mime, String... exts)370 private static void assertPutThrowsNpe(String mime, String... exts) { 371 assertThrowsNpe(() -> MimeMap.builder().addMimeMapping(mime, Arrays.asList(exts))); 372 } 373 assertPutThrowsIae(final String mime, final String... exts)374 private static void assertPutThrowsIae(final String mime, final String... exts) { 375 assertThrowsIae(() -> MimeMap.builder().addMimeMapping(mime, Arrays.asList(exts))); 376 } 377 assertThrowsNpe(Runnable runnable)378 private static void assertThrowsNpe(Runnable runnable) { 379 try { 380 runnable.run(); 381 fail(); 382 } catch (NullPointerException expected) { 383 } 384 } 385 assertThrowsIae(Runnable runnable)386 private static void assertThrowsIae(Runnable runnable) { 387 try { 388 runnable.run(); 389 fail(); 390 } catch (IllegalArgumentException expected) { 391 } 392 } 393 hashCodeValue()394 @Test public void hashCodeValue() { 395 assertEquals(0, emptyMap.hashCode()); 396 MimeMap a = MimeMap.builder().addMimeMapping("mime/test", "test").build(); 397 MimeMap b = a.buildUpon().addMimeMapping("foo/bar", "baz").build(); 398 assertTrue(0 != a.hashCode()); 399 assertTrue((a.hashCode() != b.hashCode())); 400 } 401 empty_copies()402 @Test public void empty_copies() { 403 assertEqualsButNotSame(emptyMap, MimeMap.builder().build()); 404 assertEqualsButNotSame(emptyMap, emptyMap.buildUpon().build()); 405 } 406 407 /** Creates a map from alternating key/value arguments, useful for test assertions. */ makeMap(String... keysAndValues)408 private static Map<String, String> makeMap(String... keysAndValues) { 409 if (keysAndValues.length % 2 != 0) { 410 throw new IllegalArgumentException( 411 "Invalid length " + keysAndValues.length + ": " + keysAndValues); 412 } 413 Map<String, String> result = new HashMap<>(); 414 for (int i = 0; i < keysAndValues.length; i += 2) { 415 String key = keysAndValues[i]; 416 String value = keysAndValues[i + 1]; 417 result.put(key, value); 418 } 419 return result; 420 421 } 422 423 /** 424 * Asserts that the given {@code MimeMap} has exactly the given mime -> ext and ext -> mime 425 * mappings, but no others. 426 */ assertMap( Map<String, String> expectedMimeToExt, Map<String, String> expectedExtToMime, MimeMap mimeMap)427 private static<T> void assertMap( 428 Map<String, String> expectedMimeToExt, 429 Map<String, String> expectedExtToMime, 430 MimeMap mimeMap) 431 { 432 MimeMap.Builder expectedBuilder = MimeMap.builder(); 433 for (Map.Entry<String, String> entry : expectedExtToMime.entrySet()) { 434 String ext = entry.getKey(); 435 String mime = entry.getValue(); 436 assertEquals(ext + ": " + mimeMap, mime, mimeMap.guessMimeTypeFromExtension(ext)); 437 expectedBuilder.addMimeMapping("?" + mime, ext); 438 } 439 for (Map.Entry<String, String> entry : expectedMimeToExt.entrySet()) { 440 String mime = entry.getKey(); 441 String ext = entry.getValue(); 442 assertEquals(mime + ": " + mimeMap, ext, mimeMap.guessExtensionFromMimeType(mime)); 443 expectedBuilder.addMimeMapping(mime, "?" + ext); 444 } 445 // Check that there are no unexpected additional mappings. 446 assertEqualsButNotSame(expectedBuilder.build(), mimeMap); 447 } 448 assertEqualsButNotSame(T a, T b)449 private static<T> void assertEqualsButNotSame(T a, T b) { 450 assertEquals(a, b); 451 assertEquals(b, a); 452 assertNotSame(a, b); 453 assertEquals(a.hashCode(), b.hashCode()); 454 } 455 456 } 457