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, () -> {
162             lifecyclePhasesHelper.isLaterPhase("install", null);
163         });
164 
165         assertThrows(IllegalArgumentException.class, () -> {
166             lifecyclePhasesHelper.isLaterPhase("install", "unknown phase");
167         });
168 
169         assertThrows(IllegalArgumentException.class, () -> {
170             lifecyclePhasesHelper.isLaterPhase("unknown phase", "install");
171         });
172     }
173 
174     @Test
175     void getCleanSegment() {
176         MojoExecution clean = mockedMojoExecution("clean");
177         List<MojoExecution> cleanSegment = lifecyclePhasesHelper.getCleanSegment(
178                 projectMock, Arrays.asList(clean, mockedMojoExecution("compile"), mockedMojoExecution("install")));
179         assertEquals(singletonList(clean), cleanSegment);
180     }
181 
182     /**
183      * checks empty result if no mojos bound to clean lifecycle
184      */
185     @Test
186     void getEmptyCleanSegment() {
187         List<MojoExecution> cleanSegment = lifecyclePhasesHelper.getCleanSegment(
188                 projectMock, Arrays.asList(mockedMojoExecution("compile"), mockedMojoExecution("install")));
189         assertEquals(emptyList(), cleanSegment);
190     }
191 
192     /**
193      * checks empty result if no mojos bound to clean lifecycle in simulated forked execution
194      */
195     @Test
196     void getEmptyCleanSegmentIfForked() {
197 
198         MojoExecution origin = mockedMojoExecution("install");
199         publishForkedProjectEvent(origin);
200 
201         List<MojoExecution> cleanSegment = lifecyclePhasesHelper.getCleanSegment(
202                 projectMock,
203                 Arrays.asList(
204                         // null lifecycle phase is possible in forked executions
205                         mockedMojoExecution(null), mockedMojoExecution(null)));
206 
207         assertEquals(emptyList(), cleanSegment);
208     }
209 
210     /**
211      * if forked execution lifecycle phase is overridden to originating MojoExecution
212      */
213     @Test
214     void getCleanSegmentForkedAnyLifecyclePhase() {
215         MojoExecution origin = mockedMojoExecution("install");
216         publishForkedProjectEvent(origin);
217 
218         List<MojoExecution> cleanSegment = lifecyclePhasesHelper.getCleanSegment(
219                 projectMock,
220                 Arrays.asList(
221                         // clean is overridden to "install" phase assuming forked execution
222                         mockedMojoExecution("clean")));
223 
224         assertEquals(emptyList(), cleanSegment);
225     }
226 
227     @Test
228     void testCachedSegment() {
229         MojoExecution compile = mockedMojoExecution("compile");
230         MojoExecution test = mockedMojoExecution("test");
231         List<MojoExecution> mojoExecutions = Arrays.asList(compile, test, mockedMojoExecution("install"));
232 
233         Build build = mock(Build.class);
234         when(build.getHighestCompletedGoal()).thenReturn("test");
235 
236         List<MojoExecution> cachedSegment = lifecyclePhasesHelper.getCachedSegment(projectMock, mojoExecutions, build);
237 
238         assertThat(cachedSegment).containsExactly(compile, test);
239     }
240 
241     @Test
242     void testEmptyCachedSegment() {
243         MojoExecution compile = mockedMojoExecution("compile");
244         MojoExecution test = mockedMojoExecution("test");
245         MojoExecution install = mockedMojoExecution("install");
246         List<MojoExecution> mojoExecutions = Arrays.asList(compile, test, install);
247 
248         Build build = mock(Build.class);
249         when(build.getHighestCompletedGoal()).thenReturn("clean");
250 
251         List<MojoExecution> cachedSegment = lifecyclePhasesHelper.getCachedSegment(projectMock, mojoExecutions, build);
252 
253         assertThat(cachedSegment).isEmpty();
254     }
255 
256     @Test
257     void testCachedSegmentForked() {
258         MojoExecution me1 = mockedMojoExecution(null);
259         MojoExecution me2 = mockedMojoExecution(null);
260 
261         List<MojoExecution> mojoExecutions = Arrays.asList(me1, me2);
262 
263         MojoExecution origin = mockedMojoExecution("install");
264         publishForkedProjectEvent(origin);
265 
266         Build build = mock(Build.class);
267         when(build.getHighestCompletedGoal()).thenReturn("site");
268 
269         List<MojoExecution> cachedSegment = lifecyclePhasesHelper.getCachedSegment(projectMock, mojoExecutions, build);
270 
271         assertEquals(mojoExecutions, cachedSegment);
272     }
273 
274     @ParameterizedTest
275     @ValueSource(strings = {"install", "site"})
276     void testAllInCachedSegment() {
277         MojoExecution compile = mockedMojoExecution("compile");
278         MojoExecution test = mockedMojoExecution("test");
279         MojoExecution install = mockedMojoExecution("install");
280         List<MojoExecution> mojoExecutions = Arrays.asList(compile, test, install);
281 
282         Build build = mock(Build.class);
283         when(build.getHighestCompletedGoal()).thenReturn("site");
284 
285         List<MojoExecution> cachedSegment = lifecyclePhasesHelper.getCachedSegment(projectMock, mojoExecutions, build);
286 
287         assertEquals(mojoExecutions, cachedSegment);
288     }
289 
290     @Test
291     void testPostCachedSegment() {
292         MojoExecution compile = mockedMojoExecution("compile");
293         MojoExecution test = mockedMojoExecution("test");
294         MojoExecution install = mockedMojoExecution("install");
295         List<MojoExecution> mojoExecutions = Arrays.asList(compile, test, install);
296 
297         Build build = mock(Build.class);
298         when(build.getHighestCompletedGoal()).thenReturn("compile");
299 
300         List<MojoExecution> notCachedSegment =
301                 lifecyclePhasesHelper.getPostCachedSegment(projectMock, mojoExecutions, build);
302 
303         assertThat(notCachedSegment).containsExactly(test, install);
304     }
305 
306     @Test
307     void testAllPostCachedSegment() {
308         MojoExecution compile = mockedMojoExecution("compile");
309         MojoExecution test = mockedMojoExecution("test");
310         MojoExecution install = mockedMojoExecution("install");
311         List<MojoExecution> mojoExecutions = Arrays.asList(compile, test, install);
312 
313         Build build = mock(Build.class);
314         when(build.getHighestCompletedGoal()).thenReturn("clean");
315 
316         List<MojoExecution> notCachedSegment =
317                 lifecyclePhasesHelper.getPostCachedSegment(projectMock, mojoExecutions, build);
318 
319         assertThat(notCachedSegment).isEqualTo(mojoExecutions);
320     }
321 
322     @Test
323     void testPostCachedSegmentForked() {
324         MojoExecution me1 = mockedMojoExecution(null);
325         MojoExecution me2 = mockedMojoExecution(null);
326 
327         List<MojoExecution> mojoExecutions = Arrays.asList(me1, me2);
328 
329         MojoExecution origin = mockedMojoExecution("install");
330         publishForkedProjectEvent(origin);
331 
332         Build build = mock(Build.class);
333         when(build.getHighestCompletedGoal()).thenReturn("package");
334 
335         List<MojoExecution> cachedSegment =
336                 lifecyclePhasesHelper.getPostCachedSegment(projectMock, mojoExecutions, build);
337 
338         assertThat(cachedSegment).isEqualTo(mojoExecutions);
339     }
340 
341     @ParameterizedTest
342     @ValueSource(strings = {"install", "site"})
343     void testEmptyPostCachedSegmentInclusive() {
344         MojoExecution compile = mockedMojoExecution("compile");
345         MojoExecution test = mockedMojoExecution("test");
346         MojoExecution install = mockedMojoExecution("install");
347         List<MojoExecution> mojoExecutions = Arrays.asList(compile, test, install);
348 
349         Build cachedBuild = mock(Build.class);
350         when(cachedBuild.getHighestCompletedGoal()).thenReturn("install");
351 
352         List<MojoExecution> notCachedSegment =
353                 lifecyclePhasesHelper.getPostCachedSegment(projectMock, mojoExecutions, cachedBuild);
354 
355         assertThat(notCachedSegment).isEmpty();
356     }
357 
358     private void publishForkedProjectEvent(MojoExecution origin) {
359 
360         ExecutionEvent eventMock = mock(ExecutionEvent.class);
361 
362         when(eventMock.getProject()).thenReturn(projectMock);
363         when(eventMock.getMojoExecution()).thenReturn(origin);
364         when(eventMock.getType()).thenReturn(ExecutionEvent.Type.ForkedProjectStarted);
365 
366         lifecyclePhasesHelper.forkedProjectStarted(eventMock);
367     }
368 
369     @NotNull
370     private static MojoExecution mockedMojoExecution(String phase) {
371         MojoExecution mojoExecution = mock(MojoExecution.class);
372         when(mojoExecution.getLifecyclePhase()).thenReturn(phase);
373         when(mojoExecution.toString()).thenReturn(phase);
374         return mojoExecution;
375     }
376 }