8081845: JShell: Need way to refresh relative to external state
authorrfield
Mon, 11 Jan 2016 08:41:00 -0800
changeset 34999 aacf94dab449
parent 34998 416dfba03d33
child 35000 952a7b4652f0
8081845: JShell: Need way to refresh relative to external state Summary: Add the ability to record and replay relevant parts of history Reviewed-by: jlahoda
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java
langtools/test/jdk/jshell/ToolReloadTest.java
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java	Mon Jan 11 17:08:20 2016 +0100
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java	Mon Jan 11 08:41:00 2016 -0800
@@ -1,3 +1,4 @@
+
 /*
  * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
@@ -90,8 +91,10 @@
 import java.util.Optional;
 import java.util.ResourceBundle;
 import java.util.Spliterators;
+import java.util.function.Function;
 import java.util.function.Supplier;
 import static java.util.stream.Collectors.toList;
+import static jdk.jshell.Snippet.SubKind.VAR_VALUE_SUBKIND;
 
 /**
  * Command line REPL tool for Java using the JShell API.
@@ -102,6 +105,7 @@
     private static final Pattern LINEBREAK = Pattern.compile("\\R");
     private static final Pattern HISTORY_ALL_START_FILENAME = Pattern.compile(
             "((?<cmd>(all|history|start))(\\z|\\p{javaWhitespace}+))?(?<filename>.*)");
+    private static final String RECORD_SEPARATOR = "\u241E";
 
     final InputStream cmdin;
     final PrintStream cmdout;
@@ -150,9 +154,14 @@
     private String cmdlineStartup = null;
     private String editor = null;
 
-    static final Preferences PREFS = Preferences.userRoot().node("tool/REPL");
+    // Commands and snippets which should be replayed
+    private List<String> replayableHistory;
+    private List<String> replayableHistoryPrevious;
+
+    static final Preferences PREFS = Preferences.userRoot().node("tool/JShell");
 
     static final String STARTUP_KEY = "STARTUP";
+    static final String REPLAY_RESTORE_KEY = "REPLAY_RESTORE";
 
     static final String DEFAULT_STARTUP =
             "\n" +
@@ -165,11 +174,14 @@
             "import java.util.regex.*;\n" +
             "void printf(String format, Object... args) { System.out.printf(format, args); }\n";
 
-    // Tool id (tid) mapping
+    // Tool id (tid) mapping: the three name spaces
     NameSpace mainNamespace;
     NameSpace startNamespace;
     NameSpace errorNamespace;
+
+    // Tool id (tid) mapping: the current name spaces
     NameSpace currentNameSpace;
+
     Map<Snippet,SnippetInfo> mapSnippet;
 
     void debug(String format, Object... args) {
@@ -252,6 +264,12 @@
     private void start(IOContext in, List<String> loadList) {
         resetState(); // Initialize
 
+        // Read replay history from last jshell session into previous history
+        String prevReplay = PREFS.get(REPLAY_RESTORE_KEY, null);
+        if (prevReplay != null) {
+            replayableHistoryPrevious = Arrays.asList(prevReplay.split(RECORD_SEPARATOR));
+        }
+
         for (String loadFile : loadList) {
             cmdOpen(loadFile);
         }
@@ -370,6 +388,10 @@
         mapSnippet = new LinkedHashMap<>();
         currentNameSpace = startNamespace;
 
+        // Reset the replayable history, saving the old for restore
+        replayableHistoryPrevious = replayableHistory;
+        replayableHistory = new ArrayList<>();
+
         state = JShell.builder()
                 .in(userin)
                 .out(userout)
@@ -382,7 +404,8 @@
         analysis = state.sourceCodeAnalysis();
         shutdownSubscription = state.onShutdown((JShell deadState) -> {
             if (deadState == state) {
-                hard("State engine terminated.  See /history");
+                hard("State engine terminated.");
+                hard("Restore definitions with: /reload restore");
                 live = false;
             }
         });
@@ -392,7 +415,6 @@
             state.addToClasspath(cmdlineClasspath);
         }
 
-
         String start;
         if (cmdlineStartup == null) {
             start = PREFS.get(STARTUP_KEY, "<nada>");
@@ -431,7 +453,7 @@
             String incomplete = "";
             while (live) {
                 String prompt;
-                if (in.interactiveOutput() && displayPrompt) {
+                if (displayPrompt) {
                     prompt = testPrompt
                                     ? incomplete.isEmpty()
                                             ? "\u0005" //ENQ
@@ -480,6 +502,12 @@
         }
     }
 
+    private void addToReplayHistory(String s) {
+        if (currentNameSpace == mainNamespace) {
+            replayableHistory.add(s);
+        }
+    }
+
     private String processSourceCatchingReset(String src) {
         try {
             input.beforeUserCode();
@@ -516,7 +544,12 @@
                 fluff("Type /help for help.");
             }
         } else if (candidates.length == 1) {
-            candidates[0].run.accept(arg);
+            Command command = candidates[0];
+
+            // If comand was successful and is of a replayable kind, add it the replayable history
+            if (command.run.apply(arg) && command.kind == CommandKind.REPLAY) {
+                addToReplayHistory((command.command + " " + arg).trim());
+            }
         } else {
             hard("Command: %s is ambiguous: %s", cmd, Arrays.stream(candidates).map(c -> c.command).collect(Collectors.joining(", ")));
             fluff("Type /help for help.");
@@ -546,15 +579,15 @@
         public final String command;
         public final String params;
         public final String description;
-        public final Consumer<String> run;
+        public final Function<String,Boolean> run;
         public final CompletionProvider completions;
         public final CommandKind kind;
 
-        public Command(String command, String params, String description, Consumer<String> run, CompletionProvider completions) {
+        public Command(String command, String params, String description, Function<String,Boolean> run, CompletionProvider completions) {
             this(command, params, description, run, completions, CommandKind.NORMAL);
         }
 
-        public Command(String command, String params, String description, Consumer<String> run, CompletionProvider completions, CommandKind kind) {
+        public Command(String command, String params, String description, Function<String,Boolean> run, CompletionProvider completions, CommandKind kind) {
             this.command = command;
             this.params = params;
             this.description = description;
@@ -571,6 +604,7 @@
 
     enum CommandKind {
         NORMAL,
+        REPLAY,
         HIDDEN,
         HELP_ONLY;
     }
@@ -602,6 +636,7 @@
 
     private static final CompletionProvider EMPTY_COMPLETION_PROVIDER = new FixedCompletionProvider();
     private static final CompletionProvider KEYWORD_COMPLETION_PROVIDER = new FixedCompletionProvider("all ", "start ", "history ");
+    private static final CompletionProvider RELOAD_OPTIONS_COMPLETION_PROVIDER = new FixedCompletionProvider("restore", "quiet");
     private static final CompletionProvider FILE_COMPLETION_PROVIDER = fileCompletions(p -> true);
     private final Map<String, Command> commands = new LinkedHashMap<>();
     private void registerCommand(Command cmd) {
@@ -674,6 +709,16 @@
         };
     }
 
+    private static CompletionProvider reloadCompletion() {
+        return (code, cursor, anchor) -> {
+            List<Suggestion> result = new ArrayList<>();
+            int pastSpace = code.indexOf(' ') + 1; // zero if no space
+            result.addAll(RELOAD_OPTIONS_COMPLETION_PROVIDER.completionSuggestions(code.substring(pastSpace), cursor - pastSpace, anchor));
+            anchor[0] += pastSpace;
+            return result;
+        };
+    }
+
     // Table of commands -- with command forms, argument kinds, help message, implementation, ...
 
     {
@@ -688,7 +733,8 @@
                                     editCompletion()));
         registerCommand(new Command("/drop", "<name or id>", "delete a source entry referenced by name or id",
                                     arg -> cmdDrop(arg),
-                                    editCompletion()));
+                                    editCompletion(),
+                                    CommandKind.REPLAY));
         registerCommand(new Command("/save", "[all|history|start] <file>", "save: <none> - current source;\n" +
                                                                            "      all - source including overwritten, failed, and start-up code;\n" +
                                                                            "      history - editing history;\n" +
@@ -716,6 +762,9 @@
         registerCommand(new Command("/reset", null, "reset everything in the REPL",
                                     arg -> cmdReset(),
                                     EMPTY_COMPLETION_PROVIDER));
+        registerCommand(new Command("/reload", "[restore] [quiet]", "reset and replay relevant history -- current or previous (restore)",
+                                    arg -> cmdReload(arg),
+                                    reloadCompletion()));
         registerCommand(new Command("/feedback", "<level>", "feedback information: off, concise, normal, verbose, default, or ?",
                                     arg -> cmdFeedback(arg),
                                     new FixedCompletionProvider("off", "concise", "normal", "verbose", "default", "?")));
@@ -724,7 +773,8 @@
                                     EMPTY_COMPLETION_PROVIDER));
         registerCommand(new Command("/classpath", "<path>", "add a path to the classpath",
                                     arg -> cmdClasspath(arg),
-                                    classPathCompletion()));
+                                    classPathCompletion(),
+                                    CommandKind.REPLAY));
         registerCommand(new Command("/history", null, "history of what you have typed",
                                     arg -> cmdHistory(),
                                     EMPTY_COMPLETION_PROVIDER));
@@ -801,25 +851,29 @@
 
     // --- Command implementations ---
 
-    void cmdSetEditor(String arg) {
+    boolean cmdSetEditor(String arg) {
         if (arg.isEmpty()) {
             hard("/seteditor requires a path argument");
+            return false;
         } else {
             editor = arg;
             fluff("Editor set to: %s", arg);
+            return true;
         }
     }
 
-    void cmdClasspath(String arg) {
+    boolean cmdClasspath(String arg) {
         if (arg.isEmpty()) {
             hard("/classpath requires a path argument");
+            return false;
         } else {
             state.addToClasspath(toPathResolvingUserHome(arg).toString());
             fluff("Path %s added to classpath", arg);
+            return true;
         }
     }
 
-    void cmdDebug(String arg) {
+    boolean cmdDebug(String arg) {
         if (arg.isEmpty()) {
             debug = !debug;
             InternalDebugControl.setDebugFlags(state, debug ? InternalDebugControl.DBG_GEN : 0);
@@ -860,20 +914,26 @@
                     default:
                         hard("Unknown debugging option: %c", ch);
                         fluff("Use: 0 r g f c d");
-                        break;
+                        return false;
                 }
             }
             InternalDebugControl.setDebugFlags(state, flags);
         }
+        return true;
     }
 
-    private void cmdExit() {
+    private boolean cmdExit() {
         regenerateOnDeath = false;
         live = false;
+        if (!replayableHistory.isEmpty()) {
+            PREFS.put(REPLAY_RESTORE_KEY, replayableHistory.stream().reduce(
+                    (a, b) -> a + RECORD_SEPARATOR + b).get());
+        }
         fluff("Goodbye\n");
+        return true;
     }
 
-    private void cmdFeedback(String arg) {
+    private boolean cmdFeedback(String arg) {
         switch (arg) {
             case "":
             case "d":
@@ -905,12 +965,13 @@
                 hard("  default");
                 hard("You may also use just the first letter, for example: /f c");
                 hard("In interactive mode 'default' is the same as 'normal', from a file it is the same as 'off'");
-                return;
+                return false;
         }
         fluff("Feedback mode: %s", feedback.name().toLowerCase());
+        return true;
     }
 
-    void cmdHelp() {
+    boolean cmdHelp() {
         int synopsisLen = 0;
         Map<String, String> synopsis2Description = new LinkedHashMap<>();
         for (Command cmd : new LinkedHashSet<>(commands.values())) {
@@ -936,14 +997,16 @@
         cmdout.println("Supported shortcuts include:");
         cmdout.println("<tab>       -- show possible completions for the current text");
         cmdout.println("Shift-<tab> -- for current method or constructor invocation, show a synopsis of the method/constructor");
+        return true;
     }
 
-    private void cmdHistory() {
+    private boolean cmdHistory() {
         cmdout.println();
         for (String s : input.currentSessionHistory()) {
             // No number prefix, confusing with snippet ids
             cmdout.printf("%s\n", s);
         }
+        return true;
     }
 
     /**
@@ -1010,23 +1073,23 @@
         }
     }
 
-    private void cmdDrop(String arg) {
+    private boolean cmdDrop(String arg) {
         if (arg.isEmpty()) {
             hard("In the /drop argument, please specify an import, variable, method, or class to drop.");
             hard("Specify by id or name. Use /list to see ids. Use /reset to reset all state.");
-            return;
+            return false;
         }
         Stream<Snippet> stream = argToSnippets(arg, false);
         if (stream == null) {
             hard("No definition or id named %s found.  See /classes, /methods, /vars, or /list", arg);
-            return;
+            return false;
         }
         List<Snippet> snippets = stream
                 .filter(sn -> state.status(sn).isActive && sn instanceof PersistentSnippet)
                 .collect(toList());
         if (snippets.isEmpty()) {
             hard("The argument did not specify an active import, variable, method, or class to drop.");
-            return;
+            return false;
         }
         if (snippets.size() > 1) {
             hard("The argument references more than one import, variable, method, or class.");
@@ -1034,17 +1097,18 @@
             for (Snippet sn : snippets) {
                 cmdout.printf("%4s : %s\n", sn.id(), sn.source().replace("\n", "\n       "));
             }
-            return;
+            return false;
         }
         PersistentSnippet psn = (PersistentSnippet) snippets.get(0);
         state.drop(psn).forEach(this::handleEvent);
+        return true;
     }
 
-    private void cmdEdit(String arg) {
+    private boolean cmdEdit(String arg) {
         Stream<Snippet> stream = argToSnippets(arg, true);
         if (stream == null) {
             hard("No definition or id named %s found.  See /classes, /methods, /vars, or /list", arg);
-            return;
+            return false;
         }
         Set<String> srcSet = new LinkedHashSet<>();
         stream.forEachOrdered(sn -> {
@@ -1078,6 +1142,7 @@
         } else {
             ExternalEditor.edit(editor, errorHandler, src, saveHandler, input);
         }
+        return true;
     }
     //where
     // receives editor requests to save
@@ -1135,10 +1200,9 @@
         }
     }
 
-    private void cmdList(String arg) {
+    private boolean cmdList(String arg) {
         if (arg.equals("history")) {
-            cmdHistory();
-            return;
+            return cmdHistory();
         }
         Stream<Snippet> stream = argToSnippets(arg, true);
         if (stream == null) {
@@ -1148,7 +1212,7 @@
             } else {
                 hard("No definition or id named %s found.  There are no active definitions.", arg);
             }
-            return;
+            return false;
         }
 
         // prevent double newline on empty list
@@ -1160,38 +1224,72 @@
             }
             cmdout.printf("%4s : %s\n", sn.id(), sn.source().replace("\n", "\n       "));
         });
+        return true;
     }
 
-    private void cmdOpen(String filename) {
+    private boolean cmdOpen(String filename) {
         if (filename.isEmpty()) {
             hard("The /open command requires a filename argument.");
+            return false;
         } else {
             try {
                 run(new FileScannerIOContext(toPathResolvingUserHome(filename).toString()));
             } catch (FileNotFoundException e) {
                 hard("File '%s' is not found: %s", filename, e.getMessage());
+                return false;
             } catch (Exception e) {
                 hard("Exception while reading file: %s", e);
+                return false;
             }
         }
+        return true;
     }
 
-    private void cmdPrompt() {
+    private boolean cmdPrompt() {
         displayPrompt = !displayPrompt;
         fluff("Prompt will %sdisplay. Use /prompt to toggle.", displayPrompt ? "" : "NOT ");
         concise("Prompt: %s", displayPrompt ? "on" : "off");
+        return true;
+    }
+
+    private boolean cmdReset() {
+        live = false;
+        fluff("Resetting state.");
+        return true;
     }
 
-    private void cmdReset() {
-        live = false;
-        fluff("Resetting state.");
+    private boolean cmdReload(String arg) {
+        Iterable<String> history = replayableHistory;
+        boolean echo = true;
+        if (arg.length() > 0) {
+            if ("restore".startsWith(arg)) {
+                if (replayableHistoryPrevious == null) {
+                    hard("No previous history to restore\n", arg);
+                    return false;
+                }
+                history = replayableHistoryPrevious;
+            } else if ("quiet".startsWith(arg)) {
+                echo = false;
+            } else {
+                hard("Invalid argument to reload command: %s\nUse 'restore', 'quiet', or no argument\n", arg);
+                return false;
+            }
+        }
+        fluff("Restarting and restoring %s.",
+                history == replayableHistoryPrevious
+                        ? "from previous state"
+                        : "state");
+        resetState();
+        run(new ReloadIOContext(history,
+                echo? cmdout : null));
+        return true;
     }
 
-    private void cmdSave(String arg_filename) {
+    private boolean cmdSave(String arg_filename) {
         Matcher mat = HISTORY_ALL_START_FILENAME.matcher(arg_filename);
         if (!mat.find()) {
             hard("Malformed argument to the /save command: %s", arg_filename);
-            return;
+            return false;
         }
         boolean useHistory = false;
         String saveAll = "";
@@ -1211,7 +1309,7 @@
         String filename = mat.group("filename");
         if (filename == null ||filename.isEmpty()) {
             hard("The /save command requires a filename argument.");
-            return;
+            return false;
         }
         try (BufferedWriter writer = Files.newBufferedWriter(toPathResolvingUserHome(filename),
                 Charset.defaultCharset(),
@@ -1234,12 +1332,15 @@
             }
         } catch (FileNotFoundException e) {
             hard("File '%s' for save is not accessible: %s", filename, e.getMessage());
+            return false;
         } catch (Exception e) {
             hard("Exception while saving: %s", e);
+            return false;
         }
+        return true;
     }
 
-    private void cmdSetStart(String filename) {
+    private boolean cmdSetStart(String filename) {
         if (filename.isEmpty()) {
             hard("The /setstart command requires a filename argument.");
         } else {
@@ -1249,30 +1350,36 @@
                 PREFS.put(STARTUP_KEY, init);
             } catch (AccessDeniedException e) {
                 hard("File '%s' for /setstart is not accessible.", filename);
+                return false;
             } catch (NoSuchFileException e) {
                 hard("File '%s' for /setstart is not found.", filename);
+                return false;
             } catch (Exception e) {
                 hard("Exception while reading start set file: %s", e);
+                return false;
             }
         }
+        return true;
     }
 
-    private void cmdVars() {
+    private boolean cmdVars() {
         for (VarSnippet vk : state.variables()) {
             String val = state.status(vk) == Status.VALID
                     ? state.varValue(vk)
                     : "(not-active)";
             hard("  %s %s = %s", vk.typeName(), vk.name(), val);
         }
+        return true;
     }
 
-    private void cmdMethods() {
+    private boolean cmdMethods() {
         for (MethodSnippet mk : state.methods()) {
             hard("  %s %s", mk.name(), mk.signature());
         }
+        return true;
     }
 
-    private void cmdClasses() {
+    private boolean cmdClasses() {
         for (TypeDeclSnippet ck : state.types()) {
             String kind;
             switch (ck.subKind()) {
@@ -1295,15 +1402,17 @@
             }
             hard("  %s %s", kind, ck.name());
         }
+        return true;
     }
 
-    private void cmdImports() {
+    private boolean cmdImports() {
         state.imports().forEach(ik -> {
             hard("  import %s%s", ik.isStatic() ? "static " : "", ik.fullname());
         });
+        return true;
     }
 
-    private void cmdUseHistoryEntry(int index) {
+    private boolean cmdUseHistoryEntry(int index) {
         List<Snippet> keys = state.snippets();
         if (index < 0)
             index += keys.size();
@@ -1313,7 +1422,9 @@
             rerunSnippet(keys.get(index));
         } else {
             hard("Cannot find snippet %d", index + 1);
+            return false;
         }
+        return true;
     }
 
     private boolean rerunHistoryEntryById(String id) {
@@ -1425,10 +1536,24 @@
     private boolean processCompleteSource(String source) throws IllegalStateException {
         debug("Compiling: %s", source);
         boolean failed = false;
+        boolean isActive = false;
         List<SnippetEvent> events = state.eval(source);
         for (SnippetEvent e : events) {
+            // Report the event, recording failure
             failed |= handleEvent(e);
+
+            // If any main snippet is active, this should be replayable
+            // also ignore var value queries
+            isActive |= e.causeSnippet() == null &&
+                    e.status().isActive &&
+                    e.snippet().subKind() != VAR_VALUE_SUBKIND;
         }
+        // If this is an active snippet and it didn't cause the backend to die,
+        // add it to the replayable history
+        if (isActive && live) {
+            addToReplayHistory(source);
+        }
+
         return failed;
     }
 
@@ -1784,31 +1909,11 @@
     }
 }
 
-class ScannerIOContext extends IOContext {
-
-    private final Scanner scannerIn;
-    private final PrintStream pStream;
-
-    public ScannerIOContext(Scanner scannerIn, PrintStream pStream) {
-        this.scannerIn = scannerIn;
-        this.pStream = pStream;
-    }
-
-    @Override
-    public String readLine(String prompt, String prefix) {
-        if (pStream != null && prompt != null) {
-            pStream.print(prompt);
-        }
-        if (scannerIn.hasNextLine()) {
-            return scannerIn.nextLine();
-        } else {
-            return null;
-        }
-    }
+abstract class NonInteractiveIOContext extends IOContext {
 
     @Override
     public boolean interactiveOutput() {
-        return true;
+        return false;
     }
 
     @Override
@@ -1817,11 +1922,6 @@
     }
 
     @Override
-    public void close() {
-        scannerIn.close();
-    }
-
-    @Override
     public boolean terminalEditorRunning() {
         return false;
     }
@@ -1847,19 +1947,62 @@
     }
 }
 
+class ScannerIOContext extends NonInteractiveIOContext {
+    private final Scanner scannerIn;
+
+    ScannerIOContext(Scanner scannerIn) {
+        this.scannerIn = scannerIn;
+    }
+
+    @Override
+    public String readLine(String prompt, String prefix) {
+        if (scannerIn.hasNextLine()) {
+            return scannerIn.nextLine();
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void close() {
+        scannerIn.close();
+    }
+}
+
 class FileScannerIOContext extends ScannerIOContext {
 
-    public FileScannerIOContext(String fn) throws FileNotFoundException {
+    FileScannerIOContext(String fn) throws FileNotFoundException {
         this(new FileReader(fn));
     }
 
-    public FileScannerIOContext(Reader rdr) throws FileNotFoundException {
-        super(new Scanner(rdr), null);
+    FileScannerIOContext(Reader rdr) throws FileNotFoundException {
+        super(new Scanner(rdr));
+    }
+}
+
+class ReloadIOContext extends NonInteractiveIOContext {
+    private final Iterator<String> it;
+    private final PrintStream echoStream;
+
+    ReloadIOContext(Iterable<String> history, PrintStream echoStream) {
+        this.it = history.iterator();
+        this.echoStream = echoStream;
     }
 
     @Override
-    public boolean interactiveOutput() {
-        return false;
+    public String readLine(String prompt, String prefix) {
+        String s = it.hasNext()
+                ? it.next()
+                : null;
+        if (echoStream != null && s != null) {
+            String p = "-: ";
+            String p2 = "\n   ";
+            echoStream.printf("%s%s\n", p, s.replace("\n", p2));
+        }
+        return s;
+    }
+
+    @Override
+    public void close() {
     }
 }
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ToolReloadTest.java	Mon Jan 11 08:41:00 2016 -0800
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8081845
+ * @summary Tests for /reload in JShell tool
+ * @library /tools/lib
+ * @build KullaTesting TestingInputStream ToolBox Compiler
+ * @run testng ToolReloadTest
+ */
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.function.Function;
+
+import org.testng.annotations.Test;
+
+
+@Test
+public class ToolReloadTest extends ReplToolTesting {
+
+    public void testReloadSnippets() {
+        test(
+                (a) -> assertVariable(a, "int", "x", "5", "5"),
+                (a) -> assertMethod(a, "int m(int z) { return z * z; }",
+                        "(int)int", "m"),
+                (a) -> evaluateExpression(a, "int", "m(x)", "25"),
+                (a) -> assertCommand(a, "/reload",
+                        "|  Restarting and restoring state.\n" +
+                        "-: int x = 5;\n" +
+                        "-: int m(int z) { return z * z; }\n" +
+                        "-: m(x)\n"),
+                (a) -> evaluateExpression(a, "int", "m(x)", "25"),
+                (a) -> assertCommandCheckOutput(a, "/vars", assertVariables()),
+                (a) -> assertCommandCheckOutput(a, "/methods", assertMethods())
+        );
+    }
+
+    public void testReloadClasspath() {
+        Function<String,String> prog = (s) -> String.format(
+                "package pkg; public class A { public String toString() { return \"%s\"; } }\n", s);
+        Compiler compiler = new Compiler();
+        Path outDir = Paths.get("testClasspathDirectory");
+        compiler.compile(outDir, prog.apply("A"));
+        Path classpath = compiler.getPath(outDir);
+        test(
+                (a) -> assertCommand(a, "/classpath " + classpath,
+                        String.format("|  Path %s added to classpath\n", classpath)),
+                (a) -> assertMethod(a, "String foo() { return (new pkg.A()).toString(); }",
+                        "()String", "foo"),
+                (a) -> assertVariable(a, "String", "v", "foo()", "\"A\""),
+                (a) -> {
+                       if (!a) compiler.compile(outDir, prog.apply("Aprime"));
+                       assertCommand(a, "/reload",
+                        "|  Restarting and restoring state.\n" +
+                        "-: /classpath " + classpath + "\n" +
+                        "-: String foo() { return (new pkg.A()).toString(); }\n" +
+                        "-: String v = foo();\n");
+                       },
+                (a) -> assertCommand(a, "v", "|  Variable v of type String has value \"Aprime\"\n"),
+                (a) -> evaluateExpression(a, "String", "foo()", "\"Aprime\""),
+                (a) -> evaluateExpression(a, "pkg.A", "new pkg.A();", "\"Aprime\"")
+        );
+    }
+
+    public void testReloadDrop() {
+        test(false, new String[]{"-nostartup"},
+                a -> assertVariable(a, "int", "a"),
+                a -> dropVariable(a, "/dr 1", "int a = 0"),
+                a -> assertMethod(a, "int b() { return 0; }", "()I", "b"),
+                a -> dropMethod(a, "/drop b", "b ()I"),
+                a -> assertClass(a, "class A {}", "class", "A"),
+                a -> dropClass(a, "/dr A", "class A"),
+                a -> assertCommand(a, "/reload",
+                        "|  Restarting and restoring state.\n" +
+                        "-: int a;\n" +
+                        "-: /drop 1\n" +
+                        "-: int b() { return 0; }\n" +
+                        "-: /drop b\n" +
+                        "-: class A {}\n" +
+                        "-: /drop A\n"),
+                a -> assertCommandCheckOutput(a, "/vars", assertVariables()),
+                a -> assertCommandCheckOutput(a, "/methods", assertMethods()),
+                a -> assertCommandCheckOutput(a, "/classes", assertClasses()),
+                a -> assertCommandCheckOutput(a, "/imports", assertImports())
+        );
+    }
+
+    public void testReloadRepeat() {
+        test(false, new String[]{"-nostartup"},
+                (a) -> assertVariable(a, "int", "c", "7", "7"),
+                (a) -> assertCommand(a, "++c", null),
+                (a) -> assertCommand(a, "/!", null),
+                (a) -> assertCommand(a, "/2", null),
+                (a) -> assertCommand(a, "/-1", null),
+                (a) -> assertCommand(a, "/reload",
+                        "|  Restarting and restoring state.\n" +
+                        "-: int c = 7;\n" +
+                        "-: ++c\n" +
+                        "-: ++c\n" +
+                        "-: ++c\n" +
+                        "-: ++c\n"
+                ),
+                (a) -> assertCommand(a, "c", "|  Variable c of type int has value 11\n"),
+                (a) -> assertCommand(a, "$4", "|  Variable $4 of type int has value 10\n")
+        );
+    }
+
+    public void testReloadIgnore() {
+        test(false, new String[]{"-nostartup"},
+                (a) -> assertCommand(a, "(-)", null),
+                (a) -> assertCommand(a, "/list", null),
+                (a) -> assertCommand(a, "/history", null),
+                (a) -> assertCommand(a, "/help", null),
+                (a) -> assertCommand(a, "/vars", null),
+                (a) -> assertCommand(a, "/save abcd", null),
+                (a) -> assertCommand(a, "/reload",
+                        "|  Restarting and restoring state.\n")
+        );
+    }
+
+    public void testReloadResetRestore() {
+        test(
+                (a) -> assertVariable(a, "int", "x", "5", "5"),
+                (a) -> assertMethod(a, "int m(int z) { return z * z; }",
+                        "(int)int", "m"),
+                (a) -> evaluateExpression(a, "int", "m(x)", "25"),
+                (a) -> assertCommand(a, "/reset", "|  Resetting state.\n"),
+                (a) -> assertCommand(a, "/reload restore",
+                        "|  Restarting and restoring from previous state.\n" +
+                        "-: int x = 5;\n" +
+                        "-: int m(int z) { return z * z; }\n" +
+                        "-: m(x)\n"),
+                (a) -> evaluateExpression(a, "int", "m(x)", "25"),
+                (a) -> assertCommandCheckOutput(a, "/vars", assertVariables()),
+                (a) -> assertCommandCheckOutput(a, "/methods", assertMethods())
+        );
+    }
+
+    public void testReloadCrashRestore() {
+        test(
+                (a) -> assertVariable(a, "int", "x", "5", "5"),
+                (a) -> assertMethod(a, "int m(int z) { return z * z; }",
+                        "(int)int", "m"),
+                (a) -> evaluateExpression(a, "int", "m(x)", "25"),
+                (a) -> assertCommand(a, "System.exit(1);",
+                        "|  State engine terminated.\n" +
+                        "|  Restore definitions with: /reload restore\n"),
+                (a) -> assertCommand(a, "/reload restore",
+                        "|  Restarting and restoring from previous state.\n" +
+                        "-: int x = 5;\n" +
+                        "-: int m(int z) { return z * z; }\n" +
+                        "-: m(x)\n"),
+                (a) -> evaluateExpression(a, "int", "m(x)", "25"),
+                (a) -> assertCommandCheckOutput(a, "/vars", assertVariables()),
+                (a) -> assertCommandCheckOutput(a, "/methods", assertMethods())
+        );
+    }
+
+    public void testReloadExitRestore() {
+        test(false, new String[]{"-nostartup"},
+                (a) -> assertVariable(a, "int", "x", "5", "5"),
+                (a) -> assertMethod(a, "int m(int z) { return z * z; }",
+                        "(int)int", "m"),
+                (a) -> evaluateExpression(a, "int", "m(x)", "25")
+        );
+        test(false, new String[]{"-nostartup"},
+                (a) -> assertCommand(a, "/reload restore",
+                        "|  Restarting and restoring from previous state.\n" +
+                        "-: int x = 5;\n" +
+                        "-: int m(int z) { return z * z; }\n" +
+                        "-: m(x)\n"),
+                (a) -> evaluateExpression(a, "int", "m(x)", "25")
+        );
+    }
+}