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}