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;
20  
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.List;
24  
25  import org.apache.maven.buildcache.xml.Build;
26  import org.apache.maven.execution.ExecutionEvent;
27  import org.apache.maven.execution.MavenSession;
28  import org.apache.maven.lifecycle.DefaultLifecycles;
29  import org.apache.maven.lifecycle.Lifecycle;
30  import org.apache.maven.lifecycle.internal.stub.LifecyclesTestUtils;
31  import org.apache.maven.plugin.MojoExecution;
32  import org.apache.maven.project.MavenProject;
33  import org.jetbrains.annotations.NotNull;
34  import org.junit.jupiter.api.BeforeEach;
35  import org.junit.jupiter.api.Test;
36  import org.junit.jupiter.params.ParameterizedTest;
37  import org.junit.jupiter.params.provider.ValueSource;
38  
39  import static java.util.Collections.emptyList;
40  import static java.util.Collections.singletonList;
41  import static org.assertj.core.api.Assertions.assertThat;
42  import static org.junit.jupiter.api.Assertions.assertEquals;
43  import static org.junit.jupiter.api.Assertions.assertFalse;
44  import static org.junit.jupiter.api.Assertions.assertThrows;
45  import static org.junit.jupiter.api.Assertions.assertTrue;
46  import static org.junit.jupiter.api.Assumptions.assumeTrue;
47  import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
48  import static org.mockito.Mockito.mock;
49  import static org.mockito.Mockito.when;
50  
51  class LifecyclePhasesHelperTest {
52  
53      private LifecyclePhasesHelper lifecyclePhasesHelper;
54      private MavenProject projectMock;
55      private DefaultLifecycles defaultLifecycles;
56      private Lifecycle cleanLifecycle;
57  
58      @BeforeEach
59      void setUp() {
60  
61          defaultLifecycles = LifecyclesTestUtils.createDefaultLifecycles();
62          cleanLifecycle = defaultLifecycles.getLifeCycles().stream()
63                  .filter(lifecycle -> lifecycle.getId().equals("clean"))
64                  .findFirst()
65                  .orElseThrow(() -> new RuntimeException("Clean phase not found"));
66  
67          MavenSession session = mock(MavenSession.class, RETURNS_DEEP_STUBS);
68          lifecyclePhasesHelper = new LifecyclePhasesHelper(session, defaultLifecycles, cleanLifecycle);
69  
70          projectMock = mock(MavenProject.class);
71      }
72  
73      /**
74       * Checks that the last MojoExecution lifecycle phase is considered the highest
75       */
76      @Test
77      void resolveHighestLifecyclePhaseNormal() {
78          String phase = lifecyclePhasesHelper.resolveHighestLifecyclePhase(
79                  projectMock,
80                  Arrays.asList(
81                          mockedMojoExecution("clean"), mockedMojoExecution("compile"), mockedMojoExecution("install")));
82          assertEquals("install", phase);
83      }
84  
85      /**
86       * Checks that for forked execution lifecycle phase is inherited from originating MojoExecution
87       */
88      @Test
89      void resolveHighestLifecyclePhaseForked() {
90  
91          MojoExecution origin = mockedMojoExecution("install");
92          publishForkedProjectEvent(origin);
93  
94          String phase = lifecyclePhasesHelper.resolveHighestLifecyclePhase(
95                  projectMock, Arrays.asList(mockedMojoExecution(null)));
96  
97          assertEquals("install", phase);
98      }
99  
100     /**
101      * Checks proper identification of phases not belonging to clean lifecycle
102      */
103     @Test
104     void isLaterPhaseThanClean() {
105         List<Lifecycle> afterClean = new ArrayList<>(defaultLifecycles.getLifeCycles());
106 
107         assumeTrue(afterClean.remove(cleanLifecycle));
108         assumeTrue(afterClean.size() > 0);
109 
110         assertTrue(afterClean.stream()
111                 .flatMap(it -> it.getPhases().stream())
112                 .allMatch(lifecyclePhasesHelper::isLaterPhaseThanClean));
113     }
114 
115     /**
116      * Checks proper identification of phases belonging to clean lifecycle
117      */
118     @Test
119     void isLaterPhaseThanCleanNegative() {
120         assertTrue(cleanLifecycle.getPhases().stream().noneMatch(lifecyclePhasesHelper::isLaterPhaseThanClean));
121     }
122 
123     @Test
124     void isLaterPhaseThanBuild() {
125         List<Lifecycle> afterClean = new ArrayList<>(defaultLifecycles.getLifeCycles());
126 
127         assumeTrue(afterClean.remove(cleanLifecycle));
128         assumeTrue(afterClean.size() > 0);
129 
130         Build buildMock = mock(Build.class);
131         when(buildMock.getHighestCompletedGoal()).thenReturn("clean");
132 
133         assertTrue(afterClean.stream()
134                 .flatMap(it -> it.getPhases().stream())
135                 .allMatch(phase -> lifecyclePhasesHelper.isLaterPhaseThanBuild(phase, buildMock)));
136     }
137 
138     @Test
139     void isLaterPhaseThanBuildNegative() {
140         List<Lifecycle> afterClean = new ArrayList<>(defaultLifecycles.getLifeCycles());
141 
142         assumeTrue(afterClean.remove(cleanLifecycle));
143         assumeTrue(afterClean.size() > 0);
144 
145         Build buildMock = mock(Build.class);
146         when(buildMock.getHighestCompletedGoal()).thenReturn("install");
147 
148         assertFalse(afterClean.stream()
149                 .flatMap(it -> it.getPhases().stream())
150                 .allMatch(phase -> lifecyclePhasesHelper.isLaterPhaseThanBuild(phase, buildMock)));
151     }
152 
153     @Test
154     void isLaterPhase() {
155         assertTrue(lifecyclePhasesHelper.isLaterPhase("install", "compile"));
156         assertTrue(lifecyclePhasesHelper.isLaterPhase("package", "clean"));
157         assertTrue(lifecyclePhasesHelper.isLaterPhase("site", "install"));
158         assertFalse(lifecyclePhasesHelper.isLaterPhase("test", "site"));
159         assertFalse(lifecyclePhasesHelper.isLaterPhase("clean", "site"));
160 
161         assertThrows(IllegalArgumentException.class, () -> lifecyclePhasesHelper.isLaterPhase("install", null));
162 
163         assertThrows(
164                 IllegalArgumentException.class, () -> lifecyclePhasesHelper.isLaterPhase("install", "unknown phase"));
165 
166         assertThrows(
167                 IllegalArgumentException.class, () -> lifecyclePhasesHelper.isLaterPhase("unknown phase", "install"));
168     }
169 
170     @Test
171     void getCleanSegment() {
172         MojoExecution clean = mockedMojoExecution("clean");
173         List<MojoExecution> cleanSegment = lifecyclePhasesHelper.getCleanSegment(
174                 projectMock, Arrays.asList(clean, mockedMojoExecution("compile"), mockedMojoExecution("install")));
175         assertEquals(singletonList(clean), cleanSegment);
176     }
177 
178     /**
179      * checks empty result if no mojos bound to clean lifecycle
180      */
181     @Test
182     void getEmptyCleanSegment() {
183         List<MojoExecution> cleanSegment = lifecyclePhasesHelper.getCleanSegment(
184                 projectMock, Arrays.asList(mockedMojoExecution("compile"), mockedMojoExecution("install")));
185         assertEquals(emptyList(), cleanSegment);
186     }
187 
188     /**
189      * checks empty result if no mojos bound to clean lifecycle in simulated forked execution
190      */
191     @Test
192     void getEmptyCleanSegmentIfForked() {
193 
194         MojoExecution origin = mockedMojoExecution("install");
195         publishForkedProjectEvent(origin);
196 
197         List<MojoExecution> cleanSegment = lifecyclePhasesHelper.getCleanSegment(
198                 projectMock,
199                 Arrays.asList(
200                         // null lifecycle phase is possible in forked executions
201                         mockedMojoExecution(null), mockedMojoExecution(null)));
202 
203         assertEquals(emptyList(), cleanSegment);
204     }
205 
206     /**
207      * if forked execution lifecycle phase is overridden to originating MojoExecution
208      */
209     @Test
210     void getCleanSegmentForkedAnyLifecyclePhase() {
211         MojoExecution origin = mockedMojoExecution("install");
212         publishForkedProjectEvent(origin);
213 
214         List<MojoExecution> cleanSegment = lifecyclePhasesHelper.getCleanSegment(
215                 projectMock,
216                 Arrays.asList(
217                         // clean is overridden to "install" phase assuming forked execution
218                         mockedMojoExecution("clean")));
219 
220         assertEquals(emptyList(), cleanSegment);
221     }
222 
223     @Test
224     void testCachedSegment() {
225         MojoExecution compile = mockedMojoExecution("compile");
226         MojoExecution test = mockedMojoExecution("test");
227         List<MojoExecution> mojoExecutions = Arrays.asList(compile, test, mockedMojoExecution("install"));
228 
229         Build build = mock(Build.class);
230         when(build.getHighestCompletedGoal()).thenReturn("test");
231 
232         List<MojoExecution> cachedSegment = lifecyclePhasesHelper.getCachedSegment(projectMock, mojoExecutions, build);
233 
234         assertThat(cachedSegment).containsExactly(compile, test);
235     }
236 
237     @Test
238     void testEmptyCachedSegment() {
239         MojoExecution compile = mockedMojoExecution("compile");
240         MojoExecution test = mockedMojoExecution("test");
241         MojoExecution install = mockedMojoExecution("install");
242         List<MojoExecution> mojoExecutions = Arrays.asList(compile, test, install);
243 
244         Build build = mock(Build.class);
245         when(build.getHighestCompletedGoal()).thenReturn("clean");
246 
247         List<MojoExecution> cachedSegment = lifecyclePhasesHelper.getCachedSegment(projectMock, mojoExecutions, build);
248 
249         assertThat(cachedSegment).isEmpty();
250     }
251 
252     @Test
253     void testCachedSegmentForked() {
254         MojoExecution me1 = mockedMojoExecution(null);
255         MojoExecution me2 = mockedMojoExecution(null);
256 
257         List<MojoExecution> mojoExecutions = Arrays.asList(me1, me2);
258 
259         MojoExecution origin = mockedMojoExecution("install");
260         publishForkedProjectEvent(origin);
261 
262         Build build = mock(Build.class);
263         when(build.getHighestCompletedGoal()).thenReturn("site");
264 
265         List<MojoExecution> cachedSegment = lifecyclePhasesHelper.getCachedSegment(projectMock, mojoExecutions, build);
266 
267         assertEquals(mojoExecutions, cachedSegment);
268     }
269 
270     @ParameterizedTest
271     @ValueSource(strings = {"install", "site"})
272     void testAllInCachedSegment() {
273         MojoExecution compile = mockedMojoExecution("compile");
274         MojoExecution test = mockedMojoExecution("test");
275         MojoExecution install = mockedMojoExecution("install");
276         List<MojoExecution> mojoExecutions = Arrays.asList(compile, test, install);
277 
278         Build build = mock(Build.class);
279         when(build.getHighestCompletedGoal()).thenReturn("site");
280 
281         List<MojoExecution> cachedSegment = lifecyclePhasesHelper.getCachedSegment(projectMock, mojoExecutions, build);
282 
283         assertEquals(mojoExecutions, cachedSegment);
284     }
285 
286     @Test
287     void testPostCachedSegment() {
288         MojoExecution compile = mockedMojoExecution("compile");
289         MojoExecution test = mockedMojoExecution("test");
290         MojoExecution install = mockedMojoExecution("install");
291         List<MojoExecution> mojoExecutions = Arrays.asList(compile, test, install);
292 
293         Build build = mock(Build.class);
294         when(build.getHighestCompletedGoal()).thenReturn("compile");
295 
296         List<MojoExecution> notCachedSegment =
297                 lifecyclePhasesHelper.getPostCachedSegment(projectMock, mojoExecutions, build);
298 
299         assertThat(notCachedSegment).containsExactly(test, install);
300     }
301 
302     @Test
303     void testAllPostCachedSegment() {
304         MojoExecution compile = mockedMojoExecution("compile");
305         MojoExecution test = mockedMojoExecution("test");
306         MojoExecution install = mockedMojoExecution("install");
307         List<MojoExecution> mojoExecutions = Arrays.asList(compile, test, install);
308 
309         Build build = mock(Build.class);
310         when(build.getHighestCompletedGoal()).thenReturn("clean");
311 
312         List<MojoExecution> notCachedSegment =
313                 lifecyclePhasesHelper.getPostCachedSegment(projectMock, mojoExecutions, build);
314 
315         assertThat(notCachedSegment).isEqualTo(mojoExecutions);
316     }
317 
318     @Test
319     void testPostCachedSegmentForked() {
320         MojoExecution me1 = mockedMojoExecution(null);
321         MojoExecution me2 = mockedMojoExecution(null);
322 
323         List<MojoExecution> mojoExecutions = Arrays.asList(me1, me2);
324 
325         MojoExecution origin = mockedMojoExecution("install");
326         publishForkedProjectEvent(origin);
327 
328         Build build = mock(Build.class);
329         when(build.getHighestCompletedGoal()).thenReturn("package");
330 
331         List<MojoExecution> cachedSegment =
332                 lifecyclePhasesHelper.getPostCachedSegment(projectMock, mojoExecutions, build);
333 
334         assertThat(cachedSegment).isEqualTo(mojoExecutions);
335     }
336 
337     @ParameterizedTest
338     @ValueSource(strings = {"install", "site"})
339     void testEmptyPostCachedSegmentInclusive() {
340         MojoExecution compile = mockedMojoExecution("compile");
341         MojoExecution test = mockedMojoExecution("test");
342         MojoExecution install = mockedMojoExecution("install");
343         List<MojoExecution> mojoExecutions = Arrays.asList(compile, test, install);
344 
345         Build cachedBuild = mock(Build.class);
346         when(cachedBuild.getHighestCompletedGoal()).thenReturn("install");
347 
348         List<MojoExecution> notCachedSegment =
349                 lifecyclePhasesHelper.getPostCachedSegment(projectMock, mojoExecutions, cachedBuild);
350 
351         assertThat(notCachedSegment).isEmpty();
352     }
353 
354     private void publishForkedProjectEvent(MojoExecution origin) {
355 
356         ExecutionEvent eventMock = mock(ExecutionEvent.class);
357 
358         when(eventMock.getProject()).thenReturn(projectMock);
359         when(eventMock.getMojoExecution()).thenReturn(origin);
360         when(eventMock.getType()).thenReturn(ExecutionEvent.Type.ForkedProjectStarted);
361 
362         lifecyclePhasesHelper.forkedProjectStarted(eventMock);
363     }
364 
365     @NotNull
366     private static MojoExecution mockedMojoExecution(String phase) {
367         MojoExecution mojoExecution = mock(MojoExecution.class);
368         when(mojoExecution.getLifecyclePhase()).thenReturn(phase);
369         when(mojoExecution.toString()).thenReturn(phase);
370         return mojoExecution;
371     }
372 }