Skip to content

Commit cd03d1b

Browse files
aschemanGerd Aschemannclaude
authored
[#12340] Add DefaultLookupTest for lookupOptional (#12385)
The backported NPE fix for DefaultLookup.lookupOptional (#12340) — switching Optional.of to Optional.ofNullable so a null component lookup returns an empty Optional instead of throwing NullPointerException — shipped without a regression test. Add DefaultLookupTest in maven-core covering both lookupOptional overloads across four scenarios: null component -> empty Optional (the guarded NPE case), present component -> value, ComponentLookupException caused by NoSuchElementException -> empty, and any other cause -> rethrown as LookupException. Co-authored-by: Gerd Aschemann <ascheman@apache.org> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 9e4150d commit cd03d1b

1 file changed

Lines changed: 151 additions & 0 deletions

File tree

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.internal.impl;
20+
21+
import java.util.NoSuchElementException;
22+
import java.util.Optional;
23+
24+
import org.apache.maven.api.services.LookupException;
25+
import org.codehaus.plexus.PlexusContainer;
26+
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
27+
import org.junit.jupiter.api.BeforeEach;
28+
import org.junit.jupiter.api.Test;
29+
30+
import static org.junit.jupiter.api.Assertions.assertFalse;
31+
import static org.junit.jupiter.api.Assertions.assertSame;
32+
import static org.junit.jupiter.api.Assertions.assertThrows;
33+
import static org.junit.jupiter.api.Assertions.assertTrue;
34+
import static org.mockito.Mockito.mock;
35+
import static org.mockito.Mockito.when;
36+
37+
class DefaultLookupTest {
38+
39+
private PlexusContainer container;
40+
private DefaultLookup lookup;
41+
42+
@BeforeEach
43+
void setUp() {
44+
container = mock(PlexusContainer.class);
45+
lookup = new DefaultLookup(container);
46+
}
47+
48+
/**
49+
* Regression guard for the NPE fix (#12340): when the container returns {@code null} for a type
50+
* lookup, {@code lookupOptional} must wrap it with {@link Optional#ofNullable} and return an empty
51+
* {@link Optional} rather than throwing a {@link NullPointerException} from {@code Optional.of}.
52+
*/
53+
@Test
54+
void lookupOptionalByTypeReturnsEmptyWhenContainerReturnsNull() throws Exception {
55+
when(container.lookup(String.class)).thenReturn(null);
56+
57+
Optional<String> result = lookup.lookupOptional(String.class);
58+
59+
assertTrue(result.isEmpty(), "expected empty Optional when container returns null");
60+
}
61+
62+
/**
63+
* Same regression guard as above for the {@code (Class, String)} overload.
64+
*/
65+
@Test
66+
void lookupOptionalByTypeAndNameReturnsEmptyWhenContainerReturnsNull() throws Exception {
67+
when(container.lookup(String.class, "hint")).thenReturn(null);
68+
69+
Optional<String> result = lookup.lookupOptional(String.class, "hint");
70+
71+
assertTrue(result.isEmpty(), "expected empty Optional when container returns null");
72+
}
73+
74+
@Test
75+
void lookupOptionalByTypeReturnsValueWhenPresent() throws Exception {
76+
String value = "component";
77+
when(container.lookup(String.class)).thenReturn(value);
78+
79+
Optional<String> result = lookup.lookupOptional(String.class);
80+
81+
assertTrue(result.isPresent());
82+
assertSame(value, result.get());
83+
}
84+
85+
@Test
86+
void lookupOptionalByTypeAndNameReturnsValueWhenPresent() throws Exception {
87+
String value = "component";
88+
when(container.lookup(String.class, "hint")).thenReturn(value);
89+
90+
Optional<String> result = lookup.lookupOptional(String.class, "hint");
91+
92+
assertTrue(result.isPresent());
93+
assertSame(value, result.get());
94+
}
95+
96+
/**
97+
* A {@link ComponentLookupException} whose cause is a {@link NoSuchElementException} signals an
98+
* absent component and must be translated into an empty {@link Optional}.
99+
*/
100+
@Test
101+
void lookupOptionalReturnsEmptyOnNoSuchElementCause() throws Exception {
102+
ComponentLookupException cle =
103+
new ComponentLookupException(new NoSuchElementException(), String.class.getName(), "");
104+
assertSame(NoSuchElementException.class, cle.getCause().getClass(), "test fixture precondition");
105+
when(container.lookup(String.class)).thenThrow(cle);
106+
107+
Optional<String> result = lookup.lookupOptional(String.class);
108+
109+
assertFalse(result.isPresent(), "expected empty Optional when component is absent");
110+
}
111+
112+
/**
113+
* Same as above for the {@code (Class, String)} overload, which has its own catch/translate logic.
114+
*/
115+
@Test
116+
void lookupOptionalByTypeAndNameReturnsEmptyOnNoSuchElementCause() throws Exception {
117+
ComponentLookupException cle =
118+
new ComponentLookupException(new NoSuchElementException(), String.class.getName(), "hint");
119+
assertSame(NoSuchElementException.class, cle.getCause().getClass(), "test fixture precondition");
120+
when(container.lookup(String.class, "hint")).thenThrow(cle);
121+
122+
Optional<String> result = lookup.lookupOptional(String.class, "hint");
123+
124+
assertFalse(result.isPresent(), "expected empty Optional when component is absent");
125+
}
126+
127+
/**
128+
* A {@link ComponentLookupException} whose cause is NOT a {@link NoSuchElementException} is
129+
* rethrown as a {@link LookupException} rather than swallowed into an empty {@link Optional}.
130+
*/
131+
@Test
132+
void lookupOptionalRethrowsOnNonNoSuchElementCause() throws Exception {
133+
ComponentLookupException cle =
134+
new ComponentLookupException(new IllegalStateException("boom"), String.class.getName(), "");
135+
when(container.lookup(String.class)).thenThrow(cle);
136+
137+
assertThrows(LookupException.class, () -> lookup.lookupOptional(String.class));
138+
}
139+
140+
/**
141+
* Same as above for the {@code (Class, String)} overload.
142+
*/
143+
@Test
144+
void lookupOptionalByTypeAndNameRethrowsOnNonNoSuchElementCause() throws Exception {
145+
ComponentLookupException cle =
146+
new ComponentLookupException(new IllegalStateException("boom"), String.class.getName(), "hint");
147+
when(container.lookup(String.class, "hint")).thenThrow(cle);
148+
149+
assertThrows(LookupException.class, () -> lookup.lookupOptional(String.class, "hint"));
150+
}
151+
}

0 commit comments

Comments
 (0)