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.plugins.jarsigner;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.time.Duration;
24  import java.util.LinkedHashMap;
25  import java.util.Map;
26  import java.util.concurrent.ExecutorService;
27  import java.util.concurrent.Executors;
28  import java.util.concurrent.Future;
29  import java.util.concurrent.Semaphore;
30  import java.util.concurrent.ThreadFactory;
31  import java.util.concurrent.TimeUnit;
32  
33  import org.apache.maven.artifact.Artifact;
34  import org.apache.maven.plugin.MojoExecutionException;
35  import org.apache.maven.plugin.logging.Log;
36  import org.apache.maven.project.MavenProject;
37  import org.apache.maven.shared.jarsigner.JarSigner;
38  import org.apache.maven.shared.jarsigner.JarSignerSignRequest;
39  import org.junit.After;
40  import org.junit.Before;
41  import org.junit.Rule;
42  import org.junit.Test;
43  import org.junit.rules.TemporaryFolder;
44  
45  import static org.apache.maven.plugins.jarsigner.TestJavaToolResults.RESULT_ERROR;
46  import static org.apache.maven.plugins.jarsigner.TestJavaToolResults.RESULT_OK;
47  import static org.hamcrest.CoreMatchers.containsString;
48  import static org.hamcrest.MatcherAssert.assertThat;
49  import static org.junit.Assert.*;
50  import static org.mockito.ArgumentMatchers.any;
51  import static org.mockito.ArgumentMatchers.contains;
52  import static org.mockito.ArgumentMatchers.isA;
53  import static org.mockito.Mockito.*;
54  
55  public class JarsignerSignMojoParallelTest {
56      @Rule
57      public TemporaryFolder folder = new TemporaryFolder();
58  
59      private MavenProject project = mock(MavenProject.class);
60      private JarSigner jarSigner = mock(JarSigner.class);
61      private File projectDir;
62      private Map<String, String> configuration = new LinkedHashMap<>();
63      private MojoTestCreator<JarsignerSignMojo> mojoTestCreator;
64      private ExecutorService executor;
65      private Log log;
66  
67      @Before
68      public void setUp() throws Exception {
69          projectDir = folder.newFolder("dummy-project");
70          configuration.put("processMainArtifact", "false");
71          mojoTestCreator =
72                  new MojoTestCreator<JarsignerSignMojo>(JarsignerSignMojo.class, project, projectDir, jarSigner);
73          log = mock(Log.class);
74          mojoTestCreator.setLog(log);
75          executor =
76                  Executors.newSingleThreadExecutor(namedThreadFactory(getClass().getSimpleName()));
77      }
78  
79      @After
80      public void tearDown() {
81          executor.shutdown();
82      }
83  
84      @Test(timeout = 30000)
85      public void test10Files2Parallel() throws Exception {
86          configuration.put("archiveDirectory", createArchives(10).getPath());
87          configuration.put("threadCount", "2");
88  
89          // Make one jar file wait until some external event happens and let nine pass
90          Semaphore semaphore = new Semaphore(9);
91          when(jarSigner.execute(isA(JarSignerSignRequest.class))).then(invocation -> {
92              semaphore.acquire();
93              return RESULT_OK;
94          });
95          JarsignerSignMojo mojo = mojoTestCreator.configure(configuration);
96  
97          Future<Void> future = executor.submit(() -> {
98              mojo.execute();
99              return null;
100         });
101 
102         // Wait until 10 invocation of execute() has happened (nine files are done and one are hanging)
103         verify(jarSigner, timeout(Duration.ofSeconds(10).toMillis()).times(10)).execute(any());
104         // Even though 10 invocations of execute() have happened, mojo is not yet done executing (it is waiting for one)
105         assertFalse(future.isDone());
106 
107         semaphore.release(); // Release the one waiting jar file
108         future.get(10, TimeUnit.SECONDS); // Wait for entire Mojo to finish
109         assertTrue(future.isDone());
110     }
111 
112     @Test(timeout = 30000)
113     public void test10Files2Parallel3Hanging() throws Exception {
114         configuration.put("archiveDirectory", createArchives(10).getPath());
115         configuration.put("threadCount", "2");
116 
117         // Make three jar files wait until some external event happens and let seven pass
118         Semaphore semaphore = new Semaphore(7);
119         when(jarSigner.execute(isA(JarSignerSignRequest.class))).then(invocation -> {
120             semaphore.acquire();
121             return RESULT_OK;
122         });
123         JarsignerSignMojo mojo = mojoTestCreator.configure(configuration);
124 
125         Future<Void> future = executor.submit(() -> {
126             mojo.execute();
127             return null;
128         });
129 
130         // Wait until 9 invocations to execute has happened (2 is ongoing and 1 has not yet happened)
131         verify(jarSigner, timeout(Duration.ofSeconds(10).toMillis()).times(9)).execute(any());
132         assertFalse(future.isDone());
133 
134         semaphore.release(); // Release one waiting jar file
135 
136         // Wait until 10 invocation to execute has happened (8 are done and 2 are hanging)
137         verify(jarSigner, timeout(Duration.ofSeconds(10).toMillis()).times(10)).execute(any());
138 
139         semaphore.release(2); // Release last two jar files
140         future.get(10, TimeUnit.SECONDS); // Wait for entire Mojo to finish
141         assertTrue(future.isDone());
142     }
143 
144     @Test(timeout = 30000)
145     public void test10Files1Parallel() throws Exception {
146         configuration.put("archiveDirectory", createArchives(10).getPath());
147         configuration.put("threadCount", "1");
148 
149         // Make one jar file wait until some external event happens and let nine pass
150         Semaphore semaphore = new Semaphore(9);
151         when(jarSigner.execute(isA(JarSignerSignRequest.class))).then(invocation -> {
152             semaphore.acquire();
153             return RESULT_OK;
154         });
155         JarsignerSignMojo mojo = mojoTestCreator.configure(configuration);
156 
157         Future<Void> future = executor.submit(() -> {
158             mojo.execute();
159             return null;
160         });
161 
162         // Wait until 10 invocation to execute has happened (nine has finished and one is hanging).
163         verify(jarSigner, timeout(Duration.ofSeconds(10).toMillis()).times(10)).execute(any());
164         assertFalse(future.isDone());
165 
166         semaphore.release(); // Release the one waiting jar file
167         future.get(10, TimeUnit.SECONDS); // Wait for entire Mojo to finish
168         assertTrue(future.isDone());
169     }
170 
171     @Test(timeout = 30000)
172     public void test10Files2ParallelOneFail() throws Exception {
173         configuration.put("archiveDirectory", createArchives(10).getPath());
174         configuration.put("threadCount", "2");
175 
176         when(jarSigner.execute(isA(JarSignerSignRequest.class)))
177                 .thenReturn(RESULT_OK)
178                 .thenReturn(RESULT_OK)
179                 .thenReturn(RESULT_ERROR)
180                 .thenReturn(RESULT_OK);
181         JarsignerSignMojo mojo = mojoTestCreator.configure(configuration);
182 
183         MojoExecutionException mojoException = assertThrows(MojoExecutionException.class, () -> {
184             mojo.execute();
185         });
186 
187         assertThat(mojoException.getMessage(), containsString(String.valueOf("Failed executing 'jarsigner ")));
188     }
189 
190     @Test
191     public void testInvalidThreadCount() throws Exception {
192         Artifact mainArtifact = TestArtifacts.createJarArtifact(projectDir, "my-project.jar");
193         when(project.getArtifact()).thenReturn(mainArtifact);
194         when(jarSigner.execute(any(JarSignerSignRequest.class))).thenReturn(RESULT_OK);
195         configuration.put("processMainArtifact", "true");
196         configuration.put("threadCount", "0"); // Setting an "invalid" value
197         JarsignerSignMojo mojo = mojoTestCreator.configure(configuration);
198 
199         mojo.execute();
200 
201         verify(jarSigner, times(1)).execute(any());
202         verify(log).warn(contains("Invalid threadCount value"));
203         verify(log).warn(contains("Was '0'"));
204     }
205 
206     private File createArchives(int numberOfArchives) throws IOException {
207         File archiveDirectory = new File(projectDir, "my_archive_dir");
208         archiveDirectory.mkdir();
209         for (int i = 0; i < numberOfArchives; i++) {
210             TestArtifacts.createDummyZipFile(new File(archiveDirectory, "archive" + i + ".jar"));
211         }
212         return archiveDirectory;
213     }
214 
215     private static ThreadFactory namedThreadFactory(String threadNamePrefix) {
216         return r -> new Thread(r, threadNamePrefix + "-Thread");
217     }
218 }