// 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.inject.Scopes.SINGLETON; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.easymock.EasyMock.expect; 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.ListMultimap; import com.google.common.collect.Ordering; import com.google.gerrit.common.data.SubmitRecord; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Branch; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.ChangeMessage; import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.PatchSetApproval; import com.google.gerrit.reviewdb.client.PatchSetInfo; import com.google.gerrit.reviewdb.client.Project; 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.project.ChangeControl; import com.google.gerrit.server.project.ProjectCache; import com.google.gerrit.server.util.TimeUtil; 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.easymock.EasyMock; 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.PersonIdent; 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.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()); } private Change newChange() { Change.Id changeId = new Change.Id(1); Change c = new Change( new Change.Key("Iabcd1234abcd1234abcd1234abcd1234abcd1234"), changeId, changeOwner.getAccount().getId(), new Branch.NameKey(project, "master"), TimeUtil.nowTs()); incrementPatchSet(c); return c; } private ChangeUpdate newUpdate(Change c, final IdentifiedUser user) throws Exception { return injector.createChildInjector(new FactoryModule() { @Override public void configure() { factory(ChangeUpdate.Factory.class); bind(IdentifiedUser.class).toInstance(user); } }).getInstance(ChangeUpdate.Factory.class).create( stubChangeControl(c, user), TimeUtil.nowTs(), Ordering.<String> natural()); } private ChangeNotes newNotes(Change c) throws OrmException { return new ChangeNotes(repoManager, c).load(); } private static void incrementPatchSet(Change change) { PatchSet.Id curr = change.currentPatchSetId(); PatchSetInfo ps = new PatchSetInfo(new PatchSet.Id( change.getId(), curr != null ? curr.get() + 1 : 1)); ps.setSubject("Change subject"); change.setCurrentPatchSet(ps); } 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 ChangeControl stubChangeControl(Change c, IdentifiedUser user) { ChangeControl ctl = EasyMock.createNiceMock(ChangeControl.class); expect(ctl.getChange()).andStubReturn(c); expect(ctl.getCurrentUser()).andStubReturn(user); EasyMock.replay(ctl); return ctl; } 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; } }