blob: 29d244942eafb143f5dff524e2ff6d1b09f87660 [file] [log] [blame]
// Copyright (C) 2013 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.gerrit.server.notedb.ReviewerState.CC;
import static com.google.gerrit.server.notedb.ReviewerState.REVIEWER;
import static com.google.gerrit.testutil.TestChanges.incrementPatchSet;
import static com.google.inject.Scopes.SINGLETON;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.CommentRange;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.config.AnonymousCowardNameProvider;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.FactoryModule;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitModule;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.VersionedMetaData.BatchMetaDataUpdate;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.notedb.CommentsInNotesUtil;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.util.TimeUtil;
import com.google.gerrit.testutil.TestChanges;
import com.google.gerrit.testutil.FakeAccountCache;
import com.google.gerrit.testutil.FakeRealm;
import com.google.gerrit.testutil.InMemoryRepositoryManager;
import com.google.gwtorm.client.KeyUtil;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.StandardKeyEncoder;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.util.Providers;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.notes.Note;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.joda.time.DateTime;
import org.joda.time.DateTimeUtils;
import org.joda.time.DateTimeUtils.MillisProvider;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicLong;
public class ChangeNotesTest {
private static final TimeZone TZ =
TimeZone.getTimeZone("America/Los_Angeles");
private PersonIdent serverIdent;
private Project.NameKey project;
private InMemoryRepositoryManager repoManager;
private InMemoryRepository repo;
private FakeAccountCache accountCache;
private IdentifiedUser changeOwner;
private IdentifiedUser otherUser;
private Injector injector;
private String systemTimeZone;
private volatile long clockStepMs;
@Before
public void setUp() throws Exception {
setTimeForTesting();
KeyUtil.setEncoderImpl(new StandardKeyEncoder());
serverIdent = new PersonIdent(
"Gerrit Server", "noreply@gerrit.com", TimeUtil.nowTs(), TZ);
project = new Project.NameKey("test-project");
repoManager = new InMemoryRepositoryManager();
repo = repoManager.createRepository(project);
accountCache = new FakeAccountCache();
Account co = new Account(new Account.Id(1), TimeUtil.nowTs());
co.setFullName("Change Owner");
co.setPreferredEmail("change@owner.com");
accountCache.put(co);
Account ou = new Account(new Account.Id(2), TimeUtil.nowTs());
ou.setFullName("Other Account");
ou.setPreferredEmail("other@account.com");
accountCache.put(ou);
injector = Guice.createInjector(new FactoryModule() {
@Override
public void configure() {
install(new GitModule());
bind(NotesMigration.class).toInstance(NotesMigration.allEnabled());
bind(GitRepositoryManager.class).toInstance(repoManager);
bind(ProjectCache.class).toProvider(Providers.<ProjectCache> of(null));
bind(CapabilityControl.Factory.class)
.toProvider(Providers.<CapabilityControl.Factory> of(null));
bind(Config.class).annotatedWith(GerritServerConfig.class)
.toInstance(new Config());
bind(String.class).annotatedWith(AnonymousCowardName.class)
.toProvider(AnonymousCowardNameProvider.class);
bind(String.class).annotatedWith(CanonicalWebUrl.class)
.toInstance("http://localhost:8080/");
bind(Realm.class).to(FakeRealm.class);
bind(GroupBackend.class).to(SystemGroupBackend.class).in(SINGLETON);
bind(AccountCache.class).toInstance(accountCache);
bind(PersonIdent.class).annotatedWith(GerritPersonIdent.class)
.toInstance(serverIdent);
bind(GitReferenceUpdated.class)
.toInstance(GitReferenceUpdated.DISABLED);
}
});
IdentifiedUser.GenericFactory userFactory =
injector.getInstance(IdentifiedUser.GenericFactory.class);
changeOwner = userFactory.create(co.getId());
otherUser = userFactory.create(ou.getId());
}
private void setTimeForTesting() {
systemTimeZone = System.setProperty("user.timezone", "US/Eastern");
clockStepMs = MILLISECONDS.convert(1, SECONDS);
final AtomicLong clockMs = new AtomicLong(
new DateTime(2009, 9, 30, 17, 0, 0).getMillis());
DateTimeUtils.setCurrentMillisProvider(new MillisProvider() {
@Override
public long getMillis() {
return clockMs.getAndAdd(clockStepMs);
}
});
}
@After
public void resetTime() {
DateTimeUtils.setCurrentMillisSystem();
System.setProperty("user.timezone", systemTimeZone);
}
@Test
public void approvalsCommitFormatSimple() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.putApproval("Verified", (short) 1);
update.putApproval("Code-Review", (short) -1);
update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
update.putReviewer(otherUser.getAccount().getId(), CC);
update.commit();
assertEquals("refs/changes/01/1/meta", update.getRefName());
RevWalk walk = new RevWalk(repo);
try {
RevCommit commit = walk.parseCommit(update.getRevision());
walk.parseBody(commit);
assertEquals("Update patch set 1\n"
+ "\n"
+ "Patch-set: 1\n"
+ "Reviewer: Change Owner <1@gerrit>\n"
+ "CC: Other Account <2@gerrit>\n"
+ "Label: Code-Review=-1\n"
+ "Label: Verified=+1\n",
commit.getFullMessage());
PersonIdent author = commit.getAuthorIdent();
assertEquals("Change Owner", author.getName());
assertEquals("1@gerrit", author.getEmailAddress());
assertEquals(new Date(c.getCreatedOn().getTime() + 1000),
author.getWhen());
assertEquals(TimeZone.getTimeZone("GMT-7:00"), author.getTimeZone());
PersonIdent committer = commit.getCommitterIdent();
assertEquals("Gerrit Server", committer.getName());
assertEquals("noreply@gerrit.com", committer.getEmailAddress());
assertEquals(author.getWhen(), committer.getWhen());
assertEquals(author.getTimeZone(), committer.getTimeZone());
} finally {
walk.release();
}
}
@Test
public void changeMessageCommitFormatSimple() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.setChangeMessage("Just a little code change.\n"
+ "How about a new line");
update.commit();
assertEquals("refs/changes/01/1/meta", update.getRefName());
RevWalk walk = new RevWalk(repo);
try {
RevCommit commit = walk.parseCommit(update.getRevision());
walk.parseBody(commit);
assertEquals("Update patch set 1\n"
+ "\n"
+ "Just a little code change.\n"
+ "How about a new line\n"
+ "\n"
+ "Patch-set: 1\n",
commit.getFullMessage());
} finally {
walk.release();
}
}
@Test
public void approvalTombstoneCommitFormat() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.removeApproval("Code-Review");
update.commit();
RevWalk walk = new RevWalk(repo);
try {
RevCommit commit = walk.parseCommit(update.getRevision());
walk.parseBody(commit);
assertEquals("Update patch set 1\n"
+ "\n"
+ "Patch-set: 1\n"
+ "Label: -Code-Review\n",
commit.getFullMessage());
} finally {
walk.release();
}
}
@Test
public void submitCommitFormat() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.setSubject("Submit patch set 1");
update.submit(ImmutableList.of(
submitRecord("NOT_READY", null,
submitLabel("Verified", "OK", changeOwner.getAccountId()),
submitLabel("Code-Review", "NEED", null)),
submitRecord("NOT_READY", null,
submitLabel("Verified", "OK", changeOwner.getAccountId()),
submitLabel("Alternative-Code-Review", "NEED", null))));
update.commit();
RevWalk walk = new RevWalk(repo);
try {
RevCommit commit = walk.parseCommit(update.getRevision());
walk.parseBody(commit);
assertEquals("Submit patch set 1\n"
+ "\n"
+ "Patch-set: 1\n"
+ "Status: submitted\n"
+ "Submitted-with: NOT_READY\n"
+ "Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
+ "Submitted-with: NEED: Code-Review\n"
+ "Submitted-with: NOT_READY\n"
+ "Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
+ "Submitted-with: NEED: Alternative-Code-Review\n",
commit.getFullMessage());
PersonIdent author = commit.getAuthorIdent();
assertEquals("Change Owner", author.getName());
assertEquals("1@gerrit", author.getEmailAddress());
assertEquals(new Date(c.getCreatedOn().getTime() + 1000),
author.getWhen());
assertEquals(TimeZone.getTimeZone("GMT-7:00"), author.getTimeZone());
PersonIdent committer = commit.getCommitterIdent();
assertEquals("Gerrit Server", committer.getName());
assertEquals("noreply@gerrit.com", committer.getEmailAddress());
assertEquals(author.getWhen(), committer.getWhen());
assertEquals(author.getTimeZone(), committer.getTimeZone());
} finally {
walk.release();
}
}
@Test
public void submitWithErrorMessage() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.setSubject("Submit patch set 1");
update.submit(ImmutableList.of(
submitRecord("RULE_ERROR", "Problem with patch set:\n1")));
update.commit();
RevWalk walk = new RevWalk(repo);
try {
RevCommit commit = walk.parseCommit(update.getRevision());
walk.parseBody(commit);
assertEquals("Submit patch set 1\n"
+ "\n"
+ "Patch-set: 1\n"
+ "Status: submitted\n"
+ "Submitted-with: RULE_ERROR Problem with patch set: 1\n",
commit.getFullMessage());
} finally {
walk.release();
}
}
@Test
public void approvalsOnePatchSet() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.putApproval("Verified", (short) 1);
update.putApproval("Code-Review", (short) -1);
update.commit();
ChangeNotes notes = newNotes(c);
assertEquals(1, notes.getApprovals().keySet().size());
List<PatchSetApproval> psas =
notes.getApprovals().get(c.currentPatchSetId());
assertEquals(2, psas.size());
assertEquals(c.currentPatchSetId(), psas.get(0).getPatchSetId());
assertEquals(1, psas.get(0).getAccountId().get());
assertEquals("Code-Review", psas.get(0).getLabel());
assertEquals((short) -1, psas.get(0).getValue());
assertEquals(truncate(after(c, 1000)), psas.get(0).getGranted());
assertEquals(c.currentPatchSetId(), psas.get(1).getPatchSetId());
assertEquals(1, psas.get(1).getAccountId().get());
assertEquals("Verified", psas.get(1).getLabel());
assertEquals((short) 1, psas.get(1).getValue());
assertEquals(psas.get(0).getGranted(), psas.get(1).getGranted());
}
@Test
public void approvalsMultiplePatchSets() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.putApproval("Code-Review", (short) -1);
update.commit();
PatchSet.Id ps1 = c.currentPatchSetId();
incrementPatchSet(c);
update = newUpdate(c, changeOwner);
update.putApproval("Code-Review", (short) 1);
update.commit();
PatchSet.Id ps2 = c.currentPatchSetId();
ChangeNotes notes = newNotes(c);
ListMultimap<PatchSet.Id, PatchSetApproval> psas = notes.getApprovals();
assertEquals(2, notes.getApprovals().keySet().size());
PatchSetApproval psa1 = Iterables.getOnlyElement(psas.get(ps1));
assertEquals(ps1, psa1.getPatchSetId());
assertEquals(1, psa1.getAccountId().get());
assertEquals("Code-Review", psa1.getLabel());
assertEquals((short) -1, psa1.getValue());
assertEquals(truncate(after(c, 1000)), psa1.getGranted());
PatchSetApproval psa2 = Iterables.getOnlyElement(psas.get(ps2));
assertEquals(ps2, psa2.getPatchSetId());
assertEquals(1, psa2.getAccountId().get());
assertEquals("Code-Review", psa2.getLabel());
assertEquals((short) +1, psa2.getValue());
assertEquals(truncate(after(c, 2000)), psa2.getGranted());
}
@Test
public void approvalsMultipleApprovals() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.putApproval("Code-Review", (short) -1);
update.commit();
ChangeNotes notes = newNotes(c);
PatchSetApproval psa = Iterables.getOnlyElement(
notes.getApprovals().get(c.currentPatchSetId()));
assertEquals("Code-Review", psa.getLabel());
assertEquals((short) -1, psa.getValue());
update = newUpdate(c, changeOwner);
update.putApproval("Code-Review", (short) 1);
update.commit();
notes = newNotes(c);
psa = Iterables.getOnlyElement(
notes.getApprovals().get(c.currentPatchSetId()));
assertEquals("Code-Review", psa.getLabel());
assertEquals((short) 1, psa.getValue());
}
@Test
public void approvalsMultipleUsers() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.putApproval("Code-Review", (short) -1);
update.commit();
update = newUpdate(c, otherUser);
update.putApproval("Code-Review", (short) 1);
update.commit();
ChangeNotes notes = newNotes(c);
assertEquals(1, notes.getApprovals().keySet().size());
List<PatchSetApproval> psas =
notes.getApprovals().get(c.currentPatchSetId());
assertEquals(2, psas.size());
assertEquals(c.currentPatchSetId(), psas.get(0).getPatchSetId());
assertEquals(1, psas.get(0).getAccountId().get());
assertEquals("Code-Review", psas.get(0).getLabel());
assertEquals((short) -1, psas.get(0).getValue());
assertEquals(truncate(after(c, 1000)), psas.get(0).getGranted());
assertEquals(c.currentPatchSetId(), psas.get(1).getPatchSetId());
assertEquals(2, psas.get(1).getAccountId().get());
assertEquals("Code-Review", psas.get(1).getLabel());
assertEquals((short) 1, psas.get(1).getValue());
assertEquals(truncate(after(c, 2000)), psas.get(1).getGranted());
}
@Test
public void approvalsTombstone() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.putApproval("Not-For-Long", (short) 1);
update.commit();
ChangeNotes notes = newNotes(c);
PatchSetApproval psa = Iterables.getOnlyElement(
notes.getApprovals().get(c.currentPatchSetId()));
assertEquals(1, psa.getAccountId().get());
assertEquals("Not-For-Long", psa.getLabel());
assertEquals((short) 1, psa.getValue());
update = newUpdate(c, changeOwner);
update.removeApproval("Not-For-Long");
update.commit();
notes = newNotes(c);
assertTrue(notes.getApprovals().isEmpty());
}
@Test
public void multipleReviewers() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
update.putReviewer(otherUser.getAccount().getId(), REVIEWER);
update.commit();
ChangeNotes notes = newNotes(c);
assertEquals(ImmutableSetMultimap.of(
REVIEWER, new Account.Id(1),
REVIEWER, new Account.Id(2)),
notes.getReviewers());
}
@Test
public void reviewerTypes() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
update.putReviewer(otherUser.getAccount().getId(), CC);
update.commit();
ChangeNotes notes = newNotes(c);
assertEquals(ImmutableSetMultimap.of(
REVIEWER, new Account.Id(1),
CC, new Account.Id(2)),
notes.getReviewers());
}
@Test
public void oneReviewerMultipleTypes() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.putReviewer(otherUser.getAccount().getId(), REVIEWER);
update.commit();
ChangeNotes notes = newNotes(c);
assertEquals(ImmutableSetMultimap.of(
REVIEWER, new Account.Id(2)),
notes.getReviewers());
update = newUpdate(c, otherUser);
update.putReviewer(otherUser.getAccount().getId(), CC);
update.commit();
notes = newNotes(c);
assertEquals(ImmutableSetMultimap.of(
CC, new Account.Id(2)),
notes.getReviewers());
}
@Test
public void removeReviewer() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.putReviewer(otherUser.getAccount().getId(), REVIEWER);
update.commit();
update = newUpdate(c, changeOwner);
update.putApproval("Code-Review", (short) 1);
update.commit();
update = newUpdate(c, otherUser);
update.putApproval("Code-Review", (short) 1);
update.commit();
ChangeNotes notes = newNotes(c);
List<PatchSetApproval> psas =
notes.getApprovals().get(c.currentPatchSetId());
assertEquals(2, psas.size());
assertEquals(changeOwner.getAccount().getId(), psas.get(0).getAccountId());
assertEquals(otherUser.getAccount().getId(), psas.get(1).getAccountId());
update = newUpdate(c, changeOwner);
update.removeReviewer(otherUser.getAccount().getId());
update.commit();
notes = newNotes(c);
psas = notes.getApprovals().get(c.currentPatchSetId());
assertEquals(1, psas.size());
assertEquals(changeOwner.getAccount().getId(), psas.get(0).getAccountId());
}
@Test
public void submitRecords() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.setSubject("Submit patch set 1");
update.submit(ImmutableList.of(
submitRecord("NOT_READY", null,
submitLabel("Verified", "OK", changeOwner.getAccountId()),
submitLabel("Code-Review", "NEED", null)),
submitRecord("NOT_READY", null,
submitLabel("Verified", "OK", changeOwner.getAccountId()),
submitLabel("Alternative-Code-Review", "NEED", null))));
update.commit();
ChangeNotes notes = newNotes(c);
List<SubmitRecord> recs = notes.getSubmitRecords();
assertEquals(2, recs.size());
assertEquals(submitRecord("NOT_READY", null,
submitLabel("Verified", "OK", changeOwner.getAccountId()),
submitLabel("Code-Review", "NEED", null)), recs.get(0));
assertEquals(submitRecord("NOT_READY", null,
submitLabel("Verified", "OK", changeOwner.getAccountId()),
submitLabel("Alternative-Code-Review", "NEED", null)), recs.get(1));
}
@Test
public void latestSubmitRecordsOnly() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.setSubject("Submit patch set 1");
update.submit(ImmutableList.of(
submitRecord("OK", null,
submitLabel("Code-Review", "OK", otherUser.getAccountId()))));
update.commit();
incrementPatchSet(c);
update = newUpdate(c, changeOwner);
update.setSubject("Submit patch set 2");
update.submit(ImmutableList.of(
submitRecord("OK", null,
submitLabel("Code-Review", "OK", changeOwner.getAccountId()))));
update.commit();
ChangeNotes notes = newNotes(c);
assertEquals(submitRecord("OK", null,
submitLabel("Code-Review", "OK", changeOwner.getAccountId())),
Iterables.getOnlyElement(notes.getSubmitRecords()));
}
@Test
public void multipleUpdatesInBatch() throws Exception {
Change c = newChange();
ChangeUpdate update1 = newUpdate(c, changeOwner);
update1.putApproval("Verified", (short) 1);
ChangeUpdate update2 = newUpdate(c, otherUser);
update2.putApproval("Code-Review", (short) 2);
BatchMetaDataUpdate batch = update1.openUpdate();
try {
batch.write(update1, new CommitBuilder());
batch.write(update2, new CommitBuilder());
batch.commit();
} finally {
batch.close();
}
ChangeNotes notes = newNotes(c);
List<PatchSetApproval> psas =
notes.getApprovals().get(c.currentPatchSetId());
assertEquals(2, psas.size());
assertEquals(changeOwner.getAccount().getId(), psas.get(0).getAccountId());
assertEquals("Verified", psas.get(0).getLabel());
assertEquals((short) 1, psas.get(0).getValue());
assertEquals(otherUser.getAccount().getId(), psas.get(1).getAccountId());
assertEquals("Code-Review", psas.get(1).getLabel());
assertEquals((short) 2, psas.get(1).getValue());
}
@Test
public void changeMessageOnePatchSet() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
update.setChangeMessage("Just a little code change.\n");
update.commit();
PatchSet.Id ps1 = c.currentPatchSetId();
ChangeNotes notes = newNotes(c);
ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
notes.getChangeMessages();
assertEquals(1, changeMessages.keySet().size());
ChangeMessage cm = Iterables.getOnlyElement(changeMessages.get(ps1));
assertEquals("Just a little code change.\n",
cm.getMessage());
assertEquals(changeOwner.getAccount().getId(),
cm.getAuthor());
assertEquals(ps1, cm.getPatchSetId());
}
@Test
public void changeMessagesMultiplePatchSets() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
update.setChangeMessage("This is the change message for the first PS.");
update.commit();
PatchSet.Id ps1 = c.currentPatchSetId();
incrementPatchSet(c);
update = newUpdate(c, changeOwner);
update.setChangeMessage("This is the change message for the second PS.");
update.commit();
PatchSet.Id ps2 = c.currentPatchSetId();
ChangeNotes notes = newNotes(c);
ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
notes.getChangeMessages();
assertEquals(2, changeMessages.keySet().size());
ChangeMessage cm1 = Iterables.getOnlyElement(changeMessages.get(ps1));
assertEquals("This is the change message for the first PS.",
cm1.getMessage());
assertEquals(changeOwner.getAccount().getId(),
cm1.getAuthor());
ChangeMessage cm2 = Iterables.getOnlyElement(changeMessages.get(ps2));
assertEquals(ps1, cm1.getPatchSetId());
assertEquals("This is the change message for the second PS.",
cm2.getMessage());
assertEquals(changeOwner.getAccount().getId(), cm2.getAuthor());
assertEquals(ps2, cm2.getPatchSetId());
}
@Test
public void noChangeMessage() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
update.commit();
RevWalk walk = new RevWalk(repo);
try {
RevCommit commit = walk.parseCommit(update.getRevision());
walk.parseBody(commit);
assertEquals("Update patch set 1\n"
+ "\n"
+ "Patch-set: 1\n"
+ "Reviewer: Change Owner <1@gerrit>\n",
commit.getFullMessage());
} finally {
walk.release();
}
ChangeNotes notes = newNotes(c);
ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
notes.getChangeMessages();
assertEquals(0, changeMessages.keySet().size());
}
@Test
public void changeMessageWithTrailingDoubleNewline() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.setChangeMessage("Testing trailing double newline\n"
+ "\n");
update.commit();
PatchSet.Id ps1 = c.currentPatchSetId();
RevWalk walk = new RevWalk(repo);
try {
RevCommit commit = walk.parseCommit(update.getRevision());
walk.parseBody(commit);
assertEquals("Update patch set 1\n"
+ "\n"
+ "Testing trailing double newline\n"
+ "\n"
+ "\n"
+ "\n"
+ "Patch-set: 1\n",
commit.getFullMessage());
} finally {
walk.release();
}
ChangeNotes notes = newNotes(c);
ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
notes.getChangeMessages();
assertEquals(1, changeMessages.keySet().size());
ChangeMessage cm1 = Iterables.getOnlyElement(changeMessages.get(ps1));
assertEquals("Testing trailing double newline\n" + "\n", cm1.getMessage());
assertEquals(changeOwner.getAccount().getId(), cm1.getAuthor());
}
@Test
public void changeMessageWithMultipleParagraphs() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.setChangeMessage("Testing paragraph 1\n"
+ "\n"
+ "Testing paragraph 2\n"
+ "\n"
+ "Testing paragraph 3");
update.commit();
PatchSet.Id ps1 = c.currentPatchSetId();
RevWalk walk = new RevWalk(repo);
try {
RevCommit commit = walk.parseCommit(update.getRevision());
walk.parseBody(commit);
assertEquals("Update patch set 1\n"
+ "\n"
+ "Testing paragraph 1\n"
+ "\n"
+ "Testing paragraph 2\n"
+ "\n"
+ "Testing paragraph 3\n"
+ "\n"
+ "Patch-set: 1\n",
commit.getFullMessage());
} finally {
walk.release();
}
ChangeNotes notes = newNotes(c);
ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
notes.getChangeMessages();
assertEquals(1, changeMessages.keySet().size());
ChangeMessage cm1 = Iterables.getOnlyElement(changeMessages.get(ps1));
assertEquals("Testing paragraph 1\n"
+ "\n"
+ "Testing paragraph 2\n"
+ "\n"
+ "Testing paragraph 3", cm1.getMessage());
assertEquals(changeOwner.getAccount().getId(), cm1.getAuthor());
}
@Test
public void changeMessageMultipleInOnePatchSet() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
update.setChangeMessage("First change message.\n");
update.commit();
PatchSet.Id ps1 = c.currentPatchSetId();
update = newUpdate(c, changeOwner);
update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
update.setChangeMessage("Second change message.\n");
update.commit();
ChangeNotes notes = newNotes(c);
ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
notes.getChangeMessages();
assertEquals(1, changeMessages.keySet().size());
List<ChangeMessage> cm = changeMessages.get(ps1);
assertEquals(2, cm.size());
assertEquals("First change message.\n",
cm.get(0).getMessage());
assertEquals(changeOwner.getAccount().getId(),
cm.get(0).getAuthor());
assertEquals(ps1, cm.get(0).getPatchSetId());
assertEquals("Second change message.\n",
cm.get(1).getMessage());
assertEquals(changeOwner.getAccount().getId(),
cm.get(1).getAuthor());
assertEquals(ps1, cm.get(1).getPatchSetId());
}
@Test
public void patchLineCommentNotesFormatSide1() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, otherUser);
String uuid = "uuid";
String message1 = "comment 1";
String message2 = "comment 2";
String message3 = "comment 3";
CommentRange range1 = new CommentRange(1, 1, 2, 1);
Timestamp time1 = TimeUtil.nowTs();
Timestamp time2 = TimeUtil.nowTs();
Timestamp time3 = TimeUtil.nowTs();
PatchSet.Id psId = c.currentPatchSetId();
PatchLineComment comment1 = newPublishedPatchLineComment(psId, "file1",
uuid, range1, range1.getEndLine(), otherUser, null, time1, message1,
(short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(comment1);
update.commit();
update = newUpdate(c, otherUser);
CommentRange range2 = new CommentRange(2, 1, 3, 1);
PatchLineComment comment2 = newPublishedPatchLineComment(psId, "file1",
uuid, range2, range2.getEndLine(), otherUser, null, time2, message2,
(short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(comment2);
update.commit();
update = newUpdate(c, otherUser);
CommentRange range3 = new CommentRange(3, 1, 4, 1);
PatchLineComment comment3 = newPublishedPatchLineComment(psId, "file2",
uuid, range3, range3.getEndLine(), otherUser, null, time3, message3,
(short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(comment3);
update.commit();
ChangeNotes notes = newNotes(c);
RevWalk walk = new RevWalk(repo);
ArrayList<Note> notesInTree =
Lists.newArrayList(notes.getNoteMap().iterator());
Note note = Iterables.getOnlyElement(notesInTree);
byte[] bytes =
walk.getObjectReader().open(
note.getData(), Constants.OBJ_BLOB).getBytes();
String noteString = new String(bytes, UTF_8);
assertEquals("Patch-set: 1\n"
+ "Revision: abcd1234abcd1234abcd1234abcd1234abcd1234\n"
+ "File: file1\n"
+ "\n"
+ "1:1-2:1\n"
+ CommentsInNotesUtil.formatTime(serverIdent, time1) + "\n"
+ "Author: Other Account <2@gerrit>\n"
+ "UUID: uuid\n"
+ "Bytes: 9\n"
+ "comment 1\n"
+ "\n"
+ "2:1-3:1\n"
+ CommentsInNotesUtil.formatTime(serverIdent, time2) + "\n"
+ "Author: Other Account <2@gerrit>\n"
+ "UUID: uuid\n"
+ "Bytes: 9\n"
+ "comment 2\n"
+ "\n"
+ "File: file2\n"
+ "\n"
+ "3:1-4:1\n"
+ CommentsInNotesUtil.formatTime(serverIdent, time3) + "\n"
+ "Author: Other Account <2@gerrit>\n"
+ "UUID: uuid\n"
+ "Bytes: 9\n"
+ "comment 3\n"
+ "\n",
noteString);
}
@Test
public void patchLineCommentNotesFormatSide0() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, otherUser);
String uuid = "uuid";
String message1 = "comment 1";
String message2 = "comment 2";
CommentRange range1 = new CommentRange(1, 1, 2, 1);
Timestamp time1 = TimeUtil.nowTs();
Timestamp time2 = TimeUtil.nowTs();
PatchSet.Id psId = c.currentPatchSetId();
PatchLineComment comment1 = newPublishedPatchLineComment(psId, "file1",
uuid, range1, range1.getEndLine(), otherUser, null, time1, message1,
(short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(comment1);
update.commit();
update = newUpdate(c, otherUser);
CommentRange range2 = new CommentRange(2, 1, 3, 1);
PatchLineComment comment2 = newPublishedPatchLineComment(psId, "file1",
uuid, range2, range2.getEndLine(), otherUser, null, time2, message2,
(short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(comment2);
update.commit();
ChangeNotes notes = newNotes(c);
RevWalk walk = new RevWalk(repo);
ArrayList<Note> notesInTree =
Lists.newArrayList(notes.getNoteMap().iterator());
Note note = Iterables.getOnlyElement(notesInTree);
byte[] bytes =
walk.getObjectReader().open(
note.getData(), Constants.OBJ_BLOB).getBytes();
String noteString = new String(bytes, UTF_8);
assertEquals("Base-for-patch-set: 1\n"
+ "Revision: abcd1234abcd1234abcd1234abcd1234abcd1234\n"
+ "File: file1\n"
+ "\n"
+ "1:1-2:1\n"
+ CommentsInNotesUtil.formatTime(serverIdent, time1) + "\n"
+ "Author: Other Account <2@gerrit>\n"
+ "UUID: uuid\n"
+ "Bytes: 9\n"
+ "comment 1\n"
+ "\n"
+ "2:1-3:1\n"
+ CommentsInNotesUtil.formatTime(serverIdent, time2) + "\n"
+ "Author: Other Account <2@gerrit>\n"
+ "UUID: uuid\n"
+ "Bytes: 9\n"
+ "comment 2\n"
+ "\n",
noteString);
}
@Test
public void patchLineCommentMultipleOnePatchsetOneFileBothSides()
throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, otherUser);
String uuid = "uuid";
String messageForBase = "comment for base";
String messageForPS = "comment for ps";
CommentRange range = new CommentRange(1, 1, 2, 1);
Timestamp now = TimeUtil.nowTs();
PatchSet.Id psId = c.currentPatchSetId();
PatchLineComment commentForBase =
newPublishedPatchLineComment(psId, "filename", uuid,
range, range.getEndLine(), otherUser, null, now, messageForBase,
(short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(commentForBase);
update.commit();
update = newUpdate(c, otherUser);
PatchLineComment commentForPS =
newPublishedPatchLineComment(psId, "filename", uuid,
range, range.getEndLine(), otherUser, null, now, messageForPS,
(short) 1, "abcd4567abcd4567abcd4567abcd4567abcd4567");
update.setPatchSetId(psId);
update.putComment(commentForPS);
update.commit();
ChangeNotes notes = newNotes(c);
Multimap<PatchSet.Id, PatchLineComment> commentsForBase =
notes.getBaseComments();
Multimap<PatchSet.Id, PatchLineComment> commentsForPS =
notes.getPatchSetComments();
assertEquals(commentsForBase.size(), 1);
assertEquals(commentsForPS.size(), 1);
assertEquals(commentForBase,
Iterables.getOnlyElement(commentsForBase.get(psId)));
assertEquals(commentForPS,
Iterables.getOnlyElement(commentsForPS.get(psId)));
}
@Test
public void patchLineCommentMultipleOnePatchsetOneFile() throws Exception {
Change c = newChange();
String uuid = "uuid";
CommentRange range = new CommentRange(1, 1, 2, 1);
PatchSet.Id psId = c.currentPatchSetId();
String filename = "filename";
short side = (short) 1;
ChangeUpdate update = newUpdate(c, otherUser);
Timestamp timeForComment1 = TimeUtil.nowTs();
Timestamp timeForComment2 = TimeUtil.nowTs();
PatchLineComment comment1 = newPublishedPatchLineComment(psId, filename,
uuid, range, range.getEndLine(), otherUser, null, timeForComment1,
"comment 1", side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(comment1);
update.commit();
update = newUpdate(c, otherUser);
PatchLineComment comment2 = newPublishedPatchLineComment(psId, filename,
uuid, range, range.getEndLine(), otherUser, null, timeForComment2,
"comment 2", side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(comment2);
update.commit();
ChangeNotes notes = newNotes(c);
Multimap<PatchSet.Id, PatchLineComment> commentsForBase =
notes.getBaseComments();
Multimap<PatchSet.Id, PatchLineComment> commentsForPS =
notes.getPatchSetComments();
assertEquals(commentsForBase.size(), 0);
assertEquals(commentsForPS.size(), 2);
ImmutableList<PatchLineComment> commentsForThisPS =
(ImmutableList<PatchLineComment>) commentsForPS.get(psId);
assertEquals(commentsForThisPS.size(), 2);
PatchLineComment commentFromNotes1 = commentsForThisPS.get(0);
PatchLineComment commentFromNotes2 = commentsForThisPS.get(1);
assertEquals(comment1, commentFromNotes1);
assertEquals(comment2, commentFromNotes2);
}
@Test
public void patchLineCommentMultipleOnePatchsetMultipleFiles()
throws Exception {
Change c = newChange();
String uuid = "uuid";
CommentRange range = new CommentRange(1, 1, 2, 1);
PatchSet.Id psId = c.currentPatchSetId();
String filename1 = "filename1";
String filename2 = "filename2";
short side = (short) 1;
ChangeUpdate update = newUpdate(c, otherUser);
Timestamp now = TimeUtil.nowTs();
PatchLineComment comment1 = newPublishedPatchLineComment(psId, filename1,
uuid, range, range.getEndLine(), otherUser, null, now, "comment 1",
side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(comment1);
update.commit();
update = newUpdate(c, otherUser);
PatchLineComment comment2 = newPublishedPatchLineComment(psId, filename2,
uuid, range, range.getEndLine(), otherUser, null, now, "comment 2",
side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(comment2);
update.commit();
ChangeNotes notes = newNotes(c);
Multimap<PatchSet.Id, PatchLineComment> commentsForBase =
notes.getBaseComments();
Multimap<PatchSet.Id, PatchLineComment> commentsForPS =
notes.getPatchSetComments();
assertEquals(commentsForBase.size(), 0);
assertEquals(commentsForPS.size(), 2);
ImmutableList<PatchLineComment> commentsForThisPS =
(ImmutableList<PatchLineComment>) commentsForPS.get(psId);
assertEquals(commentsForThisPS.size(), 2);
PatchLineComment commentFromNotes1 = commentsForThisPS.get(0);
PatchLineComment commentFromNotes2 = commentsForThisPS.get(1);
assertEquals(comment1, commentFromNotes1);
assertEquals(comment2, commentFromNotes2);
}
@Test
public void patchLineCommentMultiplePatchsets() throws Exception {
Change c = newChange();
String uuid = "uuid";
CommentRange range = new CommentRange(1, 1, 2, 1);
PatchSet.Id ps1 = c.currentPatchSetId();
String filename = "filename1";
short side = (short) 1;
ChangeUpdate update = newUpdate(c, otherUser);
Timestamp now = TimeUtil.nowTs();
PatchLineComment comment1 = newPublishedPatchLineComment(ps1, filename,
uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps1",
side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(ps1);
update.putComment(comment1);
update.commit();
incrementPatchSet(c);
PatchSet.Id ps2 = c.currentPatchSetId();
update = newUpdate(c, otherUser);
now = TimeUtil.nowTs();
PatchLineComment comment2 = newPublishedPatchLineComment(ps2, filename,
uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps2",
side, "abcd4567abcd4567abcd4567abcd4567abcd4567");
update.setPatchSetId(ps2);
update.putComment(comment2);
update.commit();
ChangeNotes notes = newNotes(c);
LinkedListMultimap<PatchSet.Id, PatchLineComment> commentsForBase =
LinkedListMultimap.create(notes.getBaseComments());
LinkedListMultimap<PatchSet.Id, PatchLineComment> commentsForPS =
LinkedListMultimap.create(notes.getPatchSetComments());
assertEquals(commentsForBase.keys().size(), 0);
assertEquals(commentsForPS.values().size(), 2);
List<PatchLineComment> commentsForPS1 = commentsForPS.get(ps1);
assertEquals(commentsForPS1.size(), 1);
PatchLineComment commentFromPs1 = commentsForPS1.get(0);
List<PatchLineComment> commentsForPS2 = commentsForPS.get(ps2);
assertEquals(commentsForPS2.size(), 1);
PatchLineComment commentFromPs2 = commentsForPS2.get(0);
assertEquals(comment1, commentFromPs1);
assertEquals(comment2, commentFromPs2);
}
private Change newChange() {
return TestChanges.newChange(project, changeOwner);
}
private PatchLineComment newPublishedPatchLineComment(PatchSet.Id psId,
String filename, String UUID, CommentRange range, int line,
IdentifiedUser commenter, String parentUUID, Timestamp t,
String message, short side, String commitSHA1) {
return newPatchLineComment(psId, filename, UUID, range, line, commenter,
parentUUID, t, message, side, commitSHA1, Status.PUBLISHED);
}
private PatchLineComment newPatchLineComment(PatchSet.Id psId,
String filename, String UUID, CommentRange range, int line,
IdentifiedUser commenter, String parentUUID, Timestamp t,
String message, short side, String commitSHA1, Status status) {
PatchLineComment comment = new PatchLineComment(
new PatchLineComment.Key(
new Patch.Key(psId, filename), UUID),
line, commenter.getAccountId(), parentUUID, t);
comment.setSide(side);
comment.setMessage(message);
comment.setRange(range);
comment.setRevId(new RevId(commitSHA1));
comment.setStatus(status);
return comment;
}
private ChangeUpdate newUpdate(Change c, IdentifiedUser user)
throws Exception {
return TestChanges.newUpdate(injector, repoManager, c, user);
}
private ChangeNotes newNotes(Change c) throws OrmException {
return new ChangeNotes(repoManager, c).load();
}
private static Timestamp truncate(Timestamp ts) {
return new Timestamp((ts.getTime() / 1000) * 1000);
}
private static Timestamp after(Change c, long millis) {
return new Timestamp(c.getCreatedOn().getTime() + millis);
}
private static SubmitRecord submitRecord(String status,
String errorMessage, SubmitRecord.Label... labels) {
SubmitRecord rec = new SubmitRecord();
rec.status = SubmitRecord.Status.valueOf(status);
rec.errorMessage = errorMessage;
if (labels.length > 0) {
rec.labels = ImmutableList.copyOf(labels);
}
return rec;
}
private static SubmitRecord.Label submitLabel(String name, String status,
Account.Id appliedBy) {
SubmitRecord.Label label = new SubmitRecord.Label();
label.label = name;
label.status = SubmitRecord.Label.Status.valueOf(status);
label.appliedBy = appliedBy;
return label;
}
}