View Javadoc
1   package org.eclipse.aether.internal.impl.synccontext;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.eclipse.aether.RepositorySystemSession;
23  import org.eclipse.aether.SyncContext;
24  import org.eclipse.aether.artifact.DefaultArtifact;
25  import org.eclipse.aether.internal.impl.synccontext.named.*;
26  import org.eclipse.aether.named.NamedLockFactory;
27  import org.eclipse.aether.repository.LocalRepository;
28  import org.eclipse.aether.spi.synccontext.SyncContextFactory;
29  import org.junit.AfterClass;
30  import org.junit.Assert;
31  import org.junit.Before;
32  import org.junit.Test;
33  
34  import java.io.IOException;
35  import java.nio.file.Files;
36  import java.nio.file.Paths;
37  import java.util.Arrays;
38  import java.util.HashMap;
39  import java.util.Objects;
40  import java.util.concurrent.CountDownLatch;
41  import java.util.concurrent.TimeUnit;
42  
43  import static org.hamcrest.MatcherAssert.assertThat;
44  import static org.hamcrest.Matchers.greaterThanOrEqualTo;
45  import static org.mockito.Mockito.mock;
46  import static org.mockito.Mockito.when;
47  
48  /**
49   * UT support for {@link SyncContextFactory}.
50   */
51  public abstract class NamedLockFactoryAdapterTestSupport
52  {
53      private static final long ADAPTER_TIME = 1000L;
54  
55      private static final TimeUnit ADAPTER_TIME_UNIT = TimeUnit.MILLISECONDS;
56  
57      /**
58       * Subclass MAY populate this field but subclass must take care of proper cleanup as well, if needed!
59       */
60      protected static NameMapper nameMapper = new DiscriminatingNameMapper(new GAVNameMapper());
61  
62      /**
63       * Subclass MUST populate this field but subclass must take care of proper cleanup as well, if needed! Once set,
64       * subclass must MUST call {@link #createAdapter()}.
65       */
66      protected static NamedLockFactory namedLockFactory;
67  
68      private static NamedLockFactoryAdapter adapter;
69  
70      private RepositorySystemSession session;
71  
72      public static void createAdapter() {
73          Objects.requireNonNull(namedLockFactory, "NamedLockFactory not set");
74          adapter = new NamedLockFactoryAdapter(nameMapper, namedLockFactory);
75      }
76  
77      @AfterClass
78      public static void cleanupAdapter() {
79          if (adapter != null) {
80              adapter.shutdown();
81          }
82      }
83  
84      @Before
85      public void before() throws IOException {
86          Files.createDirectories(Paths.get(System.getProperty("java.io.tmpdir"))); // hack for Surefire
87          LocalRepository localRepository = new LocalRepository(Files.createTempDirectory("test").toFile());
88          session = mock(RepositorySystemSession.class);
89          when(session.getLocalRepository()).thenReturn(localRepository);
90          HashMap<String, Object> config = new HashMap<>();
91          config.put(NamedLockFactoryAdapter.TIME_KEY, String.valueOf(ADAPTER_TIME));
92          config.put(NamedLockFactoryAdapter.TIME_UNIT_KEY, ADAPTER_TIME_UNIT.name());
93          when(session.getConfigProperties()).thenReturn(config);
94      }
95  
96      @Test
97      public void justCreateAndClose() {
98          adapter.newInstance(session, false).close();
99      }
100 
101     @Test
102     public void justAcquire() {
103         try (SyncContext syncContext = adapter.newInstance(session, false)) {
104             syncContext.acquire(
105                     Arrays.asList(new DefaultArtifact("groupId:artifactId:1.0"), new DefaultArtifact("groupId:artifactId:1.1")),
106                     null
107             );
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(
158                 new Access(true, winners, losers, adapter, session,
159                         new Access(true, winners, losers, adapter, session, null)
160                 )
161         );
162         t1.start();
163         t1.join();
164         winners.await();
165         losers.await();
166     }
167 
168     @Test(timeout = 5000)
169     public void nestedExclusiveShared() throws InterruptedException {
170         CountDownLatch winners = new CountDownLatch(2); // we expect 2 winners
171         CountDownLatch losers = new CountDownLatch(0); // we expect 0 losers
172         Thread t1 = new Thread(
173                 new Access(false, winners, losers, adapter, session,
174                         new Access(true, winners, losers, adapter, session, null)
175                 )
176         );
177         t1.start();
178         t1.join();
179         winners.await();
180         losers.await();
181     }
182 
183     @Test(timeout = 5000)
184     public void nestedExclusiveExclusive() throws InterruptedException {
185         CountDownLatch winners = new CountDownLatch(2); // we expect 2 winners
186         CountDownLatch losers = new CountDownLatch(0); // we expect 0 losers
187         Thread t1 = new Thread(
188                 new Access(false, winners, losers, adapter, session,
189                         new Access(false, winners, losers, adapter, session, null)
190                 )
191         );
192         t1.start();
193         t1.join();
194         winners.await();
195         losers.await();
196     }
197 
198     @Test(timeout = 5000)
199     public void nestedSharedExclusive() throws InterruptedException {
200         CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner (outer)
201         CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser (inner)
202         Thread t1 = new Thread(
203                 new Access(true, winners, losers, adapter, session,
204                         new Access(false, winners, losers, adapter, session, null)
205                 )
206         );
207         t1.start();
208         t1.join();
209         winners.await();
210         losers.await();
211     }
212 
213     @Test
214     public void fullyConsumeLockTime() throws InterruptedException {
215         long start = System.nanoTime();
216         CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner
217         CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser
218         Thread t1 = new Thread(new Access(false, winners, losers, adapter, session, null));
219         Thread t2 = new Thread(new Access(false, winners, losers, adapter, session, null));
220         t1.start();
221         t2.start();
222         t1.join();
223         t2.join();
224         winners.await();
225         losers.await();
226         long end = System.nanoTime();
227         long duration = end - start;
228         long expectedDuration = ADAPTER_TIME_UNIT.toNanos(ADAPTER_TIME);
229         assertThat(duration, greaterThanOrEqualTo(expectedDuration)); // equal in ideal case
230     }
231 
232     @Test
233     public void releasedExclusiveAllowAccess() throws InterruptedException {
234         CountDownLatch winners = new CountDownLatch(2); // we expect 1 winner
235         CountDownLatch losers = new CountDownLatch(0); // we expect 1 loser
236         Thread t1 = new Thread(new Access(false, winners, losers, adapter, session, null));
237         new Access(false, winners, losers, adapter, session, null).run();
238         t1.start();
239         t1.join();
240         winners.await();
241         losers.await();
242     }
243 
244     private static class Access implements Runnable {
245         final boolean shared;
246         final CountDownLatch winner;
247         final CountDownLatch loser;
248         final NamedLockFactoryAdapter adapter;
249         final RepositorySystemSession session;
250         final Access chained;
251 
252         public Access(boolean shared,
253                       CountDownLatch winner,
254                       CountDownLatch loser,
255                       NamedLockFactoryAdapter adapter,
256                       RepositorySystemSession session,
257                       Access chained) {
258             this.shared = shared;
259             this.winner = winner;
260             this.loser = loser;
261             this.adapter = adapter;
262             this.session = session;
263             this.chained = chained;
264         }
265 
266         @Override
267         public void run() {
268             try {
269                 try (SyncContext syncContext = adapter.newInstance(session, shared)) {
270                     syncContext.acquire(
271                             Arrays.asList(new DefaultArtifact("groupId:artifactId:1.0"), new DefaultArtifact("groupId:artifactId:1.1")),
272                             null
273                     );
274                     winner.countDown();
275                     if (chained != null) {
276                         chained.run();
277                     }
278                     loser.await();
279                 } catch (IllegalStateException e) {
280                     e.printStackTrace(); // for ref purposes
281                     loser.countDown();
282                     winner.await();
283                 }
284             } catch (InterruptedException e) {
285                 Assert.fail("interrupted");
286             }
287         }
288     }
289 }