View Javadoc
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.buildcache.xml;
20  
21  import javax.inject.Provider;
22  
23  import java.io.File;
24  import java.io.IOException;
25  import java.lang.reflect.InvocationTargetException;
26  import java.lang.reflect.Method;
27  import java.nio.file.AccessMode;
28  import java.nio.file.FileSystem;
29  import java.nio.file.Path;
30  import java.nio.file.spi.FileSystemProvider;
31  import java.util.Arrays;
32  import java.util.Collections;
33  import java.util.HashMap;
34  import java.util.Map;
35  import java.util.Optional;
36  import java.util.Properties;
37  import java.util.stream.Collectors;
38  
39  import org.apache.commons.lang3.tuple.Pair;
40  import org.apache.maven.buildcache.DefaultPluginScanConfig;
41  import org.apache.maven.buildcache.hash.HashFactory;
42  import org.apache.maven.buildcache.xml.config.Configuration;
43  import org.apache.maven.buildcache.xml.config.Remote;
44  import org.apache.maven.execution.MavenExecutionRequest;
45  import org.apache.maven.execution.MavenSession;
46  import org.apache.maven.model.Plugin;
47  import org.apache.maven.model.PluginExecution;
48  import org.apache.maven.plugin.MojoExecution;
49  import org.apache.maven.rtinfo.RuntimeInformation;
50  import org.junit.jupiter.api.BeforeEach;
51  import org.junit.jupiter.api.Test;
52  import org.junit.jupiter.api.extension.ExtendWith;
53  import org.mockito.ArgumentMatchers;
54  import org.mockito.Mock;
55  import org.mockito.Mockito;
56  import org.mockito.junit.jupiter.MockitoExtension;
57  import org.mockito.junit.jupiter.MockitoSettings;
58  import org.mockito.quality.Strictness;
59  
60  import static org.junit.Assert.assertFalse;
61  import static org.junit.Assert.assertNull;
62  import static org.junit.jupiter.api.Assertions.assertEquals;
63  import static org.junit.jupiter.api.Assertions.assertTrue;
64  import static org.mockito.Mockito.doThrow;
65  import static org.mockito.Mockito.mock;
66  import static org.mockito.Mockito.when;
67  
68  @ExtendWith(MockitoExtension.class)
69  @MockitoSettings(strictness = Strictness.LENIENT)
70  @SuppressWarnings("unchecked")
71  class CacheConfigImplTest {
72  
73      @Mock
74      private MavenSession mavenSession;
75  
76      @Mock
77      private Properties mockProperties;
78  
79      @Mock
80      private MavenExecutionRequest mockMavenExecutionRequest;
81  
82      @Mock
83      private RuntimeInformation rtInfo;
84  
85      @Mock
86      private XmlService xmlService;
87  
88      @Mock
89      private File rootConfigFile;
90  
91      private org.apache.maven.buildcache.xml.config.CacheConfig testCacheConfig;
92      private CacheConfigImpl testObject;
93  
94      @BeforeEach
95      void setUp() throws IOException {
96  
97          // Setup mocking that allows us to get through happy-path initialization and focus on
98          // interactions of xml settings and/or command-line arguments and the resultant CacheConfig access methods
99  
100         // session (command-line properties)
101         when(mavenSession.getRequest()).thenReturn(mockMavenExecutionRequest);
102         when(mavenSession.getUserProperties()).thenReturn(mockProperties);
103         when(mavenSession.getSystemProperties()).thenReturn(mockProperties);
104 
105         // runtime (maven)
106         when(rtInfo.isMavenVersion(ArgumentMatchers.anyString())).thenReturn(true);
107 
108         // configuration (xml file)
109         deepMockConfigFile(rootConfigFile, true);
110         when(mockMavenExecutionRequest.getMultiModuleProjectDirectory()).thenReturn(rootConfigFile);
111         // start with empty config
112         testCacheConfig = new XmlService().loadCacheConfig("<cache></cache>".getBytes());
113         when(xmlService.loadCacheConfig(rootConfigFile)).thenReturn(testCacheConfig);
114 
115         Provider<MavenSession> provider = (() -> mavenSession);
116         // test object
117         testObject = new CacheConfigImpl(xmlService, provider, rtInfo);
118     }
119 
120     private static void deepMockConfigFile(File mockFile, boolean exists) throws IOException {
121         Path mockPath = mock(Path.class);
122         when(mockFile.toPath()).thenReturn(mockPath);
123         when(mockPath.toFile()).thenReturn(mockFile);
124         when(mockPath.resolve(ArgumentMatchers.anyString())).thenReturn(mockPath);
125 
126         // unfortunate and potentially fragile deep mocking, but helps avoid most disk involvement while working around
127         // the static nio Files.exists method
128         FileSystem mockFileSystem = mock(FileSystem.class);
129         when(mockPath.getFileSystem()).thenReturn(mockFileSystem);
130         FileSystemProvider mockProvider = mock(FileSystemProvider.class);
131         when(mockFileSystem.provider()).thenReturn(mockProvider);
132 
133         // Mock for java < 20.
134         if (!exists) {
135             doThrow(new IOException("mock IOException"))
136                     .when(mockProvider)
137                     .checkAccess(ArgumentMatchers.eq(mockPath), ArgumentMatchers.any(AccessMode.class));
138         }
139 
140         // Mock for java >= 20. Since the FileSystemProvider.exists method does not exist before v20, we use reflection
141         // to create the mock
142         Optional<Method> methodToMock = Arrays.stream(FileSystemProvider.class.getDeclaredMethods())
143                 .filter(method -> "exists".equals(method.getName()))
144                 .findAny();
145         if (methodToMock.isPresent()) {
146             Class<?>[] paramTypes = methodToMock.get().getParameterTypes();
147             Object[] params = Arrays.stream(paramTypes)
148                     .map(paramType -> Mockito.any(paramType))
149                     .toArray();
150             try {
151                 Mockito.when(methodToMock.get().invoke(mockProvider, params)).thenReturn(exists);
152             } catch (IllegalAccessException | InvocationTargetException e) {
153                 throw new RuntimeException("Error while mocking the 'exists' method from FileSystemProvider", e);
154             }
155         }
156     }
157 
158     private void assertDefaults() {
159         assertDefaults(Collections.emptyMap());
160     }
161 
162     private void assertDefaults(Pair<String, Runnable>... overrides) {
163         assertDefaults(Arrays.stream(overrides).collect(Collectors.toMap(Pair::getKey, Pair::getValue)));
164     }
165 
166     private void assertDefaults(Map<String, Runnable> overrides) {
167         Map<String, Runnable> asserts = new HashMap<>();
168         asserts.put("adjustMetaInfVersion", () -> assertFalse(testObject.adjustMetaInfVersion()));
169         asserts.put("calculateProjectVersionChecksum", () -> assertFalse(testObject.calculateProjectVersionChecksum()));
170         asserts.put("canIgnore", () -> assertFalse(testObject.canIgnore(mock(MojoExecution.class))));
171         asserts.put("getAlwaysRunPlugins", () -> assertNull(testObject.getAlwaysRunPlugins()));
172         asserts.put("getAttachedOutputs", () -> assertEquals(Collections.emptyList(), testObject.getAttachedOutputs()));
173         asserts.put("getBaselineCacheUrl", () -> assertNull(testObject.getBaselineCacheUrl()));
174         asserts.put("getDefaultGlob", () -> assertEquals("*", testObject.getDefaultGlob()));
175         asserts.put(
176                 "getEffectivePomExcludeProperties",
177                 () -> assertEquals(
178                         Collections.emptyList(), testObject.getEffectivePomExcludeProperties(mock(Plugin.class))));
179         asserts.put("getExcludePatterns", () -> assertEquals(Collections.emptyList(), testObject.getExcludePatterns()));
180         asserts.put(
181                 "getExecutionDirScanConfig",
182                 () -> assertTrue(
183                         testObject.getExecutionDirScanConfig(mock(Plugin.class), mock(PluginExecution.class))
184                                 instanceof DefaultPluginScanConfig));
185         asserts.put(
186                 "getGlobalExcludePaths",
187                 () -> assertEquals(Collections.emptyList(), testObject.getGlobalExcludePaths()));
188         asserts.put(
189                 "getGlobalIncludePaths",
190                 () -> assertEquals(Collections.emptyList(), testObject.getGlobalIncludePaths()));
191         asserts.put("getHashFactory", () -> assertEquals(HashFactory.XX, testObject.getHashFactory()));
192         asserts.put("getId", () -> assertEquals("cache", testObject.getId()));
193         asserts.put("getLocalRepositoryLocation", () -> assertNull(testObject.getLocalRepositoryLocation()));
194         asserts.put(
195                 "getLoggedProperties",
196                 () -> assertEquals(Collections.emptyList(), testObject.getLoggedProperties(mock(MojoExecution.class))));
197         asserts.put("getMaxLocalBuildsCached", () -> assertEquals(3, testObject.getMaxLocalBuildsCached()));
198         asserts.put("getMultiModule", () -> assertNull(testObject.getMultiModule()));
199         asserts.put(
200                 "getNologProperties",
201                 () -> assertEquals(Collections.emptyList(), testObject.getNologProperties(mock(MojoExecution.class))));
202         asserts.put(
203                 "getPluginDirScanConfig",
204                 () -> assertTrue(
205                         testObject.getPluginDirScanConfig(mock(Plugin.class)) instanceof DefaultPluginScanConfig));
206         asserts.put(
207                 "getTrackedProperties",
208                 () -> assertEquals(
209                         Collections.emptyList(), testObject.getTrackedProperties(mock(MojoExecution.class))));
210         asserts.put("getTransport", () -> assertEquals("resolver", testObject.getTransport()));
211         asserts.put("getUrl", () -> assertNull(testObject.getUrl()));
212         asserts.put("isBaselineDiffEnabled", () -> assertFalse(testObject.isBaselineDiffEnabled()));
213         asserts.put("isEnabled", () -> assertTrue(testObject.isEnabled()));
214         asserts.put("isFailFast", () -> assertFalse(testObject.isFailFast()));
215         asserts.put("isForcedExecution", () -> assertFalse(testObject.isForcedExecution(null)));
216         asserts.put("isLazyRestore", () -> assertFalse(testObject.isLazyRestore()));
217         asserts.put("isLogAllProperties", () -> assertFalse(testObject.isLogAllProperties(null)));
218         asserts.put("isProcessPlugins", () -> assertEquals("true", testObject.isProcessPlugins()));
219         asserts.put("isRemoteCacheEnabled", () -> assertFalse(testObject.isRemoteCacheEnabled()));
220         asserts.put("isRestoreGeneratedSources", () -> assertTrue(testObject.isRestoreGeneratedSources()));
221         asserts.put("isSaveToRemote", () -> assertFalse(testObject.isSaveToRemote()));
222         asserts.put("isSaveToRemoteFinal", () -> assertFalse(testObject.isSaveToRemoteFinal()));
223         asserts.put("isSkipCache", () -> assertFalse(testObject.isSkipCache()));
224 
225         asserts.putAll(overrides);
226 
227         asserts.values().forEach(Runnable::run);
228     }
229 
230     @Test
231     void testInitializationInvalidMavenVersion() {
232         when(rtInfo.isMavenVersion(ArgumentMatchers.anyString())).thenReturn(false);
233 
234         assertEquals(CacheState.DISABLED, testObject.initialize());
235     }
236 
237     @Test
238     void testInitializationDisabledUserProperty() {
239         when(mockProperties.getProperty(CacheConfigImpl.CACHE_ENABLED_PROPERTY_NAME))
240                 .thenReturn("false");
241 
242         assertEquals(CacheState.DISABLED, testObject.initialize());
243     }
244 
245     @Test
246     void testInitializationDisabledSystemProperty() {
247         when(mockProperties.getProperty(CacheConfigImpl.CACHE_ENABLED_PROPERTY_NAME))
248                 .thenReturn(null)
249                 .thenReturn("false");
250 
251         assertEquals(CacheState.DISABLED, testObject.initialize());
252     }
253 
254     @Test
255     void testInitializationDisabledInXML() {
256         Configuration configuration = new Configuration();
257         configuration.setEnabled(false);
258         testCacheConfig.setConfiguration(configuration);
259 
260         assertEquals(CacheState.DISABLED, testObject.initialize());
261     }
262 
263     @Test
264     void testInitializationNonExistantXMLFromProperty() {
265         when(mockProperties.getProperty(CacheConfigImpl.CONFIG_PATH_PROPERTY_NAME))
266                 .thenReturn("does-not-exist");
267 
268         assertEquals(CacheState.INITIALIZED, testObject.initialize());
269         assertDefaults();
270     }
271 
272     @Test
273     void testInitializationNonExistantXMLFromRoot() throws IOException {
274         deepMockConfigFile(rootConfigFile, false);
275 
276         assertEquals(CacheState.INITIALIZED, testObject.initialize());
277         assertDefaults();
278     }
279 
280     @Test
281     void testInitializationExplicitlyEnabledUserPropertyOverridesXML() {
282         Configuration configuration = new Configuration();
283         configuration.setEnabled(false);
284         testCacheConfig.setConfiguration(configuration);
285         when(mockProperties.getProperty(CacheConfigImpl.CACHE_ENABLED_PROPERTY_NAME))
286                 .thenReturn("true");
287 
288         assertEquals(CacheState.INITIALIZED, testObject.initialize());
289         assertDefaults();
290     }
291 
292     @Test
293     void testRemoteEnableInXMLButNoURL() {
294         Configuration configuration = new Configuration();
295         Remote remote = new Remote();
296         remote.setEnabled(true);
297         configuration.setRemote(remote);
298         testCacheConfig.setConfiguration(configuration);
299 
300         assertEquals(CacheState.INITIALIZED, testObject.initialize());
301         assertDefaults();
302     }
303 
304     @Test
305     void testRemoteEnableInXMLWithURL() {
306         Configuration configuration = new Configuration();
307         Remote remote = new Remote();
308         remote.setEnabled(true);
309         remote.setUrl("dummy.url.xyz");
310         configuration.setRemote(remote);
311         testCacheConfig.setConfiguration(configuration);
312 
313         assertEquals(CacheState.INITIALIZED, testObject.initialize());
314         assertDefaults(
315                 Pair.of("getUrl", () -> assertEquals("dummy.url.xyz", testObject.getUrl())),
316                 Pair.of("isRemoteCacheEnabled", () -> assertTrue(testObject.isRemoteCacheEnabled())));
317     }
318 
319     @Test
320     void testRemoteEnableByUserPropertyOverrideNoURL() {
321         when(mockProperties.getProperty(CacheConfigImpl.REMOTE_ENABLED_PROPERTY_NAME))
322                 .thenReturn("true");
323 
324         assertEquals(CacheState.INITIALIZED, testObject.initialize());
325         assertDefaults();
326     }
327 
328     @Test
329     void testRemoteEnableByUserPropertyOverrideWithURL() {
330         Configuration configuration = new Configuration();
331         Remote remote = new Remote();
332         remote.setUrl("dummy.url.xyz");
333         configuration.setRemote(remote);
334         testCacheConfig.setConfiguration(configuration);
335         when(mockProperties.getProperty(CacheConfigImpl.REMOTE_ENABLED_PROPERTY_NAME))
336                 .thenReturn("true");
337 
338         assertEquals(CacheState.INITIALIZED, testObject.initialize());
339         assertDefaults(
340                 Pair.of("getUrl", () -> assertEquals("dummy.url.xyz", testObject.getUrl())),
341                 Pair.of("isRemoteCacheEnabled", () -> assertTrue(testObject.isRemoteCacheEnabled())));
342     }
343 
344     @Test
345     void testRemoteDisableByUserPropertyOverride() {
346         Configuration configuration = new Configuration();
347         Remote remote = new Remote();
348         remote.setUrl("dummy.url.xyz");
349         remote.setEnabled(true);
350         configuration.setRemote(remote);
351         testCacheConfig.setConfiguration(configuration);
352         when(mockProperties.getProperty(CacheConfigImpl.REMOTE_ENABLED_PROPERTY_NAME))
353                 .thenReturn("false");
354 
355         assertEquals(CacheState.INITIALIZED, testObject.initialize());
356         assertDefaults(Pair.of("getUrl", () -> assertEquals("dummy.url.xyz", testObject.getUrl())));
357     }
358 
359     @Test
360     void testRemoveSaveEnabledInXML() {
361         Configuration configuration = new Configuration();
362         Remote remote = new Remote();
363         remote.setUrl("dummy.url.xyz");
364         remote.setEnabled(true);
365         remote.setSaveToRemote(true);
366         configuration.setRemote(remote);
367         testCacheConfig.setConfiguration(configuration);
368 
369         assertEquals(CacheState.INITIALIZED, testObject.initialize());
370         assertDefaults(
371                 Pair.of("getUrl", () -> assertEquals("dummy.url.xyz", testObject.getUrl())),
372                 Pair.of("isRemoteCacheEnabled", () -> assertTrue(testObject.isRemoteCacheEnabled())),
373                 Pair.of("isSaveToRemote", () -> assertTrue(testObject.isSaveToRemote())));
374     }
375 
376     @Test
377     void testRemoveSaveEnabledByUserProperty() {
378         Configuration configuration = new Configuration();
379         Remote remote = new Remote();
380         remote.setUrl("dummy.url.xyz");
381         remote.setEnabled(true);
382         configuration.setRemote(remote);
383         testCacheConfig.setConfiguration(configuration);
384         when(mockProperties.getProperty(CacheConfigImpl.SAVE_TO_REMOTE_PROPERTY_NAME))
385                 .thenReturn("true");
386 
387         assertEquals(CacheState.INITIALIZED, testObject.initialize());
388         assertDefaults(
389                 Pair.of("getUrl", () -> assertEquals("dummy.url.xyz", testObject.getUrl())),
390                 Pair.of("isRemoteCacheEnabled", () -> assertTrue(testObject.isRemoteCacheEnabled())),
391                 Pair.of("isSaveToRemote", () -> assertTrue(testObject.isSaveToRemote())));
392     }
393 
394     @Test
395     void testRemoveSaveDisabledByUserProperty() {
396         Configuration configuration = new Configuration();
397         Remote remote = new Remote();
398         remote.setUrl("dummy.url.xyz");
399         remote.setEnabled(true);
400         remote.setSaveToRemote(true);
401         configuration.setRemote(remote);
402         testCacheConfig.setConfiguration(configuration);
403         when(mockProperties.getProperty(CacheConfigImpl.SAVE_TO_REMOTE_PROPERTY_NAME))
404                 .thenReturn("false");
405 
406         assertEquals(CacheState.INITIALIZED, testObject.initialize());
407         assertDefaults(
408                 Pair.of("getUrl", () -> assertEquals("dummy.url.xyz", testObject.getUrl())),
409                 Pair.of("isRemoteCacheEnabled", () -> assertTrue(testObject.isRemoteCacheEnabled())));
410     }
411 
412     @Test
413     void testRemoteSaveIgnoredWhenRemoteDisabledInXML() {
414         Configuration configuration = new Configuration();
415         Remote remote = new Remote();
416         remote.setSaveToRemote(true);
417         configuration.setRemote(remote);
418         testCacheConfig.setConfiguration(configuration);
419 
420         assertEquals(CacheState.INITIALIZED, testObject.initialize());
421         assertDefaults();
422     }
423 
424     @Test
425     void testRemoteSaveIgnoredWhenRemoteDisabledUserProperty() {
426         when(mockProperties.getProperty(CacheConfigImpl.SAVE_TO_REMOTE_PROPERTY_NAME))
427                 .thenReturn("true");
428 
429         assertEquals(CacheState.INITIALIZED, testObject.initialize());
430         assertDefaults();
431     }
432 
433     @Test
434     void testRemoteSaveIgnoredWhenRemoteDisabledByUserPropertyOverride() {
435         Configuration configuration = new Configuration();
436         Remote remote = new Remote();
437         remote.setUrl("dummy.url.xyz");
438         remote.setEnabled(true);
439         remote.setSaveToRemote(true);
440         configuration.setRemote(remote);
441         testCacheConfig.setConfiguration(configuration);
442         when(mockProperties.getProperty(CacheConfigImpl.REMOTE_ENABLED_PROPERTY_NAME))
443                 .thenReturn("false");
444 
445         assertEquals(CacheState.INITIALIZED, testObject.initialize());
446         assertDefaults(Pair.of("getUrl", () -> assertEquals("dummy.url.xyz", testObject.getUrl())));
447     }
448 
449     @Test
450     void testRemoveSaveFinalEnabledByUserProperty() {
451         Configuration configuration = new Configuration();
452         Remote remote = new Remote();
453         remote.setUrl("dummy.url.xyz");
454         remote.setEnabled(true);
455         remote.setSaveToRemote(true);
456         configuration.setRemote(remote);
457         testCacheConfig.setConfiguration(configuration);
458         when(mockProperties.getProperty(CacheConfigImpl.SAVE_NON_OVERRIDEABLE_NAME))
459                 .thenReturn("true");
460 
461         assertEquals(CacheState.INITIALIZED, testObject.initialize());
462         assertDefaults(
463                 Pair.of("getUrl", () -> assertEquals("dummy.url.xyz", testObject.getUrl())),
464                 Pair.of("isRemoteCacheEnabled", () -> assertTrue(testObject.isRemoteCacheEnabled())),
465                 Pair.of("isSaveToRemote", () -> assertTrue(testObject.isSaveToRemote())),
466                 Pair.of("isSaveToRemoteFinal", () -> assertTrue(testObject.isSaveToRemoteFinal())));
467     }
468 
469     @Test
470     void testRemoveSaveFinalIgnoredWhenRemoteSaveDisabled() {
471         Configuration configuration = new Configuration();
472         Remote remote = new Remote();
473         remote.setUrl("dummy.url.xyz");
474         remote.setEnabled(true);
475         configuration.setRemote(remote);
476         testCacheConfig.setConfiguration(configuration);
477         when(mockProperties.getProperty(CacheConfigImpl.SAVE_NON_OVERRIDEABLE_NAME))
478                 .thenReturn("true");
479 
480         assertEquals(CacheState.INITIALIZED, testObject.initialize());
481         assertDefaults(
482                 Pair.of("getUrl", () -> assertEquals("dummy.url.xyz", testObject.getUrl())),
483                 Pair.of("isRemoteCacheEnabled", () -> assertTrue(testObject.isRemoteCacheEnabled())));
484     }
485 }