001/* 002 * The contents of this file are subject to the license and copyright 003 * detailed in the LICENSE and NOTICE files at the root of the source 004 * tree. 005 */ 006package org.fcrepo.kernel.impl; 007 008import static org.junit.Assert.assertEquals; 009import static org.junit.Assert.assertTrue; 010import static org.junit.Assert.fail; 011import static org.mockito.Mockito.doThrow; 012import static org.mockito.Mockito.never; 013import static org.mockito.Mockito.times; 014import static org.mockito.Mockito.verify; 015import static org.mockito.Mockito.when; 016 017import java.time.Duration; 018import java.time.Instant; 019import java.util.concurrent.Executors; 020import java.util.concurrent.Phaser; 021import java.util.concurrent.TimeUnit; 022 023import org.fcrepo.common.db.DbTransactionExecutor; 024import org.fcrepo.kernel.api.ContainmentIndex; 025import org.fcrepo.kernel.api.cache.UserTypesCache; 026import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; 027import org.fcrepo.kernel.api.exception.TransactionClosedException; 028import org.fcrepo.kernel.api.lock.ResourceLockManager; 029import org.fcrepo.kernel.api.observer.EventAccumulator; 030import org.fcrepo.kernel.api.services.MembershipService; 031import org.fcrepo.kernel.api.services.ReferenceService; 032import org.fcrepo.persistence.api.PersistentStorageSession; 033import org.fcrepo.persistence.api.PersistentStorageSessionManager; 034import org.fcrepo.persistence.api.exceptions.PersistentStorageException; 035import org.fcrepo.search.api.SearchIndex; 036import org.junit.Before; 037import org.junit.Test; 038import org.junit.runner.RunWith; 039import org.mockito.Mock; 040import org.mockito.junit.MockitoJUnitRunner; 041 042import com.google.common.base.Stopwatch; 043 044/** 045 * <p> 046 * TransactionTest class. 047 * </p> 048 * 049 * @author mohideen 050 */ 051@RunWith(MockitoJUnitRunner.Silent.class) 052public class TransactionImplTest { 053 054 private TransactionImpl testTx; 055 056 @Mock 057 private TransactionManagerImpl txManager; 058 059 @Mock 060 private PersistentStorageSessionManager pssManager; 061 062 @Mock 063 private PersistentStorageSession psSession; 064 065 @Mock 066 private ContainmentIndex containmentIndex; 067 068 @Mock 069 private SearchIndex searchIndex; 070 071 @Mock 072 private EventAccumulator eventAccumulator; 073 074 @Mock 075 private ReferenceService referenceService; 076 077 @Mock 078 private MembershipService membershipService; 079 080 @Mock 081 private ResourceLockManager resourceLockManager; 082 083 @Mock 084 private UserTypesCache userTypesCache; 085 086 @Before 087 public void setUp() { 088 testTx = new TransactionImpl("123", txManager, Duration.ofMillis(180000)); 089 when(pssManager.getSession(testTx)).thenReturn(psSession); 090 when(txManager.getPersistentStorageSessionManager()).thenReturn(pssManager); 091 when(txManager.getContainmentIndex()).thenReturn(containmentIndex); 092 when(txManager.getEventAccumulator()).thenReturn(eventAccumulator); 093 when(txManager.getReferenceService()).thenReturn(referenceService); 094 when(txManager.getMembershipService()).thenReturn(membershipService); 095 when(txManager.getSearchIndex()).thenReturn(this.searchIndex); 096 when(txManager.getDbTransactionExecutor()).thenReturn(new DbTransactionExecutor()); 097 when(txManager.getResourceLockManager()).thenReturn(resourceLockManager); 098 when(txManager.getUserTypesCache()).thenReturn(userTypesCache); 099 } 100 101 @Test 102 public void testGetId() { 103 assertEquals("123", testTx.getId()); 104 } 105 106 @Test 107 public void testDefaultShortLived() { 108 assertEquals(true, testTx.isShortLived()); 109 } 110 111 @Test 112 public void testSetShortLived() { 113 testTx.setShortLived(false); 114 assertEquals(false, testTx.isShortLived()); 115 } 116 117 @Test 118 public void testCommit() throws Exception { 119 testTx.commit(); 120 verify(psSession).commit(); 121 } 122 123 @Test 124 public void testCommitIfShortLived() throws Exception { 125 testTx.setShortLived(true); 126 testTx.commitIfShortLived(); 127 verify(psSession).commit(); 128 } 129 130 @Test 131 public void testCommitIfShortLivedOnNonShortLived() throws Exception { 132 testTx.setShortLived(false); 133 testTx.commitIfShortLived(); 134 verify(psSession, never()).commit(); 135 } 136 137 @Test(expected = TransactionClosedException.class) 138 public void testCommitExpired() throws Exception { 139 testTx.expire(); 140 try { 141 testTx.commit(); 142 } finally { 143 verify(psSession, never()).commit(); 144 } 145 } 146 147 @Test(expected = TransactionClosedException.class) 148 public void testCommitRolledbackTx() throws Exception { 149 testTx.rollback(); 150 try { 151 testTx.commit(); 152 } finally { 153 verify(psSession, never()).commit(); 154 } 155 } 156 157 @Test(expected = RepositoryRuntimeException.class) 158 public void testEnsureRollbackOnFailedCommit() throws Exception { 159 doThrow(new PersistentStorageException("Failed")).when(psSession).commit(); 160 try { 161 testTx.commit(); 162 } finally { 163 verify(psSession).commit(); 164 verify(psSession).rollback(); 165 } 166 } 167 168 @Test 169 public void testCommitAlreadyCommittedTx() throws Exception { 170 testTx.commit(); 171 testTx.commit(); 172 verify(psSession, times(1)).commit(); 173 } 174 175 @Test 176 public void testRollback() throws Exception { 177 testTx.rollback(); 178 verify(psSession).rollback(); 179 } 180 181 @Test 182 public void shouldRollbackAllWhenStorageThrowsException() throws Exception { 183 doThrow(new PersistentStorageException("storage")).when(psSession).rollback(); 184 testTx.rollback(); 185 verifyRollback(); 186 } 187 188 @Test 189 public void shouldRollbackAllWhenContainmentThrowsException() throws Exception { 190 doThrow(new RuntimeException()).when(containmentIndex).rollbackTransaction(testTx); 191 testTx.rollback(); 192 verifyRollback(); 193 } 194 195 @Test 196 public void shouldRollbackAllWhenEventsThrowsException() throws Exception { 197 doThrow(new RuntimeException()).when(eventAccumulator).clearEvents(testTx); 198 testTx.rollback(); 199 verifyRollback(); 200 } 201 202 @Test(expected = TransactionClosedException.class) 203 public void testRollbackCommited() throws Exception { 204 testTx.commit(); 205 try { 206 testTx.rollback(); 207 } finally { 208 verify(psSession, never()).rollback(); 209 } 210 } 211 212 @Test 213 public void testRollbackAlreadyRolledbackTx() throws Exception { 214 testTx.rollback(); 215 testTx.rollback(); 216 verify(psSession, times(1)).rollback(); 217 } 218 219 @Test 220 public void testExpire() { 221 testTx.expire(); 222 assertTrue(testTx.hasExpired()); 223 } 224 225 @Test 226 public void testUpdateExpiry() { 227 final Instant previousExpiry = testTx.getExpires(); 228 testTx.updateExpiry(Duration.ofSeconds(1)); 229 assertTrue(testTx.getExpires().isAfter(previousExpiry)); 230 } 231 232 @Test(expected = TransactionClosedException.class) 233 public void testUpdateExpiryOnExpired() { 234 testTx.expire(); 235 final Instant previousExpiry = testTx.getExpires(); 236 try { 237 testTx.updateExpiry(Duration.ofSeconds(1)); 238 } finally { 239 assertEquals(testTx.getExpires(), previousExpiry); 240 } 241 } 242 243 @Test 244 public void testRefresh() { 245 final Instant previousExpiry = testTx.getExpires(); 246 testTx.refresh(); 247 assertTrue(testTx.getExpires().isAfter(previousExpiry)); 248 } 249 250 @Test(expected = TransactionClosedException.class) 251 public void testRefreshOnExpired() { 252 testTx.expire(); 253 final Instant previousExpiry = testTx.getExpires(); 254 try { 255 testTx.refresh(); 256 } finally { 257 assertEquals(testTx.getExpires(), previousExpiry); 258 } 259 } 260 261 @Test 262 public void testNewTransactionNotExpired() { 263 assertTrue(testTx.getExpires().isAfter(Instant.now())); 264 } 265 266 @Test(expected = TransactionClosedException.class) 267 public void operationsShouldFailWhenTxNotOpen() { 268 testTx.commit(); 269 testTx.doInTx(() -> { 270 fail("This code should not be executed"); 271 }); 272 } 273 274 @Test 275 public void commitShouldWaitTillAllOperationsComplete() { 276 final var executor = Executors.newCachedThreadPool(); 277 final var phaser = new Phaser(2); 278 279 executor.submit(() -> { 280 testTx.doInTx(() -> { 281 phaser.arriveAndAwaitAdvance(); 282 try { 283 TimeUnit.SECONDS.sleep(2); 284 } catch (InterruptedException e) { 285 throw new RuntimeException(e); 286 } 287 }); 288 }); 289 290 phaser.arriveAndAwaitAdvance(); 291 final var stopwatch = Stopwatch.createStarted(); 292 testTx.commit(); 293 final var duration = stopwatch.stop().elapsed().toMillis(); 294 295 assertTrue(duration < 3000 && duration > 1000); 296 } 297 298 private void verifyRollback() throws PersistentStorageException { 299 verify(psSession).rollback(); 300 verify(containmentIndex).rollbackTransaction(testTx); 301 verify(eventAccumulator).clearEvents(testTx); 302 } 303}