You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2026/08/29 13:49:55 UTC
(maven) branch fix/12600 created (now b32eb23d9f)
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a change to branch fix/12600
in repository https://gitbox.apache.org/repos/asf/maven.git
at b32eb23d9f Add test for forked execution project index null guard
This branch includes the following new commits:
new b32eb23d9f Add test for forked execution project index null guard
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(maven) 01/01: Add test for forked execution project index null guard
Posted by gn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch fix/12600
in repository https://gitbox.apache.org/repos/asf/maven.git
commit b32eb23d9f5ca25193f02015704461fdb4dbec65
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Sat Aug 29 15:49:40 2026 +0200
Add test for forked execution project index null guard
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../internal/MojoExecutorForkedExecutionTest.java | 144 +++++++++++++++++++++
1 file changed, 144 insertions(+)
diff --git a/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/MojoExecutorForkedExecutionTest.java b/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/MojoExecutorForkedExecutionTest.java
new file mode 100644
index 0000000000..e0f615d293
--- /dev/null
+++ b/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/MojoExecutorForkedExecutionTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.lifecycle.internal;
+
+import javax.inject.Provider;
+
+import java.util.List;
+
+import org.apache.maven.api.Session;
+import org.apache.maven.api.SessionData;
+import org.apache.maven.api.services.MessageBuilderFactory;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.lifecycle.LifecycleExecutionException;
+import org.apache.maven.plugin.BuildPluginManager;
+import org.apache.maven.plugin.MavenPluginManager;
+import org.apache.maven.plugin.MojoExecution;
+import org.apache.maven.plugin.MojosExecutionStrategy;
+import org.apache.maven.plugin.descriptor.MojoDescriptor;
+import org.apache.maven.project.MavenProject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link MojoExecutor#executeForkedExecutions} null-guard behavior
+ * and {@link ProjectIndex} lookup semantics.
+ *
+ * @see <a href="https://github.com/apache/maven/issues/12600">MNG-12600</a>
+ */
+@ExtendWith(MockitoExtension.class)
+class MojoExecutorForkedExecutionTest {
+
+ @Mock
+ private BuildPluginManager pluginManager;
+
+ @Mock
+ private MavenPluginManager mavenPluginManager;
+
+ @Mock
+ private LifecycleDependencyResolver lifeCycleDependencyResolver;
+
+ @Mock
+ private ExecutionEventCatapult eventCatapult;
+
+ @Mock
+ private Provider<MojosExecutionStrategy> mojosExecutionStrategy;
+
+ @Mock
+ private MessageBuilderFactory messageBuilderFactory;
+
+ @InjectMocks
+ private MojoExecutor mojoExecutor;
+
+ /**
+ * Verifies that {@link ProjectIndex} returns null for projects not in the reactor,
+ * which is the precondition that caused the original NPE (issue #12600).
+ */
+ @Test
+ void projectIndexReturnsNullForUnknownProject() {
+ MavenProject knownProject = createProject("org.test", "known", "1.0");
+ ProjectIndex index = new ProjectIndex(List.of(knownProject));
+
+ // Known project has an index entry
+ assertNotNull(index.getIndices().get("org.test:known:1.0"));
+ assertEquals(0, index.getIndices().get("org.test:known:1.0"));
+ assertNotNull(index.getProjects().get("org.test:known:1.0"));
+
+ // Unknown project returns null from both maps — this is the scenario
+ // that would cause NPE via auto-unboxing without the null guard
+ assertNull(index.getIndices().get("org.test:unknown:2.0"));
+ assertNull(index.getProjects().get("org.test:unknown:2.0"));
+ }
+
+ /**
+ * Verifies that {@link MojoExecutor#executeForkedExecutions} throws a
+ * {@link LifecycleExecutionException} with a descriptive message when the
+ * forked execution references a project not present in the reactor,
+ * instead of throwing a {@link NullPointerException} from auto-unboxing.
+ */
+ @Test
+ void executeForkedExecutionsThrowsDescriptiveExceptionForUnknownProject() throws Exception {
+ MavenProject knownProject = createProject("org.test", "known", "1.0");
+ ProjectIndex projectIndex = new ProjectIndex(List.of(knownProject));
+
+ // Set up a MojoExecution with forked executions referencing an unknown project
+ MojoExecution mojoExecution = new MojoExecution(mock(MojoDescriptor.class));
+ String unknownProjectId = "org.test:unknown:2.0";
+ mojoExecution.setForkedExecutions(unknownProjectId, List.of());
+
+ // Mock the session chain: MavenSession -> Session -> SessionData -> ProjectIndex
+ MavenSession mavenSession = mock(MavenSession.class);
+ Session apiSession = mock(Session.class);
+ SessionData sessionData = mock(SessionData.class);
+
+ when(mavenSession.getSession()).thenReturn(apiSession);
+ when(mavenSession.getCurrentProject()).thenReturn(knownProject);
+ when(apiSession.getData()).thenReturn(sessionData);
+ when(sessionData.computeIfAbsent(any(), any())).thenReturn(projectIndex);
+
+ // Should throw LifecycleExecutionException, not NullPointerException
+ LifecycleExecutionException ex = assertThrows(
+ LifecycleExecutionException.class,
+ () -> mojoExecutor.executeForkedExecutions(mojoExecution, mavenSession));
+
+ assertTrue(
+ ex.getMessage().contains(unknownProjectId), "Exception message should contain the unknown project ID");
+ assertTrue(ex.getMessage().contains("not in the reactor"), "Exception message should mention the reactor");
+ }
+
+ private static MavenProject createProject(String groupId, String artifactId, String version) {
+ MavenProject project = new MavenProject();
+ project.setGroupId(groupId);
+ project.setArtifactId(artifactId);
+ project.setVersion(version);
+ return project;
+ }
+}