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.Objects;
039import java.util.concurrent.CountDownLatch;
040import java.util.concurrent.TimeUnit;
041
042import static org.mockito.Mockito.mock;
043import static org.mockito.Mockito.when;
044
045/**
046 * UT support for {@link SyncContextFactory}.
047 */
048public abstract class NamedLockFactoryAdapterTestSupport {
049    private static final long ADAPTER_TIME = 100L;
050
051    private static final TimeUnit ADAPTER_TIME_UNIT = TimeUnit.MILLISECONDS;
052
053    /**
054     * Subclass MAY populate this field but subclass must take care of proper cleanup as well, if needed!
055     */
056    protected static NameMapper nameMapper = new DiscriminatingNameMapper(new GAVNameMapper());
057
058    /**
059     * Subclass MUST populate this field but subclass must take care of proper cleanup as well, if needed! Once set,
060     * subclass must MUST call {@link #createAdapter()}.
061     */
062    protected static NamedLockFactory namedLockFactory;
063
064    private static NamedLockFactoryAdapter adapter;
065
066    private RepositorySystemSession session;
067
068    public static void createAdapter() {
069        Objects.requireNonNull(namedLockFactory, "NamedLockFactory not set");
070        adapter = new NamedLockFactoryAdapter(nameMapper, namedLockFactory, ADAPTER_TIME, ADAPTER_TIME_UNIT);
071    }
072
073    @AfterClass
074    public static void cleanupAdapter() {
075        if (adapter != null) {
076            adapter.shutdown();
077        }
078    }
079
080    @Before
081    public void before() throws IOException {
082        Files.createDirectories(Paths.get(System.getProperty("java.io.tmpdir"))); // hack for Surefire
083        LocalRepository localRepository = new LocalRepository(Files.createTempDirectory("test").toFile());
084        session = mock(RepositorySystemSession.class);
085        when(session.getLocalRepository()).thenReturn(localRepository);
086    }
087
088    @Test
089    public void justCreateAndClose() {
090        adapter.newInstance(session, false).close();
091    }
092
093    @Test
094    public void justAcquire() {
095        try (SyncContext syncContext = adapter.newInstance(session, false)) {
096            syncContext.acquire(
097                    Arrays.asList(new DefaultArtifact("groupId:artifactId:1.0"), new DefaultArtifact("groupId:artifactId:1.1")),
098                    null
099            );
100        }
101    }
102
103    @Test(timeout = 5000)
104    public void sharedAccess() throws InterruptedException {
105        CountDownLatch winners = new CountDownLatch(2); // we expect 2 winners
106        CountDownLatch losers = new CountDownLatch(0); // we expect 0 losers
107        Thread t1 = new Thread(new Access(true, winners, losers, adapter, session, null));
108        Thread t2 = new Thread(new Access(true, winners, losers, adapter, session, null));
109        t1.start();
110        t2.start();
111        t1.join();
112        t2.join();
113        winners.await();
114        losers.await();
115    }
116
117    @Test(timeout = 5000)
118    public void exclusiveAccess() throws InterruptedException {
119        CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner
120        CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser
121        Thread t1 = new Thread(new Access(false, winners, losers, adapter, session, null));
122        Thread t2 = new Thread(new Access(false, winners, losers, adapter, session, null));
123        t1.start();
124        t2.start();
125        t1.join();
126        t2.join();
127        winners.await();
128        losers.await();
129    }
130
131    @Test(timeout = 5000)
132    public void mixedAccess() throws InterruptedException {
133        CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner
134        CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser
135        Thread t1 = new Thread(new Access(true, winners, losers, adapter, session, null));
136        Thread t2 = new Thread(new Access(false, winners, losers, adapter, session, null));
137        t1.start();
138        t2.start();
139        t1.join();
140        t2.join();
141        winners.await();
142        losers.await();
143    }
144
145    @Test(timeout = 5000)
146    public void nestedSharedShared() throws InterruptedException {
147        CountDownLatch winners = new CountDownLatch(2); // we expect 2 winners
148        CountDownLatch losers = new CountDownLatch(0); // we expect 0 losers
149        Thread t1 = new Thread(
150                new Access(true, winners, losers, adapter, session,
151                        new Access(true, winners, losers, adapter, session, null)
152                )
153        );
154        t1.start();
155        t1.join();
156        winners.await();
157        losers.await();
158    }
159
160    @Test(timeout = 5000)
161    public void nestedExclusiveShared() throws InterruptedException {
162        CountDownLatch winners = new CountDownLatch(2); // we expect 2 winners
163        CountDownLatch losers = new CountDownLatch(0); // we expect 0 losers
164        Thread t1 = new Thread(
165                new Access(false, winners, losers, adapter, session,
166                        new Access(true, winners, losers, adapter, session, null)
167                )
168        );
169        t1.start();
170        t1.join();
171        winners.await();
172        losers.await();
173    }
174
175    @Test(timeout = 5000)
176    public void nestedExclusiveExclusive() throws InterruptedException {
177        CountDownLatch winners = new CountDownLatch(2); // we expect 2 winners
178        CountDownLatch losers = new CountDownLatch(0); // we expect 0 losers
179        Thread t1 = new Thread(
180                new Access(false, winners, losers, adapter, session,
181                        new Access(false, winners, losers, adapter, session, null)
182                )
183        );
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(
195                new Access(true, winners, losers, adapter, session,
196                        new Access(false, winners, losers, adapter, session, null)
197                )
198        );
199        t1.start();
200        t1.join();
201        winners.await();
202        losers.await();
203    }
204
205    private static class Access implements Runnable {
206        final boolean shared;
207        final CountDownLatch winner;
208        final CountDownLatch loser;
209        final NamedLockFactoryAdapter adapter;
210        final RepositorySystemSession session;
211        final Access chained;
212
213        public Access(boolean shared,
214                      CountDownLatch winner,
215                      CountDownLatch loser,
216                      NamedLockFactoryAdapter adapter,
217                      RepositorySystemSession session,
218                      Access chained) {
219            this.shared = shared;
220            this.winner = winner;
221            this.loser = loser;
222            this.adapter = adapter;
223            this.session = session;
224            this.chained = chained;
225        }
226
227        @Override
228        public void run() {
229            try {
230                try (SyncContext syncContext = adapter.newInstance(session, shared)) {
231                    syncContext.acquire(
232                            Arrays.asList(new DefaultArtifact("groupId:artifactId:1.0"), new DefaultArtifact("groupId:artifactId:1.1")),
233                            null
234                    );
235                    winner.countDown();
236                    if (chained != null) {
237                        chained.run();
238                    }
239                    loser.await();
240                } catch (IllegalStateException e) {
241                    e.printStackTrace(); // for ref purposes
242                    loser.countDown();
243                    winner.await();
244                }
245            } catch (InterruptedException e) {
246                Assert.fail("interrupted");
247            }
248        }
249    }
250}