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.eclipse.aether.internal.impl.synccontext;
20  
21  import java.io.IOException;
22  import java.nio.file.Files;
23  import java.nio.file.Paths;
24  import java.util.Arrays;
25  import java.util.HashMap;
26  import java.util.concurrent.CountDownLatch;
27  import java.util.concurrent.TimeUnit;
28  
29  import org.eclipse.aether.RepositorySystemSession;
30  import org.eclipse.aether.SyncContext;
31  import org.eclipse.aether.artifact.DefaultArtifact;
32  import org.eclipse.aether.internal.impl.synccontext.named.*;
33  import org.eclipse.aether.named.NamedLockFactory;
34  import org.eclipse.aether.repository.LocalRepository;
35  import org.eclipse.aether.spi.synccontext.SyncContextFactory;
36  import org.junit.AfterClass;
37  import org.junit.Assert;
38  import org.junit.Before;
39  import org.junit.Test;
40  
41  import static java.util.Objects.requireNonNull;
42  import static org.hamcrest.MatcherAssert.assertThat;
43  import static org.hamcrest.Matchers.greaterThanOrEqualTo;
44  import static org.mockito.Mockito.mock;
45  import static org.mockito.Mockito.when;
46  
47  /**
48   * UT support for {@link SyncContextFactory}.
49   */
50  public abstract class NamedLockFactoryAdapterTestSupport {
51      private static final long ADAPTER_TIME = 1000L;
52  
53      private static final TimeUnit ADAPTER_TIME_UNIT = TimeUnit.MILLISECONDS;
54  
55      /**
56       * Subclass MAY populate this field but subclass must take care of proper cleanup as well, if needed!
57       */
58      protected static NameMapper nameMapper = new DiscriminatingNameMapper(GAVNameMapper.gav());
59  
60      /**
61       * Subclass MUST populate this field but subclass must take care of proper cleanup as well, if needed! Once set,
62       * subclass must MUST call {@link #createAdapter()}.
63       */
64      protected static NamedLockFactory namedLockFactory;
65  
66      private static NamedLockFactoryAdapter adapter;
67  
68      private RepositorySystemSession session;
69  
70      public static void createAdapter() {
71          requireNonNull(namedLockFactory, "NamedLockFactory not set");
72          adapter = new NamedLockFactoryAdapter(nameMapper, namedLockFactory);
73      }
74  
75      @AfterClass
76      public static void cleanupAdapter() {
77          if (adapter != null) {
78              adapter.getNamedLockFactory().shutdown();
79          }
80      }
81  
82      @Before
83      public void before() throws IOException {
84          Files.createDirectories(Paths.get(System.getProperty("java.io.tmpdir"))); // hack for Surefire
85          LocalRepository localRepository =
86                  new LocalRepository(Files.createTempDirectory("test").toFile());
87          session = mock(RepositorySystemSession.class);
88          when(session.getLocalRepository()).thenReturn(localRepository);
89          HashMap<String, Object> config = new HashMap<>();
90          config.put(NamedLockFactoryAdapter.TIME_KEY, String.valueOf(ADAPTER_TIME));
91          config.put(NamedLockFactoryAdapter.TIME_UNIT_KEY, ADAPTER_TIME_UNIT.name());
92          when(session.getConfigProperties()).thenReturn(config);
93      }
94  
95      @Test
96      public void justCreateAndClose() {
97          adapter.newInstance(session, false).close();
98      }
99  
100     @Test
101     public void justAcquire() {
102         try (SyncContext syncContext = adapter.newInstance(session, false)) {
103             syncContext.acquire(
104                     Arrays.asList(
105                             new DefaultArtifact("groupId:artifactId:1.0"),
106                             new DefaultArtifact("groupId:artifactId:1.1")),
107                     null);
108         }
109     }
110 
111     @Test(timeout = 5000)
112     public void sharedAccess() throws InterruptedException {
113         CountDownLatch winners = new CountDownLatch(2); // we expect 2 winners
114         CountDownLatch losers = new CountDownLatch(0); // we expect 0 losers
115         Thread t1 = new Thread(new Access(true, winners, losers, adapter, session, null));
116         Thread t2 = new Thread(new Access(true, winners, losers, adapter, session, null));
117         t1.start();
118         t2.start();
119         t1.join();
120         t2.join();
121         winners.await();
122         losers.await();
123     }
124 
125     @Test(timeout = 5000)
126     public void exclusiveAccess() throws InterruptedException {
127         CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner
128         CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser
129         Thread t1 = new Thread(new Access(false, winners, losers, adapter, session, null));
130         Thread t2 = new Thread(new Access(false, winners, losers, adapter, session, null));
131         t1.start();
132         t2.start();
133         t1.join();
134         t2.join();
135         winners.await();
136         losers.await();
137     }
138 
139     @Test(timeout = 5000)
140     public void mixedAccess() throws InterruptedException {
141         CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner
142         CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser
143         Thread t1 = new Thread(new Access(true, winners, losers, adapter, session, null));
144         Thread t2 = new Thread(new Access(false, winners, losers, adapter, session, null));
145         t1.start();
146         t2.start();
147         t1.join();
148         t2.join();
149         winners.await();
150         losers.await();
151     }
152 
153     @Test(timeout = 5000)
154     public void nestedSharedShared() throws InterruptedException {
155         CountDownLatch winners = new CountDownLatch(2); // we expect 2 winners
156         CountDownLatch losers = new CountDownLatch(0); // we expect 0 losers
157         Thread t1 = new Thread(new Access(
158                 true, winners, losers, adapter, session, new Access(true, winners, losers, adapter, session, null)));
159         t1.start();
160         t1.join();
161         winners.await();
162         losers.await();
163     }
164 
165     @Test(timeout = 5000)
166     public void nestedExclusiveShared() throws InterruptedException {
167         CountDownLatch winners = new CountDownLatch(2); // we expect 2 winners
168         CountDownLatch losers = new CountDownLatch(0); // we expect 0 losers
169         Thread t1 = new Thread(new Access(
170                 false, winners, losers, adapter, session, new Access(true, winners, losers, adapter, session, null)));
171         t1.start();
172         t1.join();
173         winners.await();
174         losers.await();
175     }
176 
177     @Test(timeout = 5000)
178     public void nestedExclusiveExclusive() throws InterruptedException {
179         CountDownLatch winners = new CountDownLatch(2); // we expect 2 winners
180         CountDownLatch losers = new CountDownLatch(0); // we expect 0 losers
181         Thread t1 = new Thread(new Access(
182                 false, winners, losers, adapter, session, new Access(false, winners, losers, adapter, session, null)));
183         t1.start();
184         t1.join();
185         winners.await();
186         losers.await();
187     }
188 
189     @Test(timeout = 5000)
190     public void nestedSharedExclusive() throws InterruptedException {
191         CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner (outer)
192         CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser (inner)
193         Thread t1 = new Thread(new Access(
194                 true, winners, losers, adapter, session, new Access(false, winners, losers, adapter, session, null)));
195         t1.start();
196         t1.join();
197         winners.await();
198         losers.await();
199     }
200 
201     @Test
202     public void fullyConsumeLockTime() throws InterruptedException {
203         long start = System.nanoTime();
204         CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner
205         CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser
206         Thread t1 = new Thread(new Access(false, winners, losers, adapter, session, null));
207         Thread t2 = new Thread(new Access(false, winners, losers, adapter, session, null));
208         t1.start();
209         t2.start();
210         t1.join();
211         t2.join();
212         winners.await();
213         losers.await();
214         long end = System.nanoTime();
215         long duration = end - start;
216         long expectedDuration = ADAPTER_TIME_UNIT.toNanos(ADAPTER_TIME);
217         assertThat(duration, greaterThanOrEqualTo(expectedDuration)); // equal in ideal case
218     }
219 
220     @Test
221     public void releasedExclusiveAllowAccess() throws InterruptedException {
222         CountDownLatch winners = new CountDownLatch(2); // we expect 1 winner
223         CountDownLatch losers = new CountDownLatch(0); // we expect 1 loser
224         Thread t1 = new Thread(new Access(false, winners, losers, adapter, session, null));
225         new Access(false, winners, losers, adapter, session, null).run();
226         t1.start();
227         t1.join();
228         winners.await();
229         losers.await();
230     }
231 
232     private static class Access implements Runnable {
233         final boolean shared;
234         final CountDownLatch winner;
235         final CountDownLatch loser;
236         final NamedLockFactoryAdapter adapter;
237         final RepositorySystemSession session;
238         final Access chained;
239 
240         public Access(
241                 boolean shared,
242                 CountDownLatch winner,
243                 CountDownLatch loser,
244                 NamedLockFactoryAdapter adapter,
245                 RepositorySystemSession session,
246                 Access chained) {
247             this.shared = shared;
248             this.winner = winner;
249             this.loser = loser;
250             this.adapter = adapter;
251             this.session = session;
252             this.chained = chained;
253         }
254 
255         @Override
256         public void run() {
257             try {
258                 try (SyncContext syncContext = adapter.newInstance(session, shared)) {
259                     syncContext.acquire(
260                             Arrays.asList(
261                                     new DefaultArtifact("groupId:artifactId:1.0"),
262                                     new DefaultArtifact("groupId:artifactId:1.1")),
263                             null);
264                     winner.countDown();
265                     if (chained != null) {
266                         chained.run();
267                     }
268                     loser.await();
269                 } catch (IllegalStateException e) {
270                     e.printStackTrace(); // for ref purposes
271                     loser.countDown();
272                     winner.await();
273                 }
274             } catch (InterruptedException e) {
275                 Assert.fail("interrupted");
276             }
277         }
278     }
279 }