1 /*
2  * Copyright (c) 2016, 2020, 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 8153716 8143955 8151754 8150382 8153920 8156910 8131024 8160089 8153897 8167128 8154513 8170015 8170368 8172102 8172103  8165405 8173073 8173848 8174041 8173916 8174028 8174262 8174797 8177079 8180508 8177466 8172154 8192979 8191842 8198573 8198801 8210596 8210959 8215099 8199623 8236715 8239536 8247456 8246774 8238173
27  * @summary Simple jshell tool tests
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  * @build KullaTesting TestingInputStream
33  * @run testng ToolSimpleTest
34  */
35 
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.List;
39 import java.util.Locale;
40 import java.util.function.Consumer;
41 import java.util.regex.Pattern;
42 import java.util.stream.Collectors;
43 import java.util.stream.Stream;
44 
45 import org.testng.annotations.Test;
46 
47 import static org.testng.Assert.assertEquals;
48 import static org.testng.Assert.assertTrue;
49 
50 public class ToolSimpleTest extends ReplToolTesting {
51 
52     @Test
testRemaining()53     public void testRemaining() {
54         test(
55                 (a) -> assertCommand(a, "int z; z =", "z ==> 0"),
56                 (a) -> assertCommand(a, "5", "z ==> 5"),
57                 (a) -> assertCommand(a, "/*nada*/; int q =", ""),
58                 (a) -> assertCommand(a, "77", "q ==> 77"),
59                 (a) -> assertCommand(a, "//comment;", ""),
60                 (a) -> assertCommand(a, "int v;", "v ==> 0"),
61                 (a) -> assertCommand(a, "int v; int c",
62                         "v ==> 0\n" +
63                         "c ==> 0")
64         );
65     }
66 
67     @Test
testOpenComment()68     public void testOpenComment() {
69         test(
70                 (a) -> assertCommand(a, "int z = /* blah", ""),
71                 (a) -> assertCommand(a, "baz */ 5", "z ==> 5"),
72                 (a) -> assertCommand(a, "/** hoge ", ""),
73                 (a) -> assertCommand(a, "baz **/", ""),
74                 (a) -> assertCommand(a, "int v", "v ==> 0")
75         );
76     }
77 
78     @Test
testSwitchExpression()79     public void testSwitchExpression() {
80         test(false, new String[]{"--no-startup"},
81                 (a) -> assertCommand(a, "enum Day {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }", "|  created enum Day"),
82                 (a) -> assertCommand(a, "Day day = Day.FRIDAY;", "day ==> FRIDAY"),
83                 (a) -> assertCommand(a, "switch (day) {", ""),
84                 (a) -> assertCommand(a, "case MONDAY, FRIDAY, SUNDAY -> 6;", ""),
85                 (a) -> assertCommand(a, "case TUESDAY -> 7;", ""),
86                 (a) -> assertCommand(a, "case THURSDAY, SATURDAY -> 8;", ""),
87                 (a) -> assertCommand(a, "case WEDNESDAY -> 9;", ""),
88                 (a) -> assertCommandOutputContains(a, "}", " ==> 6")
89                 );
90     }
91 
92     @Test
testSwitchExpressionCompletion()93     public void testSwitchExpressionCompletion() {
94         test(false, new String[]{"--no-startup"},
95                 (a) -> assertCommand(a, "enum Day {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }", "|  created enum Day"),
96                 (a) -> assertCommand(a, "Day day = Day.FRIDAY;", "day ==> FRIDAY"),
97                 (a) -> assertCommand(a, "switch (day) {", ""),
98                 (a) -> assertCommand(a, "case MONDAY, FRIDAY, SUNDAY -> 6;", ""),
99                 (a) -> assertCommand(a, "case TUESDAY -> 7;", ""),
100                 (a) -> assertCommand(a, "case THURSDAY, SATURDAY -> 8;", ""),
101                 (a) -> assertCommand(a, "case WEDNESDAY -> 9;", ""),
102                 (a) -> assertCommand(a, "} +", ""),
103                 (a) -> assertCommandOutputContains(a, "1000", " ==> 1006")
104                 );
105     }
106 
107     @Test
testLessThan()108     public void testLessThan() {
109         test(
110                 (a) -> assertCommand(a, "45", "$1 ==> 45"),
111                 (a) -> assertCommand(a, "72", "$2 ==> 72"),
112                 (a) -> assertCommand(a, "$1 < $2", "$3 ==> true"),
113                 (a) -> assertCommand(a, "int a, b", "a ==> 0\n" +
114                         "b ==> 0"),
115                 (a) -> assertCommand(a, "a < b", "$6 ==> false")
116         );
117     }
118 
119     @Test
testChainedThrow()120     public void testChainedThrow() {
121         test(
122                 (a) -> assertCommand(a, "void p() throws Exception { ((String) null).toString(); }",
123                         "|  created method p()"),
124                 (a) -> assertCommand(a, "void n() throws Exception { try { p(); } catch (Exception ex) { throw new IOException(\"bar\", ex); }}",
125                         "|  created method n()"),
126                 (a) -> assertCommand(a, "void m() { try { n(); } catch (Exception ex) { throw new RuntimeException(\"foo\", ex); }}",
127                         "|  created method m()"),
128                 (a) -> assertCommand(a, "m()",
129                           "|  Exception java.lang.RuntimeException: foo\n"
130                         + "|        at m (#3:1)\n"
131                         + "|        at (#4:1)\n"
132                         + "|  Caused by: java.io.IOException: bar\n"
133                         + "|        at n (#2:1)\n"
134                         + "|        ...\n"
135                         + "|  Caused by: java.lang.NullPointerException: Cannot invoke \"String.toString()\" because \"null\" is null\n"
136                         + "|        at p (#1:1)\n"
137                         + "|        ..."),
138                 (a) -> assertCommand(a, "/drop p",
139                         "|  dropped method p()"),
140                 (a) -> assertCommand(a, "m()",
141                         "|  attempted to call method n() which cannot be invoked until method p() is declared")
142         );
143     }
144 
145     @Test
testThrowWithPercent()146     public void testThrowWithPercent() {
147         test(
148                 (a) -> assertCommandCheckOutput(a,
149                         "URI u = new URI(\"http\", null, \"h\", -1, \"a\" + (char)0x04, null, null);", (s) ->
150                                 assertTrue(s.contains("URISyntaxException") && !s.contains("JShellTool"),
151                                         "Output: '" + s + "'")),
152                 (a) -> assertCommandCheckOutput(a,
153                         "throw new Exception(\"%z\")", (s) ->
154                                 assertTrue(s.contains("java.lang.Exception") && !s.contains("UnknownFormatConversionException"),
155                                         "Output: '" + s + "'"))
156         );
157     }
158 
159     @Test
oneLineOfError()160     public void oneLineOfError() {
161         test(
162                 (a) -> assertCommand(a, "12+", null),
163                 (a) -> assertCommandCheckOutput(a, "  true", (s) ->
164                         assertTrue(s.contains("12+") && !s.contains("true"), "Output: '" + s + "'"))
165         );
166     }
167 
168     @Test
defineVariables()169     public void defineVariables() {
170         test(
171                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
172                 (a) -> assertCommandCheckOutput(a, "/vars", assertVariables()),
173                 (a) -> assertVariable(a, "int", "a"),
174                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
175                 (a) -> assertCommandCheckOutput(a, "/vars", assertVariables()),
176                 (a) -> assertVariable(a, "double", "a", "1", "1.0"),
177                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
178                 (a) -> assertCommandCheckOutput(a, "/vars", assertVariables()),
179                 (a) -> evaluateExpression(a, "double", "2 * a", "2.0"),
180                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
181                 (a) -> assertCommandCheckOutput(a, "/vars", assertVariables())
182         );
183     }
184 
185     @Test
defineMethods()186     public void defineMethods() {
187         test(
188                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
189                 (a) -> assertCommandCheckOutput(a, "/methods", assertMethods()),
190                 (a) -> assertMethod(a, "int f() { return 0; }", "()int", "f"),
191                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
192                 (a) -> assertCommandCheckOutput(a, "/methods", assertMethods()),
193                 (a) -> assertMethod(a, "void f(int a) { g(); }", "(int)void", "f"),
194                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
195                 (a) -> assertCommandCheckOutput(a, "/methods", assertMethods()),
196                 (a) -> assertMethod(a, "void g() {}", "()void", "g"),
197                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
198                 (a) -> assertCommandCheckOutput(a, "/methods", assertMethods())
199         );
200     }
201 
202     @Test
defineTypes()203     public void defineTypes() {
204         test(
205                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
206                 (a) -> assertCommandCheckOutput(a, "/types", assertClasses()),
207                 (a) -> assertClass(a, "class A { }", "class", "A"),
208                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
209                 (a) -> assertCommandCheckOutput(a, "/types", assertClasses()),
210                 (a) -> assertClass(a, "interface A { }", "interface", "A"),
211                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
212                 (a) -> assertCommandCheckOutput(a, "/types", assertClasses()),
213                 (a) -> assertClass(a, "enum A { }", "enum", "A"),
214                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
215                 (a) -> assertCommandCheckOutput(a, "/types", assertClasses()),
216                 (a) -> assertClass(a, "@interface A { }", "@interface", "A"),
217                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
218                 (a) -> assertCommandCheckOutput(a, "/types", assertClasses())
219         );
220     }
221 
222     @Test
defineImports()223     public void defineImports() {
224         test(
225                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
226                 (a) -> assertCommandCheckOutput(a, "/imports", assertImports()),
227                 (a) -> assertImport(a, "import java.util.stream.Stream;", "", "java.util.stream.Stream"),
228                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
229                 (a) -> assertCommandCheckOutput(a, "/imports", assertImports()),
230                 (a) -> assertImport(a, "import java.util.stream.*;", "", "java.util.stream.*"),
231                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
232                 (a) -> assertCommandCheckOutput(a, "/imports", assertImports()),
233                 (a) -> assertImport(a, "import static java.lang.Math.PI;", "static", "java.lang.Math.PI"),
234                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
235                 (a) -> assertCommandCheckOutput(a, "/imports", assertImports()),
236                 (a) -> assertImport(a, "import static java.lang.Math.*;", "static", "java.lang.Math.*"),
237                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
238                 (a) -> assertCommandCheckOutput(a, "/imports", assertImports())
239         );
240     }
241 
242     @Test
defineVar()243     public void defineVar() {
244         test(
245                 (a) -> assertCommand(a, "int x = 72", "x ==> 72"),
246                 (a) -> assertCommand(a, "x", "x ==> 72"),
247                 (a) -> assertCommand(a, "/vars", "|    int x = 72")
248         );
249     }
250 
251     @Test
defineUnresolvedVar()252     public void defineUnresolvedVar() {
253         test(
254                 (a) -> assertCommand(a, "undefined x",
255                         "|  created variable x, however, it cannot be referenced until class undefined is declared"),
256                 (a) -> assertCommand(a, "/vars", "|    undefined x = (not-active)")
257         );
258     }
259 
260     @Test
testUnresolved()261     public void testUnresolved() {
262         test(
263                 (a) -> assertCommand(a, "int f() { return g() + x + new A().a; }",
264                         "|  created method f(), however, it cannot be invoked until method g(), variable x, and class A are declared"),
265                 (a) -> assertCommand(a, "f()",
266                         "|  attempted to call method f() which cannot be invoked until method g(), variable x, and class A are declared"),
267                 (a) -> assertCommandOutputStartsWith(a, "int g() { return x; }",
268                         "|  created method g(), however, it cannot be invoked until variable x is declared"),
269                 (a) -> assertCommand(a, "g()", "|  attempted to call method g() which cannot be invoked until variable x is declared")
270         );
271     }
272 
273     @Test
testAbstractMethod()274     public void testAbstractMethod() {
275         test(
276                 (a) -> assertCommand(a, "abstract int f(int x);",
277                         "|  created method f(int), however, it cannot be invoked until method f(int) is declared"),
278                 (a) -> assertCommand(a, "f(13)",
279                         "|  attempted to call method f(int) which cannot be invoked until method f(int) is declared"),
280                 (a) -> assertCommand(a, " abstract void m(Blah b);",
281                         "|  created method m(Blah), however, it cannot be referenced until class Blah, and method m(Blah) are declared")
282         );
283     }
284 
285     // 8199623
286     @Test
testTwoForkedDrop()287     public void testTwoForkedDrop() {
288         test(
289                 (a) -> assertCommand(a, "void p() throws Exception { ((String) null).toString(); }",
290                         "|  created method p()"),
291                 (a) -> assertCommand(a, "void n() throws Exception { try { p(); } catch (Exception ex) { throw new IOException(\"bar\", ex); }} ",
292                         "|  created method n()"),
293                 (a) -> assertCommand(a, "void m() { try { n(); } catch (Exception ex) { throw new RuntimeException(\"foo\", ex); }}",
294                         "|  created method m()"),
295                 (a) -> assertCommand(a, "void c() throws Throwable { p(); }",
296                         "|  created method c()"),
297                 (a) -> assertCommand(a, "/drop p",
298                         "|  dropped method p()"),
299                 (a) -> assertCommand(a, "m()",
300                         "|  attempted to call method n() which cannot be invoked until method p() is declared"),
301                 (a) -> assertCommand(a, "/meth n",
302                         "|    void n()\n" +
303                         "|       which cannot be invoked until method p() is declared"),
304                 (a) -> assertCommand(a, "/meth m",
305                         "|    void m()"),
306                 (a) -> assertCommand(a, "/meth c",
307                         "|    void c()\n" +
308                                 "|       which cannot be invoked until method p() is declared")
309         );
310     }
311 
312     @Test
testUnknownCommand()313     public void testUnknownCommand() {
314         test((a) -> assertCommand(a, "/unknown",
315                 "|  Invalid command: /unknown\n" +
316                 "|  Type /help for help."));
317     }
318 
319     @Test
testEmptyClassPath()320     public void testEmptyClassPath() {
321         test(after -> assertCommand(after, "/env --class-path", "|  Argument to class-path missing."));
322     }
323 
324     @Test
testInvalidClassPath()325     public void testInvalidClassPath() {
326         test(
327                 a -> assertCommand(a, "/env --class-path snurgefusal",
328                         "|  File 'snurgefusal' for '--class-path' is not found."),
329                 a -> assertCommand(a, "/env --class-path ?",
330                         "|  File '?' for '--class-path' is not found.")
331         );
332     }
333 
334     @Test
testNoArgument()335     public void testNoArgument() {
336         test(
337                 (a) -> assertCommand(a, "/save",
338                         "|  '/save' requires a filename argument."),
339                 (a) -> assertCommand(a, "/open",
340                         "|  '/open' requires a filename argument."),
341                 (a) -> assertCommandOutputStartsWith(a, "/drop",
342                         "|  In the /drop argument, please specify an import, variable, method, or class to drop.")
343         );
344     }
345 
346     @Test
testDebug()347     public void testDebug() {
348         test(
349                 (a) -> assertCommand(a, "/deb", "|  Debugging on"),
350                 (a) -> assertCommand(a, "/debug", "|  Debugging off"),
351                 (a) -> assertCommand(a, "/debug", "|  Debugging on"),
352                 (a) -> assertCommand(a, "/deb", "|  Debugging off")
353         );
354     }
355 
356     @Test
testDrop()357     public void testDrop() {
358         test(false, new String[]{"--no-startup"},
359                 a -> assertVariable(a, "int", "a"),
360                 a -> dropVariable(a, "/drop 1", "int a = 0", "|  dropped variable a"),
361                 a -> assertMethod(a, "int b() { return 0; }", "()int", "b"),
362                 a -> dropMethod(a, "/drop 2", "int b()", "|  dropped method b()"),
363                 a -> assertClass(a, "class A {}", "class", "A"),
364                 a -> dropClass(a, "/drop 3", "class A", "|  dropped class A"),
365                 a -> assertImport(a, "import java.util.stream.*;", "", "java.util.stream.*"),
366                 a -> dropImport(a, "/drop 4", "import java.util.stream.*", ""),
367                 a -> assertCommand(a, "for (int i = 0; i < 10; ++i) {}", ""),
368                 a -> assertCommand(a, "/drop 5", ""),
369                 a -> assertCommand(a, "/list", ""),
370                 a -> assertCommandCheckOutput(a, "/vars", assertVariables()),
371                 a -> assertCommandCheckOutput(a, "/methods", assertMethods()),
372                 a -> assertCommandCheckOutput(a, "/types", assertClasses()),
373                 a -> assertCommandCheckOutput(a, "/imports", assertImports())
374         );
375         test(false, new String[]{"--no-startup"},
376                 a -> assertVariable(a, "int", "a"),
377                 a -> dropVariable(a, "/drop a", "int a = 0", "|  dropped variable a"),
378                 a -> assertMethod(a, "int b() { return 0; }", "()int", "b"),
379                 a -> dropMethod(a, "/drop b", "int b()", "|  dropped method b()"),
380                 a -> assertClass(a, "class A {}", "class", "A"),
381                 a -> dropClass(a, "/drop A", "class A", "|  dropped class A"),
382                 a -> assertCommandCheckOutput(a, "/vars", assertVariables()),
383                 a -> assertCommandCheckOutput(a, "/methods", assertMethods()),
384                 a -> assertCommandCheckOutput(a, "/types", assertClasses()),
385                 a -> assertCommandCheckOutput(a, "/imports", assertImports())
386         );
387     }
388 
389     @Test
testDropRange()390     public void testDropRange() {
391         test(false, new String[]{"--no-startup"},
392                 a -> assertVariable(a, "int", "a"),
393                 a -> assertMethod(a, "int b() { return 0; }", "()int", "b"),
394                 a -> assertClass(a, "class A {}", "class", "A"),
395                 a -> assertImport(a, "import java.util.stream.*;", "", "java.util.stream.*"),
396                 a -> assertCommand(a, "for (int i = 0; i < 10; ++i) {}", ""),
397                 a -> assertCommand(a, "/drop 3-5 b 1",
398                         "|  dropped class A\n" +
399                         "|  dropped method b()\n" +
400                         "|  dropped variable a\n"),
401                 a -> assertCommand(a, "/list", "")
402         );
403     }
404 
405     @Test
testDropNegative()406     public void testDropNegative() {
407         test(false, new String[]{"--no-startup"},
408                 a -> assertCommandOutputStartsWith(a, "/drop 0", "|  No snippet with ID: 0"),
409                 a -> assertCommandOutputStartsWith(a, "/drop a", "|  No such snippet: a"),
410                 a -> assertCommandCheckOutput(a, "/drop",
411                         assertStartsWith("|  In the /drop argument, please specify an import, variable, method, or class to drop.")),
412                 a -> assertVariable(a, "int", "a"),
413                 a -> assertCommand(a, "a", "a ==> 0"),
414                 a -> assertCommand(a, "/drop 2", ""),
415                 a -> assertCommand(a, "/drop 2",
416                         "|  This command does not accept the snippet '2' : a\n" +
417                         "|  See /types, /methods, /vars, or /list")
418         );
419     }
420 
421     @Test
testAmbiguousDrop()422     public void testAmbiguousDrop() {
423         test(
424                 a -> assertVariable(a, "int", "a"),
425                 a -> assertMethod(a, "int a() { return 0; }", "()int", "a"),
426                 a -> assertClass(a, "class a {}", "class", "a"),
427                 a -> assertCommand(a, "/drop a",
428                         "|  dropped variable a\n" +
429                         "|  dropped method a()\n" +
430                         "|  dropped class a")
431         );
432         test(
433                 a -> assertMethod(a, "int a() { return 0; }", "()int", "a"),
434                 a -> assertMethod(a, "double a(int a) { return 0; }", "(int)double", "a"),
435                 a -> assertMethod(a, "double a(double a) { return 0; }", "(double)double", "a"),
436                 a -> assertCommand(a, "/drop a",
437                         "|  dropped method a()\n" +
438                         "|  dropped method a(int)\n" +
439                         "|  dropped method a(double)\n")
440         );
441     }
442 
443     @Test
testApplicationOfPost()444     public void testApplicationOfPost() {
445         test(
446                 (a) -> assertCommand(a, "/set mode t normal -command", "|  Created new feedback mode: t"),
447                 (a) -> assertCommand(a, "/set feedback t", "|  Feedback mode: t"),
448                 (a) -> assertCommand(a, "/set format t post \"$%n\"", ""),
449                 (a) -> assertCommand(a, "/set prompt t \"+\" \"-\"", ""),
450                 (a) -> assertCommand(a, "/set prompt t", "|  /set prompt t \"+\" \"-\"$")
451         );
452     }
453 
454     @Test
testHelpLength()455     public void testHelpLength() {
456         Consumer<String> testOutput = (s) -> {
457             List<String> ss = Stream.of(s.split("\n"))
458                     .filter(l -> !l.isEmpty())
459                     .collect(Collectors.toList());
460             assertTrue(ss.size() >= 10, "Help does not print enough lines:" + s);
461         };
462         test(
463                 (a) -> assertCommandCheckOutput(a, "/?", testOutput),
464                 (a) -> assertCommandCheckOutput(a, "/help", testOutput),
465                 (a) -> assertCommandCheckOutput(a, "/help /list", testOutput)
466         );
467     }
468 
469     @Test
testHelp()470     public void testHelp() {
471         test(
472                 (a) -> assertHelp(a, "/?", "/list", "/help", "/exit", "intro"),
473                 (a) -> assertHelp(a, "/help", "/list", "/help", "/exit", "intro"),
474                 (a) -> assertHelp(a, "/help short", "shortcuts", "Tab"),
475                 (a) -> assertHelp(a, "/help keys", "line", "Shift", "imports", "history"),
476                 (a) -> assertHelp(a, "/? /li", "/list -all", "snippets"),
477                 (a) -> assertHelp(a, "/help /set prompt", "optionally contain '%s'", "quoted"),
478                 (a) -> assertHelp(a, "/help /help", "/help <command>"),
479                 (a) -> assertHelp(a, "/help li", "/list -start"),
480                 (a) -> assertHelp(a, "/help fe", "/set feedback -retain")
481         );
482     }
483 
484     @Test
testHelpStart()485     public void testHelpStart() {
486         test(
487                 (a) -> assertCommandCheckOutput(a, "/help /exit",
488                         s -> assertTrue(s.replaceAll("\\r\\n?", "\n").startsWith(
489                                 "|  \n" +
490                                 "|                                   /exit\n" +
491                                 "|                                   =====\n" +
492                                 "|  "
493                         ))
494                 )
495         );
496     }
497 
498     @Test
testHelpFormat()499     public void testHelpFormat() {
500         test(
501                 (a) -> assertCommandCheckOutput(a, "/help", s -> {
502                     String[] lines = s.split("\\R");
503                     assertTrue(lines.length > 20,
504                             "Too few lines of /help output: " + lines.length
505                           + "\n" + s);
506                     for (int i = 0; i < lines.length; ++i) {
507                         String l = lines[i];
508                         assertTrue(l.startsWith("| "),
509                                 "Expected /help line to start with | :\n" + l);
510                         assertTrue(l.length() <= 80,
511                                 "/help line too long: " + l.length() + "\n" + l);
512                     }
513                  })
514         );
515     }
516 
517     @Test
testConfusedUserPseudoCommands()518     public void testConfusedUserPseudoCommands() {
519         test(
520                 (a) -> assertHelp(a, "/-<n>", "last snippet", "digits"),
521                 (a) -> assertHelp(a, "/<id>", "last snippet", "digits")
522         );
523     }
524 
assertHelp(boolean a, String command, String... find)525     private void assertHelp(boolean a, String command, String... find) {
526         assertCommandCheckOutput(a, command, s -> {
527             for (String f : find) {
528                 assertTrue(s.contains(f),
529                         "Expected output of " + command + " to contain: " + f
530                       + "\n" + s);
531             }
532         });
533     }
534 
535     // Check that each line of output contains the corresponding string from the list
checkLineToList(String in, List<String> match)536     private void checkLineToList(String in, List<String> match) {
537         String trimmed = in.trim();
538         String[] res = trimmed.isEmpty()
539                 ? new String[0]
540                 : trimmed.split("\n");
541         assertEquals(res.length, match.size(), "Got: " + Arrays.asList(res));
542         for (int i = 0; i < match.size(); ++i) {
543             assertTrue(res[i].contains(match.get(i)));
544         }
545     }
546 
547     @Test
testListArgs()548     public void testListArgs() {
549         String arg = "qqqq";
550         List<String> startVarList = new ArrayList<>(START_UP);
551         startVarList.add("int aardvark");
552         startVarList.add("int weevil");
553         test(
554                 a -> assertCommandCheckOutput(a, "/list -all",
555                         s -> checkLineToList(s, START_UP)),
556                 a -> assertCommandOutputStartsWith(a, "/list " + arg,
557                         "|  No such snippet: " + arg),
558                 a -> assertVariable(a, "int", "aardvark"),
559                 a -> assertVariable(a, "int", "weevil"),
560                 a -> assertCommandOutputContains(a, "/list aardvark", "aardvark"),
561                 a -> assertCommandCheckOutput(a, "/list -start",
562                         s -> checkLineToList(s, START_UP)),
563                 a -> assertCommandCheckOutput(a, "/list -all",
564                         s -> checkLineToList(s, startVarList)),
565                 a -> assertCommandOutputStartsWith(a, "/list s3",
566                         "s3 : import"),
567                 a -> assertCommandCheckOutput(a, "/list 1-2 s3",
568                         s -> {
569                             assertTrue(Pattern.matches(".*aardvark.*\\R.*weevil.*\\R.*s3.*import.*", s.trim()),
570                                     "No match: " + s);
571                         }),
572                 a -> assertCommandOutputStartsWith(a, "/list " + arg,
573                         "|  No such snippet: " + arg)
574         );
575     }
576 
577     @Test
testVarsArgs()578     public void testVarsArgs() {
579         String arg = "qqqq";
580         List<String> startVarList = new ArrayList<>();
581         test(
582                 a -> assertCommandCheckOutput(a, "/vars -all",
583                         s -> checkLineToList(s, startVarList)),
584                 a -> assertCommand(a, "/vars " + arg,
585                         "|  No such snippet: " + arg),
586                 a -> assertVariable(a, "int", "aardvark"),
587                 a -> assertMethod(a, "int f() { return 0; }", "()int", "f"),
588                 a -> assertVariable(a, "int", "a"),
589                 a -> assertVariable(a, "double", "a", "1", "1.0"),
590                 a -> assertCommandOutputStartsWith(a, "/vars aardvark",
591                         "|    int aardvark = 0"),
592                 a -> assertCommandCheckOutput(a, "/vars -start",
593                         s -> checkLineToList(s, startVarList)),
594                 a -> assertCommandOutputStartsWith(a, "/vars -all",
595                         "|    int aardvark = 0\n|    int a = "),
596                 a -> assertCommandOutputStartsWith(a, "/vars 1-4",
597                         "|    int aardvark = 0\n|    int a = "),
598                 a -> assertCommandOutputStartsWith(a, "/vars f",
599                         "|  This command does not accept the snippet 'f'"),
600                 a -> assertCommand(a, "/var " + arg,
601                         "|  No such snippet: " + arg)
602         );
603     }
604 
605     @Test
testMethodsArgs()606     public void testMethodsArgs() {
607         String arg = "qqqq";
608         List<String> printingMethodList = new ArrayList<>(PRINTING_CMD_METHOD);
609         test(new String[]{"--startup", "PRINTING"},
610                 a -> assertCommandCheckOutput(a, "/methods -all",
611                         s -> checkLineToList(s, printingMethodList)),
612                 a -> assertCommandCheckOutput(a, "/methods -start",
613                         s -> checkLineToList(s, printingMethodList)),
614                 a -> assertCommandCheckOutput(a, "/methods print println printf",
615                         s -> checkLineToList(s, printingMethodList)),
616                 a -> assertCommandCheckOutput(a, "/methods println",
617                         s -> assertEquals(s.trim().split("\n").length, 10)),
618                 a -> assertCommandCheckOutput(a, "/methods",
619                         s -> checkLineToList(s, printingMethodList)),
620                 a -> assertCommandOutputStartsWith(a, "/methods " + arg,
621                         "|  No such snippet: " + arg),
622                 a -> assertMethod(a, "int f() { return 0; }", "()int", "f"),
623                 a -> assertVariable(a, "int", "aardvark"),
624                 a -> assertMethod(a, "void f(int a) { g(); }", "(int)void", "f"),
625                 a -> assertMethod(a, "void g() {}", "()void", "g"),
626                 a -> assertCommandOutputStartsWith(a, "/methods " + arg,
627                         "|  No such snippet: " + arg),
628                 a -> assertCommandOutputStartsWith(a, "/methods aardvark",
629                         "|  This command does not accept the snippet 'aardvark' : int aardvark"),
630                 a -> assertCommandCheckOutput(a, "/methods -start",
631                         s -> checkLineToList(s, printingMethodList)),
632                 a -> assertCommandCheckOutput(a, "/methods print println printf",
633                         s -> checkLineToList(s, printingMethodList)),
634                 a -> assertCommandOutputStartsWith(a, "/methods g",
635                         "|    void g()"),
636                 a -> assertCommandOutputStartsWith(a, "/methods f",
637                         "|    int f()\n" +
638                         "|    void f(int)")
639         );
640     }
641 
642     @Test
testMethodsWithErrors()643     public void testMethodsWithErrors() {
644         test(new String[]{"--no-startup"},
645                 a -> assertCommand(a, "double m(int x) { return x; }",
646                         "|  created method m(int)"),
647                 a -> assertCommand(a, "GARBAGE junk() { return TRASH; }",
648                         "|  created method junk(), however, it cannot be referenced until class GARBAGE, and variable TRASH are declared"),
649                 a -> assertCommand(a, "int w = 5;",
650                         "w ==> 5"),
651                 a -> assertCommand(a, "int tyer() { return w; }",
652                         "|  created method tyer()"),
653                 a -> assertCommand(a, "String w = \"hi\";",
654                         "w ==> \"hi\""),
655                 a -> assertCommand(a, "/methods",
656                         "|    double m(int)\n" +
657                         "|    GARBAGE junk()\n" +
658                         "|       which cannot be referenced until class GARBAGE, and variable TRASH are declared\n" +
659                         "|    int tyer()\n" +
660                         "|       which cannot be invoked until this error is corrected: \n" +
661                         "|          incompatible types: java.lang.String cannot be converted to int\n" +
662                         "|          int tyer() { return w; }\n" +
663                         "|                              ^\n")
664         );
665     }
666 
667     @Test
testTypesWithErrors()668     public void testTypesWithErrors() {
669         test(new String[]{"--no-startup"},
670                 a -> assertCommand(a, "class C extends NONE { int x; }",
671                         "|  created class C, however, it cannot be referenced until class NONE is declared"),
672                 a -> assertCommand(a, "class D { void m() { System.out.println(nada); } }",
673                         "|  created class D, however, it cannot be instantiated or its methods invoked until variable nada is declared"),
674                 a -> assertCommand(a, "/types",
675                         "|    class C\n" +
676                         "|       which cannot be referenced until class NONE is declared\n" +
677                         "|    class D\n" +
678                         "|       which cannot be instantiated or its methods invoked until variable nada is declared\n")
679         );
680     }
681 
682     @Test
testTypesArgs()683     public void testTypesArgs() {
684         String arg = "qqqq";
685         List<String> startTypeList = new ArrayList<>();
686         test(
687                 a -> assertCommandCheckOutput(a, "/types -all",
688                         s -> checkLineToList(s, startTypeList)),
689                 a -> assertCommandCheckOutput(a, "/types -start",
690                         s -> checkLineToList(s, startTypeList)),
691                 a -> assertCommandOutputStartsWith(a, "/types " + arg,
692                         "|  No such snippet: " + arg),
693                 a -> assertVariable(a, "int", "aardvark"),
694                 (a) -> assertClass(a, "class A { }", "class", "A"),
695                 (a) -> assertClass(a, "interface A { }", "interface", "A"),
696                 a -> assertCommandOutputStartsWith(a, "/types -all",
697                         "|    class A\n" +
698                         "|    interface A"),
699                 (a) -> assertClass(a, "enum E { }", "enum", "E"),
700                 (a) -> assertClass(a, "@interface B { }", "@interface", "B"),
701                 a -> assertCommand(a, "/types aardvark",
702                         "|  This command does not accept the snippet 'aardvark' : int aardvark;"),
703                 a -> assertCommandOutputStartsWith(a, "/types A",
704                         "|    interface A"),
705                 a -> assertCommandOutputStartsWith(a, "/types E",
706                         "|    enum E"),
707                 a -> assertCommandOutputStartsWith(a, "/types B",
708                         "|    @interface B"),
709                 a -> assertCommandOutputStartsWith(a, "/types " + arg,
710                         "|  No such snippet: " + arg),
711                 a -> assertCommandCheckOutput(a, "/types -start",
712                         s -> checkLineToList(s, startTypeList))
713         );
714     }
715 
716     @Test
testBlankLinesInSnippetContinuation()717     public void testBlankLinesInSnippetContinuation() {
718         test(Locale.ROOT, false, new String[]{"--no-startup"}, "",
719                 a -> assertCommand(a, "class C {",
720                         ""),
721                 a -> assertCommand(a, "",
722                         ""),
723                 a -> assertCommand(a, "",
724                         ""),
725                 a -> assertCommand(a, "  int x;",
726                         ""),
727                 a -> assertCommand(a, "",
728                         ""),
729                 a -> assertCommand(a, "",
730                         ""),
731                 a -> assertCommand(a, "}",
732                         "|  created class C"),
733                 a -> assertCommand(a, "/list",
734                         "\n" +
735                         "   1 : class C {\n" +
736                         "       \n" +
737                         "       \n" +
738                         "         int x;\n" +
739                         "       \n" +
740                         "       \n" +
741                         "       }")
742         );
743     }
744 
745     @Test
testCompoundStart()746     public void testCompoundStart() {
747         test(new String[]{"--startup", "DEFAULT", "--startup", "PRINTING"},
748                 (a) -> assertCommand(a, "printf(\"%4.2f\", Math.PI)",
749                         "", "", null, "3.14", "")
750         );
751     }
752 
753     @Test
testJavaSeStart()754     public void testJavaSeStart() {
755         test(new String[]{"--startup", "JAVASE"},
756                 (a) -> assertCommand(a, "ZoneOffsetTransitionRule.TimeDefinition.WALL",
757                         "$1 ==> WALL")
758         );
759     }
760 
761     @Test
testJavaSeSetStart()762     public void testJavaSeSetStart() {
763         test(
764                 (a) -> assertCommand(a, "/set sta JAVASE", ""),
765                 (a) -> assertCommand(a, "/reset", "|  Resetting state."),
766                 (a) -> assertCommandCheckOutput(a, "/li -a",
767                             s -> assertTrue(s.split("import ").length > 160,
768                             "not enough imports for JAVASE:\n" + s))
769         );
770     }
771 
772     @Test
defineClasses()773     public void defineClasses() {
774         test(
775                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
776                 (a) -> assertCommandCheckOutput(a, "/types", assertClasses()),
777                 (a) -> assertClass(a, "class A { }", "class", "A"),
778                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
779                 (a) -> assertCommandCheckOutput(a, "/types", assertClasses()),
780                 (a) -> assertClass(a, "interface A { }", "interface", "A"),
781                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
782                 (a) -> assertCommandCheckOutput(a, "/types", assertClasses()),
783                 (a) -> assertClass(a, "enum A { }", "enum", "A"),
784                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
785                 (a) -> assertCommandCheckOutput(a, "/types", assertClasses()),
786                 (a) -> assertClass(a, "@interface A { }", "@interface", "A"),
787                 (a) -> assertCommandCheckOutput(a, "/list", assertList()),
788                 (a) -> assertCommandCheckOutput(a, "/types", assertClasses())
789         );
790     }
791 
792     @Test
testCommandPrefix()793     public void testCommandPrefix() {
794         test(a -> assertCommandCheckOutput(a, "/s",
795                       assertStartsWith("|  Command: '/s' is ambiguous: /save, /set")),
796              a -> assertCommand(a, "int var", "var ==> 0"),
797              a -> assertCommandCheckOutput(a, "/va",
798                       assertStartsWith("|    int var = 0")),
799              a -> assertCommandCheckOutput(a, "/save",
800                       assertStartsWith("|  '/save' requires a filename argument.")));
801     }
802 
803     @Test
testOptionQ()804     public void testOptionQ() {
805         test(Locale.ROOT, false, new String[]{"-q", "--no-startup"}, "",
806                 (a) -> assertCommand(a, "1+1", "$1 ==> 2"),
807                 (a) -> assertCommand(a, "int x = 5", "")
808         );
809     }
810 
811     @Test
testOptionS()812     public void testOptionS() {
813         test(Locale.ROOT, false, new String[]{"-s", "--no-startup"}, "",
814                 (a) -> assertCommand(a, "1+1", "")
815         );
816     }
817 
818     @Test
testOptionV()819     public void testOptionV() {
820         test(new String[]{"-v", "--no-startup"},
821                 (a) -> assertCommand(a, "1+1",
822                         "$1 ==> 2\n" +
823                         "|  created scratch variable $1 : int")
824         );
825     }
826 
827     @Test
testOptionFeedback()828     public void testOptionFeedback() {
829         test(Locale.ROOT, false, new String[]{"--feedback", "concise", "--no-startup"}, "",
830                 (a) -> assertCommand(a, "1+1", "$1 ==> 2"),
831                 (a) -> assertCommand(a, "int x = 5", "")
832         );
833     }
834 
835     @Test
testCompoundOptions()836     public void testCompoundOptions() {
837         Consumer<String> confirmNoStartup = s -> {
838                     assertEquals(0, Stream.of(s.split("\n"))
839                             .filter(l -> !l.isEmpty())
840                             .count(), "Expected no lines: " + s);
841                 };
842         test(Locale.ROOT, false, new String[]{"-nq"}, "",
843                 (a) -> assertCommandCheckOutput(a, "/list -all", confirmNoStartup),
844                 (a) -> assertCommand(a, "1+1", "$1 ==> 2"),
845                 (a) -> assertCommand(a, "int x = 5", "")
846         );
847         test(Locale.ROOT, false, new String[]{"-qn"}, "",
848                 (a) -> assertCommandCheckOutput(a, "/list -all", confirmNoStartup),
849                 (a) -> assertCommand(a, "1+1", "$1 ==> 2"),
850                 (a) -> assertCommand(a, "int x = 5", "")
851         );
852         test(Locale.ROOT, false, new String[]{"-ns"}, "",
853                 (a) -> assertCommandCheckOutput(a, "/list -all", confirmNoStartup),
854                 (a) -> assertCommand(a, "1+1", "")
855         );
856     }
857 
858     @Test
testOptionR()859     public void testOptionR() {
860         test(new String[]{"-R-Dthe.sound=blorp", "--no-startup"},
861                 (a) -> assertCommand(a, "System.getProperty(\"the.sound\")",
862                         "$1 ==> \"blorp\"")
863         );
864     }
865 
866     @Test
testWrapSourceHandlerDiagCrash()867     public void testWrapSourceHandlerDiagCrash() {
868         test(new String[]{"--add-exports", "jdk.javadoc/ALL-UNNAMED"},
869                 (a) -> assertCommand(a, "1+1", "$1 ==> 2")
870          );
871     }
872 
873     @Test
test8156910()874     public void test8156910() {
875         test(
876                 (a) -> assertCommandOutputContains(a, "System.out.println(\"%5d\", 10);", "%5d"),
877                 (a) -> assertCommandOutputContains(a, "1234", "==> 1234")
878         );
879     }
880 
881     @Test
testIntersection()882     public void testIntersection() {
883         test(
884                 (a) -> assertCommandOutputContains(a, "<Z extends Runnable&CharSequence> Z get1() { return null; }", "get1()"),
885                 (a) -> assertCommandOutputContains(a, "var g1 = get1()", "g1"),
886                 (a) -> assertCommand(a, "/vars g1", "|    CharSequence&Runnable g1 = null"),
887                 (a) -> assertCommandOutputContains(a, "<Z extends Number&CharSequence> Z get2() { return null; }", "get2()"),
888                 (a) -> assertCommandOutputContains(a, "var g2 = get2()", "g2"),
889                 (a) -> assertCommand(a, "/vars g2", "|    Number&CharSequence g2 = null")
890         );
891     }
892 
893     @Test
testAnonymous()894     public void testAnonymous() {
895         test(
896                 (a) -> assertCommandOutputContains(a, "var r1 = new Object() {}", "r1"),
897                 (a) -> assertCommandOutputContains(a, "/vars r1", "|    <anonymous class extending Object> r1 = "),
898                 (a) -> assertCommandOutputContains(a, "var r2 = new Runnable() { public void run() { } }", "r2"),
899                 (a) -> assertCommandOutputContains(a, "/vars r2", "|    <anonymous class implementing Runnable> r2 = "),
900                 (a) -> assertCommandOutputContains(a, "import java.util.stream.*;", ""),
901                 (a) -> assertCommandOutputContains(a, "var list = Stream.of(1, 2, 3).map(j -> new Object() { int i = j; }).collect(Collectors.toList());",
902                                                       "list"),
903                 (a) -> assertCommandOutputContains(a, "/vars list", "|    List<<anonymous class extending Object>> list = ")
904         );
905     }
906 
907     // This is mainly interesting in the TestLocalSimpleTest case (8198573)
908     @Test
testUpdateFalsePositive()909     public void testUpdateFalsePositive() {
910         test(
911                 a -> assertClass(a, "class A { int a() { int error = 0; return error; } }", "class", "A"),
912                 a -> assertVariable(a, "A", "a", "new A()", "A@.+"),
913                 a -> assertVariable(a, "int", "error", "4711", "4711"),
914                 a -> assertCommandOutputContains(a, "a", "A@")
915         );
916     }
917 
918     @Test
testRecords()919     public void testRecords() {
920         test(new String[] {},
921                 (a) -> assertCommandOutputContains(a, "record R(int i) { public int g() { return j; } }",
922                         "|  created record R, however, it cannot be instantiated or its methods invoked until variable j is declared"),
923                 (a) -> assertCommandOutputContains(a, "new R(0)",
924                         "|  attempted to use record R which cannot be instantiated or its methods invoked until variable j is declared")
925         );
926     }
927 
928     @Test
testImportChange()929     public void testImportChange() {
930         for (String feedback : new String[] {"verbose", "normal"}) {
931             test(
932                     (a) -> assertCommandOutputContains(a, "/set feedback " + feedback, "|  Feedback mode: " + feedback),
933                     (a) -> assertCommand(a, "import java.util.*", ""),
934                     (a) -> assertCommandOutputContains(a, "var v1 = List.of(1);", "v1 ==> [1]"),
935                     (a) -> assertCommandOutputContains(a, "import java.awt.List;",
936                             "|    update replaced variable v1 which cannot be referenced until this error is corrected:"),
937                     (a) -> assertCommandOutputContains(a, "var b = java.util.List.of(\"bb\")",
938                             "b ==> [bb]"),
939                     (a) -> assertCommandOutputContains(a, "b", "b ==> [bb]")
940             );
941         }
942     }
943 
944     @Test
testSwitchStatementExpressionDisambiguation()945     public void testSwitchStatementExpressionDisambiguation() {
946         test(false, new String[]{"--no-startup"},
947                 (a) -> assertCommand(a, "switch (0) { default -> 0; }", "$1 ==> 0"),
948                 (a) -> assertCommand(a, "int i;", "i ==> 0"),
949                 (a) -> assertCommand(a, "switch (0) { case 0 -> i = 1; }", ""),
950                 (a) -> assertCommand(a, "i", "i ==> 1"),
951                 (a) -> assertCommandOutputStartsWith(a, "switch (0) { default -> throw new IllegalStateException(); }", "|  Exception java.lang.IllegalStateException")
952                 );
953         test(false, new String[]{"--no-startup", "-C-source", "-C8"},
954                 (a) -> assertCommand(a, "int i;", "i ==> 0"),
955                 (a) -> assertCommand(a, "switch (0) { default: i = 1; }", ""),
956                 (a) -> assertCommand(a, "i", "i ==> 1")
957                 );
958     }
959 }
960