8081845: JShell: Need way to refresh relative to external state

Add the ability to record and replay relevant parts of history

Reviewed-by: jlahoda
This commit is contained in:
Robert Field 2016-01-11 08:41:00 -08:00
parent 3512de403f
commit d15f6a78b3
2 changed files with 419 additions and 79 deletions

View File

@ -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.MissingResourceException;
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 @@ public class JShellTool {
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 @@ public class JShellTool {
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 @@ public class JShellTool {
"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 @@ public class JShellTool {
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 @@ public class JShellTool {
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 @@ public class JShellTool {
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 @@ public class JShellTool {
state.addToClasspath(cmdlineClasspath);
}
String start;
if (cmdlineStartup == null) {
start = PREFS.get(STARTUP_KEY, "<nada>");
@ -431,7 +453,7 @@ public class JShellTool {
String incomplete = "";
while (live) {
String prompt;
if (in.interactiveOutput() && displayPrompt) {
if (displayPrompt) {
prompt = testPrompt
? incomplete.isEmpty()
? "\u0005" //ENQ
@ -480,6 +502,12 @@ public class JShellTool {
}
}
private void addToReplayHistory(String s) {
if (currentNameSpace == mainNamespace) {
replayableHistory.add(s);
}
}
private String processSourceCatchingReset(String src) {
try {
input.beforeUserCode();
@ -516,7 +544,12 @@ public class JShellTool {
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 class JShellTool {
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 @@ public class JShellTool {
enum CommandKind {
NORMAL,
REPLAY,
HIDDEN,
HELP_ONLY;
}
@ -602,6 +636,7 @@ public class JShellTool {
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 @@ public class JShellTool {
};
}
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 @@ public class JShellTool {
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 @@ public class JShellTool {
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 @@ public class JShellTool {
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 @@ public class JShellTool {
// --- 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 @@ public class JShellTool {
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 @@ public class JShellTool {
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 @@ public class JShellTool {
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 @@ public class JShellTool {
}
}
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 @@ public class JShellTool {
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 @@ public class JShellTool {
} else {
ExternalEditor.edit(editor, errorHandler, src, saveHandler, input);
}
return true;
}
//where
// receives editor requests to save
@ -1135,10 +1200,9 @@ public class JShellTool {
}
}
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 @@ public class JShellTool {
} 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 @@ public class JShellTool {
}
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 void cmdReset() {
private boolean cmdReset() {
live = false;
fluff("Resetting state.");
return true;
}
private void cmdSave(String arg_filename) {
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 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 @@ public class JShellTool {
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 @@ public class JShellTool {
}
} 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 @@ public class JShellTool {
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 @@ public class JShellTool {
}
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 @@ public class JShellTool {
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 @@ public class JShellTool {
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 @@ public class JShellTool {
}
}
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
@ -1816,11 +1921,6 @@ class ScannerIOContext extends IOContext {
return Collections.emptyList();
}
@Override
public void close() {
scannerIn.close();
}
@Override
public boolean terminalEditorRunning() {
return false;
@ -1847,19 +1947,62 @@ class ScannerIOContext extends IOContext {
}
}
class FileScannerIOContext extends ScannerIOContext {
class ScannerIOContext extends NonInteractiveIOContext {
private final Scanner scannerIn;
public FileScannerIOContext(String fn) throws FileNotFoundException {
this(new FileReader(fn));
}
public FileScannerIOContext(Reader rdr) throws FileNotFoundException {
super(new Scanner(rdr), null);
ScannerIOContext(Scanner scannerIn) {
this.scannerIn = scannerIn;
}
@Override
public boolean interactiveOutput() {
return false;
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 {
FileScannerIOContext(String fn) throws FileNotFoundException {
this(new FileReader(fn));
}
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 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() {
}
}

View File

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