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.jupiter.api.AfterAll;
38  import org.junit.jupiter.api.BeforeEach;
39  import org.junit.jupiter.api.Test;
40  import org.junit.jupiter.api.Timeout;
41  
42  import static java.util.Objects.requireNonNull;
43  import static org.junit.jupiter.api.Assertions.*;
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 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      @AfterAll
76      static void cleanupAdapter() {
77          if (adapter != null) {
78              adapter.getNamedLockFactory().shutdown();
79          }
80      }
81  
82      @BeforeEach
83      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.CONFIG_PROP_TIME, String.valueOf(ADAPTER_TIME));
91          config.put(NamedLockFactoryAdapter.CONFIG_PROP_TIME_UNIT, ADAPTER_TIME_UNIT.name());
92          when(session.getConfigProperties()).thenReturn(config);
93      }
94  
95      @Test
96      void justCreateAndClose() {
97          adapter.newInstance(session, false).close();
98      }
99  
100     @Test
101     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
112     @Timeout(5)
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
127     @Timeout(5)
128     public void exclusiveAccess() throws InterruptedException {
129         CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner
130         CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser
131         Thread t1 = new Thread(new Access(false, winners, losers, adapter, session, null));
132         Thread t2 = new Thread(new Access(false, winners, losers, adapter, session, null));
133         t1.start();
134         t2.start();
135         t1.join();
136         t2.join();
137         winners.await();
138         losers.await();
139     }
140 
141     @Test
142     @Timeout(5)
143     public void mixedAccess() throws InterruptedException {
144         CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner
145         CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser
146         Thread t1 = new Thread(new Access(true, winners, losers, adapter, session, null));
147         Thread t2 = new Thread(new Access(false, winners, losers, adapter, session, null));
148         t1.start();
149         t2.start();
150         t1.join();
151         t2.join();
152         winners.await();
153         losers.await();
154     }
155 
156     @Test
157     @Timeout(5)
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
170     @Timeout(5)
171     public void nestedExclusiveShared() throws InterruptedException {
172         CountDownLatch winners = new CountDownLatch(2); // we expect 2 winners
173         CountDownLatch losers = new CountDownLatch(0); // we expect 0 losers
174         Thread t1 = new Thread(new Access(
175                 false, winners, losers, adapter, session, new Access(true, winners, losers, adapter, session, null)));
176         t1.start();
177         t1.join();
178         winners.await();
179         losers.await();
180     }
181 
182     @Test
183     @Timeout(5)
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(new Access(
188                 false, winners, losers, adapter, session, new Access(false, winners, losers, adapter, session, null)));
189         t1.start();
190         t1.join();
191         winners.await();
192         losers.await();
193     }
194 
195     @Test
196     @Timeout(5)
197     public void nestedSharedExclusive() throws InterruptedException {
198         CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner (outer)
199         CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser (inner)
200         Thread t1 = new Thread(new Access(
201                 true, winners, losers, adapter, session, new Access(false, winners, losers, adapter, session, null)));
202         t1.start();
203         t1.join();
204         winners.await();
205         losers.await();
206     }
207 
208     @Test
209     void fullyConsumeLockTime() throws InterruptedException {
210         long start = System.nanoTime();
211         CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner
212         CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser
213         Thread t1 = new Thread(new Access(false, winners, losers, adapter, session, null));
214         Thread t2 = new Thread(new Access(false, winners, losers, adapter, session, null));
215         t1.start();
216         t2.start();
217         t1.join();
218         t2.join();
219         winners.await();
220         losers.await();
221         long end = System.nanoTime();
222         long duration = end - start;
223         long expectedDuration = ADAPTER_TIME_UNIT.toNanos(ADAPTER_TIME);
224         assertTrue(duration >= expectedDuration); // equal in ideal case
225     }
226 
227     @Test
228     void releasedExclusiveAllowAccess() throws InterruptedException {
229         CountDownLatch winners = new CountDownLatch(2); // we expect 1 winner
230         CountDownLatch losers = new CountDownLatch(0); // we expect 1 loser
231         Thread t1 = new Thread(new Access(false, winners, losers, adapter, session, null));
232         new Access(false, winners, losers, adapter, session, null).run();
233         t1.start();
234         t1.join();
235         winners.await();
236         losers.await();
237     }
238 
239     private static class Access implements Runnable {
240         final boolean shared;
241         final CountDownLatch winner;
242         final CountDownLatch loser;
243         final NamedLockFactoryAdapter adapter;
244         final RepositorySystemSession session;
245         final Access chained;
246 
247         public Access(
248                 boolean shared,
249                 CountDownLatch winner,
250                 CountDownLatch loser,
251                 NamedLockFactoryAdapter adapter,
252                 RepositorySystemSession session,
253                 Access chained) {
254             this.shared = shared;
255             this.winner = winner;
256             this.loser = loser;
257             this.adapter = adapter;
258             this.session = session;
259             this.chained = chained;
260         }
261 
262         @Override
263         public void run() {
264             try {
265                 try (SyncContext syncContext = adapter.newInstance(session, shared)) {
266                     syncContext.acquire(
267                             Arrays.asList(
268                                     new DefaultArtifact("groupId:artifactId:1.0"),
269                                     new DefaultArtifact("groupId:artifactId:1.1")),
270                             null);
271                     winner.countDown();
272                     if (chained != null) {
273                         chained.run();
274                     }
275                     loser.await();
276                 } catch (IllegalStateException | LockUpgradeNotSupportedException e) {
277                     loser.countDown();
278                     winner.await();
279                 }
280             } catch (InterruptedException e) {
281                 fail("interrupted");
282             }
283         }
284     }
285 }