--- 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/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;