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 
  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 
  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 
  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 
 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)
 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
 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
 138     protected void startCo(Consumer<String> checkCmdOutput, String... args) {
 139         StartOptionTest.this.startExCoUoCeCn(null, checkCmdOutput, null, null, null, args);
 140     }
 141 
 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)
 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
 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
 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
 179     protected void startUo(String expectedUserOutput, String... args) {
 180         startExCoUoCeCn(0, null, expectedUserOutput, null, null, args);
 181     }
 182 
 183     @BeforeMethod
 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 
 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
 201     protected void setIn(String s) {
 202         cmdInStream = new ByteArrayInputStream(s.getBytes());
 203     }
 204 
 205     // Test load files
 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
 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
 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
 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.
 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
 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).
 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
 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
 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
 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
 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
 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
 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
 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
 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
 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
 362     public void tearDown() {
 363         cmdout = null;
 364         cmderr = null;
 365         console = null;
 366         userout = null;
 367         usererr = null;
 368         cmdInStream = null;
 369     }
 370 
 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 }