--- /dev/null 2013-01-19 23:30:52.572866804 -0800 +++ new/test/java/nio/file/Files/StreamTest.java 2013-02-01 14:41:10.572350470 -0800 @@ -0,0 +1,586 @@ +/* + * 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); + } + } +} \ No newline at end of file