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