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