8361613: System.console() should only be available for interactive terminal

Reviewed-by: jlahoda, smarks, alanb
This commit is contained in:
Naoto Sato 2025-08-22 17:50:22 +00:00
parent 19882220ec
commit ae0dac43c0
12 changed files with 330 additions and 344 deletions

View File

@ -237,10 +237,11 @@ public final class System {
private static volatile Console cons;
/**
* Returns the unique {@link java.io.Console Console} object associated
* Returns the unique {@link Console Console} object associated
* with the current Java virtual machine, if any.
*
* @return The system console, if any, otherwise {@code null}.
* @see Console
*
* @since 1.6
*/

View File

@ -50,7 +50,7 @@ public class JdkConsoleProviderImpl implements JdkConsoleProvider {
*/
@Override
public JdkConsole console(boolean isTTY, Charset inCharset, Charset outCharset) {
return new LazyDelegatingJdkConsoleImpl(inCharset, outCharset);
return isTTY ? new LazyDelegatingJdkConsoleImpl(inCharset, outCharset) : null;
}
private static class LazyDelegatingJdkConsoleImpl implements JdkConsole {

View File

@ -21,33 +21,66 @@
* questions.
*/
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static jdk.test.lib.Utils.*;
/**
* @test
* @bug 8341975 8351435
* @bug 8341975 8351435 8361613
* @summary Tests the default charset. It should honor `stdout.encoding`
* which should be the same as System.out.charset()
* @modules jdk.internal.le
* @run junit/othervm -Djdk.console=jdk.internal.le -Dstdout.encoding=UTF-8 DefaultCharsetTest
* @run junit/othervm -Djdk.console=jdk.internal.le -Dstdout.encoding=ISO-8859-1 DefaultCharsetTest
* @run junit/othervm -Djdk.console=jdk.internal.le -Dstdout.encoding=US-ASCII DefaultCharsetTest
* @run junit/othervm -Djdk.console=jdk.internal.le -Dstdout.encoding=foo DefaultCharsetTest
* @run junit/othervm -Djdk.console=jdk.internal.le DefaultCharsetTest
* @requires (os.family == "linux") | (os.family == "mac")
* @library /test/lib
* @build jdk.test.lib.Utils
* jdk.test.lib.JDKToolFinder
* jdk.test.lib.process.ProcessTools
* @run junit DefaultCharsetTest
*/
public class DefaultCharsetTest {
@Test
public void testDefaultCharset() {
@BeforeAll
static void checkExpectAvailability() {
// check "expect" command availability
var expect = Paths.get("/usr/bin/expect");
Assumptions.assumeTrue(Files.exists(expect) && Files.isExecutable(expect),
"'" + expect + "' not found. Test ignored.");
}
@ParameterizedTest
@ValueSource(strings = {"UTF-8", "ISO-8859-1", "US-ASCII", "foo", ""})
void testDefaultCharset(String stdoutEncoding) throws Exception {
// invoking "expect" command
OutputAnalyzer oa = ProcessTools.executeProcess(
"expect",
"-n",
TEST_SRC + "/defaultCharset.exp",
TEST_CLASSES,
TEST_JDK + "/bin/java",
"-Dstdout.encoding=" + stdoutEncoding,
getClass().getName());
oa.reportDiagnosticSummary();
oa.shouldHaveExitValue(0);
}
public static void main(String... args) {
var stdoutEncoding = System.getProperty("stdout.encoding");
var sysoutCharset = System.out.charset();
var consoleCharset = System.console().charset();
System.out.println("""
stdout.encoding = %s
System.out.charset() = %s
System.console().charset() = %s
""".formatted(stdoutEncoding, sysoutCharset.name(), consoleCharset.name()));
assertEquals(consoleCharset, sysoutCharset,
"Charsets for System.out and Console differ for stdout.encoding: %s".formatted(stdoutEncoding));
System.out.printf("""
stdout.encoding = %s
System.out.charset() = %s
System.console().charset() = %s
""", stdoutEncoding, sysoutCharset.name(), consoleCharset.name());
if (!consoleCharset.equals(sysoutCharset)) {
System.err.printf("Charsets for System.out and Console differ for stdout.encoding: %s%n", stdoutEncoding);
System.exit(-1);
}
}
}

View File

@ -21,28 +21,40 @@
* questions.
*/
import java.io.File;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.function.Predicate;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import static jdk.test.lib.Utils.*;
/**
* @test
* @bug 8330276 8351435
* @bug 8330276 8351435 8361613
* @summary Tests Console methods that have Locale as an argument
* @requires (os.family == "linux") | (os.family == "mac")
* @library /test/lib
* @modules jdk.internal.le jdk.localedata
* @build jdk.test.lib.Utils
* jdk.test.lib.JDKToolFinder
* jdk.test.lib.process.ProcessTools
* @modules jdk.localedata
* @run junit LocaleTest
*/
public class LocaleTest {
private static Calendar TODAY = new GregorianCalendar(2024, Calendar.APRIL, 22);
private static String FORMAT = "%1$tY-%1$tB-%1$te %1$tA";
private static final Calendar TODAY = new GregorianCalendar(2024, Calendar.APRIL, 22);
private static final String FORMAT = "%1$tY-%1$tB-%1$te %1$tA";
// We want to limit the expected strings within US-ASCII charset, as
// the native encoding is determined as such, which is used by
// the `Process` class under jtreg environment.
private static List<String> EXPECTED = List.of(
private static final List<String> EXPECTED = List.of(
String.format(Locale.UK, FORMAT, TODAY),
String.format(Locale.FRANCE, FORMAT, TODAY),
String.format(Locale.GERMANY, FORMAT, TODAY),
@ -53,56 +65,61 @@ public class LocaleTest {
String.format((Locale)null, FORMAT, TODAY)
);
public static void main(String... args) throws Throwable {
if (args.length == 0) {
// no arg will launch the child process that actually perform tests
var pb = ProcessTools.createTestJavaProcessBuilder(
"-Djdk.console=jdk.internal.le",
"LocaleTest", "dummy");
var input = new File(System.getProperty("test.src", "."), "input.txt");
pb.redirectInput(input);
var oa = ProcessTools.executeProcess(pb);
if (oa.getExitValue() == -1) {
System.out.println("System.console() returns null. Ignoring the test.");
} else {
var output = oa.asLines();
var resultText =
"""
Actual output: %s
Expected output: %s
""".formatted(output, EXPECTED);
if (!output.equals(EXPECTED)) {
throw new RuntimeException("Standard out had unexpected strings:\n" + resultText);
} else {
oa.shouldHaveExitValue(0);
System.out.println("Formatting with explicit Locale succeeded.\n" + resultText);
}
}
} else {
var con = System.console();
if (con != null) {
// tests these additional methods that take a Locale
con.format(Locale.UK, FORMAT, TODAY);
con.printf("\n");
con.printf(Locale.FRANCE, FORMAT, TODAY);
con.printf("\n");
con.readLine(Locale.GERMANY, FORMAT, TODAY);
con.printf("\n");
con.readPassword(Locale.of("es"), FORMAT, TODAY);
con.printf("\n");
@Test
void testLocale() throws Exception {
// check "expect" command availability
var expect = Paths.get("/usr/bin/expect");
Assumptions.assumeTrue(Files.exists(expect) && Files.isExecutable(expect),
"'" + expect + "' not found. Test ignored.");
// tests null locale
con.format((Locale)null, FORMAT, TODAY);
con.printf("\n");
con.printf((Locale)null, FORMAT, TODAY);
con.printf("\n");
con.readLine((Locale)null, FORMAT, TODAY);
con.printf("\n");
con.readPassword((Locale)null, FORMAT, TODAY);
} else {
// Exit with -1
System.exit(-1);
}
// invoking "expect" command
OutputAnalyzer oa = ProcessTools.executeProcess(
"expect",
"-n",
TEST_SRC + "/locale.exp",
TEST_CLASSES,
TEST_JDK + "/bin/java",
getClass().getName());
var stdout =
oa.stdoutAsLines().stream().filter(Predicate.not(String::isEmpty)).toList();
var resultText =
"""
Actual output: %s
Expected output: %s
""".formatted(stdout, EXPECTED);
if (!stdout.equals(EXPECTED)) {
throw new RuntimeException("Standard out had unexpected strings:\n" + resultText);
} else {
oa.shouldHaveExitValue(0);
System.out.println("Formatting with explicit Locale succeeded.\n" + resultText);
}
}
public static void main(String... args) throws Throwable {
var con = System.console();
if (con != null) {
// tests these additional methods that take a Locale
con.format(Locale.UK, FORMAT, TODAY);
con.printf("\n");
con.printf(Locale.FRANCE, FORMAT, TODAY);
con.printf("\n");
con.readLine(Locale.GERMANY, FORMAT, TODAY);
con.printf("\n");
con.readPassword(Locale.of("es"), FORMAT, TODAY);
con.printf("\n");
// tests null locale
con.format((Locale)null, FORMAT, TODAY);
con.printf("\n");
con.printf((Locale)null, FORMAT, TODAY);
con.printf("\n");
con.readLine((Locale)null, FORMAT, TODAY);
con.printf("\n");
con.readPassword((Locale)null, FORMAT, TODAY);
} else {
// Exit with -1
System.exit(-1);
}
}
}

View File

@ -23,21 +23,71 @@
/**
* @test
* @bug 8295803 8299689 8351435
* @bug 8295803 8299689 8351435 8361613
* @summary Tests System.console() returns correct Console (or null) from the expected
* module.
* @modules java.base/java.io:+open
* @run main/othervm ModuleSelectionTest java.base
* @run main/othervm -Djdk.console=jdk.internal.le ModuleSelectionTest jdk.internal.le
* @run main/othervm -Djdk.console=java.base ModuleSelectionTest java.base
* @run main/othervm --limit-modules java.base ModuleSelectionTest java.base
* @library /test/lib
* @build jdk.test.lib.Utils
* jdk.test.lib.process.ProcessTools
* @run junit ModuleSelectionTest
*/
import java.io.Console;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static jdk.test.lib.Utils.*;
public class ModuleSelectionTest {
private static Stream<Arguments> options() {
return Stream.of(
Arguments.of("-Djdk.console=foo", "java.base"),
Arguments.of("-Djdk.console=java.base", "java.base"),
Arguments.of("-Djdk.console=jdk.internal.le", "jdk.internal.le"),
Arguments.of("--limit-modules java.base", "java.base")
);
}
@ParameterizedTest
@MethodSource("options")
void testNonTTY(String opts) throws Exception {
opts = opts +
" --add-opens java.base/java.io=ALL-UNNAMED ModuleSelectionTest null";
OutputAnalyzer output = ProcessTools.executeTestJava(opts.split(" "));
output.reportDiagnosticSummary();
output.shouldHaveExitValue(0);
}
@ParameterizedTest
@MethodSource("options")
void testTTY(String opts, String expected) throws Exception {
// check "expect" command availability
var expect = Paths.get("/usr/bin/expect");
Assumptions.assumeTrue(Files.exists(expect) && Files.isExecutable(expect),
"'" + expect + "' not found. Test ignored.");
opts = "expect -n " + TEST_SRC + "/moduleSelection.exp " +
TEST_CLASSES + " " +
expected + " " +
TEST_JDK + "/bin/java" +
" --add-opens java.base/java.io=ALL-UNNAMED "
+ opts;
// invoking "expect" command
OutputAnalyzer output = ProcessTools.executeProcess(opts.split(" "));
output.reportDiagnosticSummary();
output.shouldHaveExitValue(0);
}
public static void main(String... args) throws Throwable {
var con = System.console();
var pc = Class.forName("java.io.ProxyingConsole");
@ -49,10 +99,7 @@ public class ModuleSelectionTest {
.findGetter(pc, "delegate", jdkc)
.invoke(con) : null;
var expected = switch (args[0]) {
case "java.base" -> istty ? "java.base" : "null";
default -> args[0];
};
var expected = args[0];
var actual = con == null ? "null" : impl.getClass().getModule().getName();
if (!actual.equals(expected)) {
@ -62,7 +109,7 @@ public class ModuleSelectionTest {
Actual: %s
""".formatted(expected, actual));
} else {
System.out.printf("%s is the expected implementation. (tty: %s)\n", impl, istty);
System.out.printf("%s is the expected implementation. (tty: %s)\n", actual, istty);
}
}
}

View File

@ -0,0 +1,32 @@
#
# Copyright (c) 2025, 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.
#
# simply invoking java under expect command
set classpath [lrange $argv 0 0]
set java [lrange $argv 1 1]
set stdoutProp [lrange $argv 2 2]
set clsname [lrange $argv 3 3]
eval spawn $java -classpath $classpath $stdoutProp $clsname
expect eof
set result [wait]
exit [lindex $result 3]

View File

@ -0,0 +1,37 @@
#
# Copyright (c) 2025, 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.
#
# simply invoking java under expect command
set classpath [lrange $argv 0 0]
set java [lrange $argv 1 1]
set clsname [lrange $argv 2 2]
eval spawn -noecho $java -classpath $classpath $clsname
# sends CR 4 times (readLine x 2, readPassword x 2)
send "\r"
send "\r"
send "\r"
send "\r"
expect eof
set result [wait]
exit [lindex $result 3]

View File

@ -0,0 +1,30 @@
#
# Copyright (c) 2025, 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.
#
# simply invoking java under expect command
set classpath [lrange $argv 0 0]
set expected [lrange $argv 1 1]
set java [lrange $argv 2 2]
set opts [lrange $argv 3 end]
eval spawn $java $opts -classpath $classpath ModuleSelectionTest $expected
expect eof

View File

@ -50,10 +50,9 @@ import static org.junit.jupiter.api.Assertions.*;
/*
* @test
* @bug 8305457 8342936 8351435 8344706
* @bug 8305457 8342936 8351435 8344706 8361613
* @summary java.lang.IO tests
* @library /test/lib
* @modules jdk.internal.le
* @run junit IO
*/
@ExtendWith(IO.TimingExtension.class)
@ -78,22 +77,6 @@ public class IO {
} catch (Exception _) { }
}
/*
* Unlike printTest, which tests a _default_ console that is normally
* jdk.internal.org.jline.JdkConsoleProviderImpl, this test tests
* jdk.internal.io.JdkConsoleImpl. Those console implementations operate
* in different conditions and, thus, are tested separately.
*
* To test jdk.internal.io.JdkConsoleImpl one needs to ensure that both
* conditions are met:
*
* - a non-existent console provider is requested
* - isatty is true
*
* To achieve isatty, the test currently uses the EXPECT(1) Unix command,
* which does not work for Windows. Later, a library like pty4j or JPty
* might be used instead of EXPECT, to cover both Unix and Windows.
*/
@ParameterizedTest
@ValueSource(strings = {"println", "print"})
public void outputTestInteractive(String mode) throws Exception {
@ -102,8 +85,6 @@ public class IO {
expect.toString(),
Path.of(testSrc, "output.exp").toAbsolutePath().toString(),
System.getProperty("test.jdk") + "/bin/java",
"--enable-preview",
"-Djdk.console=gibberish",
Path.of(testSrc, "Output.java").toAbsolutePath().toString(),
mode);
assertEquals(0, output.getExitValue());
@ -130,7 +111,7 @@ public class IO {
*/
@ParameterizedTest
@MethodSource("args")
public void inputTestInteractive(String console, String prompt) throws Exception {
public void inputTestInteractive(String prompt) throws Exception {
var testSrc = System.getProperty("test.src", ".");
var command = new ArrayList<String>();
command.add(expect.toString());
@ -138,9 +119,6 @@ public class IO {
: "input";
command.add(Path.of(testSrc, expectInputName + ".exp").toAbsolutePath().toString());
command.add(System.getProperty("test.jdk") + "/bin/java");
command.add("--enable-preview");
if (console != null)
command.add("-Djdk.console=" + console);
command.add(Path.of(testSrc, "Input.java").toAbsolutePath().toString());
command.add(prompt == null ? "0" : PROMPT_NONE.equals(prompt) ? "2" : "1");
command.add(String.valueOf(prompt));
@ -152,33 +130,11 @@ public class IO {
private static final String PROMPT_NONE = "prompt-none";
public static Stream<Arguments> args() {
// cross product: consoles x prompts
return Stream.of("jdk.internal.le", "gibberish").flatMap(console -> Stream.of(null, "?", "%s", PROMPT_NONE)
.map(prompt -> new String[]{console, prompt}).map(Arguments::of));
// prompts
return Stream.of(null, "?", "%s", PROMPT_NONE).map(Arguments::of);
}
}
@ParameterizedTest
@ValueSource(strings = {"println", "print"})
public void printTest(String mode) throws Exception {
var file = Path.of(System.getProperty("test.src", "."), "Output.java")
.toAbsolutePath().toString();
var pb = ProcessTools.createTestJavaProcessBuilder("-Djdk.console=jdk.internal.le", "--enable-preview", file, mode);
OutputAnalyzer output = ProcessTools.executeProcess(pb);
assertEquals(0, output.getExitValue());
assertTrue(output.getStderr().isEmpty());
output.reportDiagnosticSummary();
String out = output.getStdout();
// The first half of the output is produced by Console, the second
// half is produced by IO: those halves must match.
// Executing Console and IO in the same VM (as opposed to
// consecutive VM runs, which are cleaner) to be able to compare string
// representation of objects.
assertFalse(out.isBlank());
assertEquals(out.substring(0, out.length() / 2),
out.substring(out.length() / 2));
}
@Test //JDK-8342936
public void printlnNoParamsTest() throws Exception {
var file = Path.of("PrintlnNoParams.java");
@ -193,7 +149,7 @@ public class IO {
}
""");
}
var pb = ProcessTools.createTestJavaProcessBuilder("-Djdk.console=jdk.internal.le", "--enable-preview", file.toString());
var pb = ProcessTools.createTestJavaProcessBuilder(file.toString());
OutputAnalyzer output = ProcessTools.executeProcess(pb);
assertEquals(0, output.getExitValue());
assertTrue(output.getStderr().isEmpty());

View File

@ -23,16 +23,19 @@
/**
* @test
* @bug 8331535 8351435 8347050
* @bug 8331535 8351435 8347050 8361613
* @summary Verify the jdk.internal.le's console provider works properly.
* @modules jdk.internal.le
* @modules java.base/jdk.internal.io
* jdk.internal.le/jdk.internal.org.jline
* @library /test/lib
* @run main/othervm -Djdk.console=jdk.internal.le JLineConsoleProviderTest
* @run main JLineConsoleProviderTest
*/
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import jdk.internal.org.jline.JdkConsoleProviderImpl;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
@ -66,8 +69,13 @@ public class JLineConsoleProviderTest {
String input,
String expectedOut) throws Exception {
ProcessBuilder builder =
ProcessTools.createTestJavaProcessBuilder("-Djdk.console=jdk.internal.le", ConsoleTest.class.getName(),
testName);
ProcessTools.createTestJavaProcessBuilder(
"--add-exports",
"java.base/jdk.internal.io=ALL-UNNAMED",
"--add-exports",
"jdk.internal.le/jdk.internal.org.jline=ALL-UNNAMED",
ConsoleTest.class.getName(),
testName);
OutputAnalyzer output = ProcessTools.executeProcess(builder, input);
output.waitFor();
@ -98,16 +106,18 @@ public class JLineConsoleProviderTest {
public static class ConsoleTest {
public static void main(String... args) {
// directly instantiate JLine JdkConsole, simulating isTTY=true
var impl = new JdkConsoleProviderImpl().console(true, StandardCharsets.UTF_8, StandardCharsets.UTF_8);
switch (args[0]) {
case "testCorrectOutputReadLine" ->
System.console().readLine("%%s");
impl.readLine(null, "%%s");
case "testCorrectOutputReadPassword" ->
System.console().readPassword("%%s");
impl.readPassword(null, "%%s");
case "readAndPrint" ->
System.out.println("'" + System.console().readLine() + "'");
System.out.println("'" + impl.readLine() + "'");
case "readAndPrint2" -> {
System.out.println("1: '" +System.console().readLine() + "'");
System.out.println("2: '" + System.console().readLine() + "'");
System.out.println("1: '" + impl.readLine() + "'");
System.out.println("2: '" + impl.readLine() + "'");
}
default -> throw new UnsupportedOperationException(args[0]);
}

View File

@ -23,15 +23,19 @@
/**
* @test
* @bug 8333086 8344706
* @bug 8333086 8344706 8361613
* @summary Verify the JLine backend is not initialized for simple printing.
* @enablePreview
* @modules jdk.internal.le/jdk.internal.org.jline.reader
* @modules java.base/jdk.internal.io
* jdk.internal.le/jdk.internal.org.jline
* jdk.internal.le/jdk.internal.org.jline.reader
* jdk.internal.le/jdk.internal.org.jline.terminal
* @library /test/lib
* @run main LazyJdkConsoleProvider
*/
import java.nio.charset.StandardCharsets;
import jdk.internal.org.jline.JdkConsoleProviderImpl;
import jdk.internal.org.jline.reader.LineReader;
import jdk.internal.org.jline.terminal.Terminal;
@ -41,19 +45,18 @@ import jdk.test.lib.process.ProcessTools;
public class LazyJdkConsoleProvider {
public static void main(String... args) throws Throwable {
// directly instantiate JLine JdkConsole, simulating isTTY=true
switch (args.length > 0 ? args[0] : "default") {
case "write" -> {
System.console().printf("Hello!\n");
System.console().printf("Hello!");
System.console().format("\nHello!\n");
System.console().flush();
IO.println("Hello!");
IO.print("Hello!");
}
case "read" -> System.console().readLine("Hello!");
case "IO-read" -> {
IO.readln("Hello!");
var impl = new JdkConsoleProviderImpl().console(true, StandardCharsets.UTF_8, StandardCharsets.UTF_8);
impl.println("Hello!\n");
impl.println("Hello!");
impl.format(null, "\nHello!\n");
impl.flush();
}
case "read" -> new JdkConsoleProviderImpl()
.console(true, StandardCharsets.UTF_8, StandardCharsets.UTF_8)
.readLine(null, "Hello!");
case "default" -> {
new LazyJdkConsoleProvider().runTest();
}
@ -64,14 +67,15 @@ public class LazyJdkConsoleProvider {
record TestCase(String testKey, String expected, String notExpected) {}
TestCase[] testCases = new TestCase[] {
new TestCase("write", null, Terminal.class.getName()),
new TestCase("read", LineReader.class.getName(), null),
new TestCase("IO-read", null, Terminal.class.getName())
new TestCase("read", LineReader.class.getName(), null)
};
for (TestCase tc : testCases) {
ProcessBuilder builder =
ProcessTools.createTestJavaProcessBuilder("--enable-preview",
"-verbose:class",
"-Djdk.console=jdk.internal.le",
ProcessTools.createTestJavaProcessBuilder("-verbose:class",
"--add-exports",
"java.base/jdk.internal.io=ALL-UNNAMED",
"--add-exports",
"jdk.internal.le/jdk.internal.org.jline=ALL-UNNAMED",
LazyJdkConsoleProvider.class.getName(),
tc.testKey());
OutputAnalyzer output = ProcessTools.executeProcess(builder, "");

View File

@ -1,181 +0,0 @@
/*
* Copyright (c) 2024, 2025, 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 8330998 8351435
* @summary Verify that even if the stdout is redirected java.io.Console will
* use it for writing.
* @modules jdk.internal.le
* @library /test/lib
* @run main RedirectedStdOut runRedirectAllTest
* @run main/othervm --enable-native-access=ALL-UNNAMED RedirectedStdOut runRedirectOutOnly
*/
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SymbolLookup;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Optional;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
public class RedirectedStdOut {
private static final String OUTPUT = "Hello!";
public static void main(String... args) throws Throwable {
RedirectedStdOut.class.getDeclaredMethod(args[0])
.invoke(new RedirectedStdOut());
}
//verify the case where neither stdin/out/err is attached to a terminal,
//this test is weaker, but more reliable:
void runRedirectAllTest() throws Exception {
ProcessBuilder builder =
ProcessTools.createTestJavaProcessBuilder("-Djdk.console=jdk.internal.le", ConsoleTest.class.getName());
OutputAnalyzer output = ProcessTools.executeProcess(builder);
output.waitFor();
if (output.getExitValue() != 0) {
throw new AssertionError("Unexpected return value: " + output.getExitValue() +
", actualOut: " + output.getStdout() +
", actualErr: " + output.getStderr());
}
String expectedOut = OUTPUT;
String actualOut = output.getStdout();
if (!Objects.equals(expectedOut, actualOut)) {
throw new AssertionError("Unexpected stdout content. " +
"Expected: '" + expectedOut + "'" +
", got: '" + actualOut + "'");
}
String expectedErr = "";
String actualErr = output.getStderr();
if (!Objects.equals(expectedErr, actualErr)) {
throw new AssertionError("Unexpected stderr content. " +
"Expected: '" + expectedErr + "'" +
", got: '" + actualErr + "'");
}
}
//verify the case where stdin is attached to a terminal,
//this test allocates pty, and it might be skipped, if the appropriate
//native functions cannot be found
//it also leaves the VM in a broken state (with a pty attached), and so
//should run in a separate VM instance
void runRedirectOutOnly() throws Throwable {
Path stdout = Path.of(".", "stdout.txt").toAbsolutePath();
Files.deleteIfExists(stdout);
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MemorySegment parent = Arena.global().allocate(ValueLayout.ADDRESS);
MemorySegment child = Arena.global().allocate(ValueLayout.ADDRESS);
Optional<MemorySegment> openptyAddress = stdlib.find("openpty");
if (openptyAddress.isEmpty()) {
System.out.println("Cannot lookup openpty.");
//does not have forkpty, ignore
return ;
}
Optional<MemorySegment> loginttyAddress = stdlib.find("login_tty");
if (loginttyAddress.isEmpty()) {
System.out.println("Cannot lookup login_tty.");
//does not have forkpty, ignore
return ;
}
FunctionDescriptor openttyDescriptor =
FunctionDescriptor.of(ValueLayout.JAVA_INT,
ValueLayout.ADDRESS,
ValueLayout.ADDRESS,
ValueLayout.ADDRESS,
ValueLayout.ADDRESS,
ValueLayout.ADDRESS);
MethodHandle forkpty = linker.downcallHandle(openptyAddress.get(),
openttyDescriptor);
int res = (int) forkpty.invoke(parent,
child,
MemorySegment.NULL,
MemorySegment.NULL,
MemorySegment.NULL);
if (res != 0) {
throw new AssertionError();
}
//set the current VM's in/out to the terminal:
FunctionDescriptor loginttyDescriptor =
FunctionDescriptor.of(ValueLayout.JAVA_INT,
ValueLayout.JAVA_INT);
MethodHandle logintty = linker.downcallHandle(loginttyAddress.get(),
loginttyDescriptor);
logintty.invoke(child.get(ValueLayout.JAVA_INT, 0));
//createTestJavaProcessBuilder logs to (current process') System.out, but
//that may not work since the redirect. Setting System.out to a scratch value:
System.setOut(new PrintStream(new ByteArrayOutputStream()));
ProcessBuilder builder =
ProcessTools.createTestJavaProcessBuilder("-Djdk.console=jdk.internal.le", ConsoleTest.class.getName());
builder.inheritIO();
builder.redirectOutput(stdout.toFile());
OutputAnalyzer output = ProcessTools.executeProcess(builder);
output.waitFor();
String expectedOut = OUTPUT;
String actualOut = Files.readString(stdout);
if (!Objects.equals(expectedOut, actualOut)) {
throw new AssertionError("Unexpected stdout content. " +
"Expected: '" + expectedOut + "'" +
", got: '" + actualOut + "'");
}
}
public static class ConsoleTest {
public static void main(String... args) {
System.console().printf(OUTPUT);
System.exit(0);
}
}
}