diff --git a/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java b/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java index 2ae4372f3..4450f8ed1 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java +++ b/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java @@ -357,6 +357,7 @@ private String resolveExpansionProperties(Properties props, String value, String Pattern pattern = OSUtil.isWindows() ? WINDOWS_EXPANSION_VAR_PATTERN : LINUX_EXPANSION_VAR_PATTERN; Matcher matcher = pattern.matcher(value); StringBuffer sb = new StringBuffer(); + List matchedVarNames = new ArrayList<>(); while (matcher.find()) { String finalReplacement; String varName = matcher.group(1); @@ -381,10 +382,15 @@ private String resolveExpansionProperties(Properties props, String value, String finalReplacement = matcher.group(0); // Keep original } matcher.appendReplacement(sb, Matcher.quoteReplacement(finalReplacement)); - log.info(String.format("Resolving Property %s for expression %s. Resolved expression value is %s", varName , value , sb)); + matchedVarNames.add(varName); } // 4. Finalize the string matcher.appendTail(sb); + + // Log after appendTail so sb holds the complete resolved value + for (String varName : matchedVarNames) { + log.info(String.format("Resolved environment variable \"%s\" in path \"%s\" to \"%s\"", varName, value, sb)); + } return sb.toString(); } diff --git a/src/test/java/io/openliberty/tools/common/config/ExpansionVariableLogMessageTest.java b/src/test/java/io/openliberty/tools/common/config/ExpansionVariableLogMessageTest.java new file mode 100644 index 000000000..70db1c590 --- /dev/null +++ b/src/test/java/io/openliberty/tools/common/config/ExpansionVariableLogMessageTest.java @@ -0,0 +1,141 @@ +/** + * (C) Copyright IBM Corporation 2026. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.openliberty.tools.common.config; + +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Assume; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import io.openliberty.tools.common.TestLogger; +import io.openliberty.tools.common.plugins.config.ServerConfigDocument; +import io.openliberty.tools.common.plugins.util.OSUtil; +import io.openliberty.tools.common.plugins.util.ServerFeatureUtil; + +// Verifies the log message format produced by resolveExpansionProperties() after the fix for issue #2076 +public class ExpansionVariableLogMessageTest { + + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + + // Captures info() calls for assertion; all other methods delegate to TestLogger + private static class CapturingLogger extends TestLogger { + final List infoMessages = new ArrayList<>(); + + @Override + public void info(String msg) { infoMessages.add(msg); } + } + + // server.env is placed in serverDir because SERVER_CONFIG_DIR maps there, making it visible to processServerEnv() + private ServerConfigDocument buildDoc(CapturingLogger log, File serverDir, String serverEnvContent) throws Exception { + Files.write(new File(serverDir, "server.env").toPath(), serverEnvContent.getBytes()); + + Map dirMap = new HashMap<>(); + dirMap.put(ServerFeatureUtil.WLP_INSTALL_DIR, serverDir); + dirMap.put(ServerFeatureUtil.WLP_USER_DIR, serverDir); + dirMap.put(ServerFeatureUtil.SERVER_CONFIG_DIR, serverDir); + dirMap.put(ServerFeatureUtil.SERVER_OUTPUT_DIR, serverDir); + return new ServerConfigDocument(log, null, dirMap); + } + + @Test + public void testUnixStyleLogMessageFormat() throws Exception { + Assume.assumeFalse("Skipped on Windows: Unix ${VAR} pattern not active", OSUtil.isWindows()); + + CapturingLogger log = new CapturingLogger(); + File serverDir = tmp.newFolder("server-unix"); + + String envContent = "BASE=TEST\nDERIVED=${BASE}_SUFFIX\n"; + buildDoc(log, serverDir, envContent).processServerEnv(); + + String expectedMsg = "Resolved environment variable \"BASE\" in path \"${BASE}_SUFFIX\" to \"TEST_SUFFIX\""; + assertTrue("Expected log message not found.\nActual info messages: " + log.infoMessages, + log.infoMessages.stream().anyMatch(m -> m.equals(expectedMsg))); + } + + @Test + public void testUnixStyleMultipleVarsLogsBothVarNames() throws Exception { + Assume.assumeFalse("Skipped on Windows: Unix ${VAR} pattern not active", OSUtil.isWindows()); + + CapturingLogger log = new CapturingLogger(); + File serverDir = tmp.newFolder("server-unix-multi"); + + String envContent = "EXP_VAR=TEST\nEXP_VAR2=UNIX\nCOMBINED=${EXP_VAR}_${EXP_VAR2}\n"; + buildDoc(log, serverDir, envContent).processServerEnv(); + + String expectedMsg1 = "Resolved environment variable \"EXP_VAR\" in path \"${EXP_VAR}_${EXP_VAR2}\" to \"TEST_UNIX\""; + String expectedMsg2 = "Resolved environment variable \"EXP_VAR2\" in path \"${EXP_VAR}_${EXP_VAR2}\" to \"TEST_UNIX\""; + assertTrue("Expected first log message not found.\nActual: " + log.infoMessages, + log.infoMessages.stream().anyMatch(m -> m.equals(expectedMsg1))); + assertTrue("Expected second log message not found.\nActual: " + log.infoMessages, + log.infoMessages.stream().anyMatch(m -> m.equals(expectedMsg2))); + } + + @Test + public void testNoExpansionReferenceNoLogEmitted() throws Exception { + CapturingLogger log = new CapturingLogger(); + File serverDir = tmp.newFolder("server-no-expansion"); + + String envContent = "PLAIN_VAR=just_a_value\n"; + buildDoc(log, serverDir, envContent).processServerEnv(); + + assertTrue("No info log message should be emitted for plain values, but found: " + log.infoMessages, + log.infoMessages.isEmpty()); + } + + @Test + public void testWindowsStyleBackslashesPreservedInLog() throws Exception { + Assume.assumeTrue("Skipped on non-Windows: !VAR! pattern only active on Windows", OSUtil.isWindows()); + + CapturingLogger log = new CapturingLogger(); + File serverDir = tmp.newFolder("server-win"); + + // Reproduces the exact scenario from issue #2076 + String envContent = "IBM_JAVA_SEMERU_HOME=C:\\MyData\\java\\ibm-semeru-certified\nJAVA_HOME=!IBM_JAVA_SEMERU_HOME!\\jdk-21.0.10+7\n"; + buildDoc(log, serverDir, envContent).processServerEnv(); + + String expectedMsg = "Resolved environment variable \"IBM_JAVA_SEMERU_HOME\" in path \"!IBM_JAVA_SEMERU_HOME!\\jdk-21.0.10+7\" to \"C:\\MyData\\java\\ibm-semeru-certified\\jdk-21.0.10+7\""; + assertTrue("Log message with backslashes not found — backslashes may have been dropped.\nActual: " + log.infoMessages, + log.infoMessages.stream().anyMatch(m -> m.equals(expectedMsg))); + } + + @Test + public void testWindowsStyleMultipleVarsLogsBothVarNames() throws Exception { + Assume.assumeTrue("Skipped on non-Windows: !VAR! pattern only active on Windows", OSUtil.isWindows()); + + CapturingLogger log = new CapturingLogger(); + File serverDir = tmp.newFolder("server-win-multi"); + + String envContent = "EXP_VAR=TEST\nEXP_VAR3=WINDOWS\nCOMBINED=!EXP_VAR!_!EXP_VAR3!\n"; + buildDoc(log, serverDir, envContent).processServerEnv(); + + String expectedMsg1 = "Resolved environment variable \"EXP_VAR\" in path \"!EXP_VAR!_!EXP_VAR3!\" to \"TEST_WINDOWS\""; + String expectedMsg2 = "Resolved environment variable \"EXP_VAR3\" in path \"!EXP_VAR!_!EXP_VAR3!\" to \"TEST_WINDOWS\""; + assertTrue("Expected first log message not found.\nActual: " + log.infoMessages, + log.infoMessages.stream().anyMatch(m -> m.equals(expectedMsg1))); + assertTrue("Expected second log message not found.\nActual: " + log.infoMessages, + log.infoMessages.stream().anyMatch(m -> m.equals(expectedMsg2))); + } +}