diff --git a/picocli-tests-java567/src/test/java/picocli/HelpAnsiHeuristicsTest.java b/picocli-tests-java567/src/test/java/picocli/HelpAnsiHeuristicsTest.java index 4b4296638..583f804e2 100644 --- a/picocli-tests-java567/src/test/java/picocli/HelpAnsiHeuristicsTest.java +++ b/picocli-tests-java567/src/test/java/picocli/HelpAnsiHeuristicsTest.java @@ -360,6 +360,11 @@ public void testAnsiAutoJansiConsoleInstalledOverridesHintDisabled() { environmentVariables.clear(ANSI_ENVIRONMENT_VARIABLES); environmentVariables.set("CLICOLOR", "0"); // hint disabled System.setProperty("os.name", "Windows"); + // Clear the globally cached jansiConsole value that might + // have been set in a previous test to force the + // Ansi#isJansiConsoleInstalled method to recalculate + // the cached value. + Ansi.jansiConsole = null; assertTrue(Ansi.isWindows()); assertFalse(Ansi.isPseudoTTY()); assertFalse(Ansi.forceDisabled()); diff --git a/picocli-tests-java9plus/src/test/java/picocli/AutoCompleteSystemExitTest.java b/picocli-tests-java9plus/src/test/java/picocli/AutoCompleteSystemExitTest.java index 1c8e612b5..b248544bd 100644 --- a/picocli-tests-java9plus/src/test/java/picocli/AutoCompleteSystemExitTest.java +++ b/picocli-tests-java9plus/src/test/java/picocli/AutoCompleteSystemExitTest.java @@ -1,9 +1,11 @@ package picocli; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledForJreRange; import org.junit.jupiter.api.condition.JRE; +import picocli.CommandLine.Help.Ansi; import java.io.File; import java.io.FileInputStream; @@ -508,6 +510,15 @@ private String expectedCompletionScriptForNonDefault() { CommandLine.VERSION); } + @BeforeAll + static void disableAnsi() { + // Clear the globally cached jansiConsole value that might + // have been set in a previous test to force the + // Ansi#isJansiConsoleInstalled method to recalculate + // the cached value. + Ansi.jansiConsole = null; + } + @Test @DisabledForJreRange(min = JRE.JAVA_18, max = JRE.JAVA_21, disabledReason = "UnsupportedOperationException in SystemLambda.withSecurityManager") public void testAutoCompleteAppHelp() throws Exception { diff --git a/picocli-tests-java9plus/src/test/java/picocli/HelpAnsiHeuristicsTest.java b/picocli-tests-java9plus/src/test/java/picocli/HelpAnsiHeuristicsTest.java index 52b5b0de9..fdb8cec3b 100644 --- a/picocli-tests-java9plus/src/test/java/picocli/HelpAnsiHeuristicsTest.java +++ b/picocli-tests-java9plus/src/test/java/picocli/HelpAnsiHeuristicsTest.java @@ -612,6 +612,12 @@ public void testAnsiAutoJansiConsoleInstalledOverridesHintDisabled() throws Exce assertFalse(Ansi.hintEnabled()); assertFalse(Ansi.isJansiConsoleInstalled()); + + // Clear the globally cached jansiConsole value that might + // have been set in a previous test to force the + // Ansi#isJansiConsoleInstalled method to recalculate + // the cached value. + Ansi.jansiConsole = null; AnsiConsole.systemInstall(); try { assertTrue(Ansi.isJansiConsoleInstalled()); @@ -628,6 +634,7 @@ public void testAnsiAutoHintDisabledOverridesHintEnabled() throws Exception { restoreSystemProperties(() -> { System.setProperty("os.name", "Windows"); + Ansi.jansiConsole = null; withEnvironmentVariable(ANSI_ENVIRONMENT_VARIABLES[0], null) .and(ANSI_ENVIRONMENT_VARIABLES[1], null) .and(ANSI_ENVIRONMENT_VARIABLES[2], null) @@ -688,6 +695,7 @@ public void testAnsiAutoDisabledIfNoTty() throws Exception { restoreSystemProperties(() -> { System.setProperty("os.name", "Windows"); + Ansi.jansiConsole = null; assertTrue(Ansi.isWindows()); assertFalse(Ansi.isPseudoTTY()); assertFalse(Ansi.isJansiConsoleInstalled()); diff --git a/src/main/java/picocli/CommandLine.java b/src/main/java/picocli/CommandLine.java index a7b37d389..bb3ec3a4e 100644 --- a/src/main/java/picocli/CommandLine.java +++ b/src/main/java/picocli/CommandLine.java @@ -17805,7 +17805,14 @@ static boolean ansiPossible() { if (!isTTY() && !isPseudoTTY()) { return false; } return hintEnabled() || !isWindows() || isXterm() || isCygwin() || hasOsType(); } + /** Cache the result for isJansiConsoleInstalled so it doesn't repeatedly + * call Class#forName, which can cause performance issues. */ + static Boolean jansiConsole; static boolean isJansiConsoleInstalled() { + if (jansiConsole == null) { jansiConsole = calcIsJansiConsoleInstalled(); } + return jansiConsole; + } + static boolean calcIsJansiConsoleInstalled() { try { // first check if JANSI was explicitly disabled _without loading any JANSI classes_: // see https://github.com/remkop/picocli/issues/1106 diff --git a/src/test/java/picocli/HelpAnsiTest.java b/src/test/java/picocli/HelpAnsiTest.java index 2855dabca..1bbc47b8b 100644 --- a/src/test/java/picocli/HelpAnsiTest.java +++ b/src/test/java/picocli/HelpAnsiTest.java @@ -128,6 +128,10 @@ public void testAnsiEnabled() { if (isWindows && !Ansi.AUTO.enabled()) { AnsiConsole.systemInstall(); + + // The previous Ansi.enabled() call caches the result for whether or not jansi is enabled. Reset the cache value + // and force the Ansi.enabled() call to rescan the classpath for the jansi classes. + Ansi.jansiConsole = null; try { assertTrue(Ansi.AUTO.enabled()); } finally { diff --git a/src/test/java/picocli/Issue1125_1538_OptionNameOrSubcommandAsOptionValue.java b/src/test/java/picocli/Issue1125_1538_OptionNameOrSubcommandAsOptionValue.java index 2b01e2f62..bf23bc79d 100644 --- a/src/test/java/picocli/Issue1125_1538_OptionNameOrSubcommandAsOptionValue.java +++ b/src/test/java/picocli/Issue1125_1538_OptionNameOrSubcommandAsOptionValue.java @@ -9,6 +9,7 @@ import org.junit.Test; import org.junit.contrib.java.lang.system.SystemErrRule; import picocli.CommandLine.Command; +import picocli.CommandLine.Help.Ansi; import picocli.CommandLine.IParameterPreprocessor; import picocli.CommandLine.Model.ArgSpec; import picocli.CommandLine.Model.CommandSpec; @@ -111,6 +112,11 @@ public void testAmbiguousOptionsDefault() { //-x -y=123 MyCommand obj = new MyCommand(); CommandLine cmdLine = new CommandLine(obj); + // Clear the globally cached jansiConsole value that might + // have been set in a previous test to force the + // Ansi#isJansiConsoleInstalled method to recalculate + // the cached value. + Ansi.jansiConsole = null; int exitCode = cmdLine.execute("-x", "-y=123"); assertEquals(2, exitCode); String expected = String.format("" + diff --git a/src/test/java/picocli/ModelTransformerTest.java b/src/test/java/picocli/ModelTransformerTest.java index 6d5b0a747..f6a1ed7e4 100644 --- a/src/test/java/picocli/ModelTransformerTest.java +++ b/src/test/java/picocli/ModelTransformerTest.java @@ -2,6 +2,7 @@ import org.junit.Test; import picocli.CommandLine.Command; +import picocli.CommandLine.Help.Ansi; import java.io.PrintWriter; import java.io.StringWriter; @@ -34,7 +35,9 @@ public CommandLine.Model.CommandSpec transform(CommandLine.Model.CommandSpec com @Test public void testUsage() { StringWriter sw = new StringWriter(); - new CommandLine(new MyCommand()).usage(new PrintWriter(sw)); + // Explicitly disable Ansi to make sure that the cached isJansiConsoleInstalled + // value doesn't inadvertently cause the usage help to enable ansi. + new CommandLine(new MyCommand()).usage(new PrintWriter(sw), Ansi.OFF); String expected = String.format("" + "Usage: mycmd [-hV] [COMMAND]%n" + " -h, --help Show this help message and exit.%n" +