1/*
2 * Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/*
25 * @test
26 * @bug 4313887 6907737
27 * @summary Tests that walkFileTree is consistent with the native find program
28 * @requires (os.family != "windows")
29 * @library /test/lib
30 * @build jdk.test.lib.Utils
31 *        jdk.test.lib.Asserts
32 *        jdk.test.lib.JDKToolFinder
33 *        jdk.test.lib.JDKToolLauncher
34 *        jdk.test.lib.Platform
35 *        jdk.test.lib.process.*
36 *        CreateFileTree
37 * @run testng/othervm -Djava.io.tmpdir=. FindTest
38 */
39
40import java.io.IOException;
41import java.nio.file.FileSystemLoopException;
42import java.nio.file.FileVisitOption;
43import java.nio.file.FileVisitResult;
44import java.nio.file.FileVisitor;
45import java.nio.file.Files;
46import java.nio.file.Path;
47import java.nio.file.attribute.BasicFileAttributes;
48import java.util.ArrayList;
49import java.util.Arrays;
50import java.util.HashSet;
51import java.util.List;
52import java.util.Random;
53import java.util.Set;
54import java.util.stream.Collectors;
55
56import jdk.test.lib.process.OutputAnalyzer;
57import jdk.test.lib.process.ProcessTools;
58
59import org.testng.annotations.BeforeClass;
60import org.testng.annotations.Test;
61
62import static org.testng.Assert.assertEquals;
63import static org.testng.Assert.assertTrue;
64
65public class FindTest {
66
67    private static final Random rand = new Random();
68    private static final boolean isAIX = System.getProperty("os.name").equals("AIX");
69    private static Path top;
70    private static String TOP;
71
72    @BeforeClass
73    public static void createFileTree() throws Exception {
74        top = CreateFileTree.create();
75        TOP = top.toAbsolutePath().toString();
76    }
77
78    @Test
79    public void printTreeTest() throws Throwable {
80        // print the file tree and compare output with find(1)
81        assertOutputEquals(printFileTree(top), runFind("find", TOP));
82    }
83
84    @Test
85    public void printTreeFollowLinkTest() throws Throwable {
86        // print the file tree, following links, and compare output with find(1).
87
88        // On AIX "find -follow" may core dump on recursive links without '-L'
89        // see: http://www-01.ibm.com/support/docview.wss?uid=isg1IV28143
90        String[] cmds = isAIX
91                      ? new String[]{"find", "-L", TOP, "-follow"}
92                      : new String[]{"find", TOP, "-follow"};
93        OutputAnalyzer expected = runFind(cmds);
94
95        // Some versions of find(1) output cycles (sym links to ancestor
96        // directories), other versions do not. For that reason we run
97        // PrintFileTree with the -printCycles option when the output without
98        // this option differs to find(1).
99        try {
100            assertOutputEquals(printFileTree(top, "-follow"), expected);
101        } catch (AssertionError x) {
102            assertOutputEquals(printFileTree(top, "-follow", "-printCycles"), expected);
103        }
104    }
105
106    private void assertOutputEquals(List<String> actual, OutputAnalyzer expected)
107            throws IOException {
108        List<String> expectedList = Arrays.asList(expected.getStdout()
109                                          .split(System.lineSeparator()));
110        assertEquals(actual.size(), expectedList.size());
111        assertTrue(actual.removeAll(expectedList));
112    }
113
114    private OutputAnalyzer runFind(String... cmds) throws Throwable {
115        return ProcessTools.executeCommand(cmds);
116    }
117
118    /**
119     * Invokes Files.walkFileTree to traverse a file tree and prints
120     * each of the directories and files. The -follow option causes symbolic
121     * links to be followed and the -printCycles option will print links
122     * where the target of the link is an ancestor directory.
123     */
124    private static List<String> printFileTree(Path dir, String... opts) throws Exception {
125        List<Path> fileTreeList = new ArrayList<>();
126
127        List<String> optsList = Arrays.asList(opts);
128        boolean followLinks = optsList.contains("-follow");
129        boolean reportCycles = optsList.contains("-printCycles");
130
131        Set<FileVisitOption> options = new HashSet<>();
132        if (followLinks)
133            options.add(FileVisitOption.FOLLOW_LINKS);
134
135        Files.walkFileTree(dir, options, Integer.MAX_VALUE, new FileVisitor<Path>() {
136            @Override
137            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
138                fileTreeList.add(dir);
139                return FileVisitResult.CONTINUE;
140            }
141            @Override
142            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
143                fileTreeList.add(file);
144                return FileVisitResult.CONTINUE;
145            }
146            @Override
147            public FileVisitResult postVisitDirectory(Path dir, IOException exc)
148                throws IOException
149            {
150                if (exc != null)
151                    throw exc;
152                return FileVisitResult.CONTINUE;
153            }
154            @Override
155            public FileVisitResult visitFileFailed(Path file, IOException exc)
156                throws IOException
157            {
158                if (followLinks && (exc instanceof FileSystemLoopException)) {
159                    if (reportCycles)
160                        fileTreeList.add(file);
161                    return FileVisitResult.CONTINUE;
162                } else {
163                    throw exc;
164                }
165            }
166        });
167
168        return fileTreeList.stream()
169                           .map(f -> f.toAbsolutePath().toString())
170                           .collect(Collectors.toCollection(ArrayList::new));
171    }
172}
173