001package org.eclipse.aether.internal.impl.synccontext;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.eclipse.aether.RepositorySystemSession;
023import org.eclipse.aether.SyncContext;
024import org.eclipse.aether.artifact.DefaultArtifact;
025import org.eclipse.aether.internal.impl.synccontext.named.*;
026import org.eclipse.aether.named.NamedLockFactory;
027import org.eclipse.aether.repository.LocalRepository;
028import org.eclipse.aether.spi.synccontext.SyncContextFactory;
029import org.junit.AfterClass;
030import org.junit.Assert;
031import org.junit.Before;
032import org.junit.Test;
033
034import java.io.IOException;
035import java.nio.file.Files;
036import java.nio.file.Paths;
037import java.util.Arrays;
038import java.util.HashMap;
039import java.util.Objects;
040import java.util.concurrent.CountDownLatch;
041import java.util.concurrent.TimeUnit;
042
043import static org.hamcrest.MatcherAssert.assertThat;
044import static org.hamcrest.Matchers.greaterThanOrEqualTo;
045import static org.mockito.Mockito.mock;
046import static org.mockito.Mockito.when;
047
048/**
049 * UT support for {@link SyncContextFactory}.
050 */
051public abstract class NamedLockFactoryAdapterTestSupport
052{
053    private static final long ADAPTER_TIME = 1000L;
054
055    private static final TimeUnit ADAPTER_TIME_UNIT = TimeUnit.MILLISECONDS;
056
057    /**
058     * Subclass MAY populate this field but subclass must take care of proper cleanup as well, if needed!
059     */
060    protected static NameMapper nameMapper = new DiscriminatingNameMapper( GAVNameMapper.gav() );
061
062    /**
063     * Subclass MUST populate this field but subclass must take care of proper cleanup as well, if needed! Once set,
064     * subclass must MUST call {@link #createAdapter()}.
065     */
066    protected static NamedLockFactory namedLockFactory;
067
068    private static NamedLockFactoryAdapter adapter;
069
070    private RepositorySystemSession session;
071
072    public static void createAdapter() {
073        Objects.requireNonNull(namedLockFactory, "NamedLockFactory not set");
074        adapter = new NamedLockFactoryAdapter(nameMapper, namedLockFactory);
075    }
076
077    @AfterClass
078    public static void cleanupAdapter() {
079        if (adapter != null) {
080            adapter.getNamedLockFactory().shutdown();
081        }
082    }
083
084    @Before
085    public void before() throws IOException {
086        Files.createDirectories(Paths.get(System.getProperty("java.io.tmpdir"))); // hack for Surefire
087        LocalRepository localRepository = new LocalRepository(Files.createTempDirectory("test").toFile());
088        session = mock(RepositorySystemSession.class);
089        when(session.getLocalRepository()).thenReturn(localRepository);
090        HashMap<String, Object> config = new HashMap<>();
091        config.put(NamedLockFactoryAdapter.TIME_KEY, String.valueOf(ADAPTER_TIME));
092        config.put(NamedLockFactoryAdapter.TIME_UNIT_KEY, ADAPTER_TIME_UNIT.name());
093        when(session.getConfigProperties()).thenReturn(config);
094    }
095
096    @Test
097    public void justCreateAndClose() {
098        adapter.newInstance(session, false).close();
099    }
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}