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
--- 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")
+ );
+ }
+}