// Copyright (C) 2016 The Android Open Source Project // // 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.google.gerrit.server.notedb; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.truth.Truth.assertThat; import static com.google.gerrit.common.TimeUtil.nowTs; import static com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage.NOTE_DB; import static com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage.REVIEW_DB; import static com.google.gerrit.server.notedb.NoteDbChangeState.applyDelta; import static com.google.gerrit.server.notedb.NoteDbChangeState.parse; import static org.eclipse.jgit.lib.ObjectId.zeroId; import com.google.common.collect.ImmutableMap; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.server.notedb.NoteDbChangeState.Delta; import com.google.gerrit.server.notedb.NoteDbChangeState.RefState; import com.google.gerrit.testutil.GerritBaseTests; import com.google.gerrit.testutil.TestChanges; import com.google.gerrit.testutil.TestTimeUtil; import java.sql.Timestamp; import java.util.Optional; import java.util.concurrent.TimeUnit; import org.eclipse.jgit.lib.ObjectId; import org.junit.After; import org.junit.Before; import org.junit.Test; /** Unit tests for {@link NoteDbChangeState}. */ public class NoteDbChangeStateTest extends GerritBaseTests { ObjectId SHA1 = ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); ObjectId SHA2 = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); ObjectId SHA3 = ObjectId.fromString("badc0feebadc0feebadc0feebadc0feebadc0fee"); @Before public void setUp() { TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS); } @After public void tearDown() { TestTimeUtil.useSystemTime(); } @Test public void parseReviewDbWithoutDrafts() { NoteDbChangeState state = parse(new Change.Id(1), SHA1.name()); assertThat(state.getPrimaryStorage()).isEqualTo(REVIEW_DB); assertThat(state.getChangeId()).isEqualTo(new Change.Id(1)); assertThat(state.getChangeMetaId()).isEqualTo(SHA1); assertThat(state.getDraftIds()).isEmpty(); assertThat(state.getReadOnlyUntil().isPresent()).isFalse(); assertThat(state.toString()).isEqualTo(SHA1.name()); state = parse(new Change.Id(1), "R," + SHA1.name()); assertThat(state.getPrimaryStorage()).isEqualTo(REVIEW_DB); assertThat(state.getChangeId()).isEqualTo(new Change.Id(1)); assertThat(state.getChangeMetaId()).isEqualTo(SHA1); assertThat(state.getDraftIds()).isEmpty(); assertThat(state.getReadOnlyUntil().isPresent()).isFalse(); assertThat(state.toString()).isEqualTo(SHA1.name()); } @Test public void parseReviewDbWithDrafts() { String str = SHA1.name() + ",2003=" + SHA2.name() + ",1001=" + SHA3.name(); String expected = SHA1.name() + ",1001=" + SHA3.name() + ",2003=" + SHA2.name(); NoteDbChangeState state = parse(new Change.Id(1), str); assertThat(state.getPrimaryStorage()).isEqualTo(REVIEW_DB); assertThat(state.getChangeId()).isEqualTo(new Change.Id(1)); assertThat(state.getChangeMetaId()).isEqualTo(SHA1); assertThat(state.getDraftIds()) .containsExactly( new Account.Id(1001), SHA3, new Account.Id(2003), SHA2); assertThat(state.getReadOnlyUntil().isPresent()).isFalse(); assertThat(state.toString()).isEqualTo(expected); state = parse(new Change.Id(1), "R," + str); assertThat(state.getPrimaryStorage()).isEqualTo(REVIEW_DB); assertThat(state.getChangeId()).isEqualTo(new Change.Id(1)); assertThat(state.getChangeMetaId()).isEqualTo(SHA1); assertThat(state.getDraftIds()) .containsExactly( new Account.Id(1001), SHA3, new Account.Id(2003), SHA2); assertThat(state.getReadOnlyUntil().isPresent()).isFalse(); assertThat(state.toString()).isEqualTo(expected); } @Test public void parseReadOnlyUntil() { Timestamp ts = new Timestamp(12345); String str = "R=12345," + SHA1.name(); NoteDbChangeState state = parse(new Change.Id(1), str); assertThat(state.getPrimaryStorage()).isEqualTo(REVIEW_DB); assertThat(state.getChangeId()).isEqualTo(new Change.Id(1)); assertThat(state.getChangeMetaId()).isEqualTo(SHA1); assertThat(state.getReadOnlyUntil().get()).isEqualTo(ts); assertThat(state.toString()).isEqualTo(str); str = "N=12345"; state = parse(new Change.Id(1), str); assertThat(state.getPrimaryStorage()).isEqualTo(NOTE_DB); assertThat(state.getChangeId()).isEqualTo(new Change.Id(1)); assertThat(state.getRefState().isPresent()).isFalse(); assertThat(state.getReadOnlyUntil().get()).isEqualTo(ts); assertThat(state.toString()).isEqualTo(str); } @Test public void applyDeltaToNullWithNoNewMetaId() throws Exception { Change c = newChange(); assertThat(c.getNoteDbState()).isNull(); applyDelta(c, Delta.create(c.getId(), noMetaId(), noDrafts())); assertThat(c.getNoteDbState()).isNull(); applyDelta(c, Delta.create(c.getId(), noMetaId(), drafts(new Account.Id(1001), zeroId()))); assertThat(c.getNoteDbState()).isNull(); } @Test public void applyDeltaToMetaId() throws Exception { Change c = newChange(); applyDelta(c, Delta.create(c.getId(), metaId(SHA1), noDrafts())); assertThat(c.getNoteDbState()).isEqualTo(SHA1.name()); applyDelta(c, Delta.create(c.getId(), metaId(SHA2), noDrafts())); assertThat(c.getNoteDbState()).isEqualTo(SHA2.name()); // No-op delta. applyDelta(c, Delta.create(c.getId(), noMetaId(), noDrafts())); assertThat(c.getNoteDbState()).isEqualTo(SHA2.name()); // Set to zero clears the field. applyDelta(c, Delta.create(c.getId(), metaId(zeroId()), noDrafts())); assertThat(c.getNoteDbState()).isNull(); } @Test public void applyDeltaToDrafts() throws Exception { Change c = newChange(); applyDelta(c, Delta.create(c.getId(), metaId(SHA1), drafts(new Account.Id(1001), SHA2))); assertThat(c.getNoteDbState()).isEqualTo(SHA1.name() + ",1001=" + SHA2.name()); applyDelta(c, Delta.create(c.getId(), noMetaId(), drafts(new Account.Id(2003), SHA3))); assertThat(c.getNoteDbState()) .isEqualTo(SHA1.name() + ",1001=" + SHA2.name() + ",2003=" + SHA3.name()); applyDelta(c, Delta.create(c.getId(), noMetaId(), drafts(new Account.Id(2003), zeroId()))); assertThat(c.getNoteDbState()).isEqualTo(SHA1.name() + ",1001=" + SHA2.name()); applyDelta(c, Delta.create(c.getId(), metaId(SHA3), noDrafts())); assertThat(c.getNoteDbState()).isEqualTo(SHA3.name() + ",1001=" + SHA2.name()); } @Test public void applyDeltaToReadOnly() throws Exception { Timestamp ts = nowTs(); Change c = newChange(); NoteDbChangeState state = new NoteDbChangeState( c.getId(), REVIEW_DB, Optional.of(RefState.create(SHA1, ImmutableMap.of())), Optional.of(new Timestamp(ts.getTime() + 10000))); c.setNoteDbState(state.toString()); Delta delta = Delta.create(c.getId(), metaId(SHA2), noDrafts()); applyDelta(c, delta); assertThat(NoteDbChangeState.parse(c)) .isEqualTo( new NoteDbChangeState( state.getChangeId(), state.getPrimaryStorage(), Optional.of(RefState.create(SHA2, ImmutableMap.of())), state.getReadOnlyUntil())); } @Test public void parseNoteDbPrimary() { NoteDbChangeState state = parse(new Change.Id(1), "N"); assertThat(state.getPrimaryStorage()).isEqualTo(NOTE_DB); assertThat(state.getRefState().isPresent()).isFalse(); assertThat(state.getReadOnlyUntil().isPresent()).isFalse(); } @Test(expected = IllegalArgumentException.class) public void parseInvalidPrimaryStorage() { parse(new Change.Id(1), "X"); } @Test public void applyDeltaToNoteDbPrimaryIsNoOp() throws Exception { Change c = newChange(); c.setNoteDbState("N"); applyDelta(c, Delta.create(c.getId(), metaId(SHA1), drafts(new Account.Id(1001), SHA2))); assertThat(c.getNoteDbState()).isEqualTo("N"); } private static Change newChange() { return TestChanges.newChange(new Project.NameKey("project"), new Account.Id(12345)); } // Static factory methods to avoid type arguments when using as method args. private static Optional<ObjectId> noMetaId() { return Optional.empty(); } private static Optional<ObjectId> metaId(ObjectId id) { return Optional.of(id); } private static ImmutableMap<Account.Id, ObjectId> noDrafts() { return ImmutableMap.of(); } private static ImmutableMap<Account.Id, ObjectId> drafts(Object... args) { checkArgument(args.length % 2 == 0); ImmutableMap.Builder<Account.Id, ObjectId> b = ImmutableMap.builder(); for (int i = 0; i < args.length / 2; i++) { b.put((Account.Id) args[2 * i], (ObjectId) args[2 * i + 1]); } return b.build(); } }