Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/java.base/share/classes/java/lang/System.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Supplier;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
Expand Down Expand Up @@ -238,10 +237,11 @@ public static void setErr(PrintStream err) {
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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
66 changes: 48 additions & 18 deletions test/jdk/java/io/Console/DefaultCharsetTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,63 @@
* 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.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() {
@ParameterizedTest
@ValueSource(strings = {"UTF-8", "ISO-8859-1", "US-ASCII", "foo", ""})
void testDefaultCharset(String stdoutEncoding) 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.");

// 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);
}
}
}
125 changes: 71 additions & 54 deletions test/jdk/java/io/Console/LocaleTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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);
}
}
@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.");

// 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 {
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");
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);
}
// 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);
}
}
}
71 changes: 60 additions & 11 deletions test/jdk/java/io/Console/ModuleSelectionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,73 @@

/**
* @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
* @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 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 testWithoutExpect(String opts, String expected) 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 testWithExpect(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");
Expand All @@ -49,10 +101,7 @@ public static void main(String... args) throws Throwable {
.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)) {
Expand All @@ -62,7 +111,7 @@ public static void main(String... args) throws Throwable {
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);
}
}
}
Loading