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