| // Copyright (C) 2019 GerritForge Ltd |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.gerritforge.gerrit.globalrefdb; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static org.junit.Assert.fail; |
| |
| import com.google.gerrit.acceptance.AbstractDaemonTest; |
| import com.google.gerrit.entities.RefNames; |
| import java.util.Optional; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.Executors; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.ObjectIdRef; |
| import org.eclipse.jgit.lib.Ref; |
| import org.eclipse.jgit.lib.Repository; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| public class GlobalRefDatabaseTest extends AbstractDaemonTest { |
| |
| protected ExtendedGlobalRefDatabase objectUnderTest; |
| protected String refName = RefNames.REFS_HEADS + "branch"; |
| |
| private ObjectId objectId1; |
| private ObjectId objectId2; |
| private ObjectId objectId3; |
| |
| private Ref ref1; |
| private Ref ref2; |
| private Ref ref3; |
| private Ref nullRef = zerosRef(refName); |
| private Ref initialRef = ref(refName, ObjectId.zeroId()); |
| |
| private Executor executor = Executors.newFixedThreadPool(1); |
| |
| @Before |
| public void setup() throws Exception { |
| this.objectUnderTest = new FakeGlobalRefDatabase(); |
| |
| try (Repository repo = repoManager.openRepository(project)) { |
| createChange(refName); |
| ref1 = repo.getRefDatabase().exactRef(refName); |
| objectId1 = ref1.getObjectId(); |
| createChange(refName); |
| ref2 = repo.getRefDatabase().exactRef(refName); |
| objectId2 = ref2.getObjectId(); |
| createChange(refName); |
| ref3 = repo.getRefDatabase().exactRef(refName); |
| objectId3 = ref3.getObjectId(); |
| } |
| } |
| |
| @Test |
| public void shouldCreateEntryInTheGlobalRefDBWhenNullRef() { |
| assertThat(objectUnderTest.compareAndPut(project, nullRef, objectId1)).isTrue(); |
| } |
| |
| @Test |
| public void shouldCreateEntryWhenProjectDoesNotExistsInTheGlobalRefDB() { |
| assertThat(objectUnderTest.compareAndPut(project, initialRef, objectId1)).isTrue(); |
| } |
| |
| @Test |
| public void shouldUpdateEntryWithNewRef() { |
| objectUnderTest.compareAndPut(project, zerosRef(ref1.getName()), objectId1); |
| |
| assertThat(objectUnderTest.compareAndPut(project, ref1, objectId2)).isTrue(); |
| } |
| |
| @Test |
| public void shouldRejectUpdateWhenLocalRepoIsOutdated() { |
| objectUnderTest.compareAndPut(project, nullRef, objectId1); |
| objectUnderTest.compareAndPut(project, ref1, objectId2); |
| |
| assertThat(objectUnderTest.compareAndPut(project, ref1, objectId3)).isFalse(); |
| } |
| |
| @Test |
| public void shouldRejectUpdateWhenLocalRepoIsAheadOfTheGlobalRefDB() { |
| objectUnderTest.compareAndPut(project, nullRef, objectId1); |
| assertThat(objectUnderTest.compareAndPut(project, ref2, objectId3)).isFalse(); |
| } |
| |
| @Test |
| public void shouldReturnIsUpToDateWhenProjectDoesNotExistsInTheGlobalRefDB() |
| throws GlobalRefDbLockException { |
| assertThat(objectUnderTest.isUpToDate(project, initialRef)).isTrue(); |
| } |
| |
| @Test |
| public void shouldReturnIsUpToDate() throws GlobalRefDbLockException { |
| objectUnderTest.compareAndPut(project, nullRef, objectId1); |
| |
| assertThat(objectUnderTest.isUpToDate(project, ref1)).isTrue(); |
| } |
| |
| @Test |
| public void shouldReturnIsNotUpToDateWhenLocalRepoIsOutdated() throws GlobalRefDbLockException { |
| objectUnderTest.compareAndPut(project, nullRef, objectId1); |
| |
| assertThat(objectUnderTest.isUpToDate(project, nullRef)).isFalse(); |
| } |
| |
| @Test |
| public void shouldReturnIsNotUpToDateWhenLocalRepoIsAheadOfTheGlobalRefDB() |
| throws GlobalRefDbLockException { |
| objectUnderTest.compareAndPut(project, nullRef, objectId1); |
| |
| assertThat(objectUnderTest.isUpToDate(project, ref2)).isFalse(); |
| } |
| |
| /* |
| * Purpose of this test is to show how the locRef api can be used. |
| * |
| * Test scenario: |
| * 1. Client 1 acquires lock for given ref and checks if ref is up to date with the global ref-db |
| * 2. Client 1 creates new change and updates ref in global ref-db |
| * 3. While Client 1 holds the lock Client 2 is trying to update the ref in the global ref-db |
| * |
| * Result: |
| * Client 1 operations are successful. |
| * Client 2 operations are executed after Client 1 releases the lock. Client 2 operation failed |
| * because Client 1 has updated the ref in global ref-db |
| */ |
| @Test |
| public void shouldLockRef() throws Exception { |
| |
| objectUnderTest.compareAndPut(project, ref1, objectId2); |
| try (AutoCloseable refLock = objectUnderTest.lockRef(project, ref1.getName())) { |
| // simulate concurrent client trying to execute some operation while current client holds the |
| // lock |
| executor.execute(new ConcurrentClient()); |
| |
| if (objectUnderTest.isUpToDate(project, ref2)) { |
| try (Repository repo = repoManager.openRepository(project)) { |
| createChange(refName); |
| |
| Ref newRef = repo.getRefDatabase().exactRef(refName); |
| assertThat(objectUnderTest.compareAndPut(project, ref2, newRef.getObjectId())).isTrue(); |
| } |
| } |
| } |
| } |
| |
| @Test |
| public void shouldReturnObjectValueInTheGlobalRefDB() { |
| Object object = new Object(); |
| objectUnderTest.compareAndPut(project, refName, null, object); |
| |
| Optional<Object> o = objectUnderTest.get(project, refName, Object.class); |
| |
| assertThat(o.isPresent()).isTrue(); |
| assertThat(o.get()).isEqualTo(object); |
| } |
| |
| @Test |
| public void shouldReturnLongValueInTheGlobalRefDB() { |
| objectUnderTest.compareAndPut(project, refName, null, 1L); |
| |
| Optional<Long> o = objectUnderTest.get(project, refName, Long.class); |
| |
| assertThat(o.isPresent()).isTrue(); |
| assertThat(o.get()).isEqualTo(1L); |
| } |
| |
| @Test |
| public void shouldReturnEmptyIfValueIsNotInTheGlobalRefDB() { |
| Optional<Object> value = objectUnderTest.get(project, "nonExistentRef", Object.class); |
| |
| assertThat(value.isPresent()).isFalse(); |
| } |
| |
| @Test |
| public void shouldCreateGenericEntryInTheGlobalRefDBWhenFirstValue() { |
| assertThat(objectUnderTest.compareAndPut(project, refName, null, new Object())).isTrue(); |
| } |
| |
| @Test |
| public void shouldUpdateGenericEntryWithNewRef() throws Exception { |
| createChange(refName); |
| |
| Object object1 = new Object(); |
| objectUnderTest.compareAndPut(project, refName, null, object1); |
| |
| Object object2 = new Object(); |
| assertThat(objectUnderTest.compareAndPut(project, refName, object1, object2)).isTrue(); |
| } |
| |
| @Test |
| public void shouldRejectGenericUpdateWhenLocalRepoIsOutdated() throws Exception { |
| createChange(refName); |
| |
| Object object1 = new Object(); |
| objectUnderTest.compareAndPut(project, refName, null, object1); |
| |
| Object object2 = new Object(); |
| Object object3 = new Object(); |
| assertThat(objectUnderTest.compareAndPut(project, refName, object2, object3)).isFalse(); |
| } |
| |
| private Ref ref(String refName, ObjectId objectId) { |
| return new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, refName, objectId); |
| } |
| |
| private Ref zerosRef(String refName) { |
| return new ObjectIdRef.Unpeeled(Ref.Storage.NEW, refName, ObjectId.zeroId()); |
| } |
| |
| private class ConcurrentClient implements Runnable { |
| |
| @Override |
| public void run() { |
| try (AutoCloseable lock = objectUnderTest.lockRef(project, ref1.getName())) { |
| // some operations which require ref to be locked |
| |
| // compare and put should fail because other thread updated ref in the global ref-db |
| assertThat(objectUnderTest.compareAndPut(project, ref2, objectId3)).isFalse(); |
| } catch (GlobalRefDbLockException e) { |
| fail(e.getMessage()); |
| } catch (Exception e) { |
| fail(e.getMessage()); |
| } |
| } |
| } |
| } |