/* * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* @test * @bug 8006884 * @summary Unit test for java.nio.file.Files * @library .. * @build PassThroughFileSystem FaultyFileSystem * @run testng StreamTest */ import java.io.IOException; import java.io.UncheckedIOException; import java.nio.charset.Charset; import java.nio.charset.MalformedInputException; import java.nio.file.DirectoryIteratorException; import java.nio.file.DirectoryStream; import java.nio.file.FileSystemLoopException; import java.nio.file.FileVisitOption; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; import java.util.Arrays; import java.util.Comparators; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.TreeSet; import java.util.function.BiPredicate; import java.util.stream.CloseableStream; import java.util.stream.Collectors; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import static org.testng.Assert.*; @Test(groups = "unit") public class StreamTest { /** * Default test folder * testFolder - empty * - file * - dir - d1 * - f1 * - lnDir2 (../dir2) * - dir2 * - linkDir (./dir) * - linkFile(./file) */ static Path testFolder; static boolean supportsLinks; static Path[] level1; static Path[] all; static Path[] all_folowLinks; @BeforeClass void setupTestFolder() throws IOException { testFolder = TestUtil.createTemporaryDirectory(); supportsLinks = TestUtil.supportsLinks(testFolder); TreeSet set = new TreeSet<>(); // Level 1 Path empty = testFolder.resolve("empty"); Path file = testFolder.resolve("file"); Path dir = testFolder.resolve("dir"); Path dir2 = testFolder.resolve("dir2"); Files.createDirectory(empty); Files.createFile(file); Files.createDirectory(dir); Files.createDirectory(dir2); set.add(empty); set.add(file); set.add(dir); set.add(dir2); if (supportsLinks) { Path tmp = testFolder.resolve("linkDir"); Files.createSymbolicLink(tmp, dir); set.add(tmp); tmp = testFolder.resolve("linkFile"); Files.createSymbolicLink(tmp, file); set.add(tmp); } level1 = set.toArray(new Path[0]); // Level 2 Path tmp = dir.resolve("d1"); Files.createDirectory(tmp); set.add(tmp); tmp = dir.resolve("f1"); Files.createFile(tmp); set.add(tmp); if (supportsLinks) { tmp = dir.resolve("lnDir2"); Files.createSymbolicLink(tmp, dir2); set.add(tmp); } // walk include starting folder set.add(testFolder); all = set.toArray(new Path[0]); // Follow links if (supportsLinks) { tmp = testFolder.resolve("linkDir"); set.add(tmp.resolve("d1")); set.add(tmp.resolve("f1")); tmp = tmp.resolve("lnDir2"); set.add(tmp); } all_folowLinks = set.toArray(new Path[0]); } @AfterClass void cleanupTestFolder() throws IOException { TestUtil.removeAll(testFolder); } public void testBasic() { try(CloseableStream s = Files.list(testFolder)) { Object[] actual = s.sorted(Comparators.naturalOrder()).toArray(); assertEquals(actual, level1); } catch (IOException ioe) { fail("Unexpected IOException"); } try(CloseableStream s = Files.list(testFolder.resolve("empty"))) { int count = s.map(p -> 1).reduce(0, Integer::sum); assertEquals(count, 0, "Expect empty stream."); } catch (IOException ioe) { fail("Unexpected IOException"); } } public void testWalk() { try(CloseableStream s = Files.walk(testFolder)) { Object[] actual = s.sorted(Comparators.naturalOrder()).toArray(); assertEquals(actual, all); } catch (IOException ioe) { fail("Unexpected IOException"); } } public void testWalkOneLevel() { try(CloseableStream s = Files.walk(testFolder, 1)) { Object[] actual = s.filter(path -> ! path.equals(testFolder)) .sorted(Comparators.naturalOrder()) .toArray(); assertEquals(actual, level1); } catch (IOException ioe) { fail("Unexpected IOException"); } } public void testWalkFollowLink() { // If link is not supported, the directory structure won't have link. // We still want to test the behavior with FOLLOW_LINKS option. try(CloseableStream s = Files.walk(testFolder, FileVisitOption.FOLLOW_LINKS)) { Object[] actual = s.sorted(Comparators.naturalOrder()).toArray(); assertEquals(actual, all_folowLinks); } catch (IOException ioe) { fail("Unexpected IOException"); } } private void validateFileSystemLoopException(Path start, Path... causes) { try(CloseableStream s = Files.walk(start, FileVisitOption.FOLLOW_LINKS)) { try { int count = s.map(p -> 1).reduce(0, Integer::sum); fail("Should got FileSystemLoopException, but got " + count + "elements."); } catch (UncheckedIOException uioe) { IOException ioe = uioe.getCause(); if (ioe instanceof FileSystemLoopException) { FileSystemLoopException fsle = (FileSystemLoopException) ioe; boolean match = false; for (Path cause: causes) { if (fsle.getFile().equals(cause.toString())) { match = true; break; } } assertTrue(match); } else { fail("Unexpected UncheckedIOException cause " + ioe.toString()); } } } catch(IOException ex) { fail("Unexpected IOException " + ex); } } public void testWalkFollowLinkLoop() { if (!supportsLinks) { return; } // Loops. try { Path dir = testFolder.resolve("dir"); Path linkdir = testFolder.resolve("linkDir"); Path d1 = dir.resolve("d1"); Path cause = d1.resolve("lnSelf"); Files.createSymbolicLink(cause, d1); // loop in descendant. validateFileSystemLoopException(dir, cause); // loop in self validateFileSystemLoopException(d1, cause); // start from other place via link validateFileSystemLoopException(linkdir, linkdir.resolve(Paths.get("d1", "lnSelf"))); Files.delete(cause); // loop to parent. cause = d1.resolve("lnParent"); Files.createSymbolicLink(cause, dir); // loop should be detected at test/dir/d1/lnParent/d1 validateFileSystemLoopException(d1, cause.resolve("d1")); // loop should be detected at link validateFileSystemLoopException(dir, cause); // loop should be detected at test/linkdir/d1/lnParent // which is test/dir we have visited via test/linkdir validateFileSystemLoopException(linkdir, linkdir.resolve(Paths.get("d1", "lnParent"))); Files.delete(cause); // cross loop Path dir2 = testFolder.resolve("dir2"); cause = dir2.resolve("lnDir"); Files.createSymbolicLink(cause, dir); validateFileSystemLoopException(dir, dir.resolve(Paths.get("lnDir2", "lnDir"))); validateFileSystemLoopException(dir2, dir2.resolve(Paths.get("lnDir", "lnDir2"))); validateFileSystemLoopException(linkdir, linkdir.resolve(Paths.get("lnDir2", "lnDir"))); } catch(IOException ioe) { fail("Unexpected IOException " + ioe); } } private static class PathBiPredicate implements BiPredicate { private final BiPredicate pred; private final Set visited = new TreeSet(); PathBiPredicate(BiPredicate pred) { this.pred = Objects.requireNonNull(pred); } public boolean test(Path path, BasicFileAttributes attrs) { visited.add(path); return pred.test(path, attrs); } public Path[] visited() { return visited.toArray(new Path[0]); } } public void testFind() throws IOException { PathBiPredicate pred = new PathBiPredicate((path, attrs) -> true); try (CloseableStream s = Files.find(testFolder, Integer.MAX_VALUE, pred)) { Set result = s.collect(Collectors.toCollection(TreeSet::new)); assertEquals(pred.visited(), all); assertEquals(result.toArray(new Path[0]), pred.visited()); } pred = new PathBiPredicate((path, attrs) -> attrs.isSymbolicLink()); try (CloseableStream s = Files.find(testFolder, Integer.MAX_VALUE, pred)) { s.forEach(path -> assertTrue(Files.isSymbolicLink(path))); assertEquals(pred.visited(), all); } pred = new PathBiPredicate((path, attrs) -> path.getFileName().toString().startsWith("e")); try (CloseableStream s = Files.find(testFolder, Integer.MAX_VALUE, pred)) { s.forEach(path -> assertEquals(path.getFileName().toString(), "empty")); assertEquals(pred.visited(), all); } pred = new PathBiPredicate((path, attrs) -> path.getFileName().toString().startsWith("l") && attrs.isRegularFile()); try (CloseableStream s = Files.find(testFolder, Integer.MAX_VALUE, pred)) { s.forEach(path -> fail("Expect empty stream")); assertEquals(pred.visited(), all); } } // Test borrowed from BytesAndLines public void testLines() throws IOException { final Charset US_ASCII = Charset.forName("US-ASCII"); Path tmpfile = Files.createTempFile("blah", "txt"); try { // zero lines assertTrue(Files.size(tmpfile) == 0, "File should be empty"); try (CloseableStream s = Files.lines(tmpfile, US_ASCII)) { assertEquals(s.map(l -> 1).reduce(0, Integer::sum), 0, "No line expected"); } // one line byte[] hi = { (byte)'h', (byte)'i' }; Files.write(tmpfile, hi); try (CloseableStream s = Files.lines(tmpfile, US_ASCII)) { List lines = s.collect(Collectors.toList()); assertTrue(lines.size() == 1, "One line expected"); assertTrue(lines.get(0).equals("hi"), "'Hi' expected"); } // two lines using platform's line separator List expected = Arrays.asList("hi", "there"); Files.write(tmpfile, expected, US_ASCII); assertTrue(Files.size(tmpfile) > 0, "File is empty"); try (CloseableStream s = Files.lines(tmpfile, US_ASCII)) { List lines = s.collect(Collectors.toList()); assertTrue(lines.equals(expected), "Unexpected lines"); } // MalformedInputException byte[] bad = { (byte)0xff, (byte)0xff }; Files.write(tmpfile, bad); try (CloseableStream s = Files.lines(tmpfile, US_ASCII)) { try { List lines = s.collect(Collectors.toList()); throw new RuntimeException("UncheckedIOException expected"); } catch (UncheckedIOException ex) { assertTrue(ex.getCause() instanceof MalformedInputException, "MalformedInputException expected"); } } // NullPointerException try { Files.lines(null, US_ASCII); throw new RuntimeException("NullPointerException expected"); } catch (NullPointerException ignore) { } try { Files.lines(tmpfile, null); throw new RuntimeException("NullPointerException expected"); } catch (NullPointerException ignore) { } } finally { Files.delete(tmpfile); } } public void testDirectoryIteratorException() throws IOException { Path dir = testFolder.resolve("dir2"); Path trigger = dir.resolve("DirectoryIteratorException"); Files.createFile(trigger); FaultyFileSystem.FaultyFSProvider fsp = FaultyFileSystem.FaultyFSProvider.getInstance(); FaultyFileSystem fs = (FaultyFileSystem) fsp.newFileSystem(dir, null); try { fsp.setFaultyMode(false); Path fakeRoot = fs.getRoot(); try { try (CloseableStream s = Files.list(fakeRoot)) { s.forEach(path -> assertEquals(path.getFileName().toString(), "DirectoryIteratorException")); } } catch (UncheckedIOException uioe) { fail("Unexpected exception."); } fsp.setFaultyMode(true); try { try (DirectoryStream ds = Files.newDirectoryStream(fakeRoot)) { Iterator itor = ds.iterator(); while (itor.hasNext()) { itor.next(); } } fail("Shoule throw DirectoryIteratorException"); } catch (DirectoryIteratorException die) { } try { try (CloseableStream s = Files.list(fakeRoot)) { s.forEach(path -> fail("should not get here")); } } catch (UncheckedIOException uioe) { assertTrue(uioe.getCause() instanceof FaultyFileSystem.FaultyException); } catch (DirectoryIteratorException die) { fail("Should have been converted into UncheckedIOException."); } } finally { // Cleanup if (fs != null) { fs.close(); } Files.delete(trigger); } } public void testUncheckedIOException() throws IOException { Path triggerFile = testFolder.resolve(Paths.get("dir2", "IOException")); Files.createFile(triggerFile); Path triggerDir = testFolder.resolve(Paths.get("empty", "IOException")); Files.createDirectories(triggerDir); Files.createFile(triggerDir.resolve("file")); FaultyFileSystem.FaultyFSProvider fsp = FaultyFileSystem.FaultyFSProvider.getInstance(); FaultyFileSystem fs = (FaultyFileSystem) fsp.newFileSystem(testFolder, null); try { fsp.setFaultyMode(false); Path fakeRoot = fs.getRoot(); try (CloseableStream s = Files.list(fakeRoot.resolve("dir2"))) { // only one file s.forEach(path -> assertEquals(path.getFileName().toString(), "IOException")); } try (CloseableStream s = Files.walk(fakeRoot.resolve("empty"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); // ordered as depth-first assertEquals(result, new String[] { "empty", "IOException", "file"}); } fsp.setFaultyMode(true); try (CloseableStream s = Files.list(fakeRoot.resolve("dir2"))) { s.forEach(path -> fail("should have caused exception")); } catch (UncheckedIOException uioe) { assertTrue(uioe.getCause() instanceof FaultyFileSystem.FaultyException); } try (CloseableStream s = Files.walk(fakeRoot.resolve("empty"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); fail("should not reach here due to IOException"); } catch (UncheckedIOException uioe) { assertTrue(uioe.getCause() instanceof FaultyFileSystem.FaultyException); } try (CloseableStream s = Files.walk( fakeRoot.resolve("empty").resolve("IOException"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); fail("should not reach here due to IOException"); } catch (IOException ioe) { assertTrue(ioe instanceof FaultyFileSystem.FaultyException); } catch (UncheckedIOException ex) { fail("Top level should be repored as is"); } } finally { // Cleanup if (fs != null) { fs.close(); } Files.delete(triggerFile); TestUtil.removeAll(triggerDir); } } public void testSecurityException() throws IOException { Path triggerFile = testFolder.resolve(Paths.get("dir", "SecurityException")); Files.createFile(triggerFile); Path sampleFile = testFolder.resolve(Paths.get("dir", "sample")); Files.createFile(sampleFile); Path triggerDir = testFolder.resolve(Paths.get("dir2", "SecurityException")); Files.createDirectories(triggerDir); Files.createFile(triggerDir.resolve("fileInSE")); Path sample = testFolder.resolve(Paths.get("dir2", "file")); Files.createFile(sample); FaultyFileSystem.FaultyFSProvider fsp = FaultyFileSystem.FaultyFSProvider.getInstance(); FaultyFileSystem fs = (FaultyFileSystem) fsp.newFileSystem(testFolder, null); try { fsp.setFaultyMode(false); Path fakeRoot = fs.getRoot(); // validate setting try (CloseableStream s = Files.list(fakeRoot.resolve("dir"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); assertEqualsNoOrder(result, new String[] { "d1","f1", "lnDir2", "SecurityException", "sample" }); } try (CloseableStream s = Files.walk(fakeRoot.resolve("dir2"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); assertEqualsNoOrder(result, new String[] { "dir2", "SecurityException", "fileInSE", "file" }); } // execute test fsp.setFaultyMode(true); // ignore file cause SecurityException try (CloseableStream s = Files.walk(fakeRoot.resolve("dir"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); assertEqualsNoOrder(result, new String[] { "dir", "d1","f1", "lnDir2", "sample" }); } // skip folder cause SecurityException try (CloseableStream s = Files.walk(fakeRoot.resolve("dir2"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); assertEqualsNoOrder(result, new String[] { "dir2", "file" }); } // list instead of walk try (CloseableStream s = Files.list(fakeRoot.resolve("dir"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); assertEqualsNoOrder(result, new String[] { "d1","f1", "lnDir2", "sample" }); } try (CloseableStream s = Files.list(fakeRoot.resolve("dir2"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); assertEqualsNoOrder(result, new String[] { "file" }); } // root cause SecurityException should be reported try (CloseableStream s = Files.walk( fakeRoot.resolve("dir2").resolve("SecurityException"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); fail("should not reach here due to SecurityException"); } catch (SecurityException se) { assertTrue(se.getCause() instanceof FaultyFileSystem.FaultyException); } // Walk a file cause SecurityException, we should get SE try (CloseableStream s = Files.walk( fakeRoot.resolve("dir").resolve("SecurityException"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); fail("should not reach here due to SecurityException"); } catch (SecurityException se) { assertTrue(se.getCause() instanceof FaultyFileSystem.FaultyException); } // List a file cause SecurityException, we should get SE as cannot read attribute try (CloseableStream s = Files.list( fakeRoot.resolve("dir2").resolve("SecurityException"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); fail("should not reach here due to SecurityException"); } catch (SecurityException se) { assertTrue(se.getCause() instanceof FaultyFileSystem.FaultyException); } try (CloseableStream s = Files.list( fakeRoot.resolve("dir").resolve("SecurityException"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); fail("should not reach here due to SecurityException"); } catch (SecurityException se) { assertTrue(se.getCause() instanceof FaultyFileSystem.FaultyException); } } finally { // Cleanup if (fs != null) { fs.close(); } Files.delete(triggerFile); Files.delete(sampleFile); Files.delete(sample); TestUtil.removeAll(triggerDir); } } }