8177076: jshell tool: allow non-zero /exit

8190383: JShell API: no way for the jshell tool to report exit status to provider

Reviewed-by: jlahoda
This commit is contained in:
Robert Field 2017-11-14 19:33:37 -08:00
parent 60c7c6052d
commit c0689879a7
10 changed files with 586 additions and 301 deletions

@ -923,7 +923,7 @@ class ConsoleIOContext extends IOContext {
@Override
public void perform(ConsoleReader in) throws IOException {
repl.processCompleteSource("import " + type + ";");
repl.processSource("import " + type + ";");
in.println("Imported: " + type);
performToVar(in, stype);
}
@ -1028,7 +1028,7 @@ class ConsoleIOContext extends IOContext {
@Override
public void perform(ConsoleReader in) throws IOException {
repl.processCompleteSource("import " + type + ";");
repl.processSource("import " + type + ";");
in.println("Imported: " + type);
performToMethod(in, stype, codeToCursor);
}
@ -1052,7 +1052,7 @@ class ConsoleIOContext extends IOContext {
@Override
public void perform(ConsoleReader in) throws IOException {
repl.processCompleteSource("import " + fqn + ";");
repl.processSource("import " + fqn + ";");
in.println("Imported: " + fqn);
in.redrawLine();
}

@ -27,6 +27,7 @@ package jdk.internal.jshell.tool;
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;
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.asList;
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 @@ public class JShellTool implements MessageHandler {
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 @@ public class JShellTool implements MessageHandler {
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 @@ public class JShellTool implements MessageHandler {
.stream()
.collect(joining(", ")));
}
exitCode = 1;
return null;
}
}
@ -424,7 +431,12 @@ public class JShellTool implements MessageHandler {
.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 @@ public class JShellTool implements MessageHandler {
(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 @@ public class JShellTool implements MessageHandler {
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 @@ public class JShellTool implements MessageHandler {
*
* @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 @@ public class JShellTool implements MessageHandler {
// 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 @@ public class JShellTool implements MessageHandler {
for (String loadFile : commandLineArgs.nonOptions()) {
if (!runFile(loadFile, "jshell")) {
// Load file failed -- abort
return;
return 1;
}
}
// if we survived that...
@ -934,6 +951,7 @@ public class JShellTool implements MessageHandler {
}
}
closeState();
return exitCode;
}
private EditorSetting configEditor() {
@ -1071,6 +1089,7 @@ public class JShellTool implements MessageHandler {
// 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 @@ public class JShellTool implements MessageHandler {
/**
* 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 @@ public class JShellTool implements MessageHandler {
}
}
/**
* 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 @@ public class JShellTool implements MessageHandler {
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 @@ public class JShellTool implements MessageHandler {
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 @@ public class JShellTool implements MessageHandler {
}
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 @@ public class JShellTool implements MessageHandler {
.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 @@ public class JShellTool implements MessageHandler {
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 @@ public class JShellTool implements MessageHandler {
addToReplayHistory(source);
}
return failed;
return !failed;
}
// Handle incoming snippet events -- return true on failure
@ -3218,23 +3429,11 @@ public class JShellTool implements MessageHandler {
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 @@ public class JShellTool implements MessageHandler {
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 @@ class ScannerIOContext extends NonInteractiveIOContext {
scannerIn.close();
}
@Override
public int readUserInput() {
return -1;
}
@ -3700,6 +3900,7 @@ class ReloadIOContext extends NonInteractiveIOContext {
public void close() {
}
@Override
public int readUserInput() {
return -1;
}

@ -229,7 +229,8 @@ public class JShellToolBuilder implements JavaShellToolBuilder {
/**
* Run an instance of the Java shell tool as configured by the other methods
* in this interface. This call is not destructive, more than one call of
* this method may be made from a configured builder.
* this method may be made from a configured builder. The exit code from
* the Java shell tool is ignored.
*
* @param arguments the command-line arguments (including options), if any
* @throws Exception an unexpected fatal exception
@ -239,6 +240,20 @@ public class JShellToolBuilder implements JavaShellToolBuilder {
rawTool().start(arguments);
}
/**
* Run an instance of the Java shell tool as configured by the other methods
* in this interface. This call is not destructive, more than one call of
* this method may be made from a configured builder.
*
* @param arguments the command-line arguments (including options), if any
* @throws Exception an unexpected fatal exception
* @return the exit code
*/
@Override
public int start(String... arguments) throws Exception {
return rawTool().start(arguments);
}
/**
* Persistence stored in Preferences.
*/

@ -62,7 +62,8 @@ public class JShellToolProvider implements Tool {
* @param err start-up errors and execution "standard" error; use System.err
* if null
* @param arguments arguments to pass to the tool
* @return 0 for success; nonzero otherwise
* @return the exit status with which the tool explicitly exited (if any),
* otherwise 0 for success or 1 for failure
* @throws NullPointerException if the array of arguments contains
* any {@code null} elements.
*/
@ -85,13 +86,12 @@ public class JShellToolProvider implements Tool {
? (PrintStream) err
: new PrintStream(err);
try {
JavaShellToolBuilder
return JavaShellToolBuilder
.builder()
.in(xin, null)
.out(xout)
.err(xerr)
.run(arguments);
return 0;
.start(arguments);
} catch (Throwable ex) {
xerr.println(ex.getMessage());
return 1;
@ -109,13 +109,14 @@ public class JShellToolProvider implements Tool {
}
/**
* Launch the tool.
* Launch the tool and exit.
* @param arguments the command-line arguments (including options), if any
* @throws Exception an unexpected fatal exception
*/
public static void main(String[] arguments) throws Exception {
JavaShellToolBuilder
.builder()
.run(arguments);
System.exit(
JavaShellToolBuilder
.builder()
.start(arguments));
}
}

@ -108,6 +108,10 @@ jshell.err.no.snippet.with.id = No snippet with id: {0}
jshell.err.end.snippet.range.less.than.start = End of snippet range less than start: {0} - {1}
jshell.err.range.requires.id = Snippet ranges require snippet ids: {0}
jshell.err.exit.not.expression = The argument to /exit must be a valid integer expression, it is not an expression: {0}
jshell.err.exit.bad.type = The argument to /exit must be a valid integer expression. The type is {1} : {0}
jshell.err.exit.bad.value = The argument to /exit has bad value is {1} : {0}
jshell.err.drop.arg =\
In the /drop argument, please specify an import, variable, method, or class to drop.\n\
Specify by id or name. Use /list to see ids. Use /reset to reset all state.
@ -115,6 +119,7 @@ jshell.err.failed = Failed.
jshell.msg.native.method = Native Method
jshell.msg.unknown.source = Unknown Source
jshell.msg.goodbye = Goodbye
jshell.msg.goodbye.value = Goodbye ({0})
jshell.msg.help.for.help = Type /help for help.
@ -378,10 +383,17 @@ help.imports =\
List the current active jshell imports.
help.exit.summary = exit jshell
help.exit.args =
help.exit.args =[<integer-expression-snippet>]
help.exit =\
Leave the jshell tool. No work is saved.\n\
Save any work before using this command
Save any work before using this command\n\
\n\
/exit\n\t\
Leave the jshell tool. The exit status is zero.\n\n\
/exit <integer-expression-snippet>\n\t\
Evaluate the snippet. If the snippet fails or is not an integer expression,\n\t\
display the error. Otherwise leave the jshell tool with the\n\t\
value of the expression as the exit status
help.reset.summary = reset jshell
help.reset.args = \

@ -186,10 +186,29 @@ public interface JavaShellToolBuilder {
/**
* Run an instance of the Java shell tool as configured by the other methods
* in this interface. This call is not destructive, more than one call of
* this method may be made from a configured builder.
* this method may be made from a configured builder. The exit code from
* the Java shell tool is ignored.
*
* @param arguments the command-line arguments (including options), if any
* @throws Exception an unexpected fatal exception
*/
void run(String... arguments) throws Exception;
/**
* Run an instance of the Java shell tool as configured by the other methods
* in this interface. This call is not destructive, more than one call of
* this method may be made from a configured builder.
*
* @implSpec The default implementation always returns zero. Implementations
* of this interface should override this method, returning the exit status.
*
* @param arguments the command-line arguments (including options), if any
* @throws Exception an unexpected fatal exception
* @return the exit status with which the tool explicitly exited (if any),
* otherwise 0 for success or 1 for failure
*/
default int start(String... arguments) throws Exception {
run(arguments);
return 0;
}
}

@ -28,23 +28,23 @@
* Allows configuration of the tool before launching. A builder is used
* to configure and launch the tool.
* <p>
* At the simplest, a builder is retrieved, and the builder is used to run the
* At the simplest, a builder is retrieved, and the builder is used to start the
* tool:
* <pre>
* {@code
* JavaShellToolBuilder
* .builder()
* .run();
* .start();
* }
* </pre>
* The builder can be configured and the run can have arguments:
* The builder can be configured and the start can have arguments:
* <pre>
* {@code
* JavaShellToolBuilder
* .builder()
* .out(myCommandPrintStream, myOutputPrintStream)
* .locale(Locale.CANADA)
* .run("--feedback", "silent", "MyStart");
* .start("--feedback", "silent", "MyStart");
* }
* </pre>
*

@ -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 @@ import static org.testng.Assert.fail;
@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);
}
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 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);
}
// 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;

@ -21,21 +21,14 @@
* questions.
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.ServiceLoader;
import java.util.function.Consumer;
import javax.tools.Tool;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
/*
* @test
* @bug 8170044 8171343 8179856
* @bug 8170044 8171343 8179856 8185840 8190383
* @summary Test ServiceLoader launching of jshell tool
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
@ -48,30 +41,25 @@ import static org.testng.Assert.fail;
@Test
public class ToolProviderTest extends StartOptionTest {
private ByteArrayOutputStream cmdout;
private ByteArrayOutputStream cmderr;
private InputStream cmdInStream;
@BeforeMethod
// Through the provider, the console and console go to command out (we assume,
// because it works with the current tests) that console and user output are
// after command out.
@Override
public void setUp() {
cmdout = new ByteArrayOutputStream();
cmderr = new ByteArrayOutputStream();
cmdInStream = new ByteArrayInputStream("/exit\n".getBytes());
protected void startExCoUoCeCn(int expectedExitCode,
String expectedCmdOutput,
String expectedUserOutput,
String expectedError,
String expectedConsole,
String... args) {
super.startExCoUoCeCn(expectedExitCode,
(expectedCmdOutput == null? "" : expectedCmdOutput) +
(expectedConsole == null? "" : expectedConsole) +
(expectedUserOutput == null? "" : expectedUserOutput),
null, expectedError, null, args);
}
@Override
protected void start(Consumer<String> checkCmdOutput,
Consumer<String> checkUserOutput, Consumer<String> checkError,
String... args) throws Exception {
if (runShellServiceLoader(args) != 0) {
fail("Repl tool failed");
}
check(cmdout, checkCmdOutput, "cmdout");
check(cmderr, checkError, "cmderr");
}
private int runShellServiceLoader(String... args) {
protected int runShell(String... args) {
ServiceLoader<Tool> sl = ServiceLoader.load(Tool.class);
for (Tool provider : sl) {
if (provider.name().equals("jshell")) {
@ -81,38 +69,14 @@ public class ToolProviderTest extends StartOptionTest {
throw new AssertionError("Repl tool not found by ServiceLoader: " + sl);
}
// Test --show-version
@Override
public void testCommandFile() throws Exception {
String fn = writeToFile("String str = \"Hello \"\n/list\nSystem.out.println(str + str)\n/exit\n");
start("1 : String str = \"Hello \";" + "\n" + "Hello Hello", "", "--no-startup", fn, "-s");
}
@Override
public void testShowVersion() throws Exception {
start(
s -> {
assertTrue(s.startsWith("jshell "), "unexpected version: " + s);
assertTrue(s.contains("Welcome"), "Expected start (but got no welcome): " + s);
assertTrue(s.trim().contains("jshell>"), "Expected prompt, got: " + s);
},
null, null,
public void testShowVersion() {
startCo(s -> {
assertTrue(s.startsWith("jshell "), "unexpected version: " + s);
assertTrue(s.contains("Welcome"), "Expected start (but got no welcome): " + s);
assertTrue(s.trim().contains("jshell>"), "Expected prompt, got: " + s);
},
"--show-version");
}
/**
* Test that input is read with "-" and there is no extra output.
* @throws Exception
*/
@Override
public void testHypenFile() throws Exception {
cmdInStream = new ByteArrayInputStream("System.out.print(\"Hello\");\n".getBytes());
start("Hello", "", "-");
cmdInStream = new ByteArrayInputStream("System.out.print(\"Hello\");\n".getBytes());
start("Hello", "", "-", "-");
Compiler compiler = new Compiler();
Path path = compiler.getPath("markload.jsh");
compiler.writeToFile(path, "System.out.print(\"===\");");
cmdInStream = new ByteArrayInputStream("System.out.print(\"Hello\");\n".getBytes());
start("===Hello===", "", path.toString(), "-", path.toString());
}
}

@ -23,7 +23,7 @@
/**
* @test
* @bug 8177076
* @bug 8177076 8185840
* @modules
* jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
@ -107,11 +107,16 @@ public class ToolTabCommandTest extends UITesting {
waitOutput(out, Pattern.quote(getResource("help.exit.summary")) + "\n\n" +
Pattern.quote(getResource("jshell.console.see.full.documentation")) + "\n\r\u0005/exit ");
inputSink.write("\011");
waitOutput(out, Pattern.quote(getResource("help.exit")) + "\n" +
waitOutput(out, Pattern.quote(getResource("help.exit").replaceAll("\t", " ")) + "\n" +
"\r\u0005/exit ");
inputSink.write("\011");
waitOutput(out, Pattern.quote(getResource("help.exit.summary")) + "\n\n" +
Pattern.quote(getResource("jshell.console.see.full.documentation")) + "\n\r\u0005/exit ");
inputSink.write("\u0003");
inputSink.write("int zebraStripes = 11\n");
waitOutput(out, "zebraStripes ==> 11\n\u0005");
inputSink.write("/exit zeb\011");
waitOutput(out, "braStr.*es");
inputSink.write("\u0003/doesnotexist\011");
waitOutput(out, "\u0005/doesnotexist\n" +
Pattern.quote(getResource("jshell.console.no.such.command")) + "\n" +