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