This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch maven-4.0.x-test-fixes in repository https://gitbox.apache.org/repos/asf/maven.git
commit 56fcf74bfab1ed8de17961ffe2debfab98902a15 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>
