| // 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()); | |
| } | |
| } | |
| } | |
| } |