1 /* 2 * Copyright (C) 2012 The Guava 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 com.google.common.reflect; 18 19 import static org.truth0.Truth.ASSERT; 20 21 import com.google.common.base.Charsets; 22 import com.google.common.collect.ImmutableMap; 23 import com.google.common.collect.Maps; 24 import com.google.common.collect.Sets; 25 import com.google.common.io.Closer; 26 import com.google.common.io.Resources; 27 import com.google.common.reflect.ClassPath.ClassInfo; 28 import com.google.common.reflect.ClassPath.ResourceInfo; 29 import com.google.common.reflect.subpackage.ClassInSubPackage; 30 import com.google.common.testing.EqualsTester; 31 import com.google.common.testing.NullPointerTester; 32 33 import junit.framework.TestCase; 34 import org.junit.Test; 35 36 import java.io.ByteArrayInputStream; 37 import java.io.File; 38 import java.io.FileOutputStream; 39 import java.io.IOException; 40 import java.io.InputStream; 41 import java.net.URI; 42 import java.net.URISyntaxException; 43 import java.net.URL; 44 import java.net.URLClassLoader; 45 import java.util.Map; 46 import java.util.Set; 47 import java.util.jar.Attributes; 48 import java.util.jar.JarFile; 49 import java.util.jar.JarOutputStream; 50 import java.util.jar.Manifest; 51 import java.util.zip.ZipEntry; 52 53 /** 54 * Functional tests of {@link ClassPath}. 55 */ 56 public class ClassPathTest extends TestCase { 57 testGetResources()58 public void testGetResources() throws Exception { 59 Map<String, ResourceInfo> byName = Maps.newHashMap(); 60 Map<String, ResourceInfo> byToString = Maps.newHashMap(); 61 ClassPath classpath = ClassPath.from(getClass().getClassLoader()); 62 for (ResourceInfo resource : classpath.getResources()) { 63 ASSERT.that(resource.getResourceName()).isNotEqualTo(JarFile.MANIFEST_NAME); 64 ASSERT.that(resource.toString()).isNotEqualTo(JarFile.MANIFEST_NAME); 65 byName.put(resource.getResourceName(), resource); 66 byToString.put(resource.toString(), resource); 67 // TODO: This will fail on maven resources in the classes directory on a mac. 68 // assertNotNull(resource.url()); 69 } 70 String testResourceName = "com/google/common/reflect/test.txt"; 71 ASSERT.that(byName.keySet()).has().allOf( 72 "com/google/common/reflect/ClassPath.class", 73 "com/google/common/reflect/ClassPathTest.class", 74 "com/google/common/reflect/ClassPathTest$Nested.class", 75 testResourceName); 76 ASSERT.that(byToString.keySet()).has().allOf( 77 "com.google.common.reflect.ClassPath", 78 "com.google.common.reflect.ClassPathTest", 79 "com.google.common.reflect.ClassPathTest$Nested", 80 testResourceName); 81 assertEquals(getClass().getClassLoader().getResource(testResourceName), 82 byName.get("com/google/common/reflect/test.txt").url()); 83 } 84 testGetAllClasses()85 public void testGetAllClasses() throws Exception { 86 Set<String> names = Sets.newHashSet(); 87 Set<String> strings = Sets.newHashSet(); 88 Set<Class<?>> classes = Sets.newHashSet(); 89 Set<String> packageNames = Sets.newHashSet(); 90 Set<String> simpleNames = Sets.newHashSet(); 91 ClassPath classpath = ClassPath.from(getClass().getClassLoader()); 92 for (ClassInfo classInfo : classpath.getAllClasses()) { 93 if (!classInfo.getPackageName().equals(ClassPathTest.class.getPackage().getName())) { 94 continue; 95 } 96 names.add(classInfo.getName()); 97 strings.add(classInfo.toString()); 98 classes.add(classInfo.load()); 99 packageNames.add(classInfo.getPackageName()); 100 simpleNames.add(classInfo.getSimpleName()); 101 } 102 class LocalClass {} 103 Class<?> anonymousClass = new Object() {}.getClass(); 104 ASSERT.that(names).has().allOf(anonymousClass.getName(), LocalClass.class.getName(), 105 ClassPath.class.getName(), ClassPathTest.class.getName()); 106 ASSERT.that(strings).has().allOf(anonymousClass.getName(), LocalClass.class.getName(), 107 ClassPath.class.getName(), ClassPathTest.class.getName()); 108 ASSERT.that(classes).has().allOf(anonymousClass, LocalClass.class, ClassPath.class, 109 ClassPathTest.class); 110 ASSERT.that(packageNames).has().exactly(ClassPath.class.getPackage().getName()); 111 ASSERT.that(simpleNames).has().allOf("", "Local", "ClassPath", "ClassPathTest"); 112 } 113 testGetTopLevelClasses()114 public void testGetTopLevelClasses() throws Exception { 115 Set<String> names = Sets.newHashSet(); 116 Set<String> strings = Sets.newHashSet(); 117 Set<Class<?>> classes = Sets.newHashSet(); 118 Set<String> packageNames = Sets.newHashSet(); 119 Set<String> simpleNames = Sets.newHashSet(); 120 ClassPath classpath = ClassPath.from(getClass().getClassLoader()); 121 for (ClassInfo classInfo 122 : classpath.getTopLevelClasses(ClassPathTest.class.getPackage().getName())) { 123 names.add(classInfo.getName()); 124 strings.add(classInfo.toString()); 125 classes.add(classInfo.load()); 126 packageNames.add(classInfo.getPackageName()); 127 simpleNames.add(classInfo.getSimpleName()); 128 } 129 ASSERT.that(names).has().allOf(ClassPath.class.getName(), ClassPathTest.class.getName()); 130 ASSERT.that(strings).has().allOf(ClassPath.class.getName(), ClassPathTest.class.getName()); 131 ASSERT.that(classes).has().allOf(ClassPath.class, ClassPathTest.class); 132 ASSERT.that(packageNames).has().item(ClassPath.class.getPackage().getName()); 133 ASSERT.that(simpleNames).has().allOf("ClassPath", "ClassPathTest"); 134 assertFalse(classes.contains(ClassInSubPackage.class)); 135 } 136 testGetTopLevelClassesRecursive()137 public void testGetTopLevelClassesRecursive() throws Exception { 138 Set<Class<?>> classes = Sets.newHashSet(); 139 ClassPath classpath = ClassPath.from(ClassPathTest.class.getClassLoader()); 140 for (ClassInfo classInfo 141 : classpath.getTopLevelClassesRecursive(ClassPathTest.class.getPackage().getName())) { 142 if (classInfo.getName().contains("ClassPathTest")) { 143 System.err.println(""); 144 } 145 classes.add(classInfo.load()); 146 } 147 ASSERT.that(classes).has().allOf(ClassPathTest.class, ClassInSubPackage.class); 148 } 149 testGetTopLevelClasses_diamond()150 public void testGetTopLevelClasses_diamond() throws Exception { 151 ClassLoader parent = ClassPathTest.class.getClassLoader(); 152 ClassLoader sub1 = new ClassLoader(parent) {}; 153 ClassLoader sub2 = new ClassLoader(parent) {}; 154 assertEquals(findClass(ClassPath.from(sub1).getTopLevelClasses(), ClassPathTest.class), 155 findClass(ClassPath.from(sub2).getTopLevelClasses(), ClassPathTest.class)); 156 } 157 testEquals()158 public void testEquals() { 159 new EqualsTester() 160 .addEqualityGroup(classInfo(ClassPathTest.class), classInfo(ClassPathTest.class)) 161 .addEqualityGroup(classInfo(Test.class), classInfo(Test.class, getClass().getClassLoader())) 162 .addEqualityGroup( 163 new ResourceInfo("a/b/c.txt", getClass().getClassLoader()), 164 new ResourceInfo("a/b/c.txt", getClass().getClassLoader())) 165 .addEqualityGroup( 166 new ResourceInfo("x.txt", getClass().getClassLoader())) 167 .testEquals(); 168 } 169 testClassPathEntries_emptyURLClassLoader_noParent()170 public void testClassPathEntries_emptyURLClassLoader_noParent() { 171 ASSERT.that(ClassPath.getClassPathEntries(new URLClassLoader(new URL[0], null)).keySet()) 172 .isEmpty(); 173 } 174 testClassPathEntries_URLClassLoader_noParent()175 public void testClassPathEntries_URLClassLoader_noParent() throws Exception { 176 URL url1 = new URL("file:/a"); 177 URL url2 = new URL("file:/b"); 178 URLClassLoader classloader = new URLClassLoader(new URL[] {url1, url2}, null); 179 assertEquals( 180 ImmutableMap.of(url1.toURI(), classloader, url2.toURI(), classloader), 181 ClassPath.getClassPathEntries(classloader)); 182 } 183 testClassPathEntries_URLClassLoader_withParent()184 public void testClassPathEntries_URLClassLoader_withParent() throws Exception { 185 URL url1 = new URL("file:/a"); 186 URL url2 = new URL("file:/b"); 187 URLClassLoader parent = new URLClassLoader(new URL[] {url1}, null); 188 URLClassLoader child = new URLClassLoader(new URL[] {url2}, parent) {}; 189 ImmutableMap<URI, ClassLoader> classPathEntries = ClassPath.getClassPathEntries(child); 190 assertEquals(ImmutableMap.of(url1.toURI(), parent, url2.toURI(), child), classPathEntries); 191 ASSERT.that(classPathEntries.keySet()).has().exactly(url1.toURI(), url2.toURI()).inOrder(); 192 } 193 testClassPathEntries_duplicateUri_parentWins()194 public void testClassPathEntries_duplicateUri_parentWins() throws Exception { 195 URL url = new URL("file:/a"); 196 URLClassLoader parent = new URLClassLoader(new URL[] {url}, null); 197 URLClassLoader child = new URLClassLoader(new URL[] {url}, parent) {}; 198 assertEquals(ImmutableMap.of(url.toURI(), parent), ClassPath.getClassPathEntries(child)); 199 } 200 testClassPathEntries_notURLClassLoader_noParent()201 public void testClassPathEntries_notURLClassLoader_noParent() { 202 ASSERT.that(ClassPath.getClassPathEntries(new ClassLoader(null) {}).keySet()).isEmpty(); 203 } 204 testClassPathEntries_notURLClassLoader_withParent()205 public void testClassPathEntries_notURLClassLoader_withParent() throws Exception { 206 URL url = new URL("file:/a"); 207 URLClassLoader parent = new URLClassLoader(new URL[] {url}, null); 208 assertEquals( 209 ImmutableMap.of(url.toURI(), parent), 210 ClassPath.getClassPathEntries(new ClassLoader(parent) {})); 211 } 212 testClassPathEntries_notURLClassLoader_withParentAndGrandParent()213 public void testClassPathEntries_notURLClassLoader_withParentAndGrandParent() throws Exception { 214 URL url1 = new URL("file:/a"); 215 URL url2 = new URL("file:/b"); 216 URLClassLoader grandParent = new URLClassLoader(new URL[] {url1}, null); 217 URLClassLoader parent = new URLClassLoader(new URL[] {url2}, grandParent); 218 assertEquals( 219 ImmutableMap.of(url1.toURI(), grandParent, url2.toURI(), parent), 220 ClassPath.getClassPathEntries(new ClassLoader(parent) {})); 221 } 222 testClassPathEntries_notURLClassLoader_withGrandParent()223 public void testClassPathEntries_notURLClassLoader_withGrandParent() throws Exception { 224 URL url = new URL("file:/a"); 225 URLClassLoader grandParent = new URLClassLoader(new URL[] {url}, null); 226 ClassLoader parent = new ClassLoader(grandParent) {}; 227 assertEquals( 228 ImmutableMap.of(url.toURI(), grandParent), 229 ClassPath.getClassPathEntries(new ClassLoader(parent) {})); 230 } 231 testScan_classPathCycle()232 public void testScan_classPathCycle() throws IOException { 233 File jarFile = File.createTempFile("with_circular_class_path", ".jar"); 234 try { 235 writeSelfReferencingJarFile(jarFile, "test.txt"); 236 ClassPath.Scanner scanner = new ClassPath.Scanner(); 237 scanner.scan(jarFile.toURI(), ClassPathTest.class.getClassLoader()); 238 assertEquals(1, scanner.getResources().size()); 239 } finally { 240 jarFile.delete(); 241 } 242 } 243 testScanFromFile_fileNotExists()244 public void testScanFromFile_fileNotExists() throws IOException { 245 ClassLoader classLoader = ClassPathTest.class.getClassLoader(); 246 ClassPath.Scanner scanner = new ClassPath.Scanner(); 247 scanner.scanFrom(new File("no/such/file/anywhere"), classLoader); 248 ASSERT.that(scanner.getResources()).isEmpty(); 249 } 250 testScanFromFile_notJarFile()251 public void testScanFromFile_notJarFile() throws IOException { 252 ClassLoader classLoader = ClassPathTest.class.getClassLoader(); 253 File notJar = File.createTempFile("not_a_jar", "txt"); 254 ClassPath.Scanner scanner = new ClassPath.Scanner(); 255 try { 256 scanner.scanFrom(notJar, classLoader); 257 } finally { 258 notJar.delete(); 259 } 260 ASSERT.that(scanner.getResources()).isEmpty(); 261 } 262 testGetClassPathEntry()263 public void testGetClassPathEntry() throws URISyntaxException { 264 assertEquals(URI.create("file:/usr/test/dep.jar"), 265 ClassPath.Scanner.getClassPathEntry( 266 new File("/home/build/outer.jar"), "file:/usr/test/dep.jar")); 267 assertEquals(URI.create("file:/home/build/a.jar"), 268 ClassPath.Scanner.getClassPathEntry(new File("/home/build/outer.jar"), "a.jar")); 269 assertEquals(URI.create("file:/home/build/x/y/z"), 270 ClassPath.Scanner.getClassPathEntry(new File("/home/build/outer.jar"), "x/y/z")); 271 assertEquals(URI.create("file:/home/build/x/y/z.jar"), 272 ClassPath.Scanner.getClassPathEntry(new File("/home/build/outer.jar"), "x/y/z.jar")); 273 } 274 testGetClassPathFromManifest_nullManifest()275 public void testGetClassPathFromManifest_nullManifest() { 276 ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(new File("some.jar"), null)).isEmpty(); 277 } 278 testGetClassPathFromManifest_noClassPath()279 public void testGetClassPathFromManifest_noClassPath() throws IOException { 280 File jarFile = new File("base.jar"); 281 ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest(""))) 282 .isEmpty(); 283 } 284 testGetClassPathFromManifest_emptyClassPath()285 public void testGetClassPathFromManifest_emptyClassPath() throws IOException { 286 File jarFile = new File("base.jar"); 287 ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifestClasspath(""))) 288 .isEmpty(); 289 } 290 testGetClassPathFromManifest_badClassPath()291 public void testGetClassPathFromManifest_badClassPath() throws IOException { 292 File jarFile = new File("base.jar"); 293 Manifest manifest = manifestClasspath("an_invalid^path"); 294 ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest)) 295 .isEmpty(); 296 } 297 testGetClassPathFromManifest_relativeDirectory()298 public void testGetClassPathFromManifest_relativeDirectory() throws IOException { 299 File jarFile = new File("base/some.jar"); 300 // with/relative/directory is the Class-Path value in the mf file. 301 Manifest manifest = manifestClasspath("with/relative/dir"); 302 ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest)) 303 .has().exactly(new File("base/with/relative/dir").toURI()).inOrder(); 304 } 305 testGetClassPathFromManifest_relativeJar()306 public void testGetClassPathFromManifest_relativeJar() throws IOException { 307 File jarFile = new File("base/some.jar"); 308 // with/relative/directory is the Class-Path value in the mf file. 309 Manifest manifest = manifestClasspath("with/relative.jar"); 310 ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest)) 311 .has().exactly(new File("base/with/relative.jar").toURI()).inOrder(); 312 } 313 testGetClassPathFromManifest_jarInCurrentDirectory()314 public void testGetClassPathFromManifest_jarInCurrentDirectory() throws IOException { 315 File jarFile = new File("base/some.jar"); 316 // with/relative/directory is the Class-Path value in the mf file. 317 Manifest manifest = manifestClasspath("current.jar"); 318 ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest)) 319 .has().exactly(new File("base/current.jar").toURI()).inOrder(); 320 } 321 testGetClassPathFromManifest_absoluteDirectory()322 public void testGetClassPathFromManifest_absoluteDirectory() throws IOException { 323 File jarFile = new File("base/some.jar"); 324 Manifest manifest = manifestClasspath("file:/with/absolute/dir"); 325 ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest)) 326 .has().exactly(new File("/with/absolute/dir").toURI()).inOrder(); 327 } 328 testGetClassPathFromManifest_absoluteJar()329 public void testGetClassPathFromManifest_absoluteJar() throws IOException { 330 File jarFile = new File("base/some.jar"); 331 Manifest manifest = manifestClasspath("file:/with/absolute.jar"); 332 ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest)) 333 .has().exactly(new File("/with/absolute.jar").toURI()).inOrder(); 334 } 335 testGetClassPathFromManifest_multiplePaths()336 public void testGetClassPathFromManifest_multiplePaths() throws IOException { 337 File jarFile = new File("base/some.jar"); 338 Manifest manifest = manifestClasspath("file:/with/absolute.jar relative.jar relative/dir"); 339 ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest)) 340 .has().exactly( 341 new File("/with/absolute.jar").toURI(), 342 new File("base/relative.jar").toURI(), 343 new File("base/relative/dir").toURI()) 344 .inOrder(); 345 } 346 testGetClassPathFromManifest_leadingBlanks()347 public void testGetClassPathFromManifest_leadingBlanks() throws IOException { 348 File jarFile = new File("base/some.jar"); 349 Manifest manifest = manifestClasspath(" relative.jar"); 350 ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest)) 351 .has().exactly(new File("base/relative.jar").toURI()).inOrder(); 352 } 353 testGetClassPathFromManifest_trailingBlanks()354 public void testGetClassPathFromManifest_trailingBlanks() throws IOException { 355 File jarFile = new File("base/some.jar"); 356 Manifest manifest = manifestClasspath("relative.jar "); 357 ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest)) 358 .has().exactly(new File("base/relative.jar").toURI()).inOrder(); 359 } 360 testGetClassName()361 public void testGetClassName() { 362 assertEquals("abc.d.Abc", ClassPath.getClassName("abc/d/Abc.class")); 363 } 364 testResourceInfo_of()365 public void testResourceInfo_of() { 366 assertEquals(ClassInfo.class, resourceInfo(ClassPathTest.class).getClass()); 367 assertEquals(ClassInfo.class, resourceInfo(ClassPath.class).getClass()); 368 assertEquals(ClassInfo.class, resourceInfo(Nested.class).getClass()); 369 } 370 testGetSimpleName()371 public void testGetSimpleName() { 372 assertEquals("Foo", 373 new ClassInfo("Foo.class", getClass().getClassLoader()).getSimpleName()); 374 assertEquals("Foo", 375 new ClassInfo("a/b/Foo.class", getClass().getClassLoader()).getSimpleName()); 376 assertEquals("Foo", 377 new ClassInfo("a/b/Bar$Foo.class", getClass().getClassLoader()).getSimpleName()); 378 assertEquals("", 379 new ClassInfo("a/b/Bar$1.class", getClass().getClassLoader()).getSimpleName()); 380 assertEquals("Foo", 381 new ClassInfo("a/b/Bar$Foo.class", getClass().getClassLoader()).getSimpleName()); 382 assertEquals("", 383 new ClassInfo("a/b/Bar$1.class", getClass().getClassLoader()).getSimpleName()); 384 assertEquals("Local", 385 new ClassInfo("a/b/Bar$1Local.class", getClass().getClassLoader()).getSimpleName()); 386 387 } 388 testGetPackageName()389 public void testGetPackageName() { 390 assertEquals("", 391 new ClassInfo("Foo.class", getClass().getClassLoader()).getPackageName()); 392 assertEquals("a.b", 393 new ClassInfo("a/b/Foo.class", getClass().getClassLoader()).getPackageName()); 394 } 395 396 private static class Nested {} 397 testNulls()398 public void testNulls() throws IOException { 399 new NullPointerTester().testAllPublicStaticMethods(ClassPath.class); 400 new NullPointerTester() 401 .testAllPublicInstanceMethods(ClassPath.from(getClass().getClassLoader())); 402 } 403 findClass( Iterable<ClassPath.ClassInfo> classes, Class<?> cls)404 private static ClassPath.ClassInfo findClass( 405 Iterable<ClassPath.ClassInfo> classes, Class<?> cls) { 406 for (ClassPath.ClassInfo classInfo : classes) { 407 if (classInfo.getName().equals(cls.getName())) { 408 return classInfo; 409 } 410 } 411 throw new AssertionError("failed to find " + cls); 412 } 413 resourceInfo(Class<?> cls)414 private static ResourceInfo resourceInfo(Class<?> cls) { 415 return ResourceInfo.of(cls.getName().replace('.', '/') + ".class", cls.getClassLoader()); 416 } 417 classInfo(Class<?> cls)418 private static ClassInfo classInfo(Class<?> cls) { 419 return classInfo(cls, cls.getClassLoader()); 420 } 421 classInfo(Class<?> cls, ClassLoader classLoader)422 private static ClassInfo classInfo(Class<?> cls, ClassLoader classLoader) { 423 return new ClassInfo(cls.getName().replace('.', '/') + ".class", classLoader); 424 } 425 manifestClasspath(String classpath)426 private static Manifest manifestClasspath(String classpath) throws IOException { 427 return manifest("Class-Path: " + classpath + "\n"); 428 } 429 writeSelfReferencingJarFile(File jarFile, String... entries)430 private static void writeSelfReferencingJarFile(File jarFile, String... entries) 431 throws IOException { 432 Manifest manifest = new Manifest(); 433 // Without version, the manifest is silently ignored. Ugh! 434 manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); 435 manifest.getMainAttributes().put(Attributes.Name.CLASS_PATH, jarFile.getName()); 436 437 Closer closer = Closer.create(); 438 try { 439 FileOutputStream fileOut = closer.register(new FileOutputStream(jarFile)); 440 JarOutputStream jarOut = closer.register(new JarOutputStream(fileOut)); 441 for (String entry : entries) { 442 jarOut.putNextEntry(new ZipEntry(entry)); 443 Resources.copy(ClassPathTest.class.getResource(entry), jarOut); 444 jarOut.closeEntry(); 445 } 446 } catch (Throwable e) { 447 throw closer.rethrow(e); 448 } finally { 449 closer.close(); 450 } 451 } 452 manifest(String content)453 private static Manifest manifest(String content) throws IOException { 454 InputStream in = new ByteArrayInputStream(content.getBytes(Charsets.US_ASCII.name())); 455 Manifest manifest = new Manifest(); 456 manifest.read(in); 457 return manifest; 458 } 459 } 460