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