8177076: jshell tool: allow non-zero /exit
authorrfield
Tue, 14 Nov 2017 19:33:37 -0800
changeset 47840 e0f08a49f3e3
parent 47839 314ac2e2db63
child 47841 3b6fc119b32b
8177076: jshell tool: allow non-zero /exit 8190383: JShell API: no way for the jshell tool to report exit status to provider Reviewed-by: jlahoda
src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java
src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java
src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellToolBuilder.java
src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellToolProvider.java
src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties
src/jdk.jshell/share/classes/jdk/jshell/tool/JavaShellToolBuilder.java
src/jdk.jshell/share/classes/jdk/jshell/tool/package-info.java
test/langtools/jdk/jshell/StartOptionTest.java
test/langtools/jdk/jshell/ToolProviderTest.java
test/langtools/jdk/jshell/ToolTabCommandTest.java
--- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java	Tue Nov 14 12:07:55 2017 -0800
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java	Tue Nov 14 19:33:37 2017 -0800
@@ -923,7 +923,7 @@
 
                             @Override
                             public void perform(ConsoleReader in) throws IOException {
-                                repl.processCompleteSource("import " + type + ";");
+                                repl.processSource("import " + type + ";");
                                 in.println("Imported: " + type);
                                 performToVar(in, stype);
                             }
@@ -1028,7 +1028,7 @@
 
                             @Override
                             public void perform(ConsoleReader in) throws IOException {
-                                repl.processCompleteSource("import " + type + ";");
+                                repl.processSource("import " + type + ";");
                                 in.println("Imported: " + type);
                                 performToMethod(in, stype, codeToCursor);
                             }
@@ -1052,7 +1052,7 @@
 
                         @Override
                         public void perform(ConsoleReader in) throws IOException {
-                            repl.processCompleteSource("import " + fqn + ";");
+                            repl.processSource("import " + fqn + ";");
                             in.println("Imported: " + fqn);
                             in.redrawLine();
                         }
--- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java	Tue Nov 14 12:07:55 2017 -0800
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java	Tue Nov 14 19:33:37 2017 -0800
@@ -27,6 +27,7 @@
 
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
+import java.io.EOFException;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileReader;
@@ -81,10 +82,12 @@
 import jdk.jshell.JShell.Subscription;
 import jdk.jshell.MethodSnippet;
 import jdk.jshell.Snippet;
+import jdk.jshell.Snippet.Kind;
 import jdk.jshell.Snippet.Status;
 import jdk.jshell.SnippetEvent;
 import jdk.jshell.SourceCodeAnalysis;
 import jdk.jshell.SourceCodeAnalysis.CompletionInfo;
+import jdk.jshell.SourceCodeAnalysis.Completeness;
 import jdk.jshell.SourceCodeAnalysis.Suggestion;
 import jdk.jshell.TypeDeclSnippet;
 import jdk.jshell.UnresolvedReferenceException;
@@ -112,6 +115,7 @@
 import static java.util.Arrays.stream;
 import static java.util.stream.Collectors.joining;
 import static java.util.stream.Collectors.toList;
+import static jdk.jshell.Snippet.SubKind.TEMP_VAR_EXPRESSION_SUBKIND;
 import static jdk.jshell.Snippet.SubKind.VAR_VALUE_SUBKIND;
 import static java.util.stream.Collectors.toMap;
 import static jdk.internal.jshell.debug.InternalDebugControl.DBG_COMPA;
@@ -203,6 +207,7 @@
     private boolean isCurrentlyRunningStartup = false;
     private String executionControlSpec = null;
     private EditorSetting editor = BUILT_IN_EDITOR;
+    private int exitCode = 0;
 
     private static final String[] EDITOR_ENV_VARS = new String[] {
         "JSHELLEDITOR", "VISUAL", "EDITOR"};
@@ -219,6 +224,7 @@
 
     static final Pattern BUILTIN_FILE_PATTERN = Pattern.compile("\\w+");
     static final String BUILTIN_FILE_PATH_FORMAT = "/jdk/jshell/tool/resources/%s.jsh";
+    static final String INT_PREFIX = "int $$exit$$ = ";
 
     // match anything followed by whitespace
     private static final Pattern OPTION_PRE_PATTERN =
@@ -364,6 +370,7 @@
                             .stream()
                             .collect(joining(", ")));
                 }
+                exitCode = 1;
                 return null;
             }
         }
@@ -424,7 +431,12 @@
                     .collect(toList())
             );
 
-            return failed ? null : opts;
+            if (failed) {
+                exitCode = 1;
+                return null;
+            } else {
+                return opts;
+            }
         }
 
         void addOptions(OptionKind kind, Collection<String> vals) {
@@ -537,6 +549,7 @@
                     (options.has(argS) ? 1 : 0) +
                     (options.has(argV) ? 1 : 0)) > 1) {
                 msg("jshell.err.opt.feedback.one");
+                exitCode = 1;
                 return null;
             } else if (options.has(argFeedback)) {
                 feedbackMode = options.valueOf(argFeedback);
@@ -551,10 +564,12 @@
                 List<String> sts = options.valuesOf(argStart);
                 if (options.has("no-startup")) {
                     msg("jshell.err.opt.startup.conflict");
+                    exitCode = 1;
                     return null;
                 }
                 initialStartup = Startup.fromFileList(sts, "--startup", new InitMessageHandler());
                 if (initialStartup == null) {
+                    exitCode = 1;
                     return null;
                 }
             } else if (options.has(argNoStart)) {
@@ -865,13 +880,15 @@
      *
      * @param args the command-line arguments
      * @throws Exception catastrophic fatal exception
+     * @return the exit code
      */
-    public void start(String[] args) throws Exception {
+    public int start(String[] args) throws Exception {
         OptionParserCommandLine commandLineArgs = new OptionParserCommandLine();
         options = commandLineArgs.parse(args);
         if (options == null) {
-            // Abort
-            return;
+            // A null means end immediately, this may be an error or because
+            // of options like --version.  Exit code has been set.
+            return exitCode;
         }
         startup = commandLineArgs.startup();
         // initialize editor settings
@@ -883,7 +900,7 @@
             // Display just the cause (not a exception backtrace)
             cmderr.println(ex.getMessage());
             //abort
-            return;
+            return 1;
         }
         // Read replay history from last jshell session into previous history
         replayableHistoryPrevious = ReplayableHistory.fromPrevious(prefs);
@@ -891,7 +908,7 @@
         for (String loadFile : commandLineArgs.nonOptions()) {
             if (!runFile(loadFile, "jshell")) {
                 // Load file failed -- abort
-                return;
+                return 1;
             }
         }
         // if we survived that...
@@ -934,6 +951,7 @@
             }
         }
         closeState();
+        return exitCode;
     }
 
     private EditorSetting configEditor() {
@@ -1071,6 +1089,7 @@
             // The feedback mode to use was specified on the command line, use it
             if (!setFeedback(initmh, new ArgTokenizer("--feedback", initMode))) {
                 regenerateOnDeath = false;
+                exitCode = 1;
             }
         } else {
             String fb = prefs.get(FEEDBACK_KEY);
@@ -1105,55 +1124,23 @@
 
     /**
      * Main loop
+     *
      * @param in the line input/editing context
      */
     private void run(IOContext in) {
         IOContext oldInput = input;
         input = in;
         try {
-            String incomplete = "";
+            // remaining is the source left after one snippet is evaluated
+            String remaining = "";
             while (live) {
-                String prompt;
-                if (interactive()) {
-                    prompt = testPrompt
-                                    ? incomplete.isEmpty()
-                                            ? "\u0005" //ENQ
-                                            : "\u0006" //ACK
-                                    : incomplete.isEmpty()
-                                            ? feedback.getPrompt(currentNameSpace.tidNext())
-                                            : feedback.getContinuationPrompt(currentNameSpace.tidNext())
-                    ;
-                } else {
-                    prompt = "";
-                }
-                String raw;
-                try {
-                    raw = in.readLine(prompt, incomplete);
-                } catch (InputInterruptedException ex) {
-                    //input interrupted - clearing current state
-                    incomplete = "";
-                    continue;
-                }
-                if (raw == null) {
-                    //EOF
-                    if (in.interactiveOutput()) {
-                        // End after user ctrl-D
-                        regenerateOnDeath = false;
-                    }
-                    break;
-                }
-                String trimmed = trimEnd(raw);
-                if (!trimmed.isEmpty() || !incomplete.isEmpty()) {
-                    String line = incomplete + trimmed;
-
-                    // No commands in the middle of unprocessed source
-                    if (incomplete.isEmpty() && line.startsWith("/") && !line.startsWith("//") && !line.startsWith("/*")) {
-                        processCommand(line.trim());
-                    } else {
-                        incomplete = processSourceCatchingReset(line);
-                    }
-                }
+                // Get a line(s) of input
+                String src = getInput(remaining);
+                // Process the snippet or command, returning the remaining source
+                remaining = processInput(src);
             }
+        } catch (EOFException ex) {
+            // Just exit loop
         } catch (IOException ex) {
             errormsg("jshell.err.unexpected.exception", ex);
         } finally {
@@ -1161,20 +1148,125 @@
         }
     }
 
+    /**
+     * Process an input command or snippet.
+     *
+     * @param src the source to process
+     * @return any remaining input to processed
+     */
+    private String processInput(String src) {
+        if (isCommand(src)) {
+            // It is a command
+            processCommand(src.trim());
+            // No remaining input after a command
+            return "";
+        } else {
+            // It is a snipet. Separate the source from the remaining. Evaluate
+            // the source
+            CompletionInfo an = analysis.analyzeCompletion(src);
+            if (processSourceCatchingReset(trimEnd(an.source()))) {
+                // Snippet was successful use any leftover source
+                return an.remaining();
+            } else {
+                // Snippet failed, throw away any remaining source
+                return "";
+            }
+        }
+    }
+
+    /**
+     * Get the input line (or, if incomplete, lines).
+     *
+     * @param initial leading input (left over after last snippet)
+     * @return the complete input snippet or command
+     * @throws IOException on unexpected I/O error
+     */
+    private String getInput(String initial) throws IOException{
+        String src = initial;
+        while (live) { // loop while incomplete (and live)
+            if (!src.isEmpty()) {
+                // We have some source, see if it is complete, if so, use it
+                String check;
+
+                if (isCommand(src)) {
+                    // A command can only be incomplete if it is a /exit with
+                    // an argument
+                    int sp = src.indexOf(" ");
+                    if (sp < 0) return src;
+                    check = src.substring(sp).trim();
+                    if (check.isEmpty()) return src;
+                    String cmd = src.substring(0, sp);
+                    Command[] match = findCommand(cmd, c -> c.kind.isRealCommand);
+                    if (match.length != 1 || !match[0].command.equals("/exit")) {
+                        // A command with no snippet arg, so no multi-line input
+                        return src;
+                    }
+                } else {
+                    // For a snippet check the whole source
+                    check = src;
+                }
+                Completeness comp = analysis.analyzeCompletion(check).completeness();
+                if (comp.isComplete() || comp == Completeness.EMPTY) {
+                    return src;
+                }
+            }
+            String prompt = interactive()
+                    ? testPrompt
+                            ? src.isEmpty()
+                                    ? "\u0005" //ENQ -- test prompt
+                                    : "\u0006" //ACK -- test continuation prompt
+                            : src.isEmpty()
+                                    ? feedback.getPrompt(currentNameSpace.tidNext())
+                                    : feedback.getContinuationPrompt(currentNameSpace.tidNext())
+                    : "" // Non-interactive -- no prompt
+                    ;
+            String line;
+            try {
+                line = input.readLine(prompt, src);
+            } catch (InputInterruptedException ex) {
+                //input interrupted - clearing current state
+                src = "";
+                continue;
+            }
+            if (line == null) {
+                //EOF
+                if (input.interactiveOutput()) {
+                    // End after user ctrl-D
+                    regenerateOnDeath = false;
+                }
+                throw new EOFException(); // no more input
+            }
+            src = src.isEmpty()
+                    ? line
+                    : src + "\n" + line;
+        }
+        throw new EOFException(); // not longer live
+    }
+
+    private boolean isCommand(String line) {
+        return line.startsWith("/") && !line.startsWith("//") && !line.startsWith("/*");
+    }
+
     private void addToReplayHistory(String s) {
         if (!isCurrentlyRunningStartup) {
             replayableHistory.add(s);
         }
     }
 
-    private String processSourceCatchingReset(String src) {
+    /**
+     * Process a source snippet.
+     *
+     * @param src the snippet source to process
+     * @return true on success, false on failure
+     */
+    private boolean processSourceCatchingReset(String src) {
         try {
             input.beforeUserCode();
             return processSource(src);
         } catch (IllegalStateException ex) {
             hard("Resetting...");
             live = false; // Make double sure
-            return "";
+            return false;
         } finally {
             input.afterUserCode();
         }
@@ -1648,8 +1740,19 @@
                 arg -> cmdImports(),
                 EMPTY_COMPLETION_PROVIDER));
         registerCommand(new Command("/exit",
-                arg -> cmdExit(),
-                EMPTY_COMPLETION_PROVIDER));
+                arg -> cmdExit(arg),
+                (sn, c, a) -> {
+                    if (analysis == null || sn.isEmpty()) {
+                        // No completions if uninitialized or snippet not started
+                        return Collections.emptyList();
+                    } else {
+                        // Give exit code an int context by prefixing the arg
+                        List<Suggestion> suggestions = analysis.completionSuggestions(INT_PREFIX + sn,
+                                INT_PREFIX.length() + c, a);
+                        a[0] -= INT_PREFIX.length();
+                        return suggestions;
+                    }
+                }));
         registerCommand(new Command("/env",
                 arg -> cmdEnv(arg),
                 envCompletion()));
@@ -2128,10 +2231,83 @@
         return true;
     }
 
-    private boolean cmdExit() {
+    private boolean cmdExit(String arg) {
+        if (!arg.trim().isEmpty()) {
+            debug("Compiling exit: %s", arg);
+            List<SnippetEvent> events = state.eval(arg);
+            for (SnippetEvent e : events) {
+                // Only care about main snippet
+                if (e.causeSnippet() == null) {
+                    Snippet sn = e.snippet();
+
+                    // Show any diagnostics
+                    List<Diag> diagnostics = state.diagnostics(sn).collect(toList());
+                    String source = sn.source();
+                    displayDiagnostics(source, diagnostics);
+
+                    // Show any exceptions
+                    if (e.exception() != null && e.status() != Status.REJECTED) {
+                        if (displayException(e.exception())) {
+                            // Abort: an exception occurred (reported)
+                            return false;
+                        }
+                    }
+
+                    if (e.status() != Status.VALID) {
+                        // Abort: can only use valid snippets, diagnostics have been reported (above)
+                        return false;
+                    }
+                    String typeName;
+                    if (sn.kind() == Kind.EXPRESSION) {
+                        typeName = ((ExpressionSnippet) sn).typeName();
+                    } else if (sn.subKind() == TEMP_VAR_EXPRESSION_SUBKIND) {
+                        typeName = ((VarSnippet) sn).typeName();
+                    } else {
+                        // Abort: not an expression
+                        errormsg("jshell.err.exit.not.expression", arg);
+                        return false;
+                    }
+                    switch (typeName) {
+                        case "int":
+                        case "Integer":
+                        case "byte":
+                        case "Byte":
+                        case "short":
+                        case "Short":
+                            try {
+                                int i = Integer.parseInt(e.value());
+                                /**
+                                addToReplayHistory("/exit " + arg);
+                                replayableHistory.storeHistory(prefs);
+                                closeState();
+                                try {
+                                    input.close();
+                                } catch (Exception exc) {
+                                    // ignore
+                                }
+                                * **/
+                                exitCode = i;
+                                break;
+                            } catch (NumberFormatException exc) {
+                                // Abort: bad value
+                                errormsg("jshell.err.exit.bad.value", arg, e.value());
+                                return false;
+                            }
+                        default:
+                            // Abort: bad type
+                            errormsg("jshell.err.exit.bad.type", arg, typeName);
+                            return false;
+                    }
+                }
+            }
+        }
         regenerateOnDeath = false;
         live = false;
-        fluffmsg("jshell.msg.goodbye");
+        if (exitCode == 0) {
+            fluffmsg("jshell.msg.goodbye");
+        } else {
+            fluffmsg("jshell.msg.goodbye.value", exitCode);
+        }
         return true;
     }
 
@@ -2678,7 +2854,7 @@
                         }
                         String tsrc = trimNewlines(an.source());
                         if (!failed && !currSrcs.contains(tsrc)) {
-                            failed = processCompleteSource(tsrc);
+                            failed = processSource(tsrc);
                         }
                         nextSrcs.add(tsrc);
                         if (an.remaining().isEmpty()) {
@@ -3118,7 +3294,50 @@
                 .collect(toList());
     }
 
-    void displayDiagnostics(String source, Diag diag, List<String> toDisplay) {
+    /**
+     * Print out a snippet exception.
+     *
+     * @param exception the exception to print
+     * @return true on fatal exception
+     */
+    private boolean displayException(Exception exception) {
+        if (exception instanceof EvalException) {
+            printEvalException((EvalException) exception);
+            return true;
+        } else if (exception instanceof UnresolvedReferenceException) {
+            printUnresolvedException((UnresolvedReferenceException) exception);
+            return false;
+        } else {
+            error("Unexpected execution exception: %s", exception);
+            return true;
+        }
+    }
+
+    /**
+     * Display a list of diagnostics.
+     *
+     * @param source the source line with the error/warning
+     * @param diagnostics the diagnostics to display
+     */
+    private void displayDiagnostics(String source, List<Diag> diagnostics) {
+        for (Diag d : diagnostics) {
+            errormsg(d.isError() ? "jshell.msg.error" : "jshell.msg.warning");
+            List<String> disp = new ArrayList<>();
+            displayableDiagnostic(source, d, disp);
+            disp.stream()
+                    .forEach(l -> error("%s", l));
+        }
+    }
+
+    /**
+     * Convert a diagnostic into a list of pretty displayable strings with
+     * source context.
+     *
+     * @param source the source line for the error/warning
+     * @param diag the diagnostic to convert
+     * @param toDisplay a list that the displayable strings are added to
+     */
+    private void displayableDiagnostic(String source, Diag diag, List<String> toDisplay) {
         for (String line : diag.getMessage(null).split("\\r?\\n")) { // TODO: Internationalize
             if (!line.trim().startsWith("location:")) {
                 toDisplay.add(line);
@@ -3169,21 +3388,13 @@
                 diag.getStartPosition(), diag.getEndPosition());
     }
 
-    private String processSource(String srcInput) throws IllegalStateException {
-        while (true) {
-            CompletionInfo an = analysis.analyzeCompletion(srcInput);
-            if (!an.completeness().isComplete()) {
-                return an.remaining();
-            }
-            boolean failed = processCompleteSource(an.source());
-            if (failed || an.remaining().isEmpty()) {
-                return "";
-            }
-            srcInput = an.remaining();
-        }
-    }
-    //where
-    boolean processCompleteSource(String source) throws IllegalStateException {
+    /**
+     * Process a source snippet.
+     *
+     * @param source the input source
+     * @return true if the snippet succeeded
+     */
+    boolean processSource(String source) {
         debug("Compiling: %s", source);
         boolean failed = false;
         boolean isActive = false;
@@ -3204,7 +3415,7 @@
             addToReplayHistory(source);
         }
 
-        return failed;
+        return !failed;
     }
 
     // Handle incoming snippet events -- return true on failure
@@ -3218,23 +3429,11 @@
         String source = sn.source();
         if (ste.causeSnippet() == null) {
             // main event
-            for (Diag d : diagnostics) {
-                errormsg(d.isError()? "jshell.msg.error" : "jshell.msg.warning");
-                List<String> disp = new ArrayList<>();
-                displayDiagnostics(source, d, disp);
-                disp.stream()
-                        .forEach(l -> error("%s", l));
-            }
+            displayDiagnostics(source, diagnostics);
 
             if (ste.status() != Status.REJECTED) {
                 if (ste.exception() != null) {
-                    if (ste.exception() instanceof EvalException) {
-                        printEvalException((EvalException) ste.exception());
-                        return true;
-                    } else if (ste.exception() instanceof UnresolvedReferenceException) {
-                        printUnresolvedException((UnresolvedReferenceException) ste.exception());
-                    } else {
-                        error("Unexpected execution exception: %s", ste.exception());
+                    if (displayException(ste.exception())) {
                         return true;
                     }
                 } else {
@@ -3371,7 +3570,7 @@
             this.value = value;
             this.errorLines = new ArrayList<>();
             for (Diag d : errors) {
-                displayDiagnostics(sn.source(), d, errorLines);
+                displayableDiagnostic(sn.source(), d, errorLines);
             }
             if (resolve) {
                 // resolve needs error lines indented
@@ -3669,6 +3868,7 @@
         scannerIn.close();
     }
 
+    @Override
     public int readUserInput() {
         return -1;
     }
@@ -3700,6 +3900,7 @@
     public void close() {
     }
 
+    @Override
     public int readUserInput() {
         return -1;
     }
--- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellToolBuilder.java	Tue Nov 14 12:07:55 2017 -0800
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellToolBuilder.java	Tue Nov 14 19:33:37 2017 -0800
@@ -229,7 +229,8 @@
     /**
      * Run an instance of the Java shell tool as configured by the other methods
      * in this interface.  This call is not destructive, more than one call of
-     * this method may be made from a configured builder.
+     * this method may be made from a configured builder. The  exit code from
+     * the Java shell tool is ignored.
      *
      * @param arguments the command-line arguments (including options), if any
      * @throws Exception an unexpected fatal exception
@@ -240,6 +241,20 @@
     }
 
     /**
+     * Run an instance of the Java shell tool as configured by the other methods
+     * in this interface.  This call is not destructive, more than one call of
+     * this method may be made from a configured builder.
+     *
+     * @param arguments the command-line arguments (including options), if any
+     * @throws Exception an unexpected fatal exception
+     * @return the exit code
+     */
+    @Override
+    public int start(String... arguments) throws Exception {
+        return rawTool().start(arguments);
+    }
+
+    /**
      * Persistence stored in Preferences.
      */
     private static class PreferencesStorage implements PersistentStorage {
--- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellToolProvider.java	Tue Nov 14 12:07:55 2017 -0800
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellToolProvider.java	Tue Nov 14 19:33:37 2017 -0800
@@ -62,7 +62,8 @@
      * @param err start-up errors and execution "standard" error; use System.err
      * if null
      * @param arguments arguments to pass to the tool
-     * @return 0 for success; nonzero otherwise
+     * @return the exit status with which the tool explicitly exited (if any),
+     * otherwise 0 for success or 1 for failure
      * @throws NullPointerException if the array of arguments contains
      * any {@code null} elements.
      */
@@ -85,13 +86,12 @@
                                 ? (PrintStream) err
                                 : new PrintStream(err);
         try {
-            JavaShellToolBuilder
+            return JavaShellToolBuilder
                     .builder()
                     .in(xin, null)
                     .out(xout)
                     .err(xerr)
-                    .run(arguments);
-            return 0;
+                    .start(arguments);
         } catch (Throwable ex) {
             xerr.println(ex.getMessage());
             return 1;
@@ -109,13 +109,14 @@
     }
 
     /**
-     * Launch the tool.
+     * Launch the tool and exit.
      * @param arguments the command-line arguments (including options), if any
      * @throws Exception an unexpected fatal exception
      */
     public static void main(String[] arguments) throws Exception {
-        JavaShellToolBuilder
-                .builder()
-                .run(arguments);
+        System.exit(
+                JavaShellToolBuilder
+                        .builder()
+                        .start(arguments));
     }
 }
--- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties	Tue Nov 14 12:07:55 2017 -0800
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties	Tue Nov 14 19:33:37 2017 -0800
@@ -108,6 +108,10 @@
 jshell.err.end.snippet.range.less.than.start = End of snippet range less than start: {0} - {1}
 jshell.err.range.requires.id = Snippet ranges require snippet ids: {0}
 
+jshell.err.exit.not.expression = The argument to /exit must be a valid integer expression, it is not an expression: {0}
+jshell.err.exit.bad.type = The argument to /exit must be a valid integer expression. The type is {1} : {0}
+jshell.err.exit.bad.value = The argument to /exit has bad value is {1} : {0}
+
 jshell.err.drop.arg =\
 In the /drop argument, please specify an import, variable, method, or class to drop.\n\
 Specify by id or name. Use /list to see ids. Use /reset to reset all state.
@@ -115,6 +119,7 @@
 jshell.msg.native.method = Native Method
 jshell.msg.unknown.source = Unknown Source
 jshell.msg.goodbye = Goodbye
+jshell.msg.goodbye.value = Goodbye ({0})
 
 jshell.msg.help.for.help = Type /help for help.
 
@@ -378,10 +383,17 @@
 List the current active jshell imports.
 
 help.exit.summary = exit jshell
-help.exit.args =
+help.exit.args =[<integer-expression-snippet>]
 help.exit =\
 Leave the jshell tool.  No work is saved.\n\
-Save any work before using this command
+Save any work before using this command\n\
+\n\
+/exit\n\t\
+    Leave the jshell tool.  The exit status is zero.\n\n\
+/exit <integer-expression-snippet>\n\t\
+    Evaluate the snippet.  If the snippet fails or is not an integer expression,\n\t\
+    display the error.  Otherwise leave the jshell tool with the\n\t\
+    value of the expression as the exit status
 
 help.reset.summary = reset jshell
 help.reset.args = \
--- a/src/jdk.jshell/share/classes/jdk/jshell/tool/JavaShellToolBuilder.java	Tue Nov 14 12:07:55 2017 -0800
+++ b/src/jdk.jshell/share/classes/jdk/jshell/tool/JavaShellToolBuilder.java	Tue Nov 14 19:33:37 2017 -0800
@@ -186,10 +186,29 @@
     /**
      * Run an instance of the Java shell tool as configured by the other methods
      * in this interface.  This call is not destructive, more than one call of
-     * this method may be made from a configured builder.
+     * this method may be made from a configured builder. The  exit code from
+     * the Java shell tool is ignored.
      *
      * @param arguments the command-line arguments (including options), if any
      * @throws Exception an unexpected fatal exception
      */
     void run(String... arguments) throws Exception;
+
+    /**
+     * Run an instance of the Java shell tool as configured by the other methods
+     * in this interface.  This call is not destructive, more than one call of
+     * this method may be made from a configured builder.
+     *
+     * @implSpec The default implementation always returns zero. Implementations
+     * of this interface should override this method, returning the exit status.
+     *
+     * @param arguments the command-line arguments (including options), if any
+     * @throws Exception an unexpected fatal exception
+     * @return the exit status with which the tool explicitly exited (if any),
+     * otherwise 0 for success or 1 for failure
+     */
+    default int start(String... arguments) throws Exception {
+        run(arguments);
+        return 0;
+    }
 }
--- a/src/jdk.jshell/share/classes/jdk/jshell/tool/package-info.java	Tue Nov 14 12:07:55 2017 -0800
+++ b/src/jdk.jshell/share/classes/jdk/jshell/tool/package-info.java	Tue Nov 14 19:33:37 2017 -0800
@@ -28,23 +28,23 @@
  * Allows configuration of the tool before launching. A builder is used
  * to configure and launch the tool.
  * <p>
- * At the simplest, a builder is retrieved, and the builder is used to run the
+ * At the simplest, a builder is retrieved, and the builder is used to start the
  * tool:
  * <pre>
  * {@code
  *       JavaShellToolBuilder
  *             .builder()
- *             .run();
+ *             .start();
  * }
  * </pre>
- * The builder can be configured and the run can have arguments:
+ * The builder can be configured and the start can have arguments:
  * <pre>
  * {@code
  *       JavaShellToolBuilder
  *             .builder()
  *             .out(myCommandPrintStream, myOutputPrintStream)
  *             .locale(Locale.CANADA)
- *             .run("--feedback", "silent", "MyStart");
+ *             .start("--feedback", "silent", "MyStart");
  * }
  * </pre>
  *
--- a/test/langtools/jdk/jshell/StartOptionTest.java	Tue Nov 14 12:07:55 2017 -0800
+++ b/test/langtools/jdk/jshell/StartOptionTest.java	Tue Nov 14 19:33:37 2017 -0800
@@ -21,9 +21,9 @@
  * questions.
  */
 
-/*
- * @test 8151754 8080883 8160089 8170162 8166581 8172102 8171343 8178023 8186708 8179856
- * @summary Testing start-up options.
+ /*
+ * @test 8151754 8080883 8160089 8170162 8166581 8172102 8171343 8178023 8186708 8179856 8185840 8190383
+ * @summary Testing startExCe-up options.
  * @modules jdk.compiler/com.sun.tools.javac.api
  *          jdk.compiler/com.sun.tools.javac.main
  *          jdk.jdeps/com.sun.tools.javap
@@ -32,7 +32,6 @@
  * @build Compiler toolbox.ToolBox
  * @run testng StartOptionTest
  */
-
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.InputStream;
@@ -57,239 +56,308 @@
 @Test
 public class StartOptionTest {
 
-    private ByteArrayOutputStream cmdout;
-    private ByteArrayOutputStream cmderr;
-    private ByteArrayOutputStream console;
-    private ByteArrayOutputStream userout;
-    private ByteArrayOutputStream usererr;
-    private InputStream cmdInStream;
+    protected ByteArrayOutputStream cmdout;
+    protected ByteArrayOutputStream cmderr;
+    protected ByteArrayOutputStream console;
+    protected ByteArrayOutputStream userout;
+    protected ByteArrayOutputStream usererr;
+    protected InputStream cmdInStream;
 
     private JavaShellToolBuilder builder() {
         // turn on logging of launch failures
         Logger.getLogger("jdk.jshell.execution").setLevel(Level.ALL);
         return JavaShellToolBuilder
-                    .builder()
-                    .out(new PrintStream(cmdout), new PrintStream(console), new PrintStream(userout))
-                    .err(new PrintStream(cmderr), new PrintStream(usererr))
-                    .in(cmdInStream, null)
-                    .persistence(new HashMap<>())
-                    .env(new HashMap<>())
-                    .locale(Locale.ROOT);
+                .builder()
+                .out(new PrintStream(cmdout), new PrintStream(console), new PrintStream(userout))
+                .err(new PrintStream(cmderr), new PrintStream(usererr))
+                .in(cmdInStream, null)
+                .persistence(new HashMap<>())
+                .env(new HashMap<>())
+                .locale(Locale.ROOT);
     }
 
-    private void runShell(String... args) {
+    protected int runShell(String... args) {
         try {
-            builder()
-                    .run(args);
+            return builder()
+                    .start(args);
         } catch (Exception ex) {
             fail("Repl tool died with exception", ex);
         }
+        return -1; // for compiler
     }
 
     protected void check(ByteArrayOutputStream str, Consumer<String> checkOut, String label) {
         byte[] bytes = str.toByteArray();
         str.reset();
-        String out =  new String(bytes, StandardCharsets.UTF_8);
+        String out = new String(bytes, StandardCharsets.UTF_8);
         if (checkOut != null) {
             checkOut.accept(out);
         } else {
-            assertEquals("", out, label + ": Expected empty -- ");
+            assertEquals(out, "", label + ": Expected empty -- ");
         }
     }
 
-    protected void start(Consumer<String> checkCmdOutput,
-            Consumer<String> checkUserOutput, Consumer<String> checkError,
-            String... args) throws Exception {
-        runShell(args);
+    protected void checkExit(int ec, Consumer<Integer> checkCode) {
+        if (checkCode != null) {
+            checkCode.accept(ec);
+        } else {
+            assertEquals(ec, 0, "Expected standard exit code (0), but found: " + ec);
+        }
+    }
+
+    // Start and check the resultant: exit code (Ex), command output (Co),
+    // user output (Uo), command error (Ce), and console output (Cn)
+    protected void startExCoUoCeCn(Consumer<Integer> checkExitCode,
+            Consumer<String> checkCmdOutput,
+            Consumer<String> checkUserOutput,
+            Consumer<String> checkError,
+            Consumer<String> checkConsole,
+            String... args) {
+        int ec = runShell(args);
+        checkExit(ec, checkExitCode);
         check(cmdout, checkCmdOutput, "cmdout");
         check(cmderr, checkError, "cmderr");
-        check(console, null, "console");
+        check(console, checkConsole, "console");
         check(userout, checkUserOutput, "userout");
         check(usererr, null, "usererr");
     }
 
-    protected void start(String expectedCmdOutput, String expectedError, String... args) throws Exception {
-        startWithUserOutput(expectedCmdOutput, "",  expectedError, args);
+    // Start with an exit code and command error check
+    protected void startExCe(int eec, Consumer<String> checkError, String... args) {
+        StartOptionTest.this.startExCoUoCeCn(
+                (Integer ec) -> assertEquals((int) ec, eec,
+                        "Expected error exit code (" + eec + "), but found: " + ec),
+                null, null, checkError, null, args);
+    }
+
+    // Start with a command output check
+    protected void startCo(Consumer<String> checkCmdOutput, String... args) {
+        StartOptionTest.this.startExCoUoCeCn(null, checkCmdOutput, null, null, null, args);
+    }
+
+    private Consumer<String> assertOrNull(String expected, String label) {
+        return expected == null
+                ? null
+                : s -> assertEquals(s.trim(), expected.trim(), label);
     }
 
-    private void startWithUserOutput(String expectedCmdOutput, String expectedUserOutput,
-            String expectedError, String... args) throws Exception {
-        start(
-                s -> assertEquals(s.trim(), expectedCmdOutput, "cmdout: "),
-                s -> assertEquals(s.trim(), expectedUserOutput, "userout: "),
-                s -> assertEquals(s.trim(), expectedError, "cmderr: "),
+    // Start and check the resultant: exit code (Ex), command output (Co),
+    // user output (Uo), command error (Ce), and console output (Cn)
+    protected void startExCoUoCeCn(int expectedExitCode,
+            String expectedCmdOutput,
+            String expectedUserOutput,
+            String expectedError,
+            String expectedConsole,
+            String... args) {
+        startExCoUoCeCn(
+                expectedExitCode == 0
+                        ? null
+                        : (Integer i) -> assertEquals((int) i, expectedExitCode,
+                        "Expected exit code (" + expectedExitCode + "), but found: " + i),
+                assertOrNull(expectedCmdOutput, "cmdout: "),
+                assertOrNull(expectedUserOutput, "userout: "),
+                assertOrNull(expectedError, "cmderr: "),
+                assertOrNull(expectedConsole, "console: "),
                 args);
     }
 
+    // Start with an expected exit code and command error
+    protected void startExCe(int ec, String expectedError, String... args) {
+        startExCoUoCeCn(ec, null, null, expectedError, null, args);
+    }
+
+    // Start with an expected command output
+    protected void startCo(String expectedCmdOutput, String... args) {
+        startExCoUoCeCn(0, expectedCmdOutput, null, null, null, args);
+    }
+
+    // Start with an expected user output
+    protected void startUo(String expectedUserOutput, String... args) {
+        startExCoUoCeCn(0, null, expectedUserOutput, null, null, args);
+    }
+
     @BeforeMethod
     public void setUp() {
-        cmdout  = new ByteArrayOutputStream();
-        cmderr  = new ByteArrayOutputStream();
+        cmdout = new ByteArrayOutputStream();
+        cmderr = new ByteArrayOutputStream();
         console = new ByteArrayOutputStream();
         userout = new ByteArrayOutputStream();
         usererr = new ByteArrayOutputStream();
-        cmdInStream = new ByteArrayInputStream("/exit\n".getBytes());
+        setIn("/exit\n");
     }
 
-    protected String writeToFile(String stuff) throws Exception {
+    protected String writeToFile(String stuff) {
         Compiler compiler = new Compiler();
         Path p = compiler.getPath("doit.repl");
         compiler.writeToFile(p, stuff);
         return p.toString();
     }
 
-    public void testCommandFile() throws Exception {
-        String fn = writeToFile("String str = \"Hello \"\n/list\nSystem.out.println(str + str)\n/exit\n");
-        startWithUserOutput("1 : String str = \"Hello \";", "Hello Hello", "", "--no-startup", fn, "-s");
+    // Set the input from a String
+    protected void setIn(String s) {
+        cmdInStream = new ByteArrayInputStream(s.getBytes());
     }
 
-    public void testUsage() throws Exception {
+    // Test load files
+    public void testCommandFile() {
+        String fn = writeToFile("String str = \"Hello \"\n" +
+                "/list\n" +
+                "System.out.println(str + str)\n" +
+                "/exit\n");
+        startExCoUoCeCn(0,
+                "1 : String str = \"Hello \";\n",
+                "Hello Hello",
+                null,
+                null,
+                "--no-startup", fn, "-s");
+    }
+
+    // Test that the usage message is printed
+    public void testUsage() {
         for (String opt : new String[]{"-h", "--help"}) {
-            start(s -> {
+            startCo(s -> {
                 assertTrue(s.split("\n").length >= 7, "Not enough usage lines: " + s);
                 assertTrue(s.startsWith("Usage:   jshell <option>..."), "Unexpect usage start: " + s);
                 assertTrue(s.contains("--show-version"), "Expected help: " + s);
                 assertFalse(s.contains("Welcome"), "Unexpected start: " + s);
-            }, null, null, opt);
+            }, opt);
         }
     }
 
-    public void testHelpExtra() throws Exception {
+    // Test the --help-extra message
+    public void testHelpExtra() {
         for (String opt : new String[]{"-X", "--help-extra"}) {
-            start(s -> {
+            startCo(s -> {
                 assertTrue(s.split("\n").length >= 5, "Not enough help-extra lines: " + s);
                 assertTrue(s.contains("--add-exports"), "Expected --add-exports: " + s);
                 assertTrue(s.contains("--execution"), "Expected --add-exports: " + s);
                 assertFalse(s.contains("Welcome"), "Unexpected start: " + s);
-            }, null, null, opt);
+            }, opt);
         }
     }
 
-    public void testUnknown() throws Exception {
-        start(null, null,
-              s -> assertEquals(s.trim(), "Unknown option: u"), "-unknown");
-        start(null, null,
-              s -> assertEquals(s.trim(), "Unknown option: unknown"), "--unknown");
+    // Test handling of bogus options
+    public void testUnknown() {
+        startExCe(1, "Unknown option: u", "-unknown");
+        startExCe(1, "Unknown option: unknown", "--unknown");
     }
 
-    /**
-     * Test that input is read with "-" and there is no extra output.
-     * @throws Exception
-     */
-    public void testHypenFile() throws Exception {
-        cmdInStream = new ByteArrayInputStream("System.out.print(\"Hello\");\n".getBytes());
-        startWithUserOutput("", "Hello", "", "-");
-        cmdInStream = new ByteArrayInputStream("System.out.print(\"Hello\");\n".getBytes());
-        startWithUserOutput("", "Hello", "", "-", "-");
-        Compiler compiler = new Compiler();
-        Path path = compiler.getPath("markload.jsh");
-        compiler.writeToFile(path, "System.out.print(\"===\");");
-        cmdInStream = new ByteArrayInputStream("System.out.print(\"Hello\");\n".getBytes());
-        startWithUserOutput("", "===Hello===", "", path.toString(), "-", path.toString());
+    // Test that input is read with "-" and there is no extra output.
+    public void testHypenFile() {
+        setIn("System.out.print(\"Hello\");\n");
+        startUo("Hello", "-");
+        setIn("System.out.print(\"Hello\");\n");
+        startUo("Hello", "-", "-");
+        String fn = writeToFile("System.out.print(\"===\");");
+        setIn("System.out.print(\"Hello\");\n");
+        startUo("===Hello===", fn, "-", fn);
         // check that errors go to standard error
-        cmdInStream = new ByteArrayInputStream(") Foobar".getBytes());
-        start(
-                s -> assertEquals(s.trim(), "", "cmdout: empty"),
-                s -> assertEquals(s.trim(), "", "userout: empty"),
-                s -> assertTrue(s.contains("illegal start of expression"),
-                            "cmderr: illegal start of expression"),
+        setIn(") Foobar");
+        startExCe(0, s -> assertTrue(s.contains("illegal start of expression"),
+                "cmderr: illegal start of expression"),
                 "-");
     }
 
-    /**
-     * Test that non-existent load file sends output to stderr and does not start (no welcome).
-     * @throws Exception
-     */
-    public void testUnknownLoadFile() throws Exception {
-        start("", "File 'UNKNOWN' for 'jshell' is not found.", "UNKNOWN");
+    // Test that user specified exit codes are propagated
+    public void testExitCode() {
+        setIn("/exit 57\n");
+        startExCoUoCeCn(57, null, null, null, "-> /exit 57", "-s");
+        setIn("int eight = 8\n" +
+                "/exit eight + \n" +
+                " eight\n");
+        startExCoUoCeCn(16, null, null, null,
+                "-> int eight = 8\n" +
+                "-> /exit eight + \n" +
+                ">>  eight",
+                "-s");
     }
 
-    public void testStartup() throws Exception {
-        Compiler compiler = new Compiler();
-        Path p = compiler.getPath("file.txt");
-        compiler.writeToFile(p);
-        start("", "Argument to startup missing.", "--startup");
-        start("", "Conflicting options: both --startup and --no-startup were used.", "--no-startup", "--startup", p.toString());
-        start("", "Conflicting options: both --startup and --no-startup were used.", "--startup", p.toString(), "--no-startup");
-        start("", "Argument to startup missing.", "--no-startup", "--startup");
+    // Test that non-existent load file sends output to stderr and does not startExCe (no welcome).
+    public void testUnknownLoadFile() {
+        startExCe(1, "File 'UNKNOWN' for 'jshell' is not found.", "UNKNOWN");
     }
 
-    public void testStartupFailedOption() throws Exception {
-        start(
-                s -> assertEquals(s.trim(), "", "cmdout: "),
-                s -> assertEquals(s.trim(), "", "userout: "),
-                s -> assertTrue(s.contains("Unrecognized option: -hoge-foo-bar"), "cmderr: " + s),
+    // Test bad usage of the --startup option
+    public void testStartup() {
+        String fn = writeToFile("");
+        startExCe(1, "Argument to startup missing.", "--startup");
+        startExCe(1, "Conflicting options: both --startup and --no-startup were used.", "--no-startup", "--startup", fn);
+        startExCe(1, "Conflicting options: both --startup and --no-startup were used.", "--startup", fn, "--no-startup");
+        startExCe(1, "Argument to startup missing.", "--no-startup", "--startup");
+    }
+
+    // Test an option that causes the back-end to fail is propagated
+    public void testStartupFailedOption() {
+        startExCe(1, s -> assertTrue(s.contains("Unrecognized option: -hoge-foo-bar"), "cmderr: " + s),
                 "-R-hoge-foo-bar");
     }
 
-    public void testStartupUnknown() throws Exception {
-        start("", "File 'UNKNOWN' for '--startup' is not found.", "--startup", "UNKNOWN");
-        start("", "File 'UNKNOWN' for '--startup' is not found.", "--startup", "DEFAULT", "--startup", "UNKNOWN");
+    // Test the use of non-existant files with the --startup option
+    public void testStartupUnknown() {
+        startExCe(1, "File 'UNKNOWN' for '--startup' is not found.", "--startup", "UNKNOWN");
+        startExCe(1, "File 'UNKNOWN' for '--startup' is not found.", "--startup", "DEFAULT", "--startup", "UNKNOWN");
     }
 
-    public void testClasspath() throws Exception {
-        for (String cp : new String[] {"--class-path"}) {
-            start("", "Only one --class-path option may be used.", cp, ".", "--class-path", ".");
-            start("", "Argument to class-path missing.", cp);
+    // Test bad usage of --class-path option
+    public void testClasspath() {
+        for (String cp : new String[]{"--class-path"}) {
+            startExCe(1, "Only one --class-path option may be used.", cp, ".", "--class-path", ".");
+            startExCe(1, "Argument to class-path missing.", cp);
         }
     }
 
-    public void testUnknownModule() throws Exception {
-        start(
-                s -> assertEquals(s.trim(), "", "cmdout: "),
-                s -> assertEquals(s.trim(), "", "userout: "),
-                s -> assertTrue(s.contains("rror") && s.contains("unKnown"), "cmderr: " + s),
+    // Test bogus module on --add-modules option
+    public void testUnknownModule() {
+        startExCe(1, s -> assertTrue(s.contains("rror") && s.contains("unKnown"), "cmderr: " + s),
                 "--add-modules", "unKnown");
     }
 
-    public void testFeedbackOptionConflict() throws Exception {
-        start("", "Only one feedback option (--feedback, -q, -s, or -v) may be used.",
+    // Test that muliple feedback options fail
+    public void testFeedbackOptionConflict() {
+        startExCe(1, "Only one feedback option (--feedback, -q, -s, or -v) may be used.",
                 "--feedback", "concise", "--feedback", "verbose");
-        start("", "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "--feedback", "concise", "-s");
-        start("", "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "--feedback", "verbose", "-q");
-        start("", "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "--feedback", "concise", "-v");
-        start("", "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "-v", "--feedback", "concise");
-        start("", "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "-q", "-v");
-        start("", "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "-s", "-v");
-        start("", "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "-v", "-q");
-        start("", "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "-q", "-s");
+        startExCe(1, "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "--feedback", "concise", "-s");
+        startExCe(1, "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "--feedback", "verbose", "-q");
+        startExCe(1, "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "--feedback", "concise", "-v");
+        startExCe(1, "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "-v", "--feedback", "concise");
+        startExCe(1, "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "-q", "-v");
+        startExCe(1, "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "-s", "-v");
+        startExCe(1, "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "-v", "-q");
+        startExCe(1, "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "-q", "-s");
     }
 
-    public void testNegFeedbackOption() throws Exception {
-        start("", "Argument to feedback missing.", "--feedback");
-        start("", "Does not match any current feedback mode: blorp -- --feedback blorp", "--feedback", "blorp");
+    // Test bogus arguments to the --feedback option
+    public void testNegFeedbackOption() {
+        startExCe(1, "Argument to feedback missing.", "--feedback");
+        startExCe(1, "Does not match any current feedback mode: blorp -- --feedback blorp", "--feedback", "blorp");
     }
 
-    public void testVersion() throws Exception {
-        start(
-                s -> {
-                    assertTrue(s.startsWith("jshell"), "unexpected version: " + s);
-                    assertFalse(s.contains("Welcome"), "Unexpected start: " + s);
-                },
-                null, null,
+    // Test --version
+    public void testVersion() {
+        startCo(s -> {
+            assertTrue(s.startsWith("jshell"), "unexpected version: " + s);
+            assertFalse(s.contains("Welcome"), "Unexpected start: " + s);
+        },
                 "--version");
     }
 
-    public void testShowVersion() throws Exception {
-        runShell("--show-version");
-        check(cmdout,
+    // Test --show-version
+    public void testShowVersion() {
+        startExCoUoCeCn(null,
                 s -> {
                     assertTrue(s.startsWith("jshell"), "unexpected version: " + s);
                     assertTrue(s.contains("Welcome"), "Expected start (but got no welcome): " + s);
                 },
-                "cmdout");
-        check(cmderr, null, "cmderr");
-        check(console,
+                null,
+                null,
                 s -> assertTrue(s.trim().startsWith("jshell>"), "Expected prompt, got: " + s),
-                "console");
-        check(userout, null, "userout");
-        check(usererr, null, "usererr");
+                "--show-version");
     }
 
     @AfterMethod
     public void tearDown() {
-        cmdout  = null;
-        cmderr  = null;
+        cmdout = null;
+        cmderr = null;
         console = null;
         userout = null;
         usererr = null;
--- a/test/langtools/jdk/jshell/ToolProviderTest.java	Tue Nov 14 12:07:55 2017 -0800
+++ b/test/langtools/jdk/jshell/ToolProviderTest.java	Tue Nov 14 19:33:37 2017 -0800
@@ -21,21 +21,14 @@
  * questions.
  */
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.nio.file.Path;
 import java.util.ServiceLoader;
-import java.util.function.Consumer;
 import javax.tools.Tool;
-import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
 
 /*
  * @test
- * @bug 8170044 8171343 8179856
+ * @bug 8170044 8171343 8179856 8185840 8190383
  * @summary Test ServiceLoader launching of jshell tool
  * @modules jdk.compiler/com.sun.tools.javac.api
  *          jdk.compiler/com.sun.tools.javac.main
@@ -48,30 +41,25 @@
 @Test
 public class ToolProviderTest extends StartOptionTest {
 
-    private ByteArrayOutputStream cmdout;
-    private ByteArrayOutputStream cmderr;
-    private InputStream cmdInStream;
-
-    @BeforeMethod
+    // Through the provider, the console and console go to command out (we assume,
+    // because it works with the current tests) that console and user output are
+    // after command out.
     @Override
-    public void setUp() {
-        cmdout = new ByteArrayOutputStream();
-        cmderr = new ByteArrayOutputStream();
-        cmdInStream = new ByteArrayInputStream("/exit\n".getBytes());
+    protected void startExCoUoCeCn(int expectedExitCode,
+            String expectedCmdOutput,
+            String expectedUserOutput,
+            String expectedError,
+            String expectedConsole,
+            String... args) {
+        super.startExCoUoCeCn(expectedExitCode,
+                (expectedCmdOutput  == null? "" : expectedCmdOutput) +
+                (expectedConsole    == null? "" : expectedConsole) +
+                (expectedUserOutput == null? "" : expectedUserOutput),
+                null, expectedError, null, args);
     }
 
     @Override
-    protected void start(Consumer<String> checkCmdOutput,
-            Consumer<String> checkUserOutput, Consumer<String> checkError,
-            String... args) throws Exception {
-        if (runShellServiceLoader(args) != 0) {
-            fail("Repl tool failed");
-        }
-        check(cmdout, checkCmdOutput, "cmdout");
-        check(cmderr, checkError, "cmderr");
-    }
-
-    private int runShellServiceLoader(String... args) {
+    protected int runShell(String... args) {
         ServiceLoader<Tool> sl = ServiceLoader.load(Tool.class);
         for (Tool provider : sl) {
             if (provider.name().equals("jshell")) {
@@ -81,38 +69,14 @@
         throw new AssertionError("Repl tool not found by ServiceLoader: " + sl);
     }
 
-    @Override
-    public void testCommandFile() throws Exception {
-        String fn = writeToFile("String str = \"Hello \"\n/list\nSystem.out.println(str + str)\n/exit\n");
-        start("1 : String str = \"Hello \";" + "\n" + "Hello Hello", "", "--no-startup", fn, "-s");
-    }
-
+    // Test --show-version
     @Override
-    public void testShowVersion() throws Exception {
-        start(
-                s -> {
-                    assertTrue(s.startsWith("jshell "), "unexpected version: " + s);
-                    assertTrue(s.contains("Welcome"), "Expected start (but got no welcome): " + s);
-                    assertTrue(s.trim().contains("jshell>"), "Expected prompt, got: " + s);
-                },
-                null, null,
+    public void testShowVersion() {
+        startCo(s -> {
+            assertTrue(s.startsWith("jshell "), "unexpected version: " + s);
+            assertTrue(s.contains("Welcome"), "Expected start (but got no welcome): " + s);
+            assertTrue(s.trim().contains("jshell>"), "Expected prompt, got: " + s);
+        },
                 "--show-version");
     }
-    /**
-     * Test that input is read with "-" and there is no extra output.
-     * @throws Exception
-     */
-    @Override
-    public void testHypenFile() throws Exception {
-        cmdInStream = new ByteArrayInputStream("System.out.print(\"Hello\");\n".getBytes());
-        start("Hello", "", "-");
-        cmdInStream = new ByteArrayInputStream("System.out.print(\"Hello\");\n".getBytes());
-        start("Hello", "", "-", "-");
-        Compiler compiler = new Compiler();
-        Path path = compiler.getPath("markload.jsh");
-        compiler.writeToFile(path, "System.out.print(\"===\");");
-        cmdInStream = new ByteArrayInputStream("System.out.print(\"Hello\");\n".getBytes());
-        start("===Hello===", "", path.toString(), "-", path.toString());
-    }
-
 }
--- a/test/langtools/jdk/jshell/ToolTabCommandTest.java	Tue Nov 14 12:07:55 2017 -0800
+++ b/test/langtools/jdk/jshell/ToolTabCommandTest.java	Tue Nov 14 19:33:37 2017 -0800
@@ -23,7 +23,7 @@
 
 /**
  * @test
- * @bug 8177076
+ * @bug 8177076 8185840
  * @modules
  *     jdk.compiler/com.sun.tools.javac.api
  *     jdk.compiler/com.sun.tools.javac.main
@@ -107,11 +107,16 @@
             waitOutput(out, Pattern.quote(getResource("help.exit.summary")) + "\n\n" +
                             Pattern.quote(getResource("jshell.console.see.full.documentation")) + "\n\r\u0005/exit ");
             inputSink.write("\011");
-            waitOutput(out, Pattern.quote(getResource("help.exit")) + "\n" +
+            waitOutput(out, Pattern.quote(getResource("help.exit").replaceAll("\t", "    ")) + "\n" +
                             "\r\u0005/exit ");
             inputSink.write("\011");
             waitOutput(out, Pattern.quote(getResource("help.exit.summary")) + "\n\n" +
                             Pattern.quote(getResource("jshell.console.see.full.documentation")) + "\n\r\u0005/exit ");
+            inputSink.write("\u0003");
+            inputSink.write("int zebraStripes = 11\n");
+            waitOutput(out, "zebraStripes ==> 11\n\u0005");
+            inputSink.write("/exit zeb\011");
+            waitOutput(out, "braStr.*es");
             inputSink.write("\u0003/doesnotexist\011");
             waitOutput(out, "\u0005/doesnotexist\n" +
                             Pattern.quote(getResource("jshell.console.no.such.command")) + "\n" +