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