From e8a8e513419856468e39b5dfbf5522c0c3823e47 Mon Sep 17 00:00:00 2001 From: Sylwester Lachiewicz Date: Tue, 4 Aug 2026 01:14:29 +0200 Subject: [PATCH] test: prevent ambient environment from leaking into daemon builds on CI --- .../mvndaemon/mvnd/client/DefaultClient.java | 11 +++++++++- .../mvndaemon/mvnd/junit/JvmTestClient.java | 13 ++++++++++++ .../mvnd/junit/MvndTestExtension.java | 21 ++++++++++++++++++- .../mvnd/junit/NativeTestClient.java | 3 +++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/client/src/main/java-mvnd/org/mvndaemon/mvnd/client/DefaultClient.java b/client/src/main/java-mvnd/org/mvndaemon/mvnd/client/DefaultClient.java index 2a3748a15..86dd62951 100644 --- a/client/src/main/java-mvnd/org/mvndaemon/mvnd/client/DefaultClient.java +++ b/client/src/main/java-mvnd/org/mvndaemon/mvnd/client/DefaultClient.java @@ -242,6 +242,15 @@ public DefaultClient(DaemonParameters parameters) { true); } + /** + * The environment forwarded to the daemon in the {@link Message.BuildRequest}. Defaults to the + * client's own environment; overridable so tests can run hermetically (e.g. without inheriting + * an ambient {@code MAVEN_ARGS}). + */ + protected Map buildRequestEnvironment() { + return System.getenv(); + } + @Override public ExecutionResult execute(ClientOutput output, List argv) { LOGGER.debug("Starting client"); @@ -374,7 +383,7 @@ public ExecutionResult execute(ClientOutput output, List argv) { args, parameters.userDir().toString(), parameters.multiModuleProjectDirectory().toString(), - System.getenv())); + buildRequestEnvironment())); output.accept(Message.buildStatus( "Connected to daemon " + daemon.getDaemon().getId() + ", scanning for projects...")); diff --git a/integration-tests/src/test/java/org/mvndaemon/mvnd/junit/JvmTestClient.java b/integration-tests/src/test/java/org/mvndaemon/mvnd/junit/JvmTestClient.java index b18af783e..419fb5873 100644 --- a/integration-tests/src/test/java/org/mvndaemon/mvnd/junit/JvmTestClient.java +++ b/integration-tests/src/test/java/org/mvndaemon/mvnd/junit/JvmTestClient.java @@ -21,6 +21,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -42,6 +43,18 @@ public JvmTestClient(DaemonParameters parameters) { this.parameters = parameters; } + /** + * Strip any ambient {@code MAVEN_ARGS} (e.g. actions/setup-java sets it to {@code -ntp}) from the + * environment forwarded to the daemon, so it cannot override per-test flags such as + * ConcurrentDownloadsTest's transfer-progress expectation. Keeps the test daemon hermetic. + */ + @Override + protected Map buildRequestEnvironment() { + final Map env = new HashMap<>(super.buildRequestEnvironment()); + env.remove("MAVEN_ARGS"); + return env; + } + @Override public ExecutionResult execute(ClientOutput output, List argv) { setMultiModuleProjectDirectory(argv); diff --git a/integration-tests/src/test/java/org/mvndaemon/mvnd/junit/MvndTestExtension.java b/integration-tests/src/test/java/org/mvndaemon/mvnd/junit/MvndTestExtension.java index e9f22b37e..f3c810d8a 100644 --- a/integration-tests/src/test/java/org/mvndaemon/mvnd/junit/MvndTestExtension.java +++ b/integration-tests/src/test/java/org/mvndaemon/mvnd/junit/MvndTestExtension.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -235,7 +236,7 @@ public static MvndResource create( multiModuleProjectDirectory, Paths.get(System.getProperty("java.home")).toAbsolutePath().normalize(), localMavenRepository, - null, + createIsolatedSettings(testDir.resolve("settings.xml")), TimeUtils.toDuration(Environment.MVND_IDLE_TIMEOUT.getDefault()), keepAlive != null && !keepAlive.isEmpty() ? TimeUtils.toDuration(keepAlive) @@ -251,6 +252,24 @@ public static MvndResource create( return new MvndResource(parameters, registry, isNative, timeoutMs); } + /** + * Writes a minimal, isolated {@code settings.xml} and returns its path so that the daemon + * is always launched with an explicit {@code -s} and never falls back to the ambient + * {@code ${user.home}/.m2/settings.xml}. Without this, when running with {@code -Dmrm=false} + * (as CI does), the daemon reads the runner's user settings. actions/setup-java writes one + * containing {@code false}, which made {@code versions:set} + * run non-interactively and broke InteractiveTest. An empty settings keeps the defaults + * (interactive, no ambient mirrors) so the tests behave the same everywhere. + */ + static Path createIsolatedSettings(Path settingsPath) { + try { + Files.write(settingsPath, "\n".getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new RuntimeException("Could not write " + settingsPath, e); + } + return settingsPath; + } + private static void prefillLocalRepo(final Path localMavenRepository) { /* Workaround for https://github.com/apache/maven-mvnd/issues/281 */ final String preinstallArtifacts = diff --git a/integration-tests/src/test/java/org/mvndaemon/mvnd/junit/NativeTestClient.java b/integration-tests/src/test/java/org/mvndaemon/mvnd/junit/NativeTestClient.java index 7391b6e95..03ae48a6c 100644 --- a/integration-tests/src/test/java/org/mvndaemon/mvnd/junit/NativeTestClient.java +++ b/integration-tests/src/test/java/org/mvndaemon/mvnd/junit/NativeTestClient.java @@ -82,6 +82,9 @@ public ExecutionResult execute(ClientOutput output, List args) throws In .redirectErrorStream(true); final Map env = builder.environment(); + // Strip any ambient MAVEN_ARGS (e.g. actions/setup-java sets it to -ntp) so it cannot leak + // through the native binary into the daemon and override per-test flags. Keeps tests hermetic. + env.remove("MAVEN_ARGS"); if (!Environment.MVND_HOME.hasCommandLineOption(args)) { env.put(Environment.MVND_HOME.getEnvironmentVariable(), Environment.MVND_HOME.asString()); }