This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch backport-11983-to-4.0.x
in repository https://gitbox.apache.org/repos/asf/maven.git

commit a45d213586d497057af642e655bace8a86a9c413
Author: Gerd Aschemann <[email protected]>
AuthorDate: Sun Apr 26 15:44:34 2026 +0200

    Add IT for mvn script expanding ${...} in CLI arguments
    
    The eval in the mvn script causes shell expansion of ${...} patterns
    in user-provided arguments. This regression test exercises the actual
    launcher script via setForkJvm(true) and verifies that ${...} is not
    expanded by the shell.
    
    Related: #11978
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
 .../it/MavenITgh11978PlaceholderInCliArgTest.java  | 107 +++++++++++++++++++++
 .../gh-11978-placeholder-in-cli-arg/pom.xml        |  58 +++++++++++
 2 files changed, 165 insertions(+)

diff --git 
a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11978PlaceholderInCliArgTest.java
 
b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11978PlaceholderInCliArgTest.java
new file mode 100644
index 0000000000..eac9f09dd3
--- /dev/null
+++ 
b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11978PlaceholderInCliArgTest.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.maven.it;
+
+import java.nio.file.Path;
+import java.util.Properties;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * This is a test set for <a 
href="https://github.com/apache/maven/issues/11978";>gh-11978</a>.
+ *
+ * Verifies that the launcher script does not expand <code>${...}</code> 
patterns
+ * in CLI arguments. Regression test for the {@code eval exec} shell expansion
+ * that broke any argument containing Maven property placeholders.
+ *
+ * <h2>Real-world scenario</h2>
+ *
+ * <p>This bug was originally surfaced by maven-surefire-plugin integration 
tests.
+ * Surefire allows users to declare system properties for the forked test JVM 
with
+ * placeholders that surefire substitutes <em>at fork time</em>, e.g.
+ * {@code 
-DtestProperty=testValue_${surefire.threadNumber}_${surefire.forkNumber}}.
+ * The intended flow is:
+ * <ol>
+ *   <li>The user invokes {@code mvn} with the literal placeholder on the 
CLI.</li>
+ *   <li>The launcher script must pass the literal {@code ${...}} verbatim to
+ *       the JVM as a system property value.</li>
+ *   <li>The surefire plugin reads the system property, performs <em>its 
own</em>
+ *       interpolation when forking each test JVM, replacing
+ *       {@code ${surefire.threadNumber}} and {@code ${surefire.forkNumber}}
+ *       with the actual fork/thread numbers.</li>
+ *   <li>Each forked test JVM thus sees a unique substituted value.</li>
+ * </ol>
+ *
+ * <p>The bug broke step 2: the launcher script's {@code eval exec} re-parsed
+ * the command string and invoked shell variable expansion on {@code ${...}}.
+ * Names containing dots (such as {@code surefire.threadNumber}) are invalid
+ * shell variable names, so the shell aborted with {@code bad substitution}
+ * before Maven even started, breaking all surefire forked-test ITs that relied
+ * on this pattern.
+ *
+ * <h2>Test design</h2>
+ *
+ * <p>The placeholder name contains dots, mirroring surefire's real usage and
+ * deliberately producing an invalid shell variable name. Without the fix:
+ * <ul>
+ *   <li>The shell's {@code eval exec} aborts with {@code bad 
substitution}.</li>
+ *   <li>The build fails immediately and {@link Verifier#verifyErrorFreeLog()}
+ *       catches it.</li>
+ * </ul>
+ * With the fix:
+ * <ul>
+ *   <li>The literal {@code ${...}} arrives at Maven as a system property 
value.</li>
+ *   <li>Maven's recursive property interpolation resolves the unknown 
placeholder
+ *       to an empty string when reading {@code ${test.placeholder}} from the 
POM.</li>
+ *   <li>The resulting value is {@code -value__end-}.</li>
+ * </ul>
+ *
+ * <p>Note: this test cannot easily replicate surefire's late-binding 
interpolation
+ * (step 3 above) because Maven's standard property interpolation does not
+ * recursively resolve POM properties for placeholders embedded inside a system
+ * property value. Surefire performs that substitution in its own plugin code 
at
+ * fork time. The signal that proves the fix works is the absence of a
+ * {@code bad substitution} shell error, not the resolved value itself.
+ */
+class MavenITgh11978PlaceholderInCliArgTest extends 
AbstractMavenIntegrationTestCase {
+
+    @Test
+    void testIt() throws Exception {
+        Path basedir = extractResources("/gh-11978-placeholder-in-cli-arg")
+                .getAbsoluteFile()
+                .toPath();
+
+        Verifier verifier = newVerifier(basedir.toString());
+        verifier.setForkJvm(true); // NOTE: We want to go through the launcher 
script
+        // The placeholder name contains dots, which is invalid as a shell 
variable name.
+        // Without the fix, the shell's `eval exec` aborts with "bad 
substitution".
+        // With the fix, the literal ${...} arrives at Maven.
+        
verifier.addCliArgument("-Dtest.placeholder=value_${some.maven.placeholder}_end");
+        verifier.addCliArgument("validate");
+        verifier.execute();
+        verifier.verifyErrorFreeLog(); // key check: fails immediately on 
shell crash
+
+        // Sanity check: the value flowed through Maven, which resolves the 
unknown
+        // ${some.maven.placeholder} to empty during recursive interpolation.
+        Properties props = verifier.loadProperties("target/pom.properties");
+        assertEquals("-value__end-", 
props.getProperty("project.properties.pom.placeholder"));
+    }
+}
diff --git 
a/its/core-it-suite/src/test/resources/gh-11978-placeholder-in-cli-arg/pom.xml 
b/its/core-it-suite/src/test/resources/gh-11978-placeholder-in-cli-arg/pom.xml
new file mode 100644
index 0000000000..b55ef8a120
--- /dev/null
+++ 
b/its/core-it-suite/src/test/resources/gh-11978-placeholder-in-cli-arg/pom.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.apache.maven.its.gh11978</groupId>
+  <artifactId>test</artifactId>
+  <version>1.0</version>
+
+  <name>Maven Integration Test :: GH-11978</name>
+  <description>Verify that the launcher script does not expand ${...} 
placeholders in CLI arguments.</description>
+
+  <properties>
+    <pom.placeholder>-${test.placeholder}-</pom.placeholder>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.its.plugins</groupId>
+        <artifactId>maven-it-plugin-expression</artifactId>
+        <version>2.1-SNAPSHOT</version>
+        <executions>
+          <execution>
+            <id>test</id>
+            <goals>
+              <goal>eval</goal>
+            </goals>
+            <phase>validate</phase>
+            <configuration>
+              <outputFile>target/pom.properties</outputFile>
+              <expressions>
+                <expression>project/properties</expression>
+              </expressions>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>

Reply via email to