• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.google.common.reflect;
17 
18 import static com.google.common.base.Charsets.US_ASCII;
19 import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_PATH;
20 import static com.google.common.base.StandardSystemProperty.OS_NAME;
21 import static com.google.common.base.StandardSystemProperty.PATH_SEPARATOR;
22 import static com.google.common.io.MoreFiles.deleteRecursively;
23 import static com.google.common.truth.Truth.assertThat;
24 import static java.nio.file.Files.createDirectory;
25 import static java.nio.file.Files.createFile;
26 import static java.nio.file.Files.createSymbolicLink;
27 import static java.nio.file.Files.createTempDirectory;
28 import static java.util.logging.Level.WARNING;
29 
30 import com.google.common.base.Joiner;
31 import com.google.common.collect.ImmutableList;
32 import com.google.common.collect.ImmutableSet;
33 import com.google.common.io.Closer;
34 import com.google.common.io.Files;
35 import com.google.common.io.Resources;
36 import com.google.common.reflect.ClassPath.ClassInfo;
37 import com.google.common.reflect.ClassPath.ResourceInfo;
38 import com.google.common.testing.EqualsTester;
39 import com.google.common.testing.NullPointerTester;
40 import java.io.ByteArrayInputStream;
41 import java.io.File;
42 import java.io.FileOutputStream;
43 import java.io.FilePermission;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.net.MalformedURLException;
47 import java.net.URISyntaxException;
48 import java.net.URL;
49 import java.net.URLClassLoader;
50 import java.security.Permission;
51 import java.security.PermissionCollection;
52 import java.util.jar.Attributes;
53 import java.util.jar.JarOutputStream;
54 import java.util.jar.Manifest;
55 import java.util.logging.Logger;
56 import java.util.zip.ZipEntry;
57 import junit.framework.TestCase;
58 import org.junit.Test;
59 
60 /** Functional tests of {@link ClassPath}. */
61 public class ClassPathTest extends TestCase {
62   private static final Logger log = Logger.getLogger(ClassPathTest.class.getName());
63   private static final File FILE = new File(".");
64 
testEquals()65   public void testEquals() {
66     new EqualsTester()
67         .addEqualityGroup(classInfo(ClassPathTest.class), classInfo(ClassPathTest.class))
68         .addEqualityGroup(classInfo(Test.class), classInfo(Test.class, getClass().getClassLoader()))
69         .addEqualityGroup(
70             new ResourceInfo(FILE, "a/b/c.txt", getClass().getClassLoader()),
71             new ResourceInfo(FILE, "a/b/c.txt", getClass().getClassLoader()))
72         .addEqualityGroup(new ResourceInfo(FILE, "x.txt", getClass().getClassLoader()))
73         .testEquals();
74   }
75 
76   @AndroidIncompatible // Android forbids null parent ClassLoader
testClassPathEntries_emptyURLClassLoader_noParent()77   public void testClassPathEntries_emptyURLClassLoader_noParent() {
78     assertThat(ClassPath.getClassPathEntries(new URLClassLoader(new URL[0], null)).keySet())
79         .isEmpty();
80   }
81 
82   @AndroidIncompatible // Android forbids null parent ClassLoader
testClassPathEntries_URLClassLoader_noParent()83   public void testClassPathEntries_URLClassLoader_noParent() throws Exception {
84     URL url1 = new URL("file:/a");
85     URL url2 = new URL("file:/b");
86     URLClassLoader classloader = new URLClassLoader(new URL[] {url1, url2}, null);
87     assertThat(ClassPath.getClassPathEntries(classloader))
88         .containsExactly(new File("/a"), classloader, new File("/b"), classloader);
89   }
90 
91   @AndroidIncompatible // Android forbids null parent ClassLoader
testClassPathEntries_URLClassLoader_withParent()92   public void testClassPathEntries_URLClassLoader_withParent() throws Exception {
93     URL url1 = new URL("file:/a");
94     URL url2 = new URL("file:/b");
95     URLClassLoader parent = new URLClassLoader(new URL[] {url1}, null);
96     URLClassLoader child = new URLClassLoader(new URL[] {url2}, parent) {};
97     assertThat(ClassPath.getClassPathEntries(child))
98         .containsExactly(new File("/a"), parent, new File("/b"), child)
99         .inOrder();
100   }
101 
102   @AndroidIncompatible // Android forbids null parent ClassLoader
testClassPathEntries_duplicateUri_parentWins()103   public void testClassPathEntries_duplicateUri_parentWins() throws Exception {
104     URL url = new URL("file:/a");
105     URLClassLoader parent = new URLClassLoader(new URL[] {url}, null);
106     URLClassLoader child = new URLClassLoader(new URL[] {url}, parent) {};
107     assertThat(ClassPath.getClassPathEntries(child)).containsExactly(new File("/a"), parent);
108   }
109 
110   @AndroidIncompatible // Android forbids null parent ClassLoader
testClassPathEntries_notURLClassLoader_noParent()111   public void testClassPathEntries_notURLClassLoader_noParent() {
112     assertThat(ClassPath.getClassPathEntries(new ClassLoader(null) {})).isEmpty();
113   }
114 
115   @AndroidIncompatible // Android forbids null parent ClassLoader
testClassPathEntries_notURLClassLoader_withParent()116   public void testClassPathEntries_notURLClassLoader_withParent() throws Exception {
117     URL url = new URL("file:/a");
118     URLClassLoader parent = new URLClassLoader(new URL[] {url}, null);
119     assertThat(ClassPath.getClassPathEntries(new ClassLoader(parent) {}))
120         .containsExactly(new File("/a"), parent);
121   }
122 
123   @AndroidIncompatible // Android forbids null parent ClassLoader
testClassPathEntries_notURLClassLoader_withParentAndGrandParent()124   public void testClassPathEntries_notURLClassLoader_withParentAndGrandParent() throws Exception {
125     URL url1 = new URL("file:/a");
126     URL url2 = new URL("file:/b");
127     URLClassLoader grandParent = new URLClassLoader(new URL[] {url1}, null);
128     URLClassLoader parent = new URLClassLoader(new URL[] {url2}, grandParent);
129     assertThat(ClassPath.getClassPathEntries(new ClassLoader(parent) {}))
130         .containsExactly(new File("/a"), grandParent, new File("/b"), parent);
131   }
132 
133   @AndroidIncompatible // Android forbids null parent ClassLoader
testClassPathEntries_notURLClassLoader_withGrandParent()134   public void testClassPathEntries_notURLClassLoader_withGrandParent() throws Exception {
135     URL url = new URL("file:/a");
136     URLClassLoader grandParent = new URLClassLoader(new URL[] {url}, null);
137     ClassLoader parent = new ClassLoader(grandParent) {};
138     assertThat(ClassPath.getClassPathEntries(new ClassLoader(parent) {}))
139         .containsExactly(new File("/a"), grandParent);
140   }
141 
142   @AndroidIncompatible // Android forbids null parent ClassLoader
143   // https://github.com/google/guava/issues/2152
testClassPathEntries_URLClassLoader_pathWithSpace()144   public void testClassPathEntries_URLClassLoader_pathWithSpace() throws Exception {
145     URL url = new URL("file:///c:/Documents and Settings/");
146     URLClassLoader classloader = new URLClassLoader(new URL[] {url}, null);
147     assertThat(ClassPath.getClassPathEntries(classloader))
148         .containsExactly(new File("/c:/Documents and Settings/"), classloader);
149   }
150 
151   @AndroidIncompatible // Android forbids null parent ClassLoader
152   // https://github.com/google/guava/issues/2152
testClassPathEntries_URLClassLoader_pathWithEscapedSpace()153   public void testClassPathEntries_URLClassLoader_pathWithEscapedSpace() throws Exception {
154     URL url = new URL("file:///c:/Documents%20and%20Settings/");
155     URLClassLoader classloader = new URLClassLoader(new URL[] {url}, null);
156     assertThat(ClassPath.getClassPathEntries(classloader))
157         .containsExactly(new File("/c:/Documents and Settings/"), classloader);
158   }
159 
160   // https://github.com/google/guava/issues/2152
testToFile()161   public void testToFile() throws Exception {
162     assertThat(ClassPath.toFile(new URL("file:///c:/Documents%20and%20Settings/")))
163         .isEqualTo(new File("/c:/Documents and Settings/"));
164     assertThat(ClassPath.toFile(new URL("file:///c:/Documents ~ Settings, or not/11-12 12:05")))
165         .isEqualTo(new File("/c:/Documents ~ Settings, or not/11-12 12:05"));
166   }
167 
168   // https://github.com/google/guava/issues/2152
169   @AndroidIncompatible // works in newer Android versions but fails at the version we test with
testToFile_AndroidIncompatible()170   public void testToFile_AndroidIncompatible() throws Exception {
171     assertThat(ClassPath.toFile(new URL("file:///c:\\Documents ~ Settings, or not\\11-12 12:05")))
172         .isEqualTo(new File("/c:\\Documents ~ Settings, or not\\11-12 12:05"));
173     assertThat(ClassPath.toFile(new URL("file:///C:\\Program Files\\Apache Software Foundation")))
174         .isEqualTo(new File("/C:\\Program Files\\Apache Software Foundation/"));
175     assertThat(ClassPath.toFile(new URL("file:///C:\\\u20320 \u22909"))) // Chinese Ni Hao
176         .isEqualTo(new File("/C:\\\u20320 \u22909"));
177   }
178 
179 
180   @AndroidIncompatible // Android forbids null parent ClassLoader
181   // https://github.com/google/guava/issues/2152
testJarFileWithSpaces()182   public void testJarFileWithSpaces() throws Exception {
183     URL url = makeJarUrlWithName("To test unescaped spaces in jar file name.jar");
184     URLClassLoader classloader = new URLClassLoader(new URL[] {url}, null);
185     assertThat(ClassPath.from(classloader).getTopLevelClasses()).isNotEmpty();
186   }
187 
188   @AndroidIncompatible // ClassPath is documented as not supporting Android
189 
testScan_classPathCycle()190   public void testScan_classPathCycle() throws IOException {
191     File jarFile = File.createTempFile("with_circular_class_path", ".jar");
192     try {
193       writeSelfReferencingJarFile(jarFile, "test.txt");
194       assertThat(
195               new ClassPath.LocationInfo(jarFile, ClassPathTest.class.getClassLoader())
196                   .scanResources())
197           .hasSize(1);
198     } finally {
199       jarFile.delete();
200     }
201   }
202 
203   @AndroidIncompatible // Path (for symlink creation)
204 
testScanDirectory_symlinkCycle()205   public void testScanDirectory_symlinkCycle() throws IOException {
206     if (isWindows()) {
207       return; // TODO: b/136041958 - Can we detect cycles under Windows?
208     }
209     ClassLoader loader = ClassPathTest.class.getClassLoader();
210     // directory with a cycle,
211     // /root
212     //    /left
213     //       /[sibling -> right]
214     //    /right
215     //       /[sibling -> left]
216     java.nio.file.Path root = createTempDirectory("ClassPathTest");
217     try {
218       java.nio.file.Path left = createDirectory(root.resolve("left"));
219       createFile(left.resolve("some.txt"));
220 
221       java.nio.file.Path right = createDirectory(root.resolve("right"));
222       createFile(right.resolve("another.txt"));
223 
224       createSymbolicLink(left.resolve("sibling"), right);
225       createSymbolicLink(right.resolve("sibling"), left);
226       assertEquals(
227           ImmutableSet.of(
228               new ResourceInfo(FILE, "left/some.txt", loader),
229               new ResourceInfo(FILE, "left/sibling/another.txt", loader),
230               new ResourceInfo(FILE, "right/another.txt", loader),
231               new ResourceInfo(FILE, "right/sibling/some.txt", loader)),
232           new ClassPath.LocationInfo(root.toFile(), loader).scanResources());
233     } finally {
234       deleteRecursivelyOrLog(root);
235     }
236   }
237 
238   @AndroidIncompatible // Path (for symlink creation)
239 
testScanDirectory_symlinkToRootCycle()240   public void testScanDirectory_symlinkToRootCycle() throws IOException {
241     if (isWindows()) {
242       return; // TODO: b/136041958 - Can we detect cycles under Windows?
243     }
244     ClassLoader loader = ClassPathTest.class.getClassLoader();
245     // directory with a cycle,
246     // /root
247     //    /child
248     //       /[grandchild -> root]
249     java.nio.file.Path root = createTempDirectory("ClassPathTest");
250     try {
251       createFile(root.resolve("some.txt"));
252       java.nio.file.Path child = createDirectory(root.resolve("child"));
253       createSymbolicLink(child.resolve("grandchild"), root);
254       assertEquals(
255           ImmutableSet.of(new ResourceInfo(FILE, "some.txt", loader)),
256           new ClassPath.LocationInfo(root.toFile(), loader).scanResources());
257     } finally {
258       deleteRecursivelyOrLog(root);
259     }
260   }
261 
262 
testScanFromFile_fileNotExists()263   public void testScanFromFile_fileNotExists() throws IOException {
264     ClassLoader classLoader = ClassPathTest.class.getClassLoader();
265     assertThat(
266             new ClassPath.LocationInfo(new File("no/such/file/anywhere"), classLoader)
267                 .scanResources())
268         .isEmpty();
269   }
270 
271   @AndroidIncompatible // ClassPath is documented as not supporting Android
272 
testScanFromFile_notJarFile()273   public void testScanFromFile_notJarFile() throws IOException {
274     ClassLoader classLoader = ClassPathTest.class.getClassLoader();
275     File notJar = File.createTempFile("not_a_jar", "txt");
276     try {
277       assertThat(new ClassPath.LocationInfo(notJar, classLoader).scanResources()).isEmpty();
278     } finally {
279       notJar.delete();
280     }
281   }
282 
testGetClassPathEntry()283   public void testGetClassPathEntry() throws MalformedURLException, URISyntaxException {
284     if (isWindows()) {
285       return; // TODO: b/136041958 - We need to account for drive letters in the path.
286     }
287     assertEquals(
288         new File("/usr/test/dep.jar").toURI(),
289         ClassPath.getClassPathEntry(new File("/home/build/outer.jar"), "file:/usr/test/dep.jar")
290             .toURI());
291     assertEquals(
292         new File("/home/build/a.jar").toURI(),
293         ClassPath.getClassPathEntry(new File("/home/build/outer.jar"), "a.jar").toURI());
294     assertEquals(
295         new File("/home/build/x/y/z").toURI(),
296         ClassPath.getClassPathEntry(new File("/home/build/outer.jar"), "x/y/z").toURI());
297     assertEquals(
298         new File("/home/build/x/y/z.jar").toURI(),
299         ClassPath.getClassPathEntry(new File("/home/build/outer.jar"), "x/y/z.jar").toURI());
300     assertEquals(
301         "/home/build/x y.jar",
302         ClassPath.getClassPathEntry(new File("/home/build/outer.jar"), "x y.jar").getFile());
303   }
304 
testGetClassPathFromManifest_nullManifest()305   public void testGetClassPathFromManifest_nullManifest() {
306     assertThat(ClassPath.getClassPathFromManifest(new File("some.jar"), null)).isEmpty();
307   }
308 
testGetClassPathFromManifest_noClassPath()309   public void testGetClassPathFromManifest_noClassPath() throws IOException {
310     File jarFile = new File("base.jar");
311     assertThat(ClassPath.getClassPathFromManifest(jarFile, manifest(""))).isEmpty();
312   }
313 
testGetClassPathFromManifest_emptyClassPath()314   public void testGetClassPathFromManifest_emptyClassPath() throws IOException {
315     File jarFile = new File("base.jar");
316     assertThat(ClassPath.getClassPathFromManifest(jarFile, manifestClasspath(""))).isEmpty();
317   }
318 
testGetClassPathFromManifest_badClassPath()319   public void testGetClassPathFromManifest_badClassPath() throws IOException {
320     File jarFile = new File("base.jar");
321     Manifest manifest = manifestClasspath("nosuchscheme:an_invalid^path");
322     assertThat(ClassPath.getClassPathFromManifest(jarFile, manifest)).isEmpty();
323   }
324 
testGetClassPathFromManifest_pathWithStrangeCharacter()325   public void testGetClassPathFromManifest_pathWithStrangeCharacter() throws IOException {
326     File jarFile = new File("base/some.jar");
327     Manifest manifest = manifestClasspath("file:the^file.jar");
328     assertThat(ClassPath.getClassPathFromManifest(jarFile, manifest))
329         .containsExactly(fullpath("base/the^file.jar"));
330   }
331 
testGetClassPathFromManifest_relativeDirectory()332   public void testGetClassPathFromManifest_relativeDirectory() throws IOException {
333     File jarFile = new File("base/some.jar");
334     // with/relative/directory is the Class-Path value in the mf file.
335     Manifest manifest = manifestClasspath("with/relative/dir");
336     assertThat(ClassPath.getClassPathFromManifest(jarFile, manifest))
337         .containsExactly(fullpath("base/with/relative/dir"));
338   }
339 
testGetClassPathFromManifest_relativeJar()340   public void testGetClassPathFromManifest_relativeJar() throws IOException {
341     File jarFile = new File("base/some.jar");
342     // with/relative/directory is the Class-Path value in the mf file.
343     Manifest manifest = manifestClasspath("with/relative.jar");
344     assertThat(ClassPath.getClassPathFromManifest(jarFile, manifest))
345         .containsExactly(fullpath("base/with/relative.jar"));
346   }
347 
testGetClassPathFromManifest_jarInCurrentDirectory()348   public void testGetClassPathFromManifest_jarInCurrentDirectory() throws IOException {
349     File jarFile = new File("base/some.jar");
350     // with/relative/directory is the Class-Path value in the mf file.
351     Manifest manifest = manifestClasspath("current.jar");
352     assertThat(ClassPath.getClassPathFromManifest(jarFile, manifest))
353         .containsExactly(fullpath("base/current.jar"));
354   }
355 
testGetClassPathFromManifest_absoluteDirectory()356   public void testGetClassPathFromManifest_absoluteDirectory() throws IOException {
357     if (isWindows()) {
358       return; // TODO: b/136041958 - We need to account for drive letters in the path.
359     }
360     File jarFile = new File("base/some.jar");
361     Manifest manifest = manifestClasspath("file:/with/absolute/dir");
362     assertThat(ClassPath.getClassPathFromManifest(jarFile, manifest))
363         .containsExactly(fullpath("/with/absolute/dir"));
364   }
365 
testGetClassPathFromManifest_absoluteJar()366   public void testGetClassPathFromManifest_absoluteJar() throws IOException {
367     if (isWindows()) {
368       return; // TODO: b/136041958 - We need to account for drive letters in the path.
369     }
370     File jarFile = new File("base/some.jar");
371     Manifest manifest = manifestClasspath("file:/with/absolute.jar");
372     assertThat(ClassPath.getClassPathFromManifest(jarFile, manifest))
373         .containsExactly(fullpath("/with/absolute.jar"));
374   }
375 
testGetClassPathFromManifest_multiplePaths()376   public void testGetClassPathFromManifest_multiplePaths() throws IOException {
377     if (isWindows()) {
378       return; // TODO: b/136041958 - We need to account for drive letters in the path.
379     }
380     File jarFile = new File("base/some.jar");
381     Manifest manifest = manifestClasspath("file:/with/absolute.jar relative.jar  relative/dir");
382     assertThat(ClassPath.getClassPathFromManifest(jarFile, manifest))
383         .containsExactly(
384             fullpath("/with/absolute.jar"),
385             fullpath("base/relative.jar"),
386             fullpath("base/relative/dir"))
387         .inOrder();
388   }
389 
testGetClassPathFromManifest_leadingBlanks()390   public void testGetClassPathFromManifest_leadingBlanks() throws IOException {
391     File jarFile = new File("base/some.jar");
392     Manifest manifest = manifestClasspath(" relative.jar");
393     assertThat(ClassPath.getClassPathFromManifest(jarFile, manifest))
394         .containsExactly(fullpath("base/relative.jar"));
395   }
396 
testGetClassPathFromManifest_trailingBlanks()397   public void testGetClassPathFromManifest_trailingBlanks() throws IOException {
398     File jarFile = new File("base/some.jar");
399     Manifest manifest = manifestClasspath("relative.jar ");
400     assertThat(ClassPath.getClassPathFromManifest(jarFile, manifest))
401         .containsExactly(fullpath("base/relative.jar"));
402   }
403 
testGetClassName()404   public void testGetClassName() {
405     assertEquals("abc.d.Abc", ClassPath.getClassName("abc/d/Abc.class"));
406   }
407 
testResourceInfo_of()408   public void testResourceInfo_of() {
409     assertEquals(ClassInfo.class, resourceInfo(ClassPathTest.class).getClass());
410     assertEquals(ClassInfo.class, resourceInfo(ClassPath.class).getClass());
411     assertEquals(ClassInfo.class, resourceInfo(Nested.class).getClass());
412   }
413 
testGetSimpleName()414   public void testGetSimpleName() {
415     ClassLoader classLoader = getClass().getClassLoader();
416     assertEquals("Foo", new ClassInfo(FILE, "Foo.class", classLoader).getSimpleName());
417     assertEquals("Foo", new ClassInfo(FILE, "a/b/Foo.class", classLoader).getSimpleName());
418     assertEquals("Foo", new ClassInfo(FILE, "a/b/Bar$Foo.class", classLoader).getSimpleName());
419     assertEquals("", new ClassInfo(FILE, "a/b/Bar$1.class", classLoader).getSimpleName());
420     assertEquals("Foo", new ClassInfo(FILE, "a/b/Bar$Foo.class", classLoader).getSimpleName());
421     assertEquals("", new ClassInfo(FILE, "a/b/Bar$1.class", classLoader).getSimpleName());
422     assertEquals("Local", new ClassInfo(FILE, "a/b/Bar$1Local.class", classLoader).getSimpleName());
423   }
424 
testGetPackageName()425   public void testGetPackageName() {
426     assertEquals(
427         "", new ClassInfo(FILE, "Foo.class", getClass().getClassLoader()).getPackageName());
428     assertEquals(
429         "a.b", new ClassInfo(FILE, "a/b/Foo.class", getClass().getClassLoader()).getPackageName());
430   }
431 
432   // Test that ResourceInfo.urls() returns identical content to ClassLoader.getResources()
433 
434 
435   @AndroidIncompatible
testGetClassPathUrls()436   public void testGetClassPathUrls() throws Exception {
437     if (isWindows()) {
438       return; // TODO: b/136041958 - We need to account for drive letters in the path.
439     }
440     String oldPathSeparator = PATH_SEPARATOR.value();
441     String oldClassPath = JAVA_CLASS_PATH.value();
442     System.setProperty(PATH_SEPARATOR.key(), ":");
443     System.setProperty(
444         JAVA_CLASS_PATH.key(),
445         Joiner.on(":")
446             .join(
447                 "relative/path/to/some.jar",
448                 "/absolute/path/to/some.jar",
449                 "relative/path/to/class/root",
450                 "/absolute/path/to/class/root"));
451     try {
452       ImmutableList<URL> urls = ClassPath.parseJavaClassPath();
453 
454       assertThat(urls.get(0).getProtocol()).isEqualTo("file");
455       assertThat(urls.get(0).getAuthority()).isNull();
456       assertThat(urls.get(0).getPath()).endsWith("/relative/path/to/some.jar");
457 
458       assertThat(urls.get(1)).isEqualTo(new URL("file:///absolute/path/to/some.jar"));
459 
460       assertThat(urls.get(2).getProtocol()).isEqualTo("file");
461       assertThat(urls.get(2).getAuthority()).isNull();
462       assertThat(urls.get(2).getPath()).endsWith("/relative/path/to/class/root");
463 
464       assertThat(urls.get(3)).isEqualTo(new URL("file:///absolute/path/to/class/root"));
465 
466       assertThat(urls).hasSize(4);
467     } finally {
468       System.setProperty(PATH_SEPARATOR.key(), oldPathSeparator);
469       System.setProperty(JAVA_CLASS_PATH.key(), oldClassPath);
470     }
471   }
472 
contentEquals(URL left, URL right)473   private static boolean contentEquals(URL left, URL right) throws IOException {
474     return Resources.asByteSource(left).contentEquals(Resources.asByteSource(right));
475   }
476 
477   private static class Nested {}
478 
479 
testNulls()480   public void testNulls() throws IOException {
481     new NullPointerTester().testAllPublicStaticMethods(ClassPath.class);
482     new NullPointerTester()
483         .testAllPublicInstanceMethods(ClassPath.from(getClass().getClassLoader()));
484   }
485 
486   @AndroidIncompatible // ClassPath is documented as not supporting Android
487 
testLocationsFrom_idempotentScan()488   public void testLocationsFrom_idempotentScan() throws IOException {
489     ImmutableSet<ClassPath.LocationInfo> locations =
490         ClassPath.locationsFrom(getClass().getClassLoader());
491     assertThat(locations).isNotEmpty();
492     for (ClassPath.LocationInfo location : locations) {
493       ImmutableSet<ResourceInfo> resources = location.scanResources();
494       assertThat(location.scanResources()).containsExactlyElementsIn(resources);
495     }
496   }
497 
testLocationsFrom_idempotentLocations()498   public void testLocationsFrom_idempotentLocations() {
499     ImmutableSet<ClassPath.LocationInfo> locations =
500         ClassPath.locationsFrom(getClass().getClassLoader());
501     assertThat(ClassPath.locationsFrom(getClass().getClassLoader()))
502         .containsExactlyElementsIn(locations);
503   }
504 
testLocationEquals()505   public void testLocationEquals() {
506     ClassLoader child = getClass().getClassLoader();
507     ClassLoader parent = child.getParent();
508     new EqualsTester()
509         .addEqualityGroup(
510             new ClassPath.LocationInfo(new File("foo.jar"), child),
511             new ClassPath.LocationInfo(new File("foo.jar"), child))
512         .addEqualityGroup(new ClassPath.LocationInfo(new File("foo.jar"), parent))
513         .addEqualityGroup(new ClassPath.LocationInfo(new File("foo"), child))
514         .testEquals();
515   }
516 
517   @AndroidIncompatible // ClassPath is documented as not supporting Android
518 
testScanAllResources()519   public void testScanAllResources() throws IOException {
520     assertThat(scanResourceNames(ClassLoader.getSystemClassLoader()))
521         .contains("com/google/common/reflect/ClassPathTest.class");
522   }
523 
524 
testExistsThrowsSecurityException()525   public void testExistsThrowsSecurityException() throws IOException, URISyntaxException {
526     SecurityManager oldSecurityManager = System.getSecurityManager();
527     try {
528       doTestExistsThrowsSecurityException();
529     } finally {
530       System.setSecurityManager(oldSecurityManager);
531     }
532   }
533 
doTestExistsThrowsSecurityException()534   private void doTestExistsThrowsSecurityException() throws IOException, URISyntaxException {
535     File file = null;
536     // In Java 9, Logger may read the TZ database. Only disallow reading the class path URLs.
537     final PermissionCollection readClassPathFiles =
538         new FilePermission("", "read").newPermissionCollection();
539     for (URL url : ClassPath.parseJavaClassPath()) {
540       if (url.getProtocol().equalsIgnoreCase("file")) {
541         file = new File(url.toURI());
542         readClassPathFiles.add(new FilePermission(file.getAbsolutePath(), "read"));
543       }
544     }
545     assertThat(file).isNotNull();
546     SecurityManager disallowFilesSecurityManager =
547         new SecurityManager() {
548           @Override
549           public void checkPermission(Permission p) {
550             if (readClassPathFiles.implies(p)) {
551               throw new SecurityException("Disallowed: " + p);
552             }
553           }
554         };
555     System.setSecurityManager(disallowFilesSecurityManager);
556     try {
557       file.exists();
558       fail("Did not get expected SecurityException");
559     } catch (SecurityException expected) {
560     }
561     ClassPath classPath = ClassPath.from(getClass().getClassLoader());
562     // ClassPath may contain resources from the boot class loader; just not from the class path.
563     for (ResourceInfo resource : classPath.getResources()) {
564       assertThat(resource.getResourceName()).doesNotContain("com/google/common/reflect/");
565     }
566   }
567 
findClass( Iterable<ClassPath.ClassInfo> classes, Class<?> cls)568   private static ClassPath.ClassInfo findClass(
569       Iterable<ClassPath.ClassInfo> classes, Class<?> cls) {
570     for (ClassPath.ClassInfo classInfo : classes) {
571       if (classInfo.getName().equals(cls.getName())) {
572         return classInfo;
573       }
574     }
575     throw new AssertionError("failed to find " + cls);
576   }
577 
resourceInfo(Class<?> cls)578   private static ResourceInfo resourceInfo(Class<?> cls) {
579     String resource = cls.getName().replace('.', '/') + ".class";
580     ClassLoader loader = cls.getClassLoader();
581     return ResourceInfo.of(FILE, resource, loader);
582   }
583 
classInfo(Class<?> cls)584   private static ClassInfo classInfo(Class<?> cls) {
585     return classInfo(cls, cls.getClassLoader());
586   }
587 
classInfo(Class<?> cls, ClassLoader classLoader)588   private static ClassInfo classInfo(Class<?> cls, ClassLoader classLoader) {
589     String resource = cls.getName().replace('.', '/') + ".class";
590     return new ClassInfo(FILE, resource, classLoader);
591   }
592 
manifestClasspath(String classpath)593   private static Manifest manifestClasspath(String classpath) throws IOException {
594     return manifest("Class-Path: " + classpath + "\n");
595   }
596 
writeSelfReferencingJarFile(File jarFile, String... entries)597   private static void writeSelfReferencingJarFile(File jarFile, String... entries)
598       throws IOException {
599     Manifest manifest = new Manifest();
600     // Without version, the manifest is silently ignored. Ugh!
601     manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
602     manifest.getMainAttributes().put(Attributes.Name.CLASS_PATH, jarFile.getName());
603 
604     Closer closer = Closer.create();
605     try {
606       FileOutputStream fileOut = closer.register(new FileOutputStream(jarFile));
607       JarOutputStream jarOut = closer.register(new JarOutputStream(fileOut));
608       for (String entry : entries) {
609         jarOut.putNextEntry(new ZipEntry(entry));
610         Resources.copy(ClassPathTest.class.getResource(entry), jarOut);
611         jarOut.closeEntry();
612       }
613     } catch (Throwable e) {
614       throw closer.rethrow(e);
615     } finally {
616       closer.close();
617     }
618   }
619 
manifest(String content)620   private static Manifest manifest(String content) throws IOException {
621     InputStream in = new ByteArrayInputStream(content.getBytes(US_ASCII));
622     Manifest manifest = new Manifest();
623     manifest.read(in);
624     return manifest;
625   }
626 
fullpath(String path)627   private static File fullpath(String path) {
628     return new File(new File(path).toURI());
629   }
630 
makeJarUrlWithName(String name)631   private static URL makeJarUrlWithName(String name) throws IOException {
632     /*
633      * TODO: cpovirk - Use java.nio.file.Files.createTempDirectory instead of
634      * c.g.c.io.Files.createTempDir?
635      */
636     File fullPath = new File(Files.createTempDir(), name);
637     File jarFile = pickAnyJarFile();
638     Files.copy(jarFile, fullPath);
639     return fullPath.toURI().toURL();
640   }
641 
pickAnyJarFile()642   private static File pickAnyJarFile() throws IOException {
643     for (ClassPath.LocationInfo location :
644         ClassPath.locationsFrom(ClassPathTest.class.getClassLoader())) {
645       if (!location.file().isDirectory() && location.file().exists()) {
646         return location.file();
647       }
648     }
649     throw new AssertionError("Failed to find a jar file");
650   }
651 
652   @AndroidIncompatible // Path (for symlink creation)
deleteRecursivelyOrLog(java.nio.file.Path path)653   private static void deleteRecursivelyOrLog(java.nio.file.Path path) {
654     try {
655       deleteRecursively(path);
656     } catch (IOException e) {
657       log.log(WARNING, "Failure cleaning up test directory", e);
658     }
659   }
660 
scanResourceNames(ClassLoader loader)661   private static ImmutableSet<String> scanResourceNames(ClassLoader loader) throws IOException {
662     ImmutableSet.Builder<String> builder = ImmutableSet.builder();
663     for (ClassPath.LocationInfo location : ClassPath.locationsFrom(loader)) {
664       for (ResourceInfo resource : location.scanResources()) {
665         builder.add(resource.getResourceName());
666       }
667     }
668     return builder.build();
669   }
670 
isWindows()671   private static boolean isWindows() {
672     return OS_NAME.value().startsWith("Windows");
673   }
674 }
675