1 /*
2  * Copyright (c) 2015, 2018, 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 8151754 8080883 8160089 8170162 8166581 8172102 8171343 8178023 8186708 8179856 8185840 8190383
26  * @summary Testing startExCe-up options.
27  * @modules jdk.compiler/com.sun.tools.javac.api
28  *          jdk.compiler/com.sun.tools.javac.main
29  *          jdk.jdeps/com.sun.tools.javap
30  *          jdk.jshell/jdk.internal.jshell.tool
31  * @library /tools/lib
32  * @build Compiler toolbox.ToolBox
33  * @run testng StartOptionTest
34  */
35 import java.io.ByteArrayInputStream;
36 import java.io.ByteArrayOutputStream;
37 import java.io.InputStream;
38 import java.io.PrintStream;
39 import java.nio.charset.StandardCharsets;
40 import java.nio.file.Path;
41 import java.util.HashMap;
42 import java.util.Locale;
43 import java.util.function.Consumer;
44 
45 import java.util.logging.Level;
46 import java.util.logging.Logger;
47 import java.util.regex.Pattern;
48 
49 import org.testng.annotations.AfterMethod;
50 import org.testng.annotations.BeforeMethod;
51 import org.testng.annotations.Test;
52 import jdk.jshell.tool.JavaShellToolBuilder;
53 import static org.testng.Assert.assertEquals;
54 import static org.testng.Assert.assertFalse;
55 import static org.testng.Assert.assertTrue;
56 import static org.testng.Assert.fail;
57 
58 @Test
59 public class StartOptionTest {
60 
61     protected ByteArrayOutputStream cmdout;
62     protected ByteArrayOutputStream cmderr;
63     protected ByteArrayOutputStream console;
64     protected ByteArrayOutputStream userout;
65     protected ByteArrayOutputStream usererr;
66     protected InputStream cmdInStream;
67 
builder()68     private JavaShellToolBuilder builder() {
69         // turn on logging of launch failures
70         Logger.getLogger("jdk.jshell.execution").setLevel(Level.ALL);
71         return JavaShellToolBuilder
72                 .builder()
73                 .out(new PrintStream(cmdout), new PrintStream(console), new PrintStream(userout))
74                 .err(new PrintStream(cmderr), new PrintStream(usererr))
75                 .in(cmdInStream, null)
76                 .persistence(new HashMap<>())
77                 .env(new HashMap<>())
78                 .locale(Locale.ROOT);
79     }
80 
runShell(String... args)81     protected int runShell(String... args) {
82         try {
83             return builder()
84                     .start(args);
85         } catch (Exception ex) {
86             fail("Repl tool died with exception", ex);
87         }
88         return -1; // for compiler
89     }
90 
check(ByteArrayOutputStream str, Consumer<String> checkOut, String label)91     protected void check(ByteArrayOutputStream str, Consumer<String> checkOut, String label) {
92         byte[] bytes = str.toByteArray();
93         str.reset();
94         String out = new String(bytes, StandardCharsets.UTF_8);
95         out = stripAnsi(out);
96         out = out.replaceAll("[\r\n]+", "\n");
97         if (checkOut != null) {
98             checkOut.accept(out);
99         } else {
100             assertEquals(out, "", label + ": Expected empty -- ");
101         }
102     }
103 
checkExit(int ec, Consumer<Integer> checkCode)104     protected void checkExit(int ec, Consumer<Integer> checkCode) {
105         if (checkCode != null) {
106             checkCode.accept(ec);
107         } else {
108             assertEquals(ec, 0, "Expected standard exit code (0), but found: " + ec);
109         }
110     }
111 
112     // Start and check the resultant: exit code (Ex), command output (Co),
113     // user output (Uo), command error (Ce), and console output (Cn)
startExCoUoCeCn(Consumer<Integer> checkExitCode, Consumer<String> checkCmdOutput, Consumer<String> checkUserOutput, Consumer<String> checkError, Consumer<String> checkConsole, String... args)114     protected void startExCoUoCeCn(Consumer<Integer> checkExitCode,
115             Consumer<String> checkCmdOutput,
116             Consumer<String> checkUserOutput,
117             Consumer<String> checkError,
118             Consumer<String> checkConsole,
119             String... args) {
120         int ec = runShell(args);
121         checkExit(ec, checkExitCode);
122         check(cmdout, checkCmdOutput, "cmdout");
123         check(cmderr, checkError, "cmderr");
124         check(console, checkConsole, "console");
125         check(userout, checkUserOutput, "userout");
126         check(usererr, null, "usererr");
127     }
128 
129     // Start with an exit code and command error check
startExCe(int eec, Consumer<String> checkError, String... args)130     protected void startExCe(int eec, Consumer<String> checkError, String... args) {
131         StartOptionTest.this.startExCoUoCeCn(
132                 (Integer ec) -> assertEquals((int) ec, eec,
133                         "Expected error exit code (" + eec + "), but found: " + ec),
134                 null, null, checkError, null, args);
135     }
136 
137     // Start with a command output check
startCo(Consumer<String> checkCmdOutput, String... args)138     protected void startCo(Consumer<String> checkCmdOutput, String... args) {
139         StartOptionTest.this.startExCoUoCeCn(null, checkCmdOutput, null, null, null, args);
140     }
141 
assertOrNull(String expected, String label)142     private Consumer<String> assertOrNull(String expected, String label) {
143         return expected == null
144                 ? null
145                 : s -> assertEquals(s.replaceAll("\\r\\n?", "\n").trim(), expected.trim(), label);
146     }
147 
148     // Start and check the resultant: exit code (Ex), command output (Co),
149     // user output (Uo), command error (Ce), and console output (Cn)
startExCoUoCeCn(int expectedExitCode, String expectedCmdOutput, String expectedUserOutput, String expectedError, String expectedConsole, String... args)150     protected void startExCoUoCeCn(int expectedExitCode,
151             String expectedCmdOutput,
152             String expectedUserOutput,
153             String expectedError,
154             String expectedConsole,
155             String... args) {
156         startExCoUoCeCn(
157                 expectedExitCode == 0
158                         ? null
159                         : (Integer i) -> assertEquals((int) i, expectedExitCode,
160                         "Expected exit code (" + expectedExitCode + "), but found: " + i),
161                 assertOrNull(expectedCmdOutput, "cmdout: "),
162                 assertOrNull(expectedUserOutput, "userout: "),
163                 assertOrNull(expectedError, "cmderr: "),
164                 assertOrNull(expectedConsole, "console: "),
165                 args);
166     }
167 
168     // Start with an expected exit code and command error
startExCe(int ec, String expectedError, String... args)169     protected void startExCe(int ec, String expectedError, String... args) {
170         startExCoUoCeCn(ec, null, null, expectedError, null, args);
171     }
172 
173     // Start with an expected command output
startCo(String expectedCmdOutput, String... args)174     protected void startCo(String expectedCmdOutput, String... args) {
175         startExCoUoCeCn(0, expectedCmdOutput, null, null, null, args);
176     }
177 
178     // Start with an expected user output
startUo(String expectedUserOutput, String... args)179     protected void startUo(String expectedUserOutput, String... args) {
180         startExCoUoCeCn(0, null, expectedUserOutput, null, null, args);
181     }
182 
183     @BeforeMethod
setUp()184     public void setUp() {
185         cmdout = new ByteArrayOutputStream();
186         cmderr = new ByteArrayOutputStream();
187         console = new ByteArrayOutputStream();
188         userout = new ByteArrayOutputStream();
189         usererr = new ByteArrayOutputStream();
190         setIn("/exit\n");
191     }
192 
writeToFile(String stuff)193     protected String writeToFile(String stuff) {
194         Compiler compiler = new Compiler();
195         Path p = compiler.getPath("doit.repl");
196         compiler.writeToFile(p, stuff);
197         return p.toString();
198     }
199 
200     // Set the input from a String
setIn(String s)201     protected void setIn(String s) {
202         cmdInStream = new ByteArrayInputStream(s.getBytes());
203     }
204 
205     // Test load files
testCommandFile()206     public void testCommandFile() {
207         String fn = writeToFile("String str = \"Hello \"\n" +
208                 "/list\n" +
209                 "System.out.println(str + str)\n" +
210                 "/exit\n");
211         startExCoUoCeCn(0,
212                 "1 : String str = \"Hello \";\n",
213                 "Hello Hello",
214                 null,
215                 null,
216                 "--no-startup", fn, "-s");
217     }
218 
219     // Test that the usage message is printed
testUsage()220     public void testUsage() {
221         for (String opt : new String[]{"-?", "-h", "--help"}) {
222             startCo(s -> {
223                 assertTrue(s.split("\n").length >= 7, "Not enough usage lines: " + s);
224                 assertTrue(s.startsWith("Usage:   jshell <option>..."), "Unexpect usage start: " + s);
225                 assertTrue(s.contains("--show-version"), "Expected help: " + s);
226                 assertFalse(s.contains("Welcome"), "Unexpected start: " + s);
227             }, opt);
228         }
229     }
230 
231     // Test the --help-extra message
testHelpExtra()232     public void testHelpExtra() {
233         for (String opt : new String[]{"-X", "--help-extra"}) {
234             startCo(s -> {
235                 assertTrue(s.split("\n").length >= 5, "Not enough help-extra lines: " + s);
236                 assertTrue(s.contains("--add-exports"), "Expected --add-exports: " + s);
237                 assertTrue(s.contains("--execution"), "Expected --execution: " + s);
238                 assertFalse(s.contains("Welcome"), "Unexpected start: " + s);
239             }, opt);
240         }
241     }
242 
243     // Test handling of bogus options
testUnknown()244     public void testUnknown() {
245         startExCe(1, "Unknown option: u", "-unknown");
246         startExCe(1, "Unknown option: unknown", "--unknown");
247     }
248 
249     // Test that input is read with "-" and there is no extra output.
testHypenFile()250     public void testHypenFile() {
251         setIn("System.out.print(\"Hello\");\n");
252         startUo("Hello", "-");
253         setIn("System.out.print(\"Hello\");\n");
254         startUo("Hello", "-", "-");
255         String fn = writeToFile("System.out.print(\"===\");");
256         setIn("System.out.print(\"Hello\");\n");
257         startUo("===Hello===", fn, "-", fn);
258         // check that errors go to standard error
259         setIn(") Foobar");
260         startExCe(0, s -> assertTrue(s.contains("illegal start of expression"),
261                 "cmderr: illegal start of expression"),
262                 "-");
263     }
264 
265     // Test that user specified exit codes are propagated
testExitCode()266     public void testExitCode() {
267         setIn("/exit 57\n");
268         startExCoUoCeCn(57, null, null, null, "-> /exit 57", "-s");
269         setIn("int eight = 8\n" +
270                 "/exit eight + \n" +
271                 " eight\n");
272         startExCoUoCeCn(16, null, null, null,
273                 "-> int eight = 8\n" +
274                 "-> /exit eight + \n" +
275                 ">>  eight",
276                 "-s");
277     }
278 
279     // Test that non-existent load file sends output to stderr and does not startExCe (no welcome).
testUnknownLoadFile()280     public void testUnknownLoadFile() {
281         startExCe(1, "File 'UNKNOWN' for 'jshell' is not found.", "UNKNOWN");
282     }
283 
284     // Test bad usage of the --startup option
testStartup()285     public void testStartup() {
286         String fn = writeToFile("");
287         startExCe(1, "Argument to startup missing.", "--startup");
288         startExCe(1, "Conflicting options: both --startup and --no-startup were used.", "--no-startup", "--startup", fn);
289         startExCe(1, "Conflicting options: both --startup and --no-startup were used.", "--startup", fn, "--no-startup");
290         startExCe(1, "Argument to startup missing.", "--no-startup", "--startup");
291     }
292 
293     // Test an option that causes the back-end to fail is propagated
testStartupFailedOption()294     public void testStartupFailedOption() {
295         startExCe(1, s -> assertTrue(s.contains("Unrecognized option: -hoge-foo-bar"), "cmderr: " + s),
296                 "-R-hoge-foo-bar");
297     }
298 
299     // Test the use of non-existant files with the --startup option
testStartupUnknown()300     public void testStartupUnknown() {
301         startExCe(1, "File 'UNKNOWN' for '--startup' is not found.", "--startup", "UNKNOWN");
302         startExCe(1, "File 'UNKNOWN' for '--startup' is not found.", "--startup", "DEFAULT", "--startup", "UNKNOWN");
303     }
304 
305     // Test bad usage of --class-path option
testClasspath()306     public void testClasspath() {
307         for (String cp : new String[]{"--class-path"}) {
308             startExCe(1, "Only one --class-path option may be used.", cp, ".", "--class-path", ".");
309             startExCe(1, "Argument to class-path missing.", cp);
310         }
311     }
312 
313     // Test bogus module on --add-modules option
testUnknownModule()314     public void testUnknownModule() {
315         startExCe(1, s -> assertTrue(s.contains("rror") && s.contains("unKnown"), "cmderr: " + s),
316                 "--add-modules", "unKnown");
317     }
318 
319     // Test that muliple feedback options fail
testFeedbackOptionConflict()320     public void testFeedbackOptionConflict() {
321         startExCe(1, "Only one feedback option (--feedback, -q, -s, or -v) may be used.",
322                 "--feedback", "concise", "--feedback", "verbose");
323         startExCe(1, "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "--feedback", "concise", "-s");
324         startExCe(1, "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "--feedback", "verbose", "-q");
325         startExCe(1, "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "--feedback", "concise", "-v");
326         startExCe(1, "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "-v", "--feedback", "concise");
327         startExCe(1, "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "-q", "-v");
328         startExCe(1, "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "-s", "-v");
329         startExCe(1, "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "-v", "-q");
330         startExCe(1, "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "-q", "-s");
331     }
332 
333     // Test bogus arguments to the --feedback option
testNegFeedbackOption()334     public void testNegFeedbackOption() {
335         startExCe(1, "Argument to feedback missing.", "--feedback");
336         startExCe(1, "Does not match any current feedback mode: blorp -- --feedback blorp", "--feedback", "blorp");
337     }
338 
339     // Test --version
testVersion()340     public void testVersion() {
341         startCo(s -> {
342             assertTrue(s.startsWith("jshell"), "unexpected version: " + s);
343             assertFalse(s.contains("Welcome"), "Unexpected start: " + s);
344         },
345                 "--version");
346     }
347 
348     // Test --show-version
testShowVersion()349     public void testShowVersion() {
350         startExCoUoCeCn(null,
351                 s -> {
352                     assertTrue(s.startsWith("jshell"), "unexpected version: " + s);
353                     assertTrue(s.contains("Welcome"), "Expected start (but got no welcome): " + s);
354                 },
355                 null,
356                 null,
357                 s -> assertTrue(s.trim().startsWith("jshell>"), "Expected prompt, got: " + s),
358                 "--show-version");
359     }
360 
361     @AfterMethod
tearDown()362     public void tearDown() {
363         cmdout = null;
364         cmderr = null;
365         console = null;
366         userout = null;
367         usererr = null;
368         cmdInStream = null;
369     }
370 
stripAnsi(String str)371     private static String stripAnsi(String str) {
372         if (str == null) return "";
373         return ANSI_CODE_PATTERN.matcher(str).replaceAll("");
374     }
375 
376     public static final Pattern ANSI_CODE_PATTERN = Pattern.compile("\033\\[[\060-\077]*[\040-\057]*[\100-\176]");
377 }
378