1/*
2 * Copyright (c) 2015, 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 8144095 8164825 8169818 8153402 8165405 8177079 8178013 8167554
27 * @summary Test Command Completion
28 * @modules jdk.compiler/com.sun.tools.javac.api
29 *          jdk.compiler/com.sun.tools.javac.main
30 *          jdk.jdeps/com.sun.tools.javap
31 *          jdk.jshell/jdk.internal.jshell.tool
32 * @library /tools/lib
33 * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
34 * @build ReplToolTesting TestingInputStream Compiler
35 * @run testng CommandCompletionTest
36 */
37
38import java.io.IOException;
39import java.nio.file.FileSystems;
40import java.nio.file.Files;
41import java.nio.file.Path;
42import java.nio.file.Paths;
43import java.util.Arrays;
44import java.util.Collections;
45import java.util.List;
46import java.util.Locale;
47import java.util.function.Predicate;
48import java.util.stream.Collectors;
49import java.util.stream.Stream;
50import java.util.stream.StreamSupport;
51
52import org.testng.annotations.Test;
53import jdk.internal.jshell.tool.JShellTool;
54import jdk.internal.jshell.tool.JShellToolBuilder;
55import jdk.jshell.SourceCodeAnalysis.Suggestion;
56import static org.testng.Assert.assertEquals;
57import static org.testng.Assert.assertTrue;
58import static org.testng.Assert.fail;
59
60public class CommandCompletionTest extends ReplToolTesting {
61
62
63    private JShellTool repl;
64
65    @Override
66    protected void testRawRun(Locale locale, String[] args) {
67        repl = ((JShellToolBuilder) builder(locale))
68                .rawTool();
69        try {
70            repl.start(args);
71        } catch (Exception ex) {
72            fail("Repl tool died with exception", ex);
73        }
74    }
75
76    public void assertCompletion(boolean after, String code, boolean isSmart, String... expected) {
77        if (!after) {
78            setCommandInput("\n");
79        } else {
80            assertCompletion(code, isSmart, expected);
81        }
82    }
83
84    public void assertCompletion(String code, boolean isSmart, String... expected) {
85        List<String> completions = computeCompletions(code, isSmart);
86        assertEquals(completions, Arrays.asList(expected), "Command: " + code + ", output: " +
87                completions.toString());
88    }
89
90    private List<String> computeCompletions(String code, boolean isSmart) {
91        int cursor =  code.indexOf('|');
92        code = code.replace("|", "");
93        assertTrue(cursor > -1, "'|' not found: " + code);
94        List<Suggestion> completions =
95                repl.commandCompletionSuggestions(code, cursor, new int[] {-1}); //XXX: ignoring anchor for now
96        return completions.stream()
97                          .filter(s -> isSmart == s.matchesType())
98                          .map(s -> s.continuation())
99                          .distinct()
100                          .collect(Collectors.toList());
101    }
102
103    @Test
104    public void testCommand() {
105        testNoStartUp(
106                a -> assertCompletion(a, "/deb|", false),
107                a -> assertCompletion(a, "/re|", false, "/reload ", "/reset "),
108                a -> assertCompletion(a, "/h|", false, "/help ", "/history ")
109        );
110    }
111
112    @Test
113    public void testList() {
114        test(false, new String[] {"--no-startup"},
115                a -> assertCompletion(a, "/l|", false, "/list "),
116                a -> assertCompletion(a, "/list |", false, "-all", "-history", "-start "),
117                a -> assertCompletion(a, "/list -h|", false, "-history"),
118                a -> assertCompletion(a, "/list q|", false),
119                a -> assertVariable(a, "int", "xray"),
120                a -> assertCompletion(a, "/list |", false, "-all", "-history", "-start ", "1 ", "xray "),
121                a -> assertCompletion(a, "/list x|", false, "xray "),
122                a -> assertCompletion(a, "/list xray |", false)
123        );
124    }
125
126    @Test
127    public void testDrop() {
128        test(false, new String[] {"--no-startup"},
129                a -> assertCompletion(a, "/d|", false, "/drop "),
130                a -> assertClass(a, "class cTest {}", "class", "cTest"),
131                a -> assertMethod(a, "int mTest() { return 0; }", "()I", "mTest"),
132                a -> assertVariable(a, "int", "fTest"),
133                a -> assertCompletion(a, "/drop |", false, "1 ", "2 ", "3 ", "cTest ", "fTest ", "mTest "),
134                a -> assertCompletion(a, "/drop f|", false, "fTest ")
135        );
136    }
137
138    @Test
139    public void testEdit() {
140        test(false, new String[]{"--no-startup"},
141                a -> assertCompletion(a, "/e|", false, "/edit ", "/env ", "/exit "),
142                a -> assertCompletion(a, "/ed|", false, "/edit "),
143                a -> assertClass(a, "class cTest {}", "class", "cTest"),
144                a -> assertMethod(a, "int mTest() { return 0; }", "()I", "mTest"),
145                a -> assertVariable(a, "int", "fTest"),
146                a -> assertCompletion(a, "/edit |", false,
147                        "-all" , "-start " , "1 ", "2 ", "3 ", "cTest ", "fTest ", "mTest "),
148                a -> assertCompletion(a, "/edit cTest |", false,
149                        "2 ", "3 ", "fTest ", "mTest "),
150                a -> assertCompletion(a, "/edit 1 fTest |", false,
151                        "2 ", "mTest "),
152                a -> assertCompletion(a, "/edit f|", false, "fTest "),
153                a -> assertCompletion(a, "/edit mTest f|", false, "fTest ")
154        );
155    }
156
157    @Test
158    public void testHelp() {
159        testNoStartUp(
160                a -> assertCompletion(a, "/help |", false,
161                "/! ", "/-<n> ", "/<id> ", "/? ", "/drop ",
162                "/edit ", "/env ", "/exit ",
163                "/help ", "/history ", "/imports ",
164                "/list ", "/methods ", "/open ", "/reload ", "/reset ",
165                "/save ", "/set ", "/types ", "/vars ", "context ", "intro ", "rerun ", "shortcuts "),
166                a -> assertCompletion(a, "/? |", false,
167                "/! ", "/-<n> ", "/<id> ", "/? ", "/drop ",
168                "/edit ", "/env ", "/exit ",
169                "/help ", "/history ", "/imports ",
170                "/list ", "/methods ", "/open ", "/reload ", "/reset ",
171                "/save ", "/set ", "/types ", "/vars ", "context ", "intro ", "rerun ", "shortcuts "),
172                a -> assertCompletion(a, "/help /s|", false,
173                "/save ", "/set "),
174                a -> assertCompletion(a, "/help /set |", false,
175                "editor", "feedback", "format", "mode", "prompt", "start", "truncation"),
176                a -> assertCompletion(a, "/help set |", false,
177                "editor", "feedback", "format", "mode", "prompt", "start", "truncation"),
178                a -> assertCompletion(a, "/help /edit |", false),
179                a -> assertCompletion(a, "/help dr|", false,
180                "drop ")
181        );
182    }
183
184    @Test
185    public void testReload() {
186        String[] ropts = new String[] { "-add-exports ", "-add-modules ",
187            "-class-path ", "-module-path ", "-quiet ", "-restore " };
188        String[] dropts = new String[] { "--add-exports ", "--add-modules ",
189            "--class-path ", "--module-path ", "--quiet ", "--restore " };
190        testNoStartUp(
191                a -> assertCompletion(a, "/reloa |", false, ropts),
192                a -> assertCompletion(a, "/relo               |", false, ropts),
193                a -> assertCompletion(a, "/reload -|", false, ropts),
194                a -> assertCompletion(a, "/reload --|", false, dropts),
195                a -> assertCompletion(a, "/reload -restore |", false, ropts),
196                a -> assertCompletion(a, "/reload -restore --|", false, dropts),
197                a -> assertCompletion(a, "/reload -rest|", false, "-restore "),
198                a -> assertCompletion(a, "/reload --r|", false, "--restore "),
199                a -> assertCompletion(a, "/reload -q|", false, "-quiet "),
200                a -> assertCompletion(a, "/reload -add|", false, "-add-exports ", "-add-modules "),
201                a -> assertCompletion(a, "/reload -class-path . -quiet |", false, ropts)
202        );
203    }
204
205    @Test
206    public void testEnv() {
207        String[] ropts = new String[] { "-add-exports ", "-add-modules ",
208            "-class-path ", "-module-path " };
209        String[] dropts = new String[] { "--add-exports ", "--add-modules ",
210            "--class-path ", "--module-path " };
211        testNoStartUp(
212                a -> assertCompletion(a, "/env |", false, ropts),
213                a -> assertCompletion(a, "/env -|", false, ropts),
214                a -> assertCompletion(a, "/env --|", false, dropts),
215                a -> assertCompletion(a, "/env --a|", false, "--add-exports ", "--add-modules "),
216                a -> assertCompletion(a, "/env -add-|", false, "-add-exports ", "-add-modules "),
217                a -> assertCompletion(a, "/env -class-path . |", false, ropts),
218                a -> assertCompletion(a, "/env -class-path . --|", false, dropts)
219        );
220    }
221
222    @Test
223    public void testReset() {
224        String[] ropts = new String[] { "-add-exports ", "-add-modules ",
225            "-class-path ", "-module-path " };
226        String[] dropts = new String[] { "--add-exports ", "--add-modules ",
227            "--class-path ", "--module-path " };
228        testNoStartUp(
229                a -> assertCompletion(a, "/reset    |", false, ropts),
230                a -> assertCompletion(a, "/res -m|", false, "-module-path "),
231                a -> assertCompletion(a, "/res -module-|", false, "-module-path "),
232                a -> assertCompletion(a, "/res --m|", false, "--module-path "),
233                a -> assertCompletion(a, "/res --module-|", false, "--module-path "),
234                a -> assertCompletion(a, "/reset -add|", false, "-add-exports ", "-add-modules "),
235                a -> assertCompletion(a, "/rese -class-path . |", false, ropts),
236                a -> assertCompletion(a, "/rese -class-path . --|", false, dropts)
237        );
238    }
239
240    @Test
241    public void testVarsMethodsTypes() {
242        testNoStartUp(
243                a -> assertCompletion(a, "/v|", false, "/vars "),
244                a -> assertCompletion(a, "/m|", false, "/methods "),
245                a -> assertCompletion(a, "/t|", false, "/types "),
246                a -> assertClass(a, "class cTest {}", "class", "cTest"),
247                a -> assertMethod(a, "int mTest() { return 0; }", "()I", "mTest"),
248                a -> assertVariable(a, "int", "fTest"),
249                a -> assertCompletion(a, "/vars |", false, "-all", "-start ", "3 ", "fTest "),
250                a -> assertCompletion(a, "/meth |", false, "-all", "-start ", "2 ", "mTest "),
251                a -> assertCompletion(a, "/typ |", false, "-all", "-start ", "1 ", "cTest "),
252                a -> assertCompletion(a, "/var f|", false, "fTest ")
253        );
254    }
255
256    @Test
257    public void testOpen() throws IOException {
258        Compiler compiler = new Compiler();
259        testNoStartUp(
260                a -> assertCompletion(a, "/o|", false, "/open ")
261        );
262        List<String> p1 = listFiles(Paths.get(""));
263        getRootDirectories().forEach(s -> p1.add(s.toString()));
264        Collections.sort(p1);
265        testNoStartUp(
266                a -> assertCompletion(a, "/open |", false, p1.toArray(new String[p1.size()]))
267        );
268        Path classDir = compiler.getClassDir();
269        List<String> p2 = listFiles(classDir);
270        testNoStartUp(
271                a -> assertCompletion(a, "/open " + classDir + "/|", false, p2.toArray(new String[p2.size()]))
272        );
273    }
274
275    @Test
276    public void testSave() throws IOException {
277        Compiler compiler = new Compiler();
278        testNoStartUp(
279                a -> assertCompletion(a, "/s|", false, "/save ", "/set ")
280        );
281        List<String> p1 = listFiles(Paths.get(""));
282        Collections.addAll(p1, "-all ", "-history ", "-start ");
283        getRootDirectories().forEach(s -> p1.add(s.toString()));
284        Collections.sort(p1);
285        testNoStartUp(
286                a -> assertCompletion(a, "/save |", false, p1.toArray(new String[p1.size()]))
287        );
288        Path classDir = compiler.getClassDir();
289        List<String> p2 = listFiles(classDir);
290        testNoStartUp(
291                a -> assertCompletion(a, "/save " + classDir + "/|",
292                false, p2.toArray(new String[p2.size()])),
293                a -> assertCompletion(a, "/save -all " + classDir + "/|",
294                false, p2.toArray(new String[p2.size()]))
295        );
296    }
297
298    @Test
299    public void testClassPath() throws IOException {
300        Compiler compiler = new Compiler();
301        Path outDir = compiler.getPath("testClasspathCompletion");
302        Files.createDirectories(outDir);
303        Files.createDirectories(outDir.resolve("dir"));
304        createIfNeeded(outDir.resolve("test.jar"));
305        createIfNeeded(outDir.resolve("test.zip"));
306        compiler.compile(outDir, "package pkg; public class A { public String toString() { return \"A\"; } }");
307        String jarName = "test.jar";
308        compiler.jar(outDir, jarName, "pkg/A.class");
309        compiler.getPath(outDir).resolve(jarName);
310        List<String> paths = listFiles(outDir, CLASSPATH_FILTER);
311        String[] pathArray = paths.toArray(new String[paths.size()]);
312        testNoStartUp(
313                a -> assertCompletion(a, "/env -class-path " + outDir + "/|", false, pathArray),
314                a -> assertCompletion(a, "/env --class-path " + outDir + "/|", false, pathArray),
315                a -> assertCompletion(a, "/env -clas    " + outDir + "/|", false, pathArray),
316                a -> assertCompletion(a, "/env --class-p    " + outDir + "/|", false, pathArray),
317                a -> assertCompletion(a, "/env --module-path . --class-p    " + outDir + "/|", false, pathArray)
318        );
319    }
320
321    @Test
322    public void testUserHome() throws IOException {
323        List<String> completions;
324        Path home = Paths.get(System.getProperty("user.home"));
325        try (Stream<Path> content = Files.list(home)) {
326            completions = content.filter(CLASSPATH_FILTER)
327                                 .map(file -> file.getFileName().toString() + (Files.isDirectory(file) ? "/" : ""))
328                                 .sorted()
329                                 .collect(Collectors.toList());
330        }
331        testNoStartUp(
332                a -> assertCompletion(a, "/env --class-path ~/|", false, completions.toArray(new String[completions.size()]))
333        );
334    }
335
336    @Test
337    public void testSet() throws IOException {
338        List<String> p1 = listFiles(Paths.get(""));
339        getRootDirectories().forEach(s -> p1.add(s.toString()));
340        Collections.sort(p1);
341
342        String[] modes = {"concise ", "normal ", "silent ", "verbose "};
343        String[] options = {"-command", "-delete", "-quiet"};
344        String[] modesWithOptions = Stream.concat(Arrays.stream(options), Arrays.stream(modes)).sorted().toArray(String[]::new);
345        test(false, new String[] {"--no-startup"},
346                a -> assertCompletion(a, "/se|", false, "/set "),
347                a -> assertCompletion(a, "/set |", false, "editor ", "feedback ", "format ", "mode ", "prompt ", "start ", "truncation "),
348
349                // /set editor
350                a -> assertCompletion(a, "/set e|", false, "editor "),
351                a -> assertCompletion(a, "/set editor |", false, p1.toArray(new String[p1.size()])),
352
353                // /set feedback
354                a -> assertCompletion(a, "/set fe|", false, "feedback "),
355                a -> assertCompletion(a, "/set fe |", false, modes),
356
357                // /set format
358                a -> assertCompletion(a, "/set fo|", false, "format "),
359                a -> assertCompletion(a, "/set fo |", false, modes),
360
361                // /set mode
362                a -> assertCompletion(a, "/set mo|", false, "mode "),
363                a -> assertCompletion(a, "/set mo |", false),
364                a -> assertCompletion(a, "/set mo newmode |", false, modesWithOptions),
365                a -> assertCompletion(a, "/set mo newmode -|", false, options),
366                a -> assertCompletion(a, "/set mo newmode -command |", false),
367                a -> assertCompletion(a, "/set mo newmode normal |", false, options),
368
369                // /set prompt
370                a -> assertCompletion(a, "/set pro|", false, "prompt "),
371                a -> assertCompletion(a, "/set pro |", false, modes),
372
373                // /set start
374                a -> assertCompletion(a, "/set st|", false, "start "),
375                a -> assertCompletion(a, "/set st |", false, p1.toArray(new String[p1.size()])),
376
377                // /set truncation
378                a -> assertCompletion(a, "/set tr|", false, "truncation "),
379                a -> assertCompletion(a, "/set tr |", false, modes)
380        );
381    }
382
383    private void createIfNeeded(Path file) throws IOException {
384        if (!Files.exists(file))
385            Files.createFile(file);
386    }
387    private List<String> listFiles(Path path) throws IOException {
388        return listFiles(path, ACCEPT_ALL);
389    }
390
391    private List<String> listFiles(Path path, Predicate<? super Path> filter) throws IOException {
392        try (Stream<Path> stream = Files.list(path)) {
393            return stream.filter(filter)
394                         .map(p -> p.getFileName().toString() + (Files.isDirectory(p) ? "/" : ""))
395                         .sorted()
396                         .collect(Collectors.toList());
397        }
398    }
399
400    private static final Predicate<? super Path> ACCEPT_ALL =
401            (file) -> !file.endsWith(".") && !file.endsWith("..");
402
403    private static final Predicate<? super Path> CLASSPATH_FILTER =
404            (file) -> ACCEPT_ALL.test(file) &&
405                    (Files.isDirectory(file) ||
406                     file.getFileName().toString().endsWith(".jar") ||
407                     file.getFileName().toString().endsWith(".zip"));
408
409    private static Iterable<? extends Path> getRootDirectories() {
410        return StreamSupport.stream(FileSystems.getDefault()
411                                               .getRootDirectories()
412                                               .spliterator(),
413                                    false)
414                            .filter(p -> Files.exists(p))
415                            .collect(Collectors.toList());
416    }
417}
418